Preserve Unicode in streamed Responses output

Keep synthesized Responses tool-call JSON readable in raw LLM log details and show a concise agent-prefixed repeat warning in the WebUI. Cover streamed Cyrillic function-call arguments and repeat-warning display.

linkliti committed Aug 24, 2026 at 20:11 UTC eaeec455e2b64a92e105963ae301a4541cf593e9
5 files changed +48 -5
extensions/python/message_loop_result/_20_loop_control.py
+4 -1
@@ -37,7 +37,10 @@ class LoopControl(Extension):
37 self.agent._remember_llm_result_state(llm_result, assistant_message)
38 warning_message = self.agent.hist_add_warning(message=warning)
39 PrintStyle(font_color="orange", padding=True).print(warning)
40 + log_content = warning
41 + if response:
42 + log_content = f"{self.agent.agent_name}: Repeated response detected. Retrying."
43 self.agent.context.log.log(
41 - type="warning", content=warning, id=warning_message.id
44 + type="warning", content=log_content, id=warning_message.id
45 )
46 result_data["skip_default_processing"] = True
helpers/litellm_transport.py
+4 -3
@@ -540,9 +540,10 @@ class ChatCompletionsTransport:
540 if not calls:
541 return ""
542 if len(calls) == 1:
543 - return json.dumps(calls[0])
543 + return json.dumps(calls[0], ensure_ascii=False)
544 return json.dumps(
545 - {"tool_name": "parallel_tool_calls", "tool_args": {"calls": calls}}
545 + {"tool_name": "parallel_tool_calls", "tool_args": {"calls": calls}},
546 + ensure_ascii=False,
547 )
548
549 @classmethod
@@ -1118,7 +1119,7 @@ class ResponsesTransport:
1119 call = cls.function_call_object(item)
1120 if not call:
1121 return ""
1121 - return json.dumps(call)
1122 + return json.dumps(call, ensure_ascii=False)
1123
1124 @staticmethod
1125 def function_call_object(item: Any) -> dict[str, Any]:
helpers/litellm_transport.py.dox.md
+1
@@ -34,6 +34,7 @@
34 - Fall back to Chat Completions when LiteLLM's Responses mock streaming path tries to JSON-decode a real SSE stream before any output.
35 - Preserve Chat Completions tool calls from both non-streaming responses and streaming deltas as canonical `LLMResult` function-call items.
36 - Preserve Responses function calls collected from stream events when a terminal completed event omits them.
37 +- Serialize synthesized Responses function-call JSON with literal Unicode so streamed raw-response logs preserve tool arguments.
38 - Preserve provider-state metadata when Responses API calls succeed, and fall back to local replay when provider state is unsupported.
39 - Keep prompt-cache markers only for providers that accept them.
40
tests/test_message_loop_result.py
+12 -1
@@ -9,7 +9,11 @@ class FakeAgent:
9 last_response=last_response,
10 params_temporary={},
11 )
12 - self.context = SimpleNamespace(log=SimpleNamespace(log=lambda **kwargs: None))
12 + self.logs = []
13 + self.context = SimpleNamespace(
14 + log=SimpleNamespace(log=lambda **entry: self.logs.append(entry))
15 + )
16 + self.agent_name = "A0"
17 self.response = response
18 self.reasoning = reasoning
19 self.warnings = []
@@ -54,6 +58,13 @@ def test_repeat_skips_default_processing():
58
59 assert _run(agent)["skip_default_processing"] is True
60 assert agent.warnings == ["repeat"]
61 + assert agent.logs == [
62 + {
63 + "type": "warning",
64 + "content": "A0: Repeated response detected. Retrying.",
65 + "id": "warning",
66 + }
67 + ]
68
69
70 def test_result_with_reasoning_uses_default_processing():
tests/test_stream_tool_early_stop.py
+27
@@ -1715,3 +1715,30 @@ def test_responses_response_parser_groups_parallel_function_calls():
1715 ]
1716 },
1717 }
1718 +
1719 +
1720 +def test_responses_stream_parser_preserves_non_ascii_function_call_arguments():
1721 + parser = litellm_transport.ResponsesEventParser()
1722 +
1723 + parser.parse(
1724 + {
1725 + "type": "response.output_item.added",
1726 + "output_index": 0,
1727 + "item": {
1728 + "type": "function_call",
1729 + "id": "fc_1",
1730 + "name": "response",
1731 + "arguments": "",
1732 + },
1733 + }
1734 + )
1735 + parsed = parser.parse(
1736 + {
1737 + "type": "response.function_call_arguments.done",
1738 + "item_id": "fc_1",
1739 + "name": "response",
1740 + "arguments": '{"text":"привет"}',
1741 + }
1742 + )
1743 +
1744 + assert parsed["response_delta"] == '{"tool_name": "response", "tool_args": {"text": "привет"}}'