Skip to content

[BUG] Provider request formatting escapes non-ASCII in tool-result JSON and tool-call arguments (follow-up to #2636) #2660

@yatszhash

Description

@yatszhash

Checks

  • I have updated to the latest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

SDK Language

Python

Strands Version

1.41.0 (also reproduces on main)

Language Runtime Version

Python 3.11.11

Operating System

macOS (Darwin 25.5.0)

Installation Method

pip

Steps to Reproduce

Provider request formatting serializes tool-result {"json": ...} content blocks and tool-call arguments with json.dumps(...), which defaults to ensure_ascii=True and escapes non-ASCII (CJK, emoji) to \uXXXX in the request sent to the model. This is the provider-side counterpart to #2636 — that issue's @tool decorator path was fixed by #2653, but the provider request-formatting paths were not.

Minimal reproduction using the actual provider format functions (no network / API key needed):

from strands.models.anthropic import AnthropicModel
from strands.models.openai import OpenAIModel

tool_result = {"toolUseId": "c1", "status": "success", "content": [{"json": {"city": "東京"}}]}
tool_use = {"toolUseId": "c1", "name": "search", "input": {"query": "東京"}}

anthropic = AnthropicModel(client_args={"api_key": "x"}, model_id="claude-sonnet-4-5", max_tokens=8)

# Path A — tool-result {"json": ...} block (this string is sent to the model)
print(OpenAIModel.format_request_tool_message(tool_result)["content"])
print(anthropic._format_request_message_content({"toolResult": tool_result})["content"][0]["text"])

# Path B — tool-call arguments (OpenAI-family)
print(OpenAIModel.format_request_message_tool_call(tool_use)["function"]["arguments"])

Output:

{"city": "東京"}
{"city": "東京"}
{"query": "東京"}

Expected Behavior

Non-ASCII text is preserved in the request, e.g. {"city": "東京"} — matching what the SDK already does in telemetry/tracer.py and the session managers.

Actual Behavior

Non-ASCII is \uXXXX-escaped in the request sent to the model. For CJK / non-Latin scripts this inflates token usage (and cost) and hurts readability/debuggability.

This is hit by mainstream usage — e.g. agent-as-a-tool / multi-agent structured output (agent/_agent_as_tool.py emits a {"json": result.structured_output.model_dump()} content block), and the context-offloader plugin.

Additional Context

Affected request-formatting paths (model-visible):

  • Tool-result {"json": ...} blocks (Anthropic + OpenAI-family): anthropic.py, openai.py, openai_responses.py, writer.py, llamacpp.py, llamaapi.py, mistral.py, ollama.py, sagemaker.py
  • Tool-call argument serialization (OpenAI-family): openai.py, openai_responses.py, writer.py, llamacpp.py, llamaapi.py, mistral.py

Not affected / intentionally out of scope:

  • Gemini and Bedrock pass these as native structures (no json.dumps), so they are unaffected.
  • Streaming-delta serializations (gemini.py, ollama.py, bedrock.py) round-trip through json.loads in the event loop, so escaping there is a no-op.
  • Log-only / token-estimate json.dumps calls (sagemaker.py logs, model.py heuristic).

Precedent / consistency:

  • Add ensure_ascii=False to json.dumps() calls in telemetry tracer #37 already added ensure_ascii=False to telemetry/tracer.py.
  • The repo already uses ensure_ascii=False in telemetry/tracer.py and the session managers, and tests/strands/telemetry/test_tracer.py::test_serialize_non_ascii_characters locks in "non-ASCII is preserved (no \u)" as intended behavior.
  • The TypeScript SDK is unaffected (JSON.stringify does not escape non-ASCII). This is Python-specific.

Possible Solution

Add ensure_ascii=False to the json.dumps(...) calls on the model-visible paths above. I have a PR ready.

Related Issues

Follow-up to #2636. Complements #2653 (decorator path).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-modelRelated to models or model providersbugSomething isn't workingpythonPull requests that update python code

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions