fix: redact invalid JSON payload in ModelBehaviorError data (#3485) · openai/openai-agents-python@813a003 · GitHub
Skip to content

Commit 813a003

Browse files
authored
fix: redact invalid JSON payload in ModelBehaviorError data (#3485)
1 parent eda7b51 commit 813a003

5 files changed

Lines changed: 95 additions & 7 deletions

File tree

‎src/agents/tool.py‎

Lines changed: 13 additions & 4 deletions

‎tests/test_function_tool.py‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import copy
44
import dataclasses
55
import json
6+
import logging
67
import time
78
from collections.abc import Callable
89
from typing import Any, cast
@@ -11,6 +12,7 @@
1112
from pydantic import BaseModel
1213
from typing_extensions import TypedDict
1314

15+
import agents._debug as _debug
1416
import agents.tool as tool_module
1517
from agents import (
1618
Agent,
@@ -780,6 +782,68 @@ def echo(value: str) -> str:
780782
)
781783

782784

785+
@pytest.mark.asyncio
786+
async def test_function_tool_bad_json_redacts_payload_when_dont_log_tool_data(
787+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
788+
) -> None:
789+
caplog.set_level(logging.DEBUG)
790+
monkeypatch.setattr(_debug, "DONT_LOG_TOOL_DATA", True)
791+
792+
def echo(value: str) -> str:
793+
return value
794+
795+
tool = function_tool(echo, name_override="echo_tool", failure_error_function=None)
796+
bad_json = '{"secret":"SECRET_TOKEN_123"'
797+
798+
with pytest.raises(ModelBehaviorError) as exc_info:
799+
await tool.on_invoke_tool(
800+
ToolContext(
801+
None,
802+
tool_name="echo_tool",
803+
tool_call_id="1",
804+
tool_arguments=bad_json,
805+
),
806+
bad_json,
807+
)
808+
809+
assert str(exc_info.value) == "Invalid JSON input for tool echo_tool"
810+
assert exc_info.value.__cause__ is None
811+
assert exc_info.value.__context__ is None
812+
assert "SECRET_TOKEN_123" not in str(exc_info.value)
813+
assert "SECRET_TOKEN_123" not in caplog.text
814+
815+
816+
@pytest.mark.asyncio
817+
async def test_function_tool_bad_json_includes_payload_when_tool_logging_enabled(
818+
monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture
819+
) -> None:
820+
caplog.set_level(logging.DEBUG)
821+
monkeypatch.setattr(_debug, "DONT_LOG_TOOL_DATA", False)
822+
823+
def echo(value: str) -> str:
824+
return value
825+
826+
tool = function_tool(echo, name_override="echo_tool", failure_error_function=None)
827+
bad_json = '{"secret":"SECRET_TOKEN_123"'
828+
829+
with pytest.raises(ModelBehaviorError) as exc_info:
830+
await tool.on_invoke_tool(
831+
ToolContext(
832+
None,
833+
tool_name="echo_tool",
834+
tool_call_id="1",
835+
tool_arguments=bad_json,
836+
),
837+
bad_json,
838+
)
839+
840+
assert str(exc_info.value) == f"Invalid JSON input for tool echo_tool: {bad_json}"
841+
assert isinstance(exc_info.value.__cause__, json.JSONDecodeError)
842+
assert exc_info.value.__cause__.doc == bad_json
843+
assert "SECRET_TOKEN_123" in str(exc_info.value)
844+
assert "SECRET_TOKEN_123" in caplog.text
845+
846+
783847
@pytest.mark.asyncio
784848
async def test_default_failure_error_function_survives_deepcopy() -> None:
785849
def boom() -> None:

‎tests/test_run_step_execution.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
TResponseInputItem,
4747
Usage,
4848
UserError,
49+
_debug,
4950
tool_namespace,
5051
tool_output_guardrail,
5152
trace,
@@ -2814,7 +2815,11 @@ async def test_multiple_final_output_leads_to_final_output_next_step():
28142815

28152816

28162817
@pytest.mark.asyncio
2817-
async def test_input_guardrail_runs_on_invalid_json():
2818+
async def test_input_guardrail_runs_on_invalid_json(monkeypatch: pytest.MonkeyPatch):
2819+
# Opt in to payload logging so the JSON decode error chain is preserved and the
2820+
# default failure formatter can recover the friendly "parsing tool arguments" message.
2821+
monkeypatch.setattr(_debug, "DONT_LOG_TOOL_DATA", False)
2822+
28182823
guardrail_calls: list[str] = []
28192824

28202825
def guardrail(data) -> ToolGuardrailFunctionOutput:

‎tests/test_tracing_errors.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
RunContextWrapper,
1717
Runner,
1818
TResponseInputItem,
19+
_debug,
1920
)
2021

2122
from .fake_model import FakeModel
@@ -133,7 +134,11 @@ async def test_multi_turn_no_handoffs():
133134

134135

135136
@pytest.mark.asyncio
136-
async def test_tool_call_error():
137+
async def test_tool_call_error(monkeypatch: pytest.MonkeyPatch):
138+
# Opt in to tool payload logging so the friendly "parsing tool arguments" message,
139+
# which depends on inspecting the chained JSONDecodeError, is preserved.
140+
monkeypatch.setattr(_debug, "DONT_LOG_TOOL_DATA", False)
141+
137142
model = FakeModel(tracing_enabled=True)
138143

139144
agent = Agent(

‎tests/test_tracing_errors_streamed.py‎

Lines changed: 6 additions & 1 deletion

0 commit comments

Comments
 (0)