Fix Responses API output and minify user turns
Preserve non-ASCII function-call arguments in Responses API output and serialize automated structured user turns with compact JSON. Cover Cyrillic tool arguments and compact persisted Responses input items.
linkliti committed
Aug 24, 2026 at 19:51 UTC
3db4c4954ea23d22657d36862428927a32b0edc0
4 files changed
+24
-4
helpers/history.py
+1
-1
@@ -808,7 +808,7 @@ def _messages_from_record(record: Record) -> list[Message]:
808
809
810
def _json_dumps(obj):
811
- return json.dumps(obj, ensure_ascii=False)
811
+ return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
812
813
814
def _json_loads(obj):
helpers/history.py.dox.md
+1
@@ -81,6 +81,7 @@
81
- `clear_responses_provider_state(agent)` removes the active provider continuation IDs after local history rewrites while preserving stored response ID lists for later cleanup.
82
- `Message.from_dict()` normalizes legacy AI Responses metadata through `LLMResult.metadata()` so loaded chats shed transient payloads while unrelated metadata and non-AI tool-result inputs remain intact.
83
- `output_langchain()` removes leading assistant messages after grouping so provider histories always begin with a user turn; the WebUI greeting remains persisted and displayed but is not sent as an orphaned assistant message.
84
+- `_json_dumps()` emits compact JSON (`","`, `":"` separators) for serialized history and generated non-string user-turn content.
85
- Observed side-effect areas: filesystem writes, filesystem deletion, model calls, plugin state, settings/state persistence, secret handling.
86
- Imported dependency areas include: `abc`, `asyncio`, `collections`, `collections.abc`, `enum`, `helpers`, `json`, `langchain_core.messages`, `math`, `plugins._model_config.helpers.model_config`, `typing`, `uuid`.
87
helpers/llm_result.py
+3
-2
@@ -194,9 +194,10 @@ class LLMResult:
194
if not calls:
195
return ""
196
if len(calls) == 1:
197
- return json.dumps(calls[0])
197
+ return json.dumps(calls[0], ensure_ascii=False)
198
return json.dumps(
199
- {"tool_name": "parallel_tool_calls", "tool_args": {"calls": calls}}
199
+ {"tool_name": "parallel_tool_calls", "tool_args": {"calls": calls}},
200
+ ensure_ascii=False,
201
)
202
203
def to_dict(self) -> dict[str, Any]:
tests/test_responses_architecture.py
+19
-1
@@ -44,6 +44,22 @@ class _AsyncEventStream:
44
self.closed = True
45
46
47
+def test_responses_function_call_text_preserves_non_ascii_tool_args():
48
+ result = LLMResult.from_response(
49
+ {
50
+ "output": [
51
+ {
52
+ "type": "function_call",
53
+ "name": "response",
54
+ "arguments": '{"text":"привет"}',
55
+ }
56
+ ]
57
+ }
58
+ )
59
+
60
+ assert result.function_calls_text() == '{"tool_name": "response", "tool_args": {"text": "привет"}}'
61
+
62
+
63
def test_llm_result_persists_only_durable_responses_metadata():
64
result = LLMResult.from_response(
65
{
@@ -105,7 +121,9 @@ def test_history_migrates_legacy_ai_metadata_and_preserves_tool_inputs():
121
"done",
122
metadata={"responses": {"input_items": [tool_item]}},
123
)
108
- restored = history.deserialize_history(hist.serialize(), DummyAgent())
124
+ serialized = hist.serialize()
125
+ assert '"input_items":[{"type":"function_call_output"' in serialized
126
+ restored = history.deserialize_history(serialized, DummyAgent())
127
128
restored_message = restored.all_messages()[0]
129
assert restored_message.sequence == message.sequence