Skip to content

Commit 6e60677

Browse files
Adding thinkingpart to otel_events in ModelResponse (#2237)
Co-authored-by: Alex Hall <[email protected]>
1 parent 4941468 commit 6e60677

File tree

2 files changed

+54
-25
lines changed

2 files changed

+54
-25
lines changed

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -815,11 +815,16 @@ def new_event_body():
815815
},
816816
}
817817
)
818-
elif isinstance(part, TextPart):
819-
if body.get('content'):
820-
body = new_event_body()
821-
if settings.include_content:
822-
body['content'] = part.content
818+
elif isinstance(part, (TextPart, ThinkingPart)):
819+
kind = part.part_kind
820+
body.setdefault('content', []).append(
821+
{'kind': kind, **({'text': part.content} if settings.include_content else {})}
822+
)
823+
824+
if content := body.get('content'):
825+
text_content = content[0].get('text')
826+
if content == [{'kind': 'text', 'text': text_content}]:
827+
body['content'] = text_content
823828

824829
return result
825830

tests/models/test_instrumented.py

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
SystemPromptPart,
2727
TextPart,
2828
TextPartDelta,
29+
ThinkingPart,
2930
ToolCallPart,
3031
ToolReturnPart,
3132
UserPromptPart,
@@ -150,7 +151,7 @@ async def test_instrumented_model(capfire: CaptureLogfire):
150151
'context': {'trace_id': 1, 'span_id': 1, 'is_remote': False},
151152
'parent': None,
152153
'start_time': 1000000000,
153-
'end_time': 18000000000,
154+
'end_time': 16000000000,
154155
'attributes': {
155156
'gen_ai.operation.name': 'chat',
156157
'gen_ai.system': 'my_system',
@@ -284,7 +285,7 @@ async def test_instrumented_model(capfire: CaptureLogfire):
284285
'index': 0,
285286
'message': {
286287
'role': 'assistant',
287-
'content': 'text1',
288+
'content': [{'kind': 'text', 'text': 'text1'}, {'kind': 'text', 'text': 'text2'}],
288289
'tool_calls': [
289290
{
290291
'id': 'tool_call_1',
@@ -308,17 +309,6 @@ async def test_instrumented_model(capfire: CaptureLogfire):
308309
'span_id': 1,
309310
'trace_flags': 1,
310311
},
311-
{
312-
'body': {'index': 0, 'message': {'role': 'assistant', 'content': 'text2'}},
313-
'severity_number': 9,
314-
'severity_text': None,
315-
'attributes': {'gen_ai.system': 'my_system', 'event.name': 'gen_ai.choice'},
316-
'timestamp': 16000000000,
317-
'observed_timestamp': 17000000000,
318-
'trace_id': 1,
319-
'span_id': 1,
320-
'trace_flags': 1,
321-
},
322312
]
323313
)
324314

@@ -641,11 +631,13 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire):
641631
'gen_ai.system': 'my_system',
642632
},
643633
{
644-
'event.name': 'gen_ai.choice',
645634
'index': 0,
646635
'message': {
647636
'role': 'assistant',
648-
'content': 'text1',
637+
'content': [
638+
{'kind': 'text', 'text': 'text1'},
639+
{'kind': 'text', 'text': 'text2'},
640+
],
649641
'tool_calls': [
650642
{
651643
'id': 'tool_call_1',
@@ -660,12 +652,7 @@ async def test_instrumented_model_attributes_mode(capfire: CaptureLogfire):
660652
],
661653
},
662654
'gen_ai.system': 'my_system',
663-
},
664-
{
665655
'event.name': 'gen_ai.choice',
666-
'index': 0,
667-
'message': {'role': 'assistant', 'content': 'text2'},
668-
'gen_ai.system': 'my_system',
669656
},
670657
]
671658
)
@@ -879,6 +866,7 @@ def test_messages_without_content(document_content: BinaryContent):
879866
},
880867
{
881868
'role': 'assistant',
869+
'content': [{'kind': 'text'}],
882870
'gen_ai.message.index': 1,
883871
'event.name': 'gen_ai.assistant.message',
884872
},
@@ -897,6 +885,7 @@ def test_messages_without_content(document_content: BinaryContent):
897885
},
898886
{
899887
'role': 'assistant',
888+
'content': [{'kind': 'text'}],
900889
'tool_calls': [
901890
{
902891
'id': IsStr(),
@@ -935,3 +924,38 @@ def test_messages_without_content(document_content: BinaryContent):
935924
},
936925
]
937926
)
927+
928+
929+
def test_message_with_thinking_parts():
930+
messages: list[ModelMessage] = [
931+
ModelResponse(parts=[TextPart('text1'), ThinkingPart('thinking1'), TextPart('text2')]),
932+
ModelResponse(parts=[ThinkingPart('thinking2')]),
933+
ModelResponse(parts=[ThinkingPart('thinking3'), TextPart('text3')]),
934+
]
935+
settings = InstrumentationSettings()
936+
assert [InstrumentedModel.event_to_dict(e) for e in settings.messages_to_otel_events(messages)] == snapshot(
937+
[
938+
{
939+
'role': 'assistant',
940+
'content': [
941+
{'kind': 'text', 'text': 'text1'},
942+
{'kind': 'thinking', 'text': 'thinking1'},
943+
{'kind': 'text', 'text': 'text2'},
944+
],
945+
'gen_ai.message.index': 0,
946+
'event.name': 'gen_ai.assistant.message',
947+
},
948+
{
949+
'role': 'assistant',
950+
'content': [{'kind': 'thinking', 'text': 'thinking2'}],
951+
'gen_ai.message.index': 1,
952+
'event.name': 'gen_ai.assistant.message',
953+
},
954+
{
955+
'role': 'assistant',
956+
'content': [{'kind': 'thinking', 'text': 'thinking3'}, {'kind': 'text', 'text': 'text3'}],
957+
'gen_ai.message.index': 2,
958+
'event.name': 'gen_ai.assistant.message',
959+
},
960+
]
961+
)

0 commit comments

Comments
 (0)