Skip to content

Adding thinkingpart to otel_events in ModelResponse #2237

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 42 commits into from
Jul 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
d51c65b
Adding thinkingpart to otel_events in ModelResponse
adtyavrdhn Jul 17, 2025
61ea0bf
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 17, 2025
19876e5
fixing logfire tests
adtyavrdhn Jul 17, 2025
3ee7dee
Merge branch 'thinking_blocks_spans' of https://github.com/adtyavrdhn…
adtyavrdhn Jul 17, 2025
83d3398
fixing fallback tests
adtyavrdhn Jul 17, 2025
ebbf373
fix for python3.9
adtyavrdhn Jul 17, 2025
608fc13
changes as discussed
adtyavrdhn Jul 18, 2025
809db07
Merge branch 'main' of https://github.com/pydantic/pydantic-ai into t…
adtyavrdhn Jul 18, 2025
7996891
refactoring
adtyavrdhn Jul 18, 2025
7b8c7f1
lint
adtyavrdhn Jul 18, 2025
1bb2f7b
adding in test
adtyavrdhn Jul 18, 2025
1fa7350
type fix
adtyavrdhn Jul 18, 2025
0652bff
type fix
adtyavrdhn Jul 18, 2025
ab27865
test fix
adtyavrdhn Jul 18, 2025
8c8c544
removing [] value from get
adtyavrdhn Jul 18, 2025
1cc819d
Additional test scenarios
adtyavrdhn Jul 18, 2025
3e77993
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 21, 2025
f933d15
changing test order for minimizing diff
adtyavrdhn Jul 21, 2025
527d27c
adding test scenarios
adtyavrdhn Jul 21, 2025
2935889
Merge branch 'thinking_blocks_spans' of https://github.com/adtyavrdhn…
adtyavrdhn Jul 21, 2025
04e2858
coverage adding empty parts list
adtyavrdhn Jul 21, 2025
702958b
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 21, 2025
79a78e6
Adding test part
adtyavrdhn Jul 21, 2025
d5e58cc
Merge branch 'main' of https://github.com/pydantic/pydantic-ai into t…
adtyavrdhn Jul 21, 2025
fad0684
Merge branch 'thinking_blocks_spans' of https://github.com/adtyavrdhn…
adtyavrdhn Jul 21, 2025
aa4bfbd
adding cast instead of list concact
adtyavrdhn Jul 21, 2025
da7a36a
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 22, 2025
dc6ec6e
diff
adtyavrdhn Jul 22, 2025
7806405
Merge branch 'thinking_blocks_spans' of https://github.com/adtyavrdhn…
adtyavrdhn Jul 22, 2025
02e175b
refactorting
adtyavrdhn Jul 22, 2025
700a37e
rename var
adtyavrdhn Jul 22, 2025
4e11f4d
simplifying
adtyavrdhn Jul 22, 2025
dde80ae
simplifying
adtyavrdhn Jul 22, 2025
1c155f9
simplifying
adtyavrdhn Jul 22, 2025
3a3b1b6
simplifying
adtyavrdhn Jul 22, 2025
33219b6
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 22, 2025
0ea60af
suggestion
adtyavrdhn Jul 22, 2025
05b7a83
Merge branch 'thinking_blocks_spans' of https://github.com/adtyavrdhn…
adtyavrdhn Jul 22, 2025
c5b2f38
reducing diff, extracting thinking part tests into a sep test
adtyavrdhn Jul 22, 2025
7a19691
fix:
adtyavrdhn Jul 23, 2025
09ac846
Merge branch 'main' into thinking_blocks_spans
adtyavrdhn Jul 23, 2025
c23f14a
Merge branch 'main' into thinking_blocks_spans
alexmojaki Jul 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions pydantic_ai_slim/pydantic_ai/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,11 +815,16 @@ def new_event_body():
},
}
)
elif isinstance(part, TextPart):
if body.get('content'):
body = new_event_body()
if settings.include_content:
body['content'] = part.content
elif isinstance(part, (TextPart, ThinkingPart)):
kind = part.part_kind
body.setdefault('content', []).append(
{'kind': kind, **({'text': part.content} if settings.include_content else {})}
)

if content := body.get('content'):
text_content = content[0].get('text')
if content == [{'kind': 'text', 'text': text_content}]:
body['content'] = text_content

return result

Expand Down
64 changes: 44 additions & 20 deletions tests/models/test_instrumented.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
SystemPromptPart,
TextPart,
TextPartDelta,
ThinkingPart,
ToolCallPart,
ToolReturnPart,
UserPromptPart,
Expand Down Expand Up @@ -150,7 +151,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',
Expand Down Expand Up @@ -284,7 +285,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',
Expand All @@ -308,17 +309,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,
},
]
)

Expand Down Expand Up @@ -641,11 +631,13 @@ 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': 'text1',
'content': [
{'kind': 'text', 'text': 'text1'},
{'kind': 'text', 'text': 'text2'},
],
'tool_calls': [
{
'id': 'tool_call_1',
Expand All @@ -660,12 +652,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',
},
]
)
Expand Down Expand Up @@ -879,6 +866,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',
},
Expand All @@ -897,6 +885,7 @@ def test_messages_without_content(document_content: BinaryContent):
},
{
'role': 'assistant',
'content': [{'kind': 'text'}],
'tool_calls': [
{
'id': IsStr(),
Expand Down Expand Up @@ -935,3 +924,38 @@ def test_messages_without_content(document_content: BinaryContent):
},
]
)


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(
[
{
'role': 'assistant',
'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',
},
]
)