Skip to content

Commit 8acfd67

Browse files
authored
fix(core): add type key when tracing in some cases (#31825)
1 parent af3789b commit 8acfd67

File tree

2 files changed

+82
-16
lines changed

2 files changed

+82
-16
lines changed

libs/core/langchain_core/language_models/chat_models.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,9 @@ def _generate_response_from_error(error: BaseException) -> list[ChatGeneration]:
111111
def _format_for_tracing(messages: list[BaseMessage]) -> list[BaseMessage]:
112112
"""Format messages for tracing in on_chat_model_start.
113113
114-
For backward compatibility, we update image content blocks to OpenAI Chat
115-
Completions format.
114+
- Update image content blocks to OpenAI Chat Completions format (backward
115+
compatibility).
116+
- Add "type" key to content blocks that have a single key.
116117
117118
Args:
118119
messages: List of messages to format.
@@ -125,20 +126,36 @@ def _format_for_tracing(messages: list[BaseMessage]) -> list[BaseMessage]:
125126
message_to_trace = message
126127
if isinstance(message.content, list):
127128
for idx, block in enumerate(message.content):
128-
if (
129-
isinstance(block, dict)
130-
and block.get("type") == "image"
131-
and is_data_content_block(block)
132-
and block.get("source_type") != "id"
133-
):
134-
if message_to_trace is message:
135-
message_to_trace = message.model_copy()
136-
# Also shallow-copy content
137-
message_to_trace.content = list(message_to_trace.content)
138-
139-
message_to_trace.content[idx] = ( # type: ignore[index] # mypy confused by .model_copy
140-
convert_to_openai_image_block(block)
141-
)
129+
if isinstance(block, dict):
130+
# Update image content blocks to OpenAI # Chat Completions format.
131+
if (
132+
block.get("type") == "image"
133+
and is_data_content_block(block)
134+
and block.get("source_type") != "id"
135+
):
136+
if message_to_trace is message:
137+
# Shallow copy
138+
message_to_trace = message.model_copy()
139+
message_to_trace.content = list(message_to_trace.content)
140+
141+
message_to_trace.content[idx] = ( # type: ignore[index] # mypy confused by .model_copy
142+
convert_to_openai_image_block(block)
143+
)
144+
elif len(block) == 1 and "type" not in block:
145+
# Tracing assumes all content blocks have a "type" key. Here
146+
# we add this key if it is missing, and there's an obvious
147+
# choice for the type (e.g., a single key in the block).
148+
if message_to_trace is message:
149+
# Shallow copy
150+
message_to_trace = message.model_copy()
151+
message_to_trace.content = list(message_to_trace.content)
152+
key = next(iter(block))
153+
message_to_trace.content[idx] = { # type: ignore[index]
154+
"type": key,
155+
key: block[key],
156+
}
157+
else:
158+
pass
142159
messages_to_trace.append(message_to_trace)
143160

144161
return messages_to_trace

libs/core/tests/unit_tests/language_models/chat_models/test_base.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,55 @@ def test_trace_images_in_openai_format() -> None:
467467
]
468468

469469

470+
def test_trace_content_blocks_with_no_type_key() -> None:
471+
"""Test that we add a ``type`` key to certain content blocks that don't have one."""
472+
llm = ParrotFakeChatModel()
473+
messages = [
474+
{
475+
"role": "user",
476+
"content": [
477+
{
478+
"type": "text",
479+
"text": "Hello",
480+
},
481+
{
482+
"cachePoint": {"type": "default"},
483+
},
484+
],
485+
}
486+
]
487+
tracer = FakeChatModelStartTracer()
488+
response = llm.invoke(messages, config={"callbacks": [tracer]})
489+
assert tracer.messages == [
490+
[
491+
[
492+
HumanMessage(
493+
[
494+
{
495+
"type": "text",
496+
"text": "Hello",
497+
},
498+
{
499+
"type": "cachePoint",
500+
"cachePoint": {"type": "default"},
501+
},
502+
]
503+
)
504+
]
505+
]
506+
]
507+
# Test no mutation
508+
assert response.content == [
509+
{
510+
"type": "text",
511+
"text": "Hello",
512+
},
513+
{
514+
"cachePoint": {"type": "default"},
515+
},
516+
]
517+
518+
470519
def test_extend_support_to_openai_multimodal_formats() -> None:
471520
"""Test that chat models normalize OpenAI file and audio inputs."""
472521
llm = ParrotFakeChatModel()

0 commit comments

Comments
 (0)