Skip to content

Commit 7006e33

Browse files
anneheartrecordjazhang00
authored andcommitted
fix: preserve non-ASCII characters in agent input
Merge #6282 `input_schema` inputs containing non-Latin characters (Hebrew, Chinese, etc.) reach the LLM as `\uXXXX` escapes, which bloats prompt tokens (~6x for Hebrew) and degrades model responses, as reported in #6279. The escaping comes from `json.dumps` being called with its default `ensure_ascii=True` on the LLM-bound input text. Two paths were affected: - `utils/content_utils.py` - `to_user_content()` for `dict`/`list` node input (used by `workflow/_llm_agent_wrapper.py`) - `flows/llm_flows/contents.py` – `_build_task_input_user_content()`, which rebuilds a delegated task's function-call args as the sub-agent's first user turn. This path takes priority over the wrapper fallback, so it affects the common chat/root → task sub-agent delegation case. Both now serialize with `ensure_ascii=False`, matching how the output-schema path already serializes responses (`_output_schema_processor.py`), fixed earlier in #2936/#2937. Fixes #6279 Closes #6282 Co-authored-by: Jason Zhang <jasoncz@google.com> PiperOrigin-RevId: 947866027
1 parent 163cd0d commit 7006e33

4 files changed

Lines changed: 63 additions & 3 deletions

File tree

src/google/adk/flows/llm_flows/contents.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ def _build_task_input_user_content(
387387
try:
388388
import json as _json
389389

390-
text = _json.dumps(dict(fc.args))
390+
text = _json.dumps(dict(fc.args), ensure_ascii=False)
391391
except (TypeError, ValueError):
392392
text = str(fc.args)
393393
parts = [types.Part(text=text)]

src/google/adk/utils/content_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def to_user_content(value: Any) -> types.Content:
6464
deep-copied)
6565
- str -> single text part
6666
- BaseModel -> model_dump_json() text part
67-
- dict/list -> json.dumps() text part
67+
- dict/list -> json.dumps() text part (non-ASCII preserved, not escaped)
6868
- anything else -> str() text part
6969
"""
7070
if isinstance(value, types.Content):
@@ -74,7 +74,7 @@ def to_user_content(value: Any) -> types.Content:
7474
elif isinstance(value, BaseModel):
7575
text = value.model_dump_json()
7676
elif isinstance(value, (dict, list)):
77-
text = json.dumps(value)
77+
text = json.dumps(value, ensure_ascii=False)
7878
else:
7979
text = str(value)
8080
return types.Content(role='user', parts=[types.Part(text=text)])

tests/unittests/flows/llm_flows/test_contents.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,3 +1965,41 @@ def test_recover_compacted_parallel_call_reinjects_sibling_response():
19651965
if part.function_response
19661966
}
19671967
assert response_ids == {"lr-1", "reg-1"}
1968+
1969+
1970+
def test_task_input_user_content_preserves_non_ascii():
1971+
"""Delegated task input must not escape non-ASCII FC args.
1972+
1973+
A chat coordinator delegates to a task sub-agent via a function call; the
1974+
task agent's first user turn is rebuilt from the FC args. Escaping non-Latin
1975+
characters to ``\\uXXXX`` there bloats prompt tokens and degrades responses.
1976+
"""
1977+
fc_id = "fc_task_1"
1978+
events = [
1979+
Event(
1980+
invocation_id="inv1",
1981+
author="coordinator",
1982+
content=types.Content(
1983+
role="model",
1984+
parts=[
1985+
types.Part(
1986+
function_call=types.FunctionCall(
1987+
id=fc_id,
1988+
name="delegate",
1989+
args={"query": "שלום עולם", "city": "北京"},
1990+
)
1991+
)
1992+
],
1993+
),
1994+
),
1995+
]
1996+
1997+
content = contents._build_task_input_user_content( # pylint: disable=protected-access
1998+
events, isolation_scope=fc_id
1999+
)
2000+
2001+
assert content is not None and content.parts
2002+
text = content.parts[0].text
2003+
assert "שלום עולם" in text
2004+
assert "北京" in text
2005+
assert "\\u" not in text

tests/unittests/utils/test_content_utils.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,25 @@ def test_to_user_content_other_input_is_str():
6666
content = to_user_content(42)
6767
assert content.role == 'user'
6868
assert content.parts[0].text == '42'
69+
70+
71+
def test_to_user_content_dict_input_preserves_non_ascii():
72+
"""Non-ASCII input must reach the LLM as-is, not as \\uXXXX escapes.
73+
74+
Escaping (json.dumps' default ensure_ascii=True) turns each non-Latin
75+
character into a ``\\uXXXX`` sequence, which bloats prompt tokens and
76+
degrades model responses for non-English inputs.
77+
"""
78+
content = to_user_content({'query': 'שלום עולם', 'city': '北京'})
79+
text = content.parts[0].text
80+
assert 'שלום עולם' in text
81+
assert '北京' in text
82+
assert '\\u' not in text
83+
84+
85+
def test_to_user_content_list_input_preserves_non_ascii():
86+
content = to_user_content(['שלום', '你好'])
87+
text = content.parts[0].text
88+
assert 'שלום' in text
89+
assert '你好' in text
90+
assert '\\u' not in text

0 commit comments

Comments
 (0)