Skip to content

Commit a426755

Browse files
committed
fix review comments
1 parent aa02f12 commit a426755

File tree

7 files changed

+177
-39
lines changed

7 files changed

+177
-39
lines changed

src/agents/run.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
)
3030
from .lifecycle import RunHooks
3131
from .logger import logger
32-
from .memory import Session, is_server_managed_conversation_session
32+
from .memory import Session
3333
from .result import RunResult, RunResultStreaming
3434
from .run_config import (
3535
DEFAULT_MAX_TURNS,
@@ -413,7 +413,6 @@ async def run(
413413
auto_previous_response_id = kwargs.get("auto_previous_response_id", False)
414414
conversation_id = kwargs.get("conversation_id")
415415
session = kwargs.get("session")
416-
run_config_was_supplied = run_config is not None
417416

418417
if run_config is None:
419418
run_config = RunConfig()
@@ -504,19 +503,15 @@ async def run(
504503
or previous_response_id is not None
505504
or auto_previous_response_id
506505
)
507-
history_is_server_managed = (
508-
server_manages_conversation or is_server_managed_conversation_session(session)
509-
)
510506
validate_override_history_persistence_support(
511507
input=input,
512508
session=session,
513-
history_is_server_managed=history_is_server_managed,
509+
response_history_is_server_managed=server_manages_conversation,
514510
)
515511

516512
resolved_trace_include_sensitive_data = resolve_trace_include_sensitive_data(
517513
run_state=run_state,
518514
run_config=run_config,
519-
run_config_was_supplied=run_config_was_supplied,
520515
)
521516
if resolved_trace_include_sensitive_data != run_config.trace_include_sensitive_data:
522517
run_config = dataclasses.replace(
@@ -1469,7 +1464,6 @@ def run_streamed(
14691464
auto_previous_response_id = kwargs.get("auto_previous_response_id", False)
14701465
conversation_id = kwargs.get("conversation_id")
14711466
session = kwargs.get("session")
1472-
run_config_was_supplied = run_config is not None
14731467

14741468
if run_config is None:
14751469
run_config = RunConfig()
@@ -1553,18 +1547,14 @@ def run_streamed(
15531547
or previous_response_id is not None
15541548
or auto_previous_response_id
15551549
)
1556-
history_is_server_managed = (
1557-
server_manages_conversation or is_server_managed_conversation_session(session)
1558-
)
15591550
validate_override_history_persistence_support(
15601551
input=input,
15611552
session=session,
1562-
history_is_server_managed=history_is_server_managed,
1553+
response_history_is_server_managed=server_manages_conversation,
15631554
)
15641555
resolved_trace_include_sensitive_data = resolve_trace_include_sensitive_data(
15651556
run_state=run_state,
15661557
run_config=run_config,
1567-
run_config_was_supplied=run_config_was_supplied,
15681558
)
15691559
if resolved_trace_include_sensitive_data != run_config.trace_include_sensitive_data:
15701560
run_config = dataclasses.replace(

src/agents/run_config.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44
from dataclasses import dataclass, field
5-
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Optional
5+
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, Optional, cast
66

77
from typing_extensions import NotRequired, TypedDict
88

@@ -33,6 +33,14 @@ def _default_trace_include_sensitive_data() -> bool:
3333
return val.strip().lower() in ("1", "true", "yes", "on")
3434

3535

36+
_TRACE_INCLUDE_SENSITIVE_DATA_UNSET = cast(bool, object())
37+
38+
39+
def _unset_trace_include_sensitive_data() -> bool:
40+
"""Return a sentinel so RunConfig can detect explicit trace flag overrides."""
41+
return _TRACE_INCLUDE_SENSITIVE_DATA_UNSET
42+
43+
3644
@dataclass
3745
class ModelInputData:
3846
"""Container for the data that will be sent to the model."""
@@ -129,9 +137,7 @@ class RunConfig:
129137
tracing: TracingConfig | None = None
130138
"""Tracing configuration for this run."""
131139

132-
trace_include_sensitive_data: bool = field(
133-
default_factory=_default_trace_include_sensitive_data
134-
)
140+
trace_include_sensitive_data: bool = field(default_factory=_unset_trace_include_sensitive_data)
135141
"""Whether we include potentially sensitive data (for example: inputs/outputs of tool calls or
136142
LLM generations) in traces. If False, we'll still create spans for these events, but the
137143
sensitive data will not be included.
@@ -191,6 +197,21 @@ class RunConfig:
191197
- ``"omit"`` strips reasoning item IDs from model input built by the runner.
192198
"""
193199

200+
_trace_include_sensitive_data_was_explicit: bool = field(
201+
init=False,
202+
repr=False,
203+
compare=False,
204+
default=False,
205+
)
206+
207+
def __post_init__(self) -> None:
208+
if self.trace_include_sensitive_data is _TRACE_INCLUDE_SENSITIVE_DATA_UNSET:
209+
self.trace_include_sensitive_data = _default_trace_include_sensitive_data()
210+
self._trace_include_sensitive_data_was_explicit = False
211+
return
212+
213+
self._trace_include_sensitive_data_was_explicit = True
214+
194215

195216
class RunOptions(TypedDict, Generic[TContext]):
196217
"""Arguments for ``AgentRunner`` methods."""

src/agents/run_internal/agent_runner_helpers.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -115,23 +115,26 @@ def validate_override_history_persistence_support(
115115
*,
116116
input: str | list[TResponseInputItem] | RunState[Any],
117117
session: Session | None,
118-
history_is_server_managed: bool,
118+
response_history_is_server_managed: bool,
119119
) -> None:
120120
"""Fail fast when approval override persistence requirements are not satisfied."""
121121
if not isinstance(input, RunState):
122122
return
123123

124-
if input.has_pending_execution_only_approval_overrides() and not history_is_server_managed:
124+
if (
125+
input.has_pending_execution_only_approval_overrides()
126+
and not response_history_is_server_managed
127+
):
125128
raise UserError(
126129
"save_override_arguments=False is only supported when using conversation_id, "
127-
"previous_response_id, auto_previous_response_id, or a server-managed session."
130+
"previous_response_id, or auto_previous_response_id."
128131
)
129132

130133
mutations = input.get_session_history_mutations()
131134
if not mutations:
132135
return
133136

134-
if history_is_server_managed:
137+
if response_history_is_server_managed:
135138
raise UserError(
136139
"save_override_arguments requires local canonical history. "
137140
"Server-managed conversations cannot persist corrected function_call arguments. "
@@ -184,18 +187,14 @@ def resolve_trace_include_sensitive_data(
184187
*,
185188
run_state: RunState[TContext] | None,
186189
run_config: RunConfig,
187-
run_config_was_supplied: bool,
188190
) -> bool:
189-
"""Resolve whether traces may include sensitive data for this run.
190-
191-
Resumed runs preserve the stored setting unless the new RunConfig explicitly narrows it by
192-
setting `trace_include_sensitive_data=False`.
193-
"""
194-
del run_config_was_supplied
191+
"""Resolve whether traces may include sensitive data for this run."""
195192
if run_state is None:
196193
return run_config.trace_include_sensitive_data
197-
if run_config.trace_include_sensitive_data is False:
198-
return False
194+
195+
if getattr(run_config, "_trace_include_sensitive_data_was_explicit", True):
196+
return run_config.trace_include_sensitive_data
197+
199198
return run_state._trace_include_sensitive_data
200199

201200

tests/test_agent_runner.py

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,7 +1173,7 @@ def approval_tool(test: str) -> str:
11731173

11741174

11751175
@pytest.mark.asyncio
1176-
async def test_resume_supports_execution_only_override_with_server_managed_session() -> None:
1176+
async def test_resume_rejects_execution_only_override_with_marker_session() -> None:
11771177
model = FakeModel()
11781178

11791179
@function_tool(name_override="approval_tool", needs_approval=True)
@@ -1207,12 +1207,47 @@ def approval_tool(test: str) -> str:
12071207
save_override_arguments=False,
12081208
)
12091209

1210-
resumed = await Runner.run(agent, state, session=session)
1210+
with pytest.raises(UserError, match="save_override_arguments=False is only supported"):
1211+
await Runner.run(agent, state, session=session)
1212+
1213+
1214+
@pytest.mark.asyncio
1215+
async def test_resume_supports_execution_only_override_with_previous_response_id() -> None:
1216+
model = FakeModel()
1217+
1218+
@function_tool(name_override="approval_tool", needs_approval=True)
1219+
def approval_tool(test: str) -> str:
1220+
return f"result:{test}"
1221+
1222+
agent = Agent(
1223+
name="approval_agent",
1224+
model=model,
1225+
tools=[approval_tool],
1226+
tool_use_behavior="stop_on_first_tool",
1227+
)
1228+
model.add_multiple_turn_outputs(
1229+
[
1230+
[
1231+
get_function_tool_call(
1232+
"approval_tool", json.dumps({"test": "foo"}), call_id="call-1"
1233+
)
1234+
],
1235+
]
1236+
)
1237+
1238+
first = await Runner.run(agent, input="user_message", previous_response_id="resp-root")
1239+
assert first.interruptions
1240+
1241+
state = first.to_state()
1242+
state.approve(
1243+
first.interruptions[0],
1244+
override_arguments={"test": "bar"},
1245+
save_override_arguments=False,
1246+
)
1247+
1248+
resumed = await Runner.run(agent, state)
12111249

12121250
assert resumed.final_output == "result:bar"
1213-
saved_items = await session.get_items()
1214-
assert cast(dict[str, Any], saved_items[1])["arguments"] == json.dumps({"test": "foo"})
1215-
assert saved_items[2]["type"] == "function_call_output"
12161251

12171252

12181253
@pytest.mark.asyncio

tests/test_agent_runner_streamed.py

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,9 +1608,7 @@ async def test_tool(test: str) -> str:
16081608

16091609

16101610
@pytest.mark.asyncio
1611-
async def test_streaming_resume_supports_execution_only_override_with_server_managed_session() -> (
1612-
None
1613-
):
1611+
async def test_streaming_resume_rejects_execution_only_override_with_marker_session() -> None:
16141612
async def test_tool(test: str) -> str:
16151613
return f"result:{test}"
16161614

@@ -1639,7 +1637,42 @@ async def test_tool(test: str) -> str:
16391637
save_override_arguments=False,
16401638
)
16411639

1642-
resumed = Runner.run_streamed(agent, state, session=session)
1640+
with pytest.raises(UserError, match="save_override_arguments=False is only supported"):
1641+
Runner.run_streamed(agent, state, session=session)
1642+
1643+
1644+
@pytest.mark.asyncio
1645+
async def test_streaming_resume_supports_execution_only_override_with_previous_response_id() -> (
1646+
None
1647+
):
1648+
async def test_tool(test: str) -> str:
1649+
return f"result:{test}"
1650+
1651+
tool = function_tool(test_tool, name_override="test_tool", needs_approval=True)
1652+
model = FakeModel()
1653+
agent = Agent(
1654+
name="test",
1655+
model=model,
1656+
tools=[tool],
1657+
tool_use_behavior="stop_on_first_tool",
1658+
)
1659+
1660+
model.add_multiple_turn_outputs(
1661+
[[get_function_tool_call("test_tool", json.dumps({"test": "foo"}), call_id="call-resume")]]
1662+
)
1663+
1664+
first = Runner.run_streamed(agent, input="Use test_tool", previous_response_id="resp-root")
1665+
await consume_stream(first)
1666+
assert first.interruptions
1667+
1668+
state = first.to_state()
1669+
state.approve(
1670+
first.interruptions[0],
1671+
override_arguments={"test": "bar"},
1672+
save_override_arguments=False,
1673+
)
1674+
1675+
resumed = Runner.run_streamed(agent, state)
16431676
await consume_stream(resumed)
16441677

16451678
assert resumed.final_output == "result:bar"

tests/test_agent_tracing.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,51 @@ def send_email(recipient: str) -> str:
777777
assert function_span["span_data"]["output"] is None
778778

779779

780+
@pytest.mark.asyncio
781+
async def test_resumed_streaming_run_preserves_sensitive_trace_flag_for_unrelated_run_config() -> (
782+
None
783+
):
784+
model = FakeModel()
785+
786+
@function_tool(name_override="send_email", needs_approval=True)
787+
def send_email(recipient: str) -> str:
788+
return recipient
789+
790+
agent = Agent(name="trace_agent", model=model, tools=[send_email])
791+
model.add_multiple_turn_outputs(
792+
[
793+
[
794+
get_function_tool_call(
795+
"send_email", '{"recipient":"alice@example.com"}', call_id="call-1"
796+
)
797+
],
798+
[get_text_message("done")],
799+
]
800+
)
801+
802+
first = Runner.run_streamed(agent, input="first_test")
803+
async for _ in first.stream_events():
804+
pass
805+
assert first.interruptions
806+
807+
state = first.to_state()
808+
state.set_trace_include_sensitive_data(False)
809+
state.approve(first.interruptions[0], override_arguments={"recipient": "bob@example.com"})
810+
811+
resumed = Runner.run_streamed(
812+
agent,
813+
state,
814+
run_config=RunConfig(workflow_name="override_workflow"),
815+
)
816+
async for _ in resumed.stream_events():
817+
pass
818+
819+
assert resumed.final_output == "done"
820+
function_span = _get_last_function_span_export("send_email")
821+
assert function_span["span_data"]["input"] is None
822+
assert function_span["span_data"]["output"] is None
823+
824+
780825
@pytest.mark.asyncio
781826
async def test_wrapped_streaming_trace_is_single_trace():
782827
model = FakeModel()

tests/test_run_config.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,18 @@ def test_trace_include_sensitive_data_explicit_override_takes_precedence(monkeyp
138138
monkeypatch.setenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", "true")
139139
config = RunConfig(trace_include_sensitive_data=False)
140140
assert config.trace_include_sensitive_data is False
141+
142+
143+
def test_trace_include_sensitive_data_tracks_explicit_overrides(monkeypatch):
144+
"""RunConfig should distinguish explicit trace flag overrides from unrelated options."""
145+
monkeypatch.setenv("OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA", "true")
146+
147+
default_config = RunConfig()
148+
unrelated_config = RunConfig(workflow_name="custom-workflow")
149+
explicit_true_config = RunConfig(trace_include_sensitive_data=True)
150+
explicit_false_config = RunConfig(trace_include_sensitive_data=False)
151+
152+
assert default_config._trace_include_sensitive_data_was_explicit is False
153+
assert unrelated_config._trace_include_sensitive_data_was_explicit is False
154+
assert explicit_true_config._trace_include_sensitive_data_was_explicit is True
155+
assert explicit_false_config._trace_include_sensitive_data_was_explicit is True

0 commit comments

Comments
 (0)