Skip to content

Previous response id #509

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,11 @@ class ModelResponse:
usage: Usage
"""The usage information for the response."""

referenceable_id: str | None
response_id: str | None
"""An ID for the response which can be used to refer to the response in subsequent calls to the
model. Not supported by all model providers.
If using OpenAI models via the Responses API, this is the `response_id` parameter, and it can
be passed to `Runner.run`.
"""

def to_input_items(self) -> list[TResponseInputItem]:
Expand Down
8 changes: 8 additions & 0 deletions src/agents/models/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ async def get_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
*,
previous_response_id: str | None,
) -> ModelResponse:
"""Get a response from the model.

Expand All @@ -55,6 +57,8 @@ async def get_response(
output_schema: The output schema to use.
handoffs: The handoffs available to the model.
tracing: Tracing configuration.
previous_response_id: the ID of the previous response. Generally not used by the model,
except for the OpenAI Responses API.

Returns:
The full model response.
Expand All @@ -71,6 +75,8 @@ def stream_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
*,
previous_response_id: str | None,
) -> AsyncIterator[TResponseStreamEvent]:
"""Stream a response from the model.

Expand All @@ -82,6 +88,8 @@ def stream_response(
output_schema: The output schema to use.
handoffs: The handoffs available to the model.
tracing: Tracing configuration.
previous_response_id: the ID of the previous response. Generally not used by the model,
except for the OpenAI Responses API.

Returns:
An iterator of response stream events, in OpenAI Responses format.
Expand Down
5 changes: 4 additions & 1 deletion src/agents/models/openai_chatcompletions.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ async def get_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
previous_response_id: str | None,
) -> ModelResponse:
with generation_span(
model=str(self.model),
Expand Down Expand Up @@ -156,7 +157,7 @@ async def get_response(
return ModelResponse(
output=items,
usage=usage,
referenceable_id=None,
response_id=None,
)

async def stream_response(
Expand All @@ -168,6 +169,8 @@ async def stream_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
*,
previous_response_id: str | None,
) -> AsyncIterator[TResponseStreamEvent]:
"""
Yields a partial message as it is generated, as well as the usage information.
Expand Down
11 changes: 10 additions & 1 deletion src/agents/models/openai_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ async def get_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
previous_response_id: str | None,
) -> ModelResponse:
with response_span(disabled=tracing.is_disabled()) as span_response:
try:
Expand All @@ -79,6 +80,7 @@ async def get_response(
tools,
output_schema,
handoffs,
previous_response_id,
stream=False,
)

Expand Down Expand Up @@ -120,7 +122,7 @@ async def get_response(
return ModelResponse(
output=response.output,
usage=usage,
referenceable_id=response.id,
response_id=response.id,
)

async def stream_response(
Expand All @@ -132,6 +134,7 @@ async def stream_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
previous_response_id: str | None,
) -> AsyncIterator[ResponseStreamEvent]:
"""
Yields a partial message as it is generated, as well as the usage information.
Expand All @@ -145,6 +148,7 @@ async def stream_response(
tools,
output_schema,
handoffs,
previous_response_id,
stream=True,
)

Expand Down Expand Up @@ -180,6 +184,7 @@ async def _fetch_response(
tools: list[Tool],
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
previous_response_id: str | None,
stream: Literal[True],
) -> AsyncStream[ResponseStreamEvent]: ...

Expand All @@ -192,6 +197,7 @@ async def _fetch_response(
tools: list[Tool],
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
previous_response_id: str | None,
stream: Literal[False],
) -> Response: ...

Expand All @@ -203,6 +209,7 @@ async def _fetch_response(
tools: list[Tool],
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
previous_response_id: str | None,
stream: Literal[True] | Literal[False] = False,
) -> Response | AsyncStream[ResponseStreamEvent]:
list_input = ItemHelpers.input_to_new_input_list(input)
Expand All @@ -229,9 +236,11 @@ async def _fetch_response(
f"Stream: {stream}\n"
f"Tool choice: {tool_choice}\n"
f"Response format: {response_format}\n"
f"Previous response id: {previous_response_id}\n"
)

return await self._client.responses.create(
previous_response_id=self._non_null_or_not_given(previous_response_id),
instructions=self._non_null_or_not_given(system_instructions),
model=self.model,
input=list_input,
Expand Down
8 changes: 8 additions & 0 deletions src/agents/result.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def to_input_list(self) -> list[TResponseInputItem]:

return original_items + new_items

@property
def last_response_id(self) -> str | None:
"""Convenience method to get the response ID of the last model response."""
if not self.raw_responses:
return None

return self.raw_responses[-1].response_id


@dataclass
class RunResult(RunResultBase):
Expand Down
24 changes: 22 additions & 2 deletions src/agents/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ async def run(
max_turns: int = DEFAULT_MAX_TURNS,
hooks: RunHooks[TContext] | None = None,
run_config: RunConfig | None = None,
previous_response_id: str | None = None,
) -> RunResult:
"""Run a workflow starting at the given agent. The agent will run in a loop until a final
output is generated. The loop runs like so:
Expand All @@ -141,6 +142,8 @@ async def run(
AI invocation (including any tool calls that might occur).
hooks: An object that receives callbacks on various lifecycle events.
run_config: Global settings for the entire agent run.
previous_response_id: The ID of the previous response, if using OpenAI models via the
Responses API, this allows you to skip passing in input from the previous turn.

Returns:
A run result containing all the inputs, guardrail results and the output of the last
Expand Down Expand Up @@ -230,6 +233,7 @@ async def run(
run_config=run_config,
should_run_agent_start_hooks=should_run_agent_start_hooks,
tool_use_tracker=tool_use_tracker,
previous_response_id=previous_response_id,
),
)
else:
Expand All @@ -243,6 +247,7 @@ async def run(
run_config=run_config,
should_run_agent_start_hooks=should_run_agent_start_hooks,
tool_use_tracker=tool_use_tracker,
previous_response_id=previous_response_id,
)
should_run_agent_start_hooks = False

Expand Down Expand Up @@ -291,6 +296,7 @@ def run_sync(
max_turns: int = DEFAULT_MAX_TURNS,
hooks: RunHooks[TContext] | None = None,
run_config: RunConfig | None = None,
previous_response_id: str | None = None,
) -> RunResult:
"""Run a workflow synchronously, starting at the given agent. Note that this just wraps the
`run` method, so it will not work if there's already an event loop (e.g. inside an async
Expand Down Expand Up @@ -319,6 +325,8 @@ def run_sync(
AI invocation (including any tool calls that might occur).
hooks: An object that receives callbacks on various lifecycle events.
run_config: Global settings for the entire agent run.
previous_response_id: The ID of the previous response, if using OpenAI models via the
Responses API, this allows you to skip passing in input from the previous turn.

Returns:
A run result containing all the inputs, guardrail results and the output of the last
Expand All @@ -332,6 +340,7 @@ def run_sync(
max_turns=max_turns,
hooks=hooks,
run_config=run_config,
previous_response_id=previous_response_id,
)
)

Expand All @@ -344,6 +353,7 @@ def run_streamed(
max_turns: int = DEFAULT_MAX_TURNS,
hooks: RunHooks[TContext] | None = None,
run_config: RunConfig | None = None,
previous_response_id: str | None = None,
) -> RunResultStreaming:
"""Run a workflow starting at the given agent in streaming mode. The returned result object
contains a method you can use to stream semantic events as they are generated.
Expand All @@ -370,7 +380,8 @@ def run_streamed(
AI invocation (including any tool calls that might occur).
hooks: An object that receives callbacks on various lifecycle events.
run_config: Global settings for the entire agent run.

previous_response_id: The ID of the previous response, if using OpenAI models via the
Responses API, this allows you to skip passing in input from the previous turn.
Returns:
A result object that contains data about the run, as well as a method to stream events.
"""
Expand Down Expand Up @@ -428,6 +439,7 @@ def run_streamed(
hooks=hooks,
context_wrapper=context_wrapper,
run_config=run_config,
previous_response_id=previous_response_id,
)
)
return streamed_result
Expand Down Expand Up @@ -485,6 +497,7 @@ async def _run_streamed_impl(
hooks: RunHooks[TContext],
context_wrapper: RunContextWrapper[TContext],
run_config: RunConfig,
previous_response_id: str | None,
):
current_span: Span[AgentSpanData] | None = None
current_agent = starting_agent
Expand Down Expand Up @@ -554,6 +567,7 @@ async def _run_streamed_impl(
should_run_agent_start_hooks,
tool_use_tracker,
all_tools,
previous_response_id,
)
should_run_agent_start_hooks = False

Expand Down Expand Up @@ -623,6 +637,7 @@ async def _run_single_turn_streamed(
should_run_agent_start_hooks: bool,
tool_use_tracker: AgentToolUseTracker,
all_tools: list[Tool],
previous_response_id: str | None,
) -> SingleStepResult:
if should_run_agent_start_hooks:
await asyncio.gather(
Expand Down Expand Up @@ -662,6 +677,7 @@ async def _run_single_turn_streamed(
get_model_tracing_impl(
run_config.tracing_disabled, run_config.trace_include_sensitive_data
),
previous_response_id=previous_response_id,
):
if isinstance(event, ResponseCompletedEvent):
usage = (
Expand All @@ -677,7 +693,7 @@ async def _run_single_turn_streamed(
final_response = ModelResponse(
output=event.response.output,
usage=usage,
referenceable_id=event.response.id,
response_id=event.response.id,
)

streamed_result._event_queue.put_nowait(RawResponsesStreamEvent(data=event))
Expand Down Expand Up @@ -717,6 +733,7 @@ async def _run_single_turn(
run_config: RunConfig,
should_run_agent_start_hooks: bool,
tool_use_tracker: AgentToolUseTracker,
previous_response_id: str | None,
) -> SingleStepResult:
# Ensure we run the hooks before anything else
if should_run_agent_start_hooks:
Expand Down Expand Up @@ -746,6 +763,7 @@ async def _run_single_turn(
context_wrapper,
run_config,
tool_use_tracker,
previous_response_id,
)

return await cls._get_single_step_result_from_response(
Expand Down Expand Up @@ -888,6 +906,7 @@ async def _get_new_response(
context_wrapper: RunContextWrapper[TContext],
run_config: RunConfig,
tool_use_tracker: AgentToolUseTracker,
previous_response_id: str | None,
) -> ModelResponse:
model = cls._get_model(agent, run_config)
model_settings = agent.model_settings.resolve(run_config.model_settings)
Expand All @@ -903,6 +922,7 @@ async def _get_new_response(
tracing=get_model_tracing_impl(
run_config.tracing_disabled, run_config.trace_include_sensitive_data
),
previous_response_id=previous_response_id,
)

context_wrapper.usage.add(new_response.usage)
Expand Down
6 changes: 5 additions & 1 deletion tests/fake_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ async def get_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
*,
previous_response_id: str | None,
) -> ModelResponse:
self.last_turn_args = {
"system_instructions": system_instructions,
Expand Down Expand Up @@ -81,7 +83,7 @@ async def get_response(
return ModelResponse(
output=output,
usage=Usage(),
referenceable_id=None,
response_id=None,
)

async def stream_response(
Expand All @@ -93,6 +95,8 @@ async def stream_response(
output_schema: AgentOutputSchema | None,
handoffs: list[Handoff],
tracing: ModelTracing,
*,
previous_response_id: str | None,
) -> AsyncIterator[TResponseStreamEvent]:
with generation_span(disabled=not self.tracing_enabled) as span:
output = self.get_next_output()
Expand Down
Loading