fix: use json.dumps instead of str() for Codex Responses API arguments
When the Responses API returns tool call arguments as a dict,
str(dict) produces Python repr with single quotes (e.g. {'key': 'val'})
which is invalid JSON. Downstream json.loads() fails silently and the
tool gets called with empty arguments, losing all parameters.
Affects both function_call and custom_tool_call item types in
_normalize_codex_response().
This commit is contained in:
parent
6761021fb4
commit
6f85283553
2 changed files with 55 additions and 2 deletions
|
|
@ -2407,7 +2407,7 @@ class AIAgent:
|
||||||
fn_name = getattr(item, "name", "") or ""
|
fn_name = getattr(item, "name", "") or ""
|
||||||
arguments = getattr(item, "arguments", "{}")
|
arguments = getattr(item, "arguments", "{}")
|
||||||
if not isinstance(arguments, str):
|
if not isinstance(arguments, str):
|
||||||
arguments = str(arguments)
|
arguments = json.dumps(arguments, ensure_ascii=False)
|
||||||
raw_call_id = getattr(item, "call_id", None)
|
raw_call_id = getattr(item, "call_id", None)
|
||||||
raw_item_id = getattr(item, "id", None)
|
raw_item_id = getattr(item, "id", None)
|
||||||
embedded_call_id, _ = self._split_responses_tool_id(raw_item_id)
|
embedded_call_id, _ = self._split_responses_tool_id(raw_item_id)
|
||||||
|
|
@ -2428,7 +2428,7 @@ class AIAgent:
|
||||||
fn_name = getattr(item, "name", "") or ""
|
fn_name = getattr(item, "name", "") or ""
|
||||||
arguments = getattr(item, "input", "{}")
|
arguments = getattr(item, "input", "{}")
|
||||||
if not isinstance(arguments, str):
|
if not isinstance(arguments, str):
|
||||||
arguments = str(arguments)
|
arguments = json.dumps(arguments, ensure_ascii=False)
|
||||||
raw_call_id = getattr(item, "call_id", None)
|
raw_call_id = getattr(item, "call_id", None)
|
||||||
raw_item_id = getattr(item, "id", None)
|
raw_item_id = getattr(item, "id", None)
|
||||||
embedded_call_id, _ = self._split_responses_tool_id(raw_item_id)
|
embedded_call_id, _ = self._split_responses_tool_id(raw_item_id)
|
||||||
|
|
|
||||||
|
|
@ -2533,3 +2533,56 @@ class TestVprintForceOnErrors:
|
||||||
agent._vprint("debug")
|
agent._vprint("debug")
|
||||||
agent._vprint("error", force=True)
|
agent._vprint("error", force=True)
|
||||||
assert len(printed) == 2
|
assert len(printed) == 2
|
||||||
|
|
||||||
|
|
||||||
|
class TestNormalizeCodexDictArguments:
|
||||||
|
"""_normalize_codex_response must produce valid JSON strings for tool
|
||||||
|
call arguments, even when the Responses API returns them as dicts."""
|
||||||
|
|
||||||
|
def _make_codex_response(self, item_type, arguments, item_status="completed"):
|
||||||
|
"""Build a minimal Responses API response with a single tool call."""
|
||||||
|
item = SimpleNamespace(
|
||||||
|
type=item_type,
|
||||||
|
status=item_status,
|
||||||
|
)
|
||||||
|
if item_type == "function_call":
|
||||||
|
item.name = "web_search"
|
||||||
|
item.arguments = arguments
|
||||||
|
item.call_id = "call_abc123"
|
||||||
|
item.id = "fc_abc123"
|
||||||
|
elif item_type == "custom_tool_call":
|
||||||
|
item.name = "web_search"
|
||||||
|
item.input = arguments
|
||||||
|
item.call_id = "call_abc123"
|
||||||
|
item.id = "fc_abc123"
|
||||||
|
return SimpleNamespace(
|
||||||
|
output=[item],
|
||||||
|
status="completed",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_function_call_dict_arguments_produce_valid_json(self, agent):
|
||||||
|
"""dict arguments from function_call must be serialised with
|
||||||
|
json.dumps, not str(), so downstream json.loads() succeeds."""
|
||||||
|
args_dict = {"query": "weather in NYC", "units": "celsius"}
|
||||||
|
response = self._make_codex_response("function_call", args_dict)
|
||||||
|
msg, _ = agent._normalize_codex_response(response)
|
||||||
|
tc = msg.tool_calls[0]
|
||||||
|
parsed = json.loads(tc.function.arguments)
|
||||||
|
assert parsed == args_dict
|
||||||
|
|
||||||
|
def test_custom_tool_call_dict_arguments_produce_valid_json(self, agent):
|
||||||
|
"""dict arguments from custom_tool_call must also use json.dumps."""
|
||||||
|
args_dict = {"path": "/tmp/test.txt", "content": "hello"}
|
||||||
|
response = self._make_codex_response("custom_tool_call", args_dict)
|
||||||
|
msg, _ = agent._normalize_codex_response(response)
|
||||||
|
tc = msg.tool_calls[0]
|
||||||
|
parsed = json.loads(tc.function.arguments)
|
||||||
|
assert parsed == args_dict
|
||||||
|
|
||||||
|
def test_string_arguments_unchanged(self, agent):
|
||||||
|
"""String arguments must pass through without modification."""
|
||||||
|
args_str = '{"query": "test"}'
|
||||||
|
response = self._make_codex_response("function_call", args_str)
|
||||||
|
msg, _ = agent._normalize_codex_response(response)
|
||||||
|
tc = msg.tool_calls[0]
|
||||||
|
assert tc.function.arguments == args_str
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue