diff --git a/tests/trace_server/test_genai_chat_view.py b/tests/trace_server/test_genai_chat_view.py index e9789988ed76..c427d7a495ad 100644 --- a/tests/trace_server/test_genai_chat_view.py +++ b/tests/trace_server/test_genai_chat_view.py @@ -1386,6 +1386,40 @@ def test_inline_internal_ref_surfaces_without_span_content_refs() -> None: assert _assistant_payload(assistant).content_refs == [] +def test_blob_content_is_media_not_user_message_text() -> None: + """A Weave blob's `content` is base64/ref data, not displayable prose.""" + image_internal = "weave-trace-internal:///PID/object/Content:IMAGEDIGEST" + spans = [ + _span( + span_id="agent", + operation_name="invoke_agent", + agent_name="image-analyzer", + input_messages=[ + { + "role": "user", + "content": _parts( + { + "type": "blob", + "content": image_internal, + "mimeType": "image/png", + "modality": "image", + }, + _text_part("Describe this image."), + ), + } + ], + ) + ] + + messages = build_chat_messages(spans) + user = next(m for m in messages if m.type == "user_message") + + assert _user_payload(user) == AgentChatUserMessage( + text="Describe this image.", + content_refs=[image_internal], + ) + + @pytest.mark.xfail( reason=( "Known limitation (PR #7489 discussion): the inline-ref sweep " diff --git a/weave/trace_server/agents/chat_view.py b/weave/trace_server/agents/chat_view.py index e66662b79c30..d4c2ffccd4d0 100644 --- a/weave/trace_server/agents/chat_view.py +++ b/weave/trace_server/agents/chat_view.py @@ -690,9 +690,9 @@ def _display_text(content: str) -> str: """Extract human-readable text from a message content field. Content is either plain text (legacy) or a JSON-serialized parts array - (multimodal messages). For parts arrays, concatenate the text parts; - reasoning parts are excluded (they surface separately via - `reasoning_content`). For plain text, return as-is. + (multimodal messages). For parts arrays, concatenate text parts. Reasoning + surfaces through ``reasoning_content``; media surfaces through + ``content_refs``. For plain text, return as-is. """ if not content or not content.startswith("["): return content @@ -709,9 +709,12 @@ def _display_text(content: str) -> str: # concatenating it here would duplicate it in the message body. if p.get("type") == "reasoning": continue + # Media belongs in content_refs, never chat text. + if p.get("type") in _MEDIA_PART_TYPES: + continue # Support both the weave parts model (``content``) and the # OpenAI-style multimodal shape (``text``). Non-text parts (e.g. - # images) carry neither and are skipped for display. + # images) are skipped for display. if isinstance(p.get("content"), str): texts.append(p["content"]) elif isinstance(p.get("text"), str):