0cf6229441
GET /api/artifacts?conversation_id= lists ArtifactRefs without data; GET /api/artifacts/<id> returns the full artifact. Both 404 for other users. create_artifact_from_output persists ToolArtifact dicts and drops ones over 256 KB serialised. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79 lines
2.4 KiB
Python
79 lines
2.4 KiB
Python
import json
|
|
import logging
|
|
|
|
from .models import Artifact
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Artifacts whose serialised `data` exceeds this are dropped (the tool text still persists).
|
|
ARTIFACT_MAX_BYTES = 256 * 1024
|
|
|
|
|
|
def artifact_ref(artifact: Artifact) -> dict:
|
|
"""The lightweight reference sent in SSE events and the conversation payload."""
|
|
return {"id": str(artifact.id), "kind": artifact.kind, "title": artifact.title}
|
|
|
|
|
|
def artifact_summary(artifact: Artifact) -> dict:
|
|
"""ArtifactRef plus listing metadata, without `data`."""
|
|
return {
|
|
**artifact_ref(artifact),
|
|
"tool": artifact.tool,
|
|
"message_id": str(artifact.message_id) if artifact.message_id else None,
|
|
"created_at": artifact.created_at.isoformat(),
|
|
}
|
|
|
|
|
|
def artifact_full(artifact: Artifact) -> dict:
|
|
return {
|
|
**artifact_summary(artifact),
|
|
"data": artifact.data,
|
|
"source_ref": artifact.source_ref,
|
|
"conversation_id": str(artifact.conversation_id),
|
|
}
|
|
|
|
|
|
async def create_artifact_from_output(
|
|
*, output, tool: str, user, conversation, message=None
|
|
) -> Artifact | None:
|
|
"""Persist `output.artifact` if it is a ToolArtifact dict; otherwise return None.
|
|
|
|
Tools opt in via `@tool(response_format="content_and_artifact")`. Artifacts
|
|
without a `kind` (e.g. simba_search's raw Documents) are ignored, as are
|
|
ones whose `data` is not JSON-serialisable or over ARTIFACT_MAX_BYTES.
|
|
"""
|
|
raw = getattr(output, "artifact", None)
|
|
if not isinstance(raw, dict) or not raw.get("kind"):
|
|
return None
|
|
|
|
data = raw.get("data") or {}
|
|
source_ref = raw.get("source_ref")
|
|
try:
|
|
size = len(json.dumps(data).encode("utf-8"))
|
|
json.dumps(source_ref)
|
|
except (TypeError, ValueError) as e:
|
|
logger.warning(
|
|
"Dropping %s artifact from %s: not serialisable (%s)", raw["kind"], tool, e
|
|
)
|
|
return None
|
|
if size > ARTIFACT_MAX_BYTES:
|
|
logger.warning(
|
|
"Dropping %s artifact from %s: %d bytes exceeds %d",
|
|
raw["kind"],
|
|
tool,
|
|
size,
|
|
ARTIFACT_MAX_BYTES,
|
|
)
|
|
return None
|
|
|
|
return await Artifact.create(
|
|
user=user,
|
|
conversation=conversation,
|
|
message=message,
|
|
tool=tool,
|
|
kind=str(raw["kind"])[:64],
|
|
title=str(raw.get("title") or "")[:255],
|
|
data=data,
|
|
source_ref=source_ref,
|
|
)
|