Skip to content

Commit 704414c

Browse files
committed
tests: add tests for message formatting
1 parent 3367599 commit 704414c

File tree

1 file changed

+130
-0
lines changed

1 file changed

+130
-0
lines changed

tests/integrations/pydantic_ai/test_pydantic_ai.py

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import asyncio
2+
import json
23
import pytest
4+
from unittest.mock import MagicMock
35

46
from typing import Annotated
57
from pydantic import Field
68

9+
import sentry_sdk
710
from sentry_sdk.integrations.pydantic_ai import PydanticAIIntegration
11+
from sentry_sdk.integrations.pydantic_ai.spans.ai_client import _set_input_messages
812

913
from pydantic_ai import Agent
14+
from pydantic_ai.messages import BinaryContent, UserPromptPart
1015
from pydantic_ai.models.test import TestModel
1116
from pydantic_ai.exceptions import ModelRetry, UnexpectedModelBehavior
1217

@@ -2604,3 +2609,128 @@ async def test_ai_client_span_gets_agent_from_scope(sentry_init, capture_events)
26042609

26052610
# Should not crash
26062611
assert transaction is not None
2612+
2613+
2614+
def _get_messages_from_span(span_data):
2615+
"""Helper to extract and parse messages from span data."""
2616+
messages_data = span_data["gen_ai.request.messages"]
2617+
return (
2618+
json.loads(messages_data) if isinstance(messages_data, str) else messages_data
2619+
)
2620+
2621+
2622+
def _find_binary_content(messages_data, expected_modality, expected_mime_type):
2623+
"""Helper to find and verify binary content in messages."""
2624+
for msg in messages_data:
2625+
if "content" not in msg:
2626+
continue
2627+
for content_item in msg["content"]:
2628+
if content_item.get("type") == "blob":
2629+
assert content_item["modality"] == expected_modality
2630+
assert content_item["mime_type"] == expected_mime_type
2631+
assert "content" in content_item
2632+
content_str = str(content_item["content"])
2633+
assert (
2634+
f"data:{expected_mime_type};base64," in content_str
2635+
or "[Filtered]" in content_str
2636+
)
2637+
return True
2638+
return False
2639+
2640+
2641+
@pytest.mark.asyncio
2642+
async def test_binary_content_encoding_image(sentry_init, capture_events):
2643+
"""Test that BinaryContent with image data is properly encoded in messages."""
2644+
sentry_init(
2645+
integrations=[PydanticAIIntegration()],
2646+
traces_sample_rate=1.0,
2647+
send_default_pii=True,
2648+
)
2649+
2650+
events = capture_events()
2651+
2652+
with sentry_sdk.start_transaction(op="test", name="test"):
2653+
span = sentry_sdk.start_span(op="test_span")
2654+
binary_content = BinaryContent(
2655+
data=b"fake_image_data_12345", media_type="image/png"
2656+
)
2657+
user_part = UserPromptPart(content=["Look at this image:", binary_content])
2658+
mock_msg = MagicMock()
2659+
mock_msg.parts = [user_part]
2660+
mock_msg.instructions = None
2661+
2662+
_set_input_messages(span, [mock_msg])
2663+
span.finish()
2664+
2665+
(event,) = events
2666+
span_data = event["spans"][0]["data"]
2667+
messages_data = _get_messages_from_span(span_data)
2668+
assert _find_binary_content(messages_data, "image", "image/png")
2669+
2670+
2671+
@pytest.mark.asyncio
2672+
async def test_binary_content_encoding_mixed_content(sentry_init, capture_events):
2673+
"""Test that BinaryContent mixed with text content is properly handled."""
2674+
sentry_init(
2675+
integrations=[PydanticAIIntegration()],
2676+
traces_sample_rate=1.0,
2677+
send_default_pii=True,
2678+
)
2679+
2680+
events = capture_events()
2681+
2682+
with sentry_sdk.start_transaction(op="test", name="test"):
2683+
span = sentry_sdk.start_span(op="test_span")
2684+
binary_content = BinaryContent(
2685+
data=b"fake_image_bytes", media_type="image/jpeg"
2686+
)
2687+
user_part = UserPromptPart(
2688+
content=["Here is an image:", binary_content, "What do you see?"]
2689+
)
2690+
mock_msg = MagicMock()
2691+
mock_msg.parts = [user_part]
2692+
mock_msg.instructions = None
2693+
2694+
_set_input_messages(span, [mock_msg])
2695+
span.finish()
2696+
2697+
(event,) = events
2698+
span_data = event["spans"][0]["data"]
2699+
messages_data = _get_messages_from_span(span_data)
2700+
2701+
# Verify both text and binary content are present
2702+
found_text = any(
2703+
content_item.get("type") == "text"
2704+
for msg in messages_data
2705+
if "content" in msg
2706+
for content_item in msg["content"]
2707+
)
2708+
assert found_text, "Text content should be found"
2709+
assert _find_binary_content(messages_data, "image", "image/jpeg")
2710+
2711+
2712+
@pytest.mark.asyncio
2713+
async def test_binary_content_in_agent_run(sentry_init, capture_events):
2714+
"""Test that BinaryContent in actual agent run is properly captured in spans."""
2715+
agent = Agent("test", name="test_binary_agent")
2716+
2717+
sentry_init(
2718+
integrations=[PydanticAIIntegration()],
2719+
traces_sample_rate=1.0,
2720+
send_default_pii=True,
2721+
)
2722+
2723+
events = capture_events()
2724+
binary_content = BinaryContent(
2725+
data=b"fake_image_data_for_testing", media_type="image/png"
2726+
)
2727+
await agent.run(["Analyze this image:", binary_content])
2728+
2729+
(transaction,) = events
2730+
chat_spans = [s for s in transaction["spans"] if s["op"] == "gen_ai.chat"]
2731+
assert len(chat_spans) >= 1
2732+
2733+
chat_span = chat_spans[0]
2734+
if "gen_ai.request.messages" in chat_span["data"]:
2735+
messages_str = str(chat_span["data"]["gen_ai.request.messages"])
2736+
assert any(keyword in messages_str for keyword in ["blob", "image", "base64"])

0 commit comments

Comments
 (0)