Skip to content

Commit 1873417

Browse files
committed
update tests and docs
1 parent 93d3fd2 commit 1873417

File tree

4 files changed

+150
-8
lines changed

4 files changed

+150
-8
lines changed

docs/models/openai.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ As of 7:48 AM on Wednesday, April 2, 2025, in Tokyo, Japan, the weather is cloud
143143

144144
You can learn more about the differences between the Responses API and Chat Completions API in the [OpenAI API docs](https://platform.openai.com/docs/guides/responses-vs-chat-completions).
145145

146+
#### Referencing earlier responses
147+
146148
The Responses API also supports referencing earlier model responses in a new request. This is available through the `openai_previous_response_id` field in
147149
[`OpenAIResponsesModelSettings`][pydantic_ai.models.openai.OpenAIResponsesModelSettings].
148150

@@ -164,8 +166,32 @@ print(result.output)
164166

165167
By passing the `provider_response_id` from an earlier run, you can allow the model to build on its own prior reasoning without needing to resend the full message history.
166168

167-
If message history is provided and all responses come from the same OpenAI model,
168-
Pydantic AI will automatically only send the the latest request and the `previous_response_id` from the latest response to the API for efficiency.
169+
Alternatively, `openai_previous_response_id` field also supports `auto` mode. When enabled, Pydantic AI automatically selects the latest request and the most recent `provider_response_id` from message history to send to OpenAI API, leveraging server-side history instead, for improved efficiency. If `openai_previous_response_id` is not set, full history is sent.
170+
171+
```python
172+
from pydantic_ai import Agent
173+
from pydantic_ai.models.openai import OpenAIResponsesModel, OpenAIResponsesModelSettings
174+
175+
model = OpenAIResponsesModel('gpt-4o')
176+
agent = Agent(model=model)
177+
178+
result1 = agent.run_sync('Tell me a joke.')
179+
print(result1.output)
180+
#> Did you hear about the toothpaste scandal? They called it Colgate.
181+
182+
# When set to 'auto', only the latest request and the most recent provider_response_id
183+
# from history is sent to OpenAI API.
184+
model_settings = OpenAIResponsesModelSettings(openai_previous_response_id='auto')
185+
result2 = agent.run_sync(
186+
'Explain?',
187+
message_history=result1.new_messages(),
188+
model_settings=model_settings
189+
)
190+
print(result2.output)
191+
#> This is an excellent joke invented by Samuel Colvin, it needs no explanation.
192+
```
193+
It is recommended to use `auto` mode only when the history comes from a single, uninterrupted run,
194+
with all responses coming from the same OpenAI model (e.g like internal tool calls), as the server-side history will override any locally modified history.
169195

170196
## OpenAI-compatible Models
171197

pydantic_ai_slim/pydantic_ai/models/openai.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,12 @@ class OpenAIResponsesModelSettings(OpenAIChatModelSettings, total=False):
211211
`medium`, and `high`.
212212
"""
213213

214-
openai_previous_response_id: str
214+
openai_previous_response_id: Literal['auto'] | str
215215
"""The identifier of the most recent response to include in the API request.
216216
217+
When set to `auto`, the request automatically uses the most recent
218+
`provider_response_id` along with the latest request from the message history.
219+
217220
This enables the model to reference previous reasoning traces.
218221
See the [OpenAI Responses API documentation](https://platform.openai.com/docs/guides/reasoning#keeping-reasoning-items-in-context)
219222
for more information.
@@ -932,11 +935,14 @@ async def _responses_create(
932935
tool_choice = 'required'
933936
else:
934937
tool_choice = 'auto'
935-
938+
print(messages)
939+
print('-------')
936940
previous_response_id = model_settings.get('openai_previous_response_id')
937-
if not previous_response_id:
941+
if previous_response_id == 'auto':
938942
messages, previous_response_id = self._get_response_id_and_trim(messages)
939-
943+
print(messages)
944+
print(previous_response_id)
945+
print('==========')
940946
instructions, openai_messages = await self._map_messages(messages)
941947
reasoning = self._get_reasoning(model_settings)
942948

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
interactions:
2+
- request:
3+
headers:
4+
accept:
5+
- application/json
6+
accept-encoding:
7+
- gzip, deflate
8+
connection:
9+
- keep-alive
10+
content-type:
11+
- application/json
12+
host:
13+
- api.openai.com
14+
method: POST
15+
parsed_body:
16+
input:
17+
- content: What is the first secret key?
18+
role: user
19+
instructions: ''
20+
model: gpt-5
21+
text:
22+
format:
23+
type: text
24+
previous_response_id: resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b
25+
uri: https://api.openai.com/v1/responses
26+
response:
27+
headers:
28+
content-type:
29+
- application/json
30+
parsed_body:
31+
created_at: 1743075630
32+
error: null
33+
id: resp_a4168b9bda81f5c8197a5a51a20a9f4150a000497db2a4c5
34+
incomplete_details: null
35+
instructions: ''
36+
max_output_tokens: null
37+
metadata: {}
38+
model: gpt-5
39+
object: response
40+
output:
41+
- content:
42+
- annotations: []
43+
text: "sesame"
44+
type: output_text
45+
id: msg_test_previous_response_id_auto
46+
role: assistant
47+
status: completed
48+
type: message
49+
parallel_tool_calls: true
50+
previous_response_id: resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b
51+
reasoning: null
52+
status: complete
53+
status_details: null
54+
tool_calls: null
55+
total_tokens: 15
56+
usage:
57+
input_tokens: 10
58+
input_tokens_details:
59+
cached_tokens: 0
60+
output_tokens: 1
61+
output_tokens_details:
62+
reasoning_tokens: 0
63+
total_tokens: 11
64+
status:
65+
code: 200
66+
message: OK
67+
version: 1

tests/models/test_openai_responses.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,7 +1146,50 @@ async def test_openai_previous_response_id(allow_model_requests: None, openai_ap
11461146
assert result.output == snapshot('sesame')
11471147

11481148

1149-
async def test_previous_response_id_mixed_model_history(allow_model_requests: None, openai_api_key: str):
1149+
@pytest.mark.vcr()
1150+
async def test_openai_previous_response_id_auto_mode(allow_model_requests: None, openai_api_key: str):
1151+
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
1152+
history = [
1153+
ModelRequest(
1154+
parts=[
1155+
UserPromptPart(
1156+
content='The first secret key is sesame',
1157+
),
1158+
],
1159+
),
1160+
ModelResponse(
1161+
parts=[
1162+
TextPart(content='Open sesame! What would you like to unlock?'),
1163+
],
1164+
model_name='gpt-5',
1165+
provider_name='openai',
1166+
provider_response_id='resp_68b9bd97025c8195b443af591ca2345c08cb6072affe6099',
1167+
),
1168+
ModelRequest(
1169+
parts=[
1170+
UserPromptPart(
1171+
content='The second secret key is olives',
1172+
),
1173+
],
1174+
),
1175+
ModelResponse(
1176+
parts=[
1177+
TextPart(content='Understood'),
1178+
],
1179+
model_name='gpt-5',
1180+
provider_name='openai',
1181+
provider_response_id='resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b',
1182+
),
1183+
]
1184+
1185+
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
1186+
agent = Agent(model=model)
1187+
settings = OpenAIResponsesModelSettings(openai_previous_response_id='auto')
1188+
result = await agent.run('what is the first secret key', message_history=history, model_settings=settings)
1189+
assert result.output == snapshot('sesame')
1190+
1191+
1192+
async def test_openai_previous_response_id_mixed_model_history(allow_model_requests: None, openai_api_key: str):
11501193
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
11511194
history = [
11521195
ModelRequest(
@@ -1216,7 +1259,7 @@ async def test_previous_response_id_mixed_model_history(allow_model_requests: No
12161259
)
12171260

12181261

1219-
async def test_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
1262+
async def test_openai_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
12201263
"""Test if message history is trimmed when model responses are from same model"""
12211264
history = [
12221265
ModelRequest(

0 commit comments

Comments
 (0)