From d51c65b4b71f482a2150bbf4926200d3aac28a71 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Thu, 17 Jul 2025 21:58:40 +0530 Subject: [PATCH 01/28] Adding thinkingpart to otel_events in ModelResponse --- pydantic_ai_slim/pydantic_ai/messages.py | 8 ++-- tests/models/test_instrumented.py | 50 ++++++++++-------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index a43771d87..59d05e77e 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -749,11 +749,11 @@ def new_event_body(): }, } ) - elif isinstance(part, TextPart): - if body.get('content'): - body = new_event_body() + elif isinstance(part, TextPart | ThinkingPart): if settings.include_content: - body['content'] = part.content + body.setdefault('content', []).append( + {'kind': 'thinking' if isinstance(part, ThinkingPart) else 'text', 'text': part.content} + ) return result diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index b952bf716..97e9dae89 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -26,6 +26,7 @@ SystemPromptPart, TextPart, TextPartDelta, + ThinkingPart, ToolCallPart, ToolReturnPart, UserPromptPart, @@ -146,7 +147,7 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False}, 'parent': None, 'start_time': 1000000000, - 'end_time': 18000000000, + 'end_time': 16000000000, 'attributes': { 'gen_ai.operation.name': 'chat', 'gen_ai.system': 'my_system', @@ -261,7 +262,7 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': {'role': 'assistant', 'content': 'text3'}, + 'body': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text3'}]}, 'severity_number': 9, 'severity_text': None, 'attributes': { @@ -280,7 +281,7 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'index': 0, 'message': { 'role': 'assistant', - 'content': 'text1', + 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'text', 'text': 'text2'}], 'tool_calls': [ { 'id': 'tool_call_1', @@ -304,17 +305,6 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'span_id': 1, 'trace_flags': 1, }, - { - 'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text2'}}, - 'severity_number': 9, - 'severity_text': None, - 'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'}, - 'timestamp': 16000000000, - 'observed_timestamp': 17000000000, - 'trace_id': 1, - 'span_id': 1, - 'trace_flags': 1, - }, ] ) @@ -413,7 +403,10 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text1text2'}}, + 'body': { + 'index': 0, + 'message': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text1text2'}]}, + }, 'severity_number': 9, 'severity_text': None, 'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'}, @@ -513,7 +506,7 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text1'}}, + 'body': {'index': 0, 'message': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text1'}]}}, 'severity_number': 9, 'severity_text': None, 'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'}, @@ -630,18 +623,20 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): 'gen_ai.system': 'my_system', }, { - 'event.name': 'gen_ai.assistant.message', 'role': 'assistant', - 'content': 'text3', - 'gen_ai.message.index': 1, + 'content': [{'kind': 'text', 'text': 'text3'}], 'gen_ai.system': 'my_system', + 'gen_ai.message.index': 1, + 'event.name': 'gen_ai.assistant.message', }, { - 'event.name': 'gen_ai.choice', 'index': 0, 'message': { 'role': 'assistant', - 'content': 'text1', + 'content': [ + {'kind': 'text', 'text': 'text1'}, + {'kind': 'text', 'text': 'text2'}, + ], 'tool_calls': [ { 'id': 'tool_call_1', @@ -656,12 +651,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): ], }, 'gen_ai.system': 'my_system', - }, - { 'event.name': 'gen_ai.choice', - 'index': 0, - 'message': {'role': 'assistant', 'content': 'text2'}, - 'gen_ai.system': 'my_system', }, ] ) @@ -714,7 +704,7 @@ def test_messages_to_otel_events_instructions(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': 'text1', + 'content': [{'kind': 'text', 'text': 'text1'}], 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, @@ -735,7 +725,7 @@ def test_messages_to_otel_events_instructions_multiple_messages(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': 'text1', + 'content': [{'kind': 'text', 'text': 'text1'}], 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, @@ -764,7 +754,7 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): ] ), ModelRequest(parts=[UserPromptPart(content=['user_prompt6', document_content])]), - ModelResponse(parts=[TextPart('text1')]), + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1')]), ] settings = InstrumentationSettings() assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -816,7 +806,7 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): }, { 'role': 'assistant', - 'content': 'text1', + 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking1'}], 'gen_ai.message.index': 6, 'event.name': 'gen_ai.assistant.message', }, From 19876e5511742437b17fa4ae9a9d8a17aca4d88c Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Thu, 17 Jul 2025 22:08:11 +0530 Subject: [PATCH 02/28] fixing logfire tests --- tests/test_logfire.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_logfire.py b/tests/test_logfire.py index 799724179..5f2dd1e25 100644 --- a/tests/test_logfire.py +++ b/tests/test_logfire.py @@ -157,7 +157,7 @@ async def my_ret(x: int) -> str: }, { 'role': 'assistant', - 'content': '{"my_ret":"1"}', + 'content': [{'kind': 'text', 'text': '{"my_ret":"1"}'}], 'gen_ai.message.index': 3, 'event.name': 'gen_ai.assistant.message', }, @@ -595,7 +595,7 @@ async def test_feedback(capfire: CaptureLogfire) -> None: 'gen_ai.usage.input_tokens': 51, 'gen_ai.usage.output_tokens': 4, 'gen_ai.response.model': 'test', - 'events': '[{"content": "Hello", "role": "user", "gen_ai.system": "test", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success (no tool calls)"}, "gen_ai.system": "test", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "Hello", "role": "user", "gen_ai.system": "test", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "success (no tool calls)"}]}, "gen_ai.system": "test", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -611,7 +611,7 @@ async def test_feedback(capfire: CaptureLogfire) -> None: 'logfire.msg': 'agent run', 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 51, - 'all_messages_events': '[{"content": "Hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "success (no tool calls)", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "Hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "success (no tool calls)"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'gen_ai.usage.output_tokens': 4, 'final_result': 'success (no tool calls)', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', From 83d33987d573a2afe77019f79336924e4775bf71 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Thu, 17 Jul 2025 22:15:36 +0530 Subject: [PATCH 03/28] fixing fallback tests --- tests/models/test_fallback.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/models/test_fallback.py b/tests/models/test_fallback.py index 89709d4a2..ea5693dba 100644 --- a/tests/models/test_fallback.py +++ b/tests/models/test_fallback.py @@ -135,7 +135,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'gen_ai.usage.input_tokens': 51, 'gen_ai.usage.output_tokens': 1, 'gen_ai.response.model': 'function:success_response:', - 'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "success"}]}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -151,7 +151,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'logfire.msg': 'agent run', 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 51, - 'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "success", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "success"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'gen_ai.usage.output_tokens': 1, 'final_result': 'success', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', @@ -208,7 +208,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'gen_ai.usage.input_tokens': 50, 'gen_ai.usage.output_tokens': 2, 'gen_ai.response.model': 'function::success_response_stream', - 'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "hello world"}]}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -225,7 +225,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 50, 'gen_ai.usage.output_tokens': 2, - 'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "hello world", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "hello world"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', }, }, From ebbf373f4c5132745c8235422304b4fccdf65cf6 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Thu, 17 Jul 2025 22:23:18 +0530 Subject: [PATCH 04/28] fix for python3.9 --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 59d05e77e..f8d0947cc 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -749,7 +749,7 @@ def new_event_body(): }, } ) - elif isinstance(part, TextPart | ThinkingPart): + elif isinstance(part, (TextPart, ThinkingPart)): if settings.include_content: body.setdefault('content', []).append( {'kind': 'thinking' if isinstance(part, ThinkingPart) else 'text', 'text': part.content} From 608fc135cefe3e90e6aea7703db99a6198d45dac Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 18:52:25 +0530 Subject: [PATCH 05/28] changes as discussed --- pydantic_ai_slim/pydantic_ai/messages.py | 19 +++++++++++++++---- tests/models/test_fallback.py | 8 ++++---- tests/models/test_instrumented.py | 14 ++++++++------ tests/test_logfire.py | 6 +++--- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index f8d0947cc..f8d7dd45d 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -750,10 +750,21 @@ def new_event_body(): } ) elif isinstance(part, (TextPart, ThinkingPart)): - if settings.include_content: - body.setdefault('content', []).append( - {'kind': 'thinking' if isinstance(part, ThinkingPart) else 'text', 'text': part.content} - ) + kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' + existing_content = body.get('content') + if not settings.include_content: + body.setdefault('content', []).append({'kind': kind}) + else: + if existing_content: + if isinstance(existing_content, list): + body.setdefault('content', []).append({'kind': kind, 'text': part.content}) + elif isinstance(existing_content, str): + body['content'] = [{'kind': 'text', 'text': existing_content}, {'kind': kind, 'text': part.content}] + else: + if isinstance(part, TextPart): + body['content'] = part.content + elif isinstance(part, ThinkingPart): + body['content'] = [{'kind': kind, 'text': part.content}] return result diff --git a/tests/models/test_fallback.py b/tests/models/test_fallback.py index ea5693dba..89709d4a2 100644 --- a/tests/models/test_fallback.py +++ b/tests/models/test_fallback.py @@ -135,7 +135,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'gen_ai.usage.input_tokens': 51, 'gen_ai.usage.output_tokens': 1, 'gen_ai.response.model': 'function:success_response:', - 'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "success"}]}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "hello", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -151,7 +151,7 @@ def test_first_failed_instrumented(capfire: CaptureLogfire) -> None: 'logfire.msg': 'agent run', 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 51, - 'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "success"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "success", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'gen_ai.usage.output_tokens': 1, 'final_result': 'success', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', @@ -208,7 +208,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'gen_ai.usage.input_tokens': 50, 'gen_ai.usage.output_tokens': 2, 'gen_ai.response.model': 'function::success_response_stream', - 'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "hello world"}]}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "input", "role": "user", "gen_ai.system": "function", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "hello world"}, "gen_ai.system": "function", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -225,7 +225,7 @@ async def test_first_failed_instrumented_stream(capfire: CaptureLogfire) -> None 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 50, 'gen_ai.usage.output_tokens': 2, - 'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "hello world"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "input", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "hello world", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', }, }, diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 97e9dae89..67c3abf54 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -262,7 +262,7 @@ async def test_instrumented_model(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text3'}]}, + 'body': {'role': 'assistant', 'content': 'text3'}, 'severity_number': 9, 'severity_text': None, 'attributes': { @@ -405,7 +405,7 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire): { 'body': { 'index': 0, - 'message': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text1text2'}]}, + 'message': {'role': 'assistant', 'content': 'text1text2'}, }, 'severity_number': 9, 'severity_text': None, @@ -506,7 +506,7 @@ async def test_instrumented_model_stream_break(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': {'index': 0, 'message': {'role': 'assistant', 'content': [{'kind': 'text', 'text': 'text1'}]}}, + 'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text1'}}, 'severity_number': 9, 'severity_text': None, 'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'}, @@ -624,7 +624,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): }, { 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text3'}], + 'content': 'text3', 'gen_ai.system': 'my_system', 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', @@ -704,7 +704,7 @@ def test_messages_to_otel_events_instructions(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}], + 'content': 'text1', 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, @@ -725,7 +725,7 @@ def test_messages_to_otel_events_instructions_multiple_messages(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}], + 'content': 'text1', 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, @@ -865,6 +865,7 @@ def test_messages_without_content(document_content: BinaryContent): }, { 'role': 'assistant', + 'content': [{'kind': 'text'}], 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, @@ -883,6 +884,7 @@ def test_messages_without_content(document_content: BinaryContent): }, { 'role': 'assistant', + 'content': [{'kind': 'text'}], 'tool_calls': [ { 'id': IsStr(), diff --git a/tests/test_logfire.py b/tests/test_logfire.py index 5f2dd1e25..799724179 100644 --- a/tests/test_logfire.py +++ b/tests/test_logfire.py @@ -157,7 +157,7 @@ async def my_ret(x: int) -> str: }, { 'role': 'assistant', - 'content': [{'kind': 'text', 'text': '{"my_ret":"1"}'}], + 'content': '{"my_ret":"1"}', 'gen_ai.message.index': 3, 'event.name': 'gen_ai.assistant.message', }, @@ -595,7 +595,7 @@ async def test_feedback(capfire: CaptureLogfire) -> None: 'gen_ai.usage.input_tokens': 51, 'gen_ai.usage.output_tokens': 4, 'gen_ai.response.model': 'test', - 'events': '[{"content": "Hello", "role": "user", "gen_ai.system": "test", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": [{"kind": "text", "text": "success (no tool calls)"}]}, "gen_ai.system": "test", "event.name": "gen_ai.choice"}]', + 'events': '[{"content": "Hello", "role": "user", "gen_ai.system": "test", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"index": 0, "message": {"role": "assistant", "content": "success (no tool calls)"}, "gen_ai.system": "test", "event.name": "gen_ai.choice"}]', 'logfire.json_schema': '{"type": "object", "properties": {"events": {"type": "array"}, "model_request_parameters": {"type": "object"}}}', }, }, @@ -611,7 +611,7 @@ async def test_feedback(capfire: CaptureLogfire) -> None: 'logfire.msg': 'agent run', 'logfire.span_type': 'span', 'gen_ai.usage.input_tokens': 51, - 'all_messages_events': '[{"content": "Hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": [{"kind": "text", "text": "success (no tool calls)"}], "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', + 'all_messages_events': '[{"content": "Hello", "role": "user", "gen_ai.message.index": 0, "event.name": "gen_ai.user.message"}, {"role": "assistant", "content": "success (no tool calls)", "gen_ai.message.index": 1, "event.name": "gen_ai.assistant.message"}]', 'gen_ai.usage.output_tokens': 4, 'final_result': 'success (no tool calls)', 'logfire.json_schema': '{"type": "object", "properties": {"all_messages_events": {"type": "array"}, "final_result": {"type": "object"}}}', From 7996891078d732f7b96e9dea2fb4ccf8a9a18986 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 18:57:11 +0530 Subject: [PATCH 06/28] refactoring --- pydantic_ai_slim/pydantic_ai/messages.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index f8d7dd45d..8b5f4fbc2 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -755,16 +755,18 @@ def new_event_body(): if not settings.include_content: body.setdefault('content', []).append({'kind': kind}) else: - if existing_content: - if isinstance(existing_content, list): - body.setdefault('content', []).append({'kind': kind, 'text': part.content}) - elif isinstance(existing_content, str): - body['content'] = [{'kind': 'text', 'text': existing_content}, {'kind': kind, 'text': part.content}] - else: - if isinstance(part, TextPart): - body['content'] = part.content - elif isinstance(part, ThinkingPart): - body['content'] = [{'kind': kind, 'text': part.content}] + if not existing_content: + body['content'] = ( + part.content if isinstance(part, TextPart) + else [{'kind': kind, 'text': part.content}] + ) + elif isinstance(existing_content, list): + existing_content.append({'kind': kind, 'text': part.content}) + elif isinstance(existing_content, str): + body['content'] = [ + {'kind': 'text', 'text': existing_content}, + {'kind': kind, 'text': part.content} + ] return result From 7b8c7f10828e5da14fddd0147bbbc8f5990c21b0 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:22:25 +0530 Subject: [PATCH 07/28] lint --- pydantic_ai_slim/pydantic_ai/messages.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 8b5f4fbc2..cedad788a 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -757,15 +757,14 @@ def new_event_body(): else: if not existing_content: body['content'] = ( - part.content if isinstance(part, TextPart) - else [{'kind': kind, 'text': part.content}] + part.content if isinstance(part, TextPart) else [{'kind': kind, 'text': part.content}] ) elif isinstance(existing_content, list): existing_content.append({'kind': kind, 'text': part.content}) elif isinstance(existing_content, str): body['content'] = [ {'kind': 'text', 'text': existing_content}, - {'kind': kind, 'text': part.content} + {'kind': kind, 'text': part.content}, ] return result From 1bb2f7bb4f9fee8d68a4134dca78d774c7e1d7c1 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:24:54 +0530 Subject: [PATCH 08/28] adding in test --- tests/models/test_instrumented.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 67c3abf54..326d2003a 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -835,6 +835,7 @@ def test_messages_without_content(document_content: BinaryContent): messages: list[ModelMessage] = [ ModelRequest(parts=[SystemPromptPart('system_prompt')]), ModelResponse(parts=[TextPart('text1')]), + ModelResponse(parts=[ThinkingPart('thinking_1')]), ModelRequest( parts=[ UserPromptPart( @@ -849,7 +850,13 @@ def test_messages_without_content(document_content: BinaryContent): ) ] ), - ModelResponse(parts=[TextPart('text2'), ToolCallPart(tool_name='my_tool', args={'a': 13, 'b': 4})]), + ModelResponse( + parts=[ + TextPart('text2'), + ThinkingPart('thinking_2'), + ToolCallPart(tool_name='my_tool', args={'a': 13, 'b': 4}), + ] + ), ModelRequest(parts=[ToolReturnPart('tool', 'tool_return_content', 'tool_call_1')]), ModelRequest(parts=[RetryPromptPart('retry_prompt', tool_name='tool', tool_call_id='tool_call_2')]), ModelRequest(parts=[UserPromptPart(content=['user_prompt2', document_content])]), @@ -869,6 +876,12 @@ def test_messages_without_content(document_content: BinaryContent): 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, + { + 'role': 'assistant', + 'content': [{'kind': 'thinking'}], + 'gen_ai.message.index': 2, + 'event.name': 'gen_ai.assistant.message', + }, { 'content': [ {'kind': 'text'}, @@ -879,46 +892,46 @@ def test_messages_without_content(document_content: BinaryContent): {'kind': 'binary', 'media_type': 'application/pdf'}, ], 'role': 'user', - 'gen_ai.message.index': 2, + 'gen_ai.message.index': 3, 'event.name': 'gen_ai.user.message', }, { 'role': 'assistant', - 'content': [{'kind': 'text'}], + 'content': [{'kind': 'text'}, {'kind': 'thinking'}], 'tool_calls': [ { - 'id': IsStr(), + 'id': 'pyd_ai_e81c1ed5cc4c4bb8b02f53369e349298', 'type': 'function', 'function': {'name': 'my_tool'}, } ], - 'gen_ai.message.index': 3, + 'gen_ai.message.index': 4, 'event.name': 'gen_ai.assistant.message', }, { 'role': 'tool', 'id': 'tool_call_1', 'name': 'tool', - 'gen_ai.message.index': 4, + 'gen_ai.message.index': 5, 'event.name': 'gen_ai.tool.message', }, { 'role': 'tool', 'id': 'tool_call_2', 'name': 'tool', - 'gen_ai.message.index': 5, + 'gen_ai.message.index': 6, 'event.name': 'gen_ai.tool.message', }, { 'content': [{'kind': 'text'}, {'kind': 'binary', 'media_type': 'application/pdf'}], 'role': 'user', - 'gen_ai.message.index': 6, + 'gen_ai.message.index': 7, 'event.name': 'gen_ai.user.message', }, { 'content': {'kind': 'text'}, 'role': 'user', - 'gen_ai.message.index': 7, + 'gen_ai.message.index': 8, 'event.name': 'gen_ai.user.message', }, ] From 1fa73505ea75658925f17fc06f5c16d7c4efa38d Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:28:33 +0530 Subject: [PATCH 09/28] type fix --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index cedad788a..7d60711b9 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -751,7 +751,7 @@ def new_event_body(): ) elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' - existing_content = body.get('content') + existing_content = body.get('content', []) if not settings.include_content: body.setdefault('content', []).append({'kind': kind}) else: From 0652bfff751b272bba03d3263d86191bc18f3b1b Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:34:15 +0530 Subject: [PATCH 10/28] type fix --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 7d60711b9..61fae5eb3 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -760,7 +760,7 @@ def new_event_body(): part.content if isinstance(part, TextPart) else [{'kind': kind, 'text': part.content}] ) elif isinstance(existing_content, list): - existing_content.append({'kind': kind, 'text': part.content}) + body['content'] = existing_content + [{'kind': kind, 'text': part.content}] elif isinstance(existing_content, str): body['content'] = [ {'kind': 'text', 'text': existing_content}, From ab27865fad8f0044ac97ea8831c513bd7acadfd6 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:35:27 +0530 Subject: [PATCH 11/28] test fix --- tests/models/test_instrumented.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 326d2003a..29aabc727 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -900,7 +900,7 @@ def test_messages_without_content(document_content: BinaryContent): 'content': [{'kind': 'text'}, {'kind': 'thinking'}], 'tool_calls': [ { - 'id': 'pyd_ai_e81c1ed5cc4c4bb8b02f53369e349298', + 'id': IsStr(), 'type': 'function', 'function': {'name': 'my_tool'}, } From 8c8c5448e6e9c600fdd436d268533b73e86957dc Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 19:38:09 +0530 Subject: [PATCH 12/28] removing [] value from get --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 61fae5eb3..7b39bac49 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -751,7 +751,7 @@ def new_event_body(): ) elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' - existing_content = body.get('content', []) + existing_content = body.get('content') if not settings.include_content: body.setdefault('content', []).append({'kind': kind}) else: From 1cc819dc7d44649295edf0efea761a4c85b185d0 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Fri, 18 Jul 2025 20:22:42 +0530 Subject: [PATCH 13/28] Additional test scenarios --- pydantic_ai_slim/pydantic_ai/messages.py | 6 +++--- tests/models/test_instrumented.py | 26 ++++++++++++++++++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 7b39bac49..dfd076da2 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -752,9 +752,7 @@ def new_event_body(): elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' existing_content = body.get('content') - if not settings.include_content: - body.setdefault('content', []).append({'kind': kind}) - else: + if settings.include_content: if not existing_content: body['content'] = ( part.content if isinstance(part, TextPart) else [{'kind': kind, 'text': part.content}] @@ -766,6 +764,8 @@ def new_event_body(): {'kind': 'text', 'text': existing_content}, {'kind': kind, 'text': part.content}, ] + else: + body.setdefault('content', []).append({'kind': kind}) return result diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 29aabc727..53a9a3d09 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -715,7 +715,8 @@ def test_messages_to_otel_events_instructions(): def test_messages_to_otel_events_instructions_multiple_messages(): messages = [ ModelRequest(instructions='instructions', parts=[UserPromptPart('user_prompt')]), - ModelResponse(parts=[TextPart('text1')]), + ModelResponse(parts=[ThinkingPart('thinking_1'), TextPart('text1')]), + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1')]), ModelRequest(instructions='instructions2', parts=[UserPromptPart('user_prompt2')]), ] settings = InstrumentationSettings() @@ -725,11 +726,17 @@ def test_messages_to_otel_events_instructions_multiple_messages(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': 'text1', + 'content': [{'kind': 'thinking', 'text': 'thinking_1'}, {'kind': 'text', 'text': 'text1'}], 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, - {'content': 'user_prompt2', 'role': 'user', 'gen_ai.message.index': 2, 'event.name': 'gen_ai.user.message'}, + { + 'role': 'assistant', + 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking_1'}], + 'gen_ai.message.index': 2, + 'event.name': 'gen_ai.assistant.message', + }, + {'content': 'user_prompt2', 'role': 'user', 'gen_ai.message.index': 3, 'event.name': 'gen_ai.user.message'}, ] ) @@ -754,6 +761,7 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): ] ), ModelRequest(parts=[UserPromptPart(content=['user_prompt6', document_content])]), + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1'), TextPart('text_2')]), ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1')]), ] settings = InstrumentationSettings() @@ -806,10 +814,20 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): }, { 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking1'}], + 'content': [ + {'kind': 'text', 'text': 'text1'}, + {'kind': 'thinking', 'text': 'thinking1'}, + {'kind': 'text', 'text': 'text_2'}, + ], 'gen_ai.message.index': 6, 'event.name': 'gen_ai.assistant.message', }, + { + 'role': 'assistant', + 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking1'}], + 'gen_ai.message.index': 7, + 'event.name': 'gen_ai.assistant.message', + }, ] ) From f933d15edc25f436fb745b6de2e62309b840ab92 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Mon, 21 Jul 2025 18:27:15 +0530 Subject: [PATCH 14/28] changing test order for minimizing diff --- tests/models/test_instrumented.py | 32 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 53a9a3d09..ea490445e 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -853,7 +853,6 @@ def test_messages_without_content(document_content: BinaryContent): messages: list[ModelMessage] = [ ModelRequest(parts=[SystemPromptPart('system_prompt')]), ModelResponse(parts=[TextPart('text1')]), - ModelResponse(parts=[ThinkingPart('thinking_1')]), ModelRequest( parts=[ UserPromptPart( @@ -879,6 +878,7 @@ def test_messages_without_content(document_content: BinaryContent): ModelRequest(parts=[RetryPromptPart('retry_prompt', tool_name='tool', tool_call_id='tool_call_2')]), ModelRequest(parts=[UserPromptPart(content=['user_prompt2', document_content])]), ModelRequest(parts=[UserPromptPart('simple text prompt')]), + ModelResponse(parts=[ThinkingPart('thinking_1')]), ] settings = InstrumentationSettings(include_content=False) assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -895,12 +895,7 @@ def test_messages_without_content(document_content: BinaryContent): 'event.name': 'gen_ai.assistant.message', }, { - 'role': 'assistant', - 'content': [{'kind': 'thinking'}], - 'gen_ai.message.index': 2, - 'event.name': 'gen_ai.assistant.message', - }, - { + 'role': 'user', 'content': [ {'kind': 'text'}, {'kind': 'video-url'}, @@ -909,48 +904,53 @@ def test_messages_without_content(document_content: BinaryContent): {'kind': 'document-url'}, {'kind': 'binary', 'media_type': 'application/pdf'}, ], - 'role': 'user', - 'gen_ai.message.index': 3, + 'gen_ai.message.index': 2, 'event.name': 'gen_ai.user.message', }, { - 'role': 'assistant', 'content': [{'kind': 'text'}, {'kind': 'thinking'}], + 'role': 'assistant', 'tool_calls': [ { - 'id': IsStr(), + 'id': 'pyd_ai_c7fd195c5b974741b553d1e126c6cb63', 'type': 'function', 'function': {'name': 'my_tool'}, } ], - 'gen_ai.message.index': 4, + 'gen_ai.message.index': 3, 'event.name': 'gen_ai.assistant.message', }, { 'role': 'tool', 'id': 'tool_call_1', 'name': 'tool', - 'gen_ai.message.index': 5, + 'gen_ai.message.index': 4, 'event.name': 'gen_ai.tool.message', }, { 'role': 'tool', 'id': 'tool_call_2', 'name': 'tool', - 'gen_ai.message.index': 6, + 'gen_ai.message.index': 5, 'event.name': 'gen_ai.tool.message', }, { 'content': [{'kind': 'text'}, {'kind': 'binary', 'media_type': 'application/pdf'}], 'role': 'user', - 'gen_ai.message.index': 7, + 'gen_ai.message.index': 6, 'event.name': 'gen_ai.user.message', }, { 'content': {'kind': 'text'}, 'role': 'user', - 'gen_ai.message.index': 8, + 'gen_ai.message.index': 7, 'event.name': 'gen_ai.user.message', }, + { + 'content': [{'kind': 'thinking'}], + 'role': 'assistant', + 'gen_ai.message.index': 8, + 'event.name': 'gen_ai.assistant.message', + }, ] ) From 527d27cfb0780f4ab66cb5c357c5f0dd188a3857 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Mon, 21 Jul 2025 18:31:22 +0530 Subject: [PATCH 15/28] adding test scenarios --- tests/models/test_instrumented.py | 37 ++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index ea490445e..54de9e509 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -696,6 +696,9 @@ def test_messages_to_otel_events_instructions(): messages = [ ModelRequest(instructions='instructions', parts=[UserPromptPart('user_prompt')]), ModelResponse(parts=[TextPart('text1')]), + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1')]), + ModelResponse(parts=[TextPart('text1'), ToolCallPart('my_tool', {'a': 13, 'b': 4})]), + ModelResponse(parts=[ToolCallPart('my_tool', {'a': 13, 'b': 4}), TextPart('text1')]), ] settings = InstrumentationSettings() assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -708,6 +711,38 @@ def test_messages_to_otel_events_instructions(): 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, + { + 'role': 'assistant', + 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking_1'}], + 'gen_ai.message.index': 2, + 'event.name': 'gen_ai.assistant.message', + }, + { + 'role': 'assistant', + 'content': 'text1', + 'tool_calls': [ + { + 'id': IsStr(), + 'type': 'function', + 'function': {'name': 'my_tool', 'arguments': {'a': 13, 'b': 4}}, + } + ], + 'gen_ai.message.index': 3, + 'event.name': 'gen_ai.assistant.message', + }, + { + 'role': 'assistant', + 'tool_calls': [ + { + 'id': IsStr(), + 'type': 'function', + 'function': {'name': 'my_tool', 'arguments': {'a': 13, 'b': 4}}, + } + ], + 'content': 'text1', + 'gen_ai.message.index': 4, + 'event.name': 'gen_ai.assistant.message', + }, ] ) @@ -912,7 +947,7 @@ def test_messages_without_content(document_content: BinaryContent): 'role': 'assistant', 'tool_calls': [ { - 'id': 'pyd_ai_c7fd195c5b974741b553d1e126c6cb63', + 'id': IsStr(), 'type': 'function', 'function': {'name': 'my_tool'}, } From 04e2858f1edd94c1587c75d2ae3118a3c953c551 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Mon, 21 Jul 2025 19:55:50 +0530 Subject: [PATCH 16/28] coverage adding empty parts list --- tests/models/test_instrumented.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 54de9e509..e9c328b8e 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -989,3 +989,14 @@ def test_messages_without_content(document_content: BinaryContent): }, ] ) + + +def test_model_response_empty_parts_otel_events(): + """Test otel_events method with empty parts list.""" + response = ModelResponse(parts=[]) + settings = InstrumentationSettings() + events = response.otel_events(settings) + + assert len(events) == 1 + event_dict = InstrumentedModel.event_to_dict(events[0]) + assert event_dict == snapshot({'role': 'assistant', 'event.name': 'gen_ai.assistant.message'}) From 79a78e6545121da93e0cdff1ced3f2697936ac5a Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Mon, 21 Jul 2025 20:11:28 +0530 Subject: [PATCH 17/28] Adding test part --- tests/models/test_instrumented.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index e9c328b8e..e770fec94 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -699,6 +699,7 @@ def test_messages_to_otel_events_instructions(): ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1')]), ModelResponse(parts=[TextPart('text1'), ToolCallPart('my_tool', {'a': 13, 'b': 4})]), ModelResponse(parts=[ToolCallPart('my_tool', {'a': 13, 'b': 4}), TextPart('text1')]), + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1'), TextPart('text1')]), ] settings = InstrumentationSettings() assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -743,6 +744,16 @@ def test_messages_to_otel_events_instructions(): 'gen_ai.message.index': 4, 'event.name': 'gen_ai.assistant.message', }, + { + 'role': 'assistant', + 'content': [ + {'kind': 'text', 'text': 'text1'}, + {'kind': 'thinking', 'text': 'thinking_1'}, + {'kind': 'text', 'text': 'text1'}, + ], + 'gen_ai.message.index': 5, + 'event.name': 'gen_ai.assistant.message', + }, ] ) From aa4bfbd968376078f5d4f5ea07b5d2c5db72a3e7 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Mon, 21 Jul 2025 21:13:34 +0530 Subject: [PATCH 18/28] adding cast instead of list concact --- pydantic_ai_slim/pydantic_ai/messages.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 07b725f73..db7da32a0 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -766,7 +766,9 @@ def new_event_body(): part.content if isinstance(part, TextPart) else [{'kind': kind, 'text': part.content}] ) elif isinstance(existing_content, list): - body['content'] = existing_content + [{'kind': kind, 'text': part.content}] + existing_content = cast(list[dict[str, Any]], existing_content) + existing_content.append({'kind': kind, 'text': part.content}) + body['content'] = existing_content elif isinstance(existing_content, str): body['content'] = [ {'kind': 'text', 'text': existing_content}, From dc6ec6e4a745db05ec388889f8bb59876cade8e7 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 18:38:32 +0530 Subject: [PATCH 19/28] diff --- tests/models/test_instrumented.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index e770fec94..493433082 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -403,10 +403,7 @@ async def test_instrumented_model_stream(capfire: CaptureLogfire): 'trace_flags': 1, }, { - 'body': { - 'index': 0, - 'message': {'role': 'assistant', 'content': 'text1text2'}, - }, + 'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text1text2'}}, 'severity_number': 9, 'severity_text': None, 'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'}, From 02e175b613346d72f3d19aa225425ffc2863fb0c Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 19:26:09 +0530 Subject: [PATCH 20/28] refactorting --- pydantic_ai_slim/pydantic_ai/messages.py | 30 +++++++++++------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index db7da32a0..e4d4c94d1 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -759,23 +759,21 @@ def new_event_body(): ) elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' - existing_content = body.get('content') - if settings.include_content: - if not existing_content: - body['content'] = ( - part.content if isinstance(part, TextPart) else [{'kind': kind, 'text': part.content}] - ) - elif isinstance(existing_content, list): - existing_content = cast(list[dict[str, Any]], existing_content) - existing_content.append({'kind': kind, 'text': part.content}) - body['content'] = existing_content - elif isinstance(existing_content, str): - body['content'] = [ - {'kind': 'text', 'text': existing_content}, - {'kind': kind, 'text': part.content}, - ] - else: + + if not settings.include_content: body.setdefault('content', []).append({'kind': kind}) + continue + + content_list = body.setdefault('content', []) + + if isinstance(content_list, str): + content_list = [{'kind': 'text', 'text': content_list}] + body['content'] = content_list + + content_list.append({'kind': kind, 'text': part.content}) + + if len(content_list) == 1 and isinstance(part, TextPart): + body['content'] = part.content return result From 700a37e5367b5d53c2d06ecc9947e58b9d81284f Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 19:44:04 +0530 Subject: [PATCH 21/28] rename var --- pydantic_ai_slim/pydantic_ai/messages.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index e4d4c94d1..09cb9eab3 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -764,15 +764,15 @@ def new_event_body(): body.setdefault('content', []).append({'kind': kind}) continue - content_list = body.setdefault('content', []) + content = body.setdefault('content', []) - if isinstance(content_list, str): - content_list = [{'kind': 'text', 'text': content_list}] - body['content'] = content_list + if isinstance(content, str): + content = [{'kind': 'text', 'text': content}] + body['content'] = content - content_list.append({'kind': kind, 'text': part.content}) + content.append({'kind': kind, 'text': part.content}) - if len(content_list) == 1 and isinstance(part, TextPart): + if len(content) == 1 and isinstance(part, TextPart): body['content'] = part.content return result From 4e11f4d5471e9bf608ca1fbed87bb688538ee781 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 19:50:05 +0530 Subject: [PATCH 22/28] simplifying --- pydantic_ai_slim/pydantic_ai/messages.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 09cb9eab3..41941c40f 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -764,16 +764,13 @@ def new_event_body(): body.setdefault('content', []).append({'kind': kind}) continue - content = body.setdefault('content', []) + existing = body.get('content', []) + if isinstance(existing, str): + existing = [{'kind': 'text', 'text': existing}] - if isinstance(content, str): - content = [{'kind': 'text', 'text': content}] - body['content'] = content + existing.append({'kind': kind, 'text': part.content}) - content.append({'kind': kind, 'text': part.content}) - - if len(content) == 1 and isinstance(part, TextPart): - body['content'] = part.content + body['content'] = part.content if len(existing) == 1 and isinstance(part, TextPart) else existing return result From dde80ae4e561129bbdf1e22f9bb0d1bbc4ee3847 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 20:25:21 +0530 Subject: [PATCH 23/28] simplifying --- pydantic_ai_slim/pydantic_ai/messages.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 41941c40f..830f01387 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -759,18 +759,13 @@ def new_event_body(): ) elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' + body.setdefault('content', []).append( + {'kind': kind, 'text': part.content} if settings.include_content else {'kind': kind} + ) - if not settings.include_content: - body.setdefault('content', []).append({'kind': kind}) - continue - - existing = body.get('content', []) - if isinstance(existing, str): - existing = [{'kind': 'text', 'text': existing}] - - existing.append({'kind': kind, 'text': part.content}) - - body['content'] = part.content if len(existing) == 1 and isinstance(part, TextPart) else existing + if len(body.get('content', [])) == 1 and body['content'][0]['kind'] == 'text' and settings.include_content: + # If there's only one text part, we can simplify the event body + body['content'] = body['content'][0]['text'] return result From 1c155f940ca78f49b092134c265b099869173e38 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 20:25:57 +0530 Subject: [PATCH 24/28] simplifying --- pydantic_ai_slim/pydantic_ai/messages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 830f01387..95efa5aaa 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -764,7 +764,6 @@ def new_event_body(): ) if len(body.get('content', [])) == 1 and body['content'][0]['kind'] == 'text' and settings.include_content: - # If there's only one text part, we can simplify the event body body['content'] = body['content'][0]['text'] return result From 3a3b1b6851121448ededb28c91362c0f463d2d1c Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 20:27:31 +0530 Subject: [PATCH 25/28] simplifying --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 95efa5aaa..4002d52cb 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -760,7 +760,7 @@ def new_event_body(): elif isinstance(part, (TextPart, ThinkingPart)): kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' body.setdefault('content', []).append( - {'kind': kind, 'text': part.content} if settings.include_content else {'kind': kind} + {'kind': kind, **({'text': part.content} if settings.include_content else {})} ) if len(body.get('content', [])) == 1 and body['content'][0]['kind'] == 'text' and settings.include_content: From 0ea60af128c91fc7c9dafad7907635b985c28272 Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 20:39:30 +0530 Subject: [PATCH 26/28] suggestion --- pydantic_ai_slim/pydantic_ai/messages.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 4002d52cb..9cba17ee2 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -763,8 +763,10 @@ def new_event_body(): {'kind': kind, **({'text': part.content} if settings.include_content else {})} ) - if len(body.get('content', [])) == 1 and body['content'][0]['kind'] == 'text' and settings.include_content: - body['content'] = body['content'][0]['text'] + if content := body.get('content'): + text_content = content[0].get('text') + if content == [{'kind': 'text', 'text': text_content}]: + body['content'] = text_content return result From c5b2f38ae0de6da05e58a045b614233ebe3bd1df Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Tue, 22 Jul 2025 20:54:53 +0530 Subject: [PATCH 27/28] reducing diff, extracting thinking part tests into a sep test --- tests/models/test_instrumented.py | 135 +++++++++--------------------- 1 file changed, 41 insertions(+), 94 deletions(-) diff --git a/tests/models/test_instrumented.py b/tests/models/test_instrumented.py index 493433082..0acba6f72 100644 --- a/tests/models/test_instrumented.py +++ b/tests/models/test_instrumented.py @@ -620,11 +620,11 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire): 'gen_ai.system': 'my_system', }, { + 'event.name': 'gen_ai.assistant.message', 'role': 'assistant', 'content': 'text3', - 'gen_ai.system': 'my_system', 'gen_ai.message.index': 1, - 'event.name': 'gen_ai.assistant.message', + 'gen_ai.system': 'my_system', }, { 'index': 0, @@ -693,10 +693,6 @@ def test_messages_to_otel_events_instructions(): messages = [ ModelRequest(instructions='instructions', parts=[UserPromptPart('user_prompt')]), ModelResponse(parts=[TextPart('text1')]), - ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1')]), - ModelResponse(parts=[TextPart('text1'), ToolCallPart('my_tool', {'a': 13, 'b': 4})]), - ModelResponse(parts=[ToolCallPart('my_tool', {'a': 13, 'b': 4}), TextPart('text1')]), - ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1'), TextPart('text1')]), ] settings = InstrumentationSettings() assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -709,48 +705,6 @@ def test_messages_to_otel_events_instructions(): 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, - { - 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking_1'}], - 'gen_ai.message.index': 2, - 'event.name': 'gen_ai.assistant.message', - }, - { - 'role': 'assistant', - 'content': 'text1', - 'tool_calls': [ - { - 'id': IsStr(), - 'type': 'function', - 'function': {'name': 'my_tool', 'arguments': {'a': 13, 'b': 4}}, - } - ], - 'gen_ai.message.index': 3, - 'event.name': 'gen_ai.assistant.message', - }, - { - 'role': 'assistant', - 'tool_calls': [ - { - 'id': IsStr(), - 'type': 'function', - 'function': {'name': 'my_tool', 'arguments': {'a': 13, 'b': 4}}, - } - ], - 'content': 'text1', - 'gen_ai.message.index': 4, - 'event.name': 'gen_ai.assistant.message', - }, - { - 'role': 'assistant', - 'content': [ - {'kind': 'text', 'text': 'text1'}, - {'kind': 'thinking', 'text': 'thinking_1'}, - {'kind': 'text', 'text': 'text1'}, - ], - 'gen_ai.message.index': 5, - 'event.name': 'gen_ai.assistant.message', - }, ] ) @@ -758,8 +712,7 @@ def test_messages_to_otel_events_instructions(): def test_messages_to_otel_events_instructions_multiple_messages(): messages = [ ModelRequest(instructions='instructions', parts=[UserPromptPart('user_prompt')]), - ModelResponse(parts=[ThinkingPart('thinking_1'), TextPart('text1')]), - ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking_1')]), + ModelResponse(parts=[TextPart('text1')]), ModelRequest(instructions='instructions2', parts=[UserPromptPart('user_prompt2')]), ] settings = InstrumentationSettings() @@ -769,17 +722,11 @@ def test_messages_to_otel_events_instructions_multiple_messages(): {'content': 'user_prompt', 'role': 'user', 'gen_ai.message.index': 0, 'event.name': 'gen_ai.user.message'}, { 'role': 'assistant', - 'content': [{'kind': 'thinking', 'text': 'thinking_1'}, {'kind': 'text', 'text': 'text1'}], + 'content': 'text1', 'gen_ai.message.index': 1, 'event.name': 'gen_ai.assistant.message', }, - { - 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking_1'}], - 'gen_ai.message.index': 2, - 'event.name': 'gen_ai.assistant.message', - }, - {'content': 'user_prompt2', 'role': 'user', 'gen_ai.message.index': 3, 'event.name': 'gen_ai.user.message'}, + {'content': 'user_prompt2', 'role': 'user', 'gen_ai.message.index': 2, 'event.name': 'gen_ai.user.message'}, ] ) @@ -804,8 +751,7 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): ] ), ModelRequest(parts=[UserPromptPart(content=['user_prompt6', document_content])]), - ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1'), TextPart('text_2')]), - ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1')]), + ModelResponse(parts=[TextPart('text1')]), ] settings = InstrumentationSettings() assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -857,20 +803,10 @@ def test_messages_to_otel_events_image_url(document_content: BinaryContent): }, { 'role': 'assistant', - 'content': [ - {'kind': 'text', 'text': 'text1'}, - {'kind': 'thinking', 'text': 'thinking1'}, - {'kind': 'text', 'text': 'text_2'}, - ], + 'content': 'text1', 'gen_ai.message.index': 6, 'event.name': 'gen_ai.assistant.message', }, - { - 'role': 'assistant', - 'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'thinking', 'text': 'thinking1'}], - 'gen_ai.message.index': 7, - 'event.name': 'gen_ai.assistant.message', - }, ] ) @@ -910,18 +846,11 @@ def test_messages_without_content(document_content: BinaryContent): ) ] ), - ModelResponse( - parts=[ - TextPart('text2'), - ThinkingPart('thinking_2'), - ToolCallPart(tool_name='my_tool', args={'a': 13, 'b': 4}), - ] - ), + ModelResponse(parts=[TextPart('text2'), ToolCallPart(tool_name='my_tool', args={'a': 13, 'b': 4})]), ModelRequest(parts=[ToolReturnPart('tool', 'tool_return_content', 'tool_call_1')]), ModelRequest(parts=[RetryPromptPart('retry_prompt', tool_name='tool', tool_call_id='tool_call_2')]), ModelRequest(parts=[UserPromptPart(content=['user_prompt2', document_content])]), ModelRequest(parts=[UserPromptPart('simple text prompt')]), - ModelResponse(parts=[ThinkingPart('thinking_1')]), ] settings = InstrumentationSettings(include_content=False) assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( @@ -938,7 +867,6 @@ def test_messages_without_content(document_content: BinaryContent): 'event.name': 'gen_ai.assistant.message', }, { - 'role': 'user', 'content': [ {'kind': 'text'}, {'kind': 'video-url'}, @@ -947,12 +875,13 @@ def test_messages_without_content(document_content: BinaryContent): {'kind': 'document-url'}, {'kind': 'binary', 'media_type': 'application/pdf'}, ], + 'role': 'user', 'gen_ai.message.index': 2, 'event.name': 'gen_ai.user.message', }, { - 'content': [{'kind': 'text'}, {'kind': 'thinking'}], 'role': 'assistant', + 'content': [{'kind': 'text'}], 'tool_calls': [ { 'id': IsStr(), @@ -989,22 +918,40 @@ def test_messages_without_content(document_content: BinaryContent): 'gen_ai.message.index': 7, 'event.name': 'gen_ai.user.message', }, + ] + ) + + +def test_message_with_thinking_parts(): + messages: list[ModelMessage] = [ + ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1'), TextPart('text2')]), + ModelResponse(parts=[ThinkingPart('thinking2')]), + ModelResponse(parts=[ThinkingPart('thinking3'), TextPart('text3')]), + ] + settings = InstrumentationSettings() + assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot( + [ { - 'content': [{'kind': 'thinking'}], 'role': 'assistant', - 'gen_ai.message.index': 8, + 'content': [ + {'kind': 'text', 'text': 'text1'}, + {'kind': 'thinking', 'text': 'thinking1'}, + {'kind': 'text', 'text': 'text2'}, + ], + 'gen_ai.message.index': 0, + 'event.name': 'gen_ai.assistant.message', + }, + { + 'role': 'assistant', + 'content': [{'kind': 'thinking', 'text': 'thinking2'}], + 'gen_ai.message.index': 1, + 'event.name': 'gen_ai.assistant.message', + }, + { + 'role': 'assistant', + 'content': [{'kind': 'thinking', 'text': 'thinking3'}, {'kind': 'text', 'text': 'text3'}], + 'gen_ai.message.index': 2, 'event.name': 'gen_ai.assistant.message', }, ] ) - - -def test_model_response_empty_parts_otel_events(): - """Test otel_events method with empty parts list.""" - response = ModelResponse(parts=[]) - settings = InstrumentationSettings() - events = response.otel_events(settings) - - assert len(events) == 1 - event_dict = InstrumentedModel.event_to_dict(events[0]) - assert event_dict == snapshot({'role': 'assistant', 'event.name': 'gen_ai.assistant.message'}) From 7a196916a0e7fd75bca39a6d5ecff5cc84eb3b4a Mon Sep 17 00:00:00 2001 From: adtyavrdhn Date: Wed, 23 Jul 2025 21:38:00 +0530 Subject: [PATCH 28/28] fix: --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 9cba17ee2..02483d128 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -758,7 +758,7 @@ def new_event_body(): } ) elif isinstance(part, (TextPart, ThinkingPart)): - kind = 'thinking' if isinstance(part, ThinkingPart) else 'text' + kind = part.part_kind body.setdefault('content', []).append( {'kind': kind, **({'text': part.content} if settings.include_content else {})} )