fix: streaming container_id and consistent Pydantic types in output

- Populate container_id on streaming code_interpreter_results by
  re-emitting at message_delta when container info arrives
- Reconstruct Pydantic OutputCodeInterpreterCall objects from plain
  dicts in _extract_tool_result_output_items so responses_output
  has uniform types across streaming and non-streaming paths
This commit is contained in:
Andrzej Pomirski
2026-03-18 12:37:13 +01:00
parent d10007cef4
commit cf8d1ac521
3 changed files with 32 additions and 13 deletions
+13 -1
View File
@@ -546,6 +546,7 @@ class ModelResponseIterator:
self._server_tool_inputs: Dict[str, Any] = {}
self.tool_results: List[Dict[str, Any]] = []
self._current_server_tool_id: Optional[str] = None
self._container_id: Optional[str] = None
def check_empty_tool_call_args(self) -> bool:
"""
@@ -726,7 +727,7 @@ class ModelResponseIterator:
type="code_interpreter_call",
id=call_id,
code=code,
container_id=None,
container_id=self._container_id,
status="completed",
outputs=log_outputs,
)
@@ -928,6 +929,17 @@ class ModelResponseIterator:
finish_reason, usage, container = self._handle_message_delta(chunk)
if container:
provider_specific_fields["container"] = container
# Store container_id and re-emit code_interpreter_results
# so stream_chunk_builder's last-value-wins picks up the
# version with container_id populated.
container_id = (
container.get("id") if isinstance(container, dict) else None
)
if container_id and self.tool_results:
self._container_id = container_id
provider_specific_fields["code_interpreter_results"] = (
self._build_code_interpreter_results()
)
elif type_chunk == "message_start":
"""
Anthropic
@@ -1738,10 +1738,7 @@ class LiteLLMCompletionResponsesConfig:
)
)
if tool_result_items:
result_by_id = {
(item.get("id") if isinstance(item, dict) else item.id): item
for item in tool_result_items
}
result_by_id = {item.id: item for item in tool_result_items}
replaced_ids = set(result_by_id.keys())
responses_output = [
(
@@ -1778,7 +1775,14 @@ class LiteLLMCompletionResponsesConfig:
continue
results = psf.get("code_interpreter_results")
if results and isinstance(results, list):
output_items.extend(results)
for item in results:
# In the streaming path, items are plain dicts after
# model_dump() in stream_chunk_builder. Reconstruct
# Pydantic objects so responses_output has a uniform type.
if isinstance(item, dict):
output_items.append(OutputCodeInterpreterCall(**item))
else:
output_items.append(item)
return output_items
@staticmethod
@@ -58,7 +58,8 @@ def test_extract_tool_result_output_items_from_pydantic_objects():
def test_extract_tool_result_output_items_from_dicts():
"""Streaming path: after model_dump(), code_interpreter_results are plain dicts."""
"""Streaming path: after model_dump(), code_interpreter_results are plain dicts.
_extract_tool_result_output_items reconstructs them as Pydantic objects."""
items = [
{
"type": "code_interpreter_call",
@@ -72,7 +73,8 @@ def test_extract_tool_result_output_items_from_dicts():
resp = _make_model_response(code_interpreter_results=items)
result = LiteLLMCompletionResponsesConfig._extract_tool_result_output_items(resp)
assert len(result) == 1
assert result[0]["id"] == "srvtoolu_01AAA"
assert isinstance(result[0], OutputCodeInterpreterCall)
assert result[0].id == "srvtoolu_01AAA"
def test_extract_tool_result_output_items_empty():
@@ -258,8 +260,9 @@ def test_end_to_end_streaming_chunks_to_code_interpreter_output():
)
assert len(tool_result_items) == 1
item = tool_result_items[0]
# Items are dicts after the model_dump path
assert item["type"] == "code_interpreter_call"
assert item["id"] == "srvtoolu_01AAA"
assert item["code"] == "echo e2e_test"
assert item["outputs"][0]["logs"] == "e2e_test\n"
# Items are reconstructed as Pydantic OutputCodeInterpreterCall objects
assert isinstance(item, OutputCodeInterpreterCall)
assert item.type == "code_interpreter_call"
assert item.id == "srvtoolu_01AAA"
assert item.code == "echo e2e_test"
assert item.outputs[0].logs == "e2e_test\n"