Skip to content

Commit c699277

Browse files
committed
Allow modifying the input sent to the model
1 parent 236536b commit c699277

File tree

1 file changed

+100
-6
lines changed

1 file changed

+100
-6
lines changed

src/agents/run.py

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import copy
55
import inspect
66
from dataclasses import dataclass, field
7-
from typing import Any, Generic, cast
7+
from typing import Any, Callable, Generic, cast
88

99
from openai.types.responses import ResponseCompletedEvent
1010
from openai.types.responses.response_prompt_param import (
@@ -56,6 +56,7 @@
5656
from .tracing.span_data import AgentSpanData
5757
from .usage import Usage
5858
from .util import _coro, _error_tracing
59+
from .util._types import MaybeAwaitable
5960

6061
DEFAULT_MAX_TURNS = 10
6162

@@ -81,6 +82,27 @@ def get_default_agent_runner() -> AgentRunner:
8182
return DEFAULT_AGENT_RUNNER
8283

8384

85+
@dataclass
86+
class ModelInputData:
87+
"""Container for the data that will be sent to the model."""
88+
89+
input: list[TResponseInputItem]
90+
instructions: str | None
91+
92+
93+
@dataclass
94+
class CallModelData(Generic[TContext]):
95+
"""Data passed to `RunConfig.call_model_input_filter` prior to model call."""
96+
97+
model_data: ModelInputData
98+
agent: Agent[TContext]
99+
context: TContext | None
100+
101+
102+
# Type alias for the optional input filter callback
103+
CallModelInputFilter = Callable[[CallModelData[Any]], MaybeAwaitable[ModelInputData]]
104+
105+
84106
@dataclass
85107
class RunConfig:
86108
"""Configures settings for the entire agent run."""
@@ -139,6 +161,16 @@ class RunConfig:
139161
An optional dictionary of additional metadata to include with the trace.
140162
"""
141163

164+
call_model_input_filter: CallModelInputFilter | None = None
165+
"""
166+
Optional callback that is invoked immediately before calling the model. It receives the current
167+
agent, context and the model input (instructions and input items), and must return a possibly
168+
modified `ModelInputData` to use for the model call.
169+
170+
This allows you to edit the input sent to the model e.g. to stay within a token limit.
171+
For example, you can use this to add a system prompt to the input.
172+
"""
173+
142174

143175
class RunOptions(TypedDict, Generic[TContext]):
144176
"""Arguments for ``AgentRunner`` methods."""
@@ -863,10 +895,40 @@ async def _run_single_turn_streamed(
863895
input = ItemHelpers.input_to_new_input_list(streamed_result.input)
864896
input.extend([item.to_input_item() for item in streamed_result.new_items])
865897

898+
# Allow user to modify model input right before the streaming call, if configured
899+
effective_instructions = system_prompt
900+
effective_input: list[TResponseInputItem] = input
901+
if run_config.call_model_input_filter is not None:
902+
try:
903+
model_input = ModelInputData(
904+
input=copy.deepcopy(effective_input),
905+
instructions=effective_instructions,
906+
)
907+
filter_payload: CallModelData[TContext] = CallModelData(
908+
model_data=model_input,
909+
agent=agent,
910+
context=context_wrapper.context,
911+
)
912+
maybe_updated = run_config.call_model_input_filter(filter_payload)
913+
updated = (
914+
await maybe_updated
915+
if inspect.isawaitable(maybe_updated)
916+
else maybe_updated
917+
)
918+
if not isinstance(updated, ModelInputData):
919+
raise UserError("call_model_input_filter must return a ModelInputData instance")
920+
effective_input = updated.input
921+
effective_instructions = updated.instructions
922+
except Exception as e:
923+
_error_tracing.attach_error_to_current_span(
924+
SpanError(message="Error in call_model_input_filter", data={"error": str(e)})
925+
)
926+
raise
927+
866928
# 1. Stream the output events
867929
async for event in model.stream_response(
868-
system_prompt,
869-
input,
930+
effective_instructions,
931+
effective_input,
870932
model_settings,
871933
all_tools,
872934
output_schema,
@@ -1034,7 +1096,6 @@ async def _get_single_step_result_from_streamed_response(
10341096
run_config: RunConfig,
10351097
tool_use_tracker: AgentToolUseTracker,
10361098
) -> SingleStepResult:
1037-
10381099
original_input = streamed_result.input
10391100
pre_step_items = streamed_result.new_items
10401101
event_queue = streamed_result._event_queue
@@ -1161,13 +1222,46 @@ async def _get_new_response(
11611222
previous_response_id: str | None,
11621223
prompt_config: ResponsePromptParam | None,
11631224
) -> ModelResponse:
1225+
# Allow user to modify model input right before the call, if configured
1226+
effective_instructions = system_prompt
1227+
effective_input: list[TResponseInputItem] = input
1228+
1229+
if run_config.call_model_input_filter is not None:
1230+
try:
1231+
model_input = ModelInputData(
1232+
input=copy.deepcopy(effective_input),
1233+
instructions=effective_instructions,
1234+
)
1235+
filter_payload: CallModelData[TContext] = CallModelData(
1236+
model_data=model_input,
1237+
agent=agent,
1238+
context=context_wrapper.context,
1239+
)
1240+
maybe_updated = run_config.call_model_input_filter(filter_payload)
1241+
updated = (
1242+
await maybe_updated
1243+
if inspect.isawaitable(maybe_updated)
1244+
else maybe_updated
1245+
)
1246+
# Basic validation to prevent accidental None returns
1247+
if not isinstance(updated, ModelInputData):
1248+
raise UserError("call_model_input_filter must return a ModelInputData instance")
1249+
effective_input = updated.input
1250+
effective_instructions = updated.instructions
1251+
except Exception as e:
1252+
# Attach to trace and re-raise as user error so it's clear
1253+
_error_tracing.attach_error_to_current_span(
1254+
SpanError(message="Error in call_model_input_filter", data={"error": str(e)})
1255+
)
1256+
raise
1257+
11641258
model = cls._get_model(agent, run_config)
11651259
model_settings = agent.model_settings.resolve(run_config.model_settings)
11661260
model_settings = RunImpl.maybe_reset_tool_choice(agent, tool_use_tracker, model_settings)
11671261

11681262
new_response = await model.get_response(
1169-
system_instructions=system_prompt,
1170-
input=input,
1263+
system_instructions=effective_instructions,
1264+
input=effective_input,
11711265
model_settings=model_settings,
11721266
tools=all_tools,
11731267
output_schema=output_schema,

0 commit comments

Comments
 (0)