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": "привет"}}'