Skip to content

Commit ceb0cf1

Browse files
committed
update tests and docs
1 parent 4f9f71a commit ceb0cf1

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
@@ -213,9 +213,12 @@ class OpenAIResponsesModelSettings(OpenAIChatModelSettings, total=False):
213213
`medium`, and `high`.
214214
"""
215215

216-
openai_previous_response_id: str
216+
openai_previous_response_id: Literal['auto'] | str
217217
"""The identifier of the most recent response to include in the API request.
218218
219+
When set to `auto`, the request automatically uses the most recent
220+
`provider_response_id` along with the latest request from the message history.
221+
219222
This enables the model to reference previous reasoning traces.
220223
See the [OpenAI Responses API documentation](https://platform.openai.com/docs/guides/reasoning#keeping-reasoning-items-in-context)
221224
for more information.
@@ -963,11 +966,14 @@ async def _responses_create(
963966
tool_choice = 'required'
964967
else:
965968
tool_choice = 'auto'
966-
969+
print(messages)
970+
print('-------')
967971
previous_response_id = model_settings.get('openai_previous_response_id')
968-
if not previous_response_id:
972+
if previous_response_id == 'auto':
969973
messages, previous_response_id = self._get_response_id_and_trim(messages)
970-
974+
print(messages)
975+
print(previous_response_id)
976+
print('==========')
971977
instructions, openai_messages = await self._map_messages(messages)
972978
reasoning = self._get_reasoning(model_settings)
973979

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
@@ -1144,7 +1144,50 @@ async def test_openai_previous_response_id(allow_model_requests: None, openai_ap
11441144
assert result.output == snapshot('sesame')
11451145

11461146

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

12161259

1217-
async def test_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
1260+
async def test_openai_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
12181261
"""Test if message history is trimmed when model responses are from same model"""
12191262
history = [
12201263
ModelRequest(

0 commit comments

Comments
 (0)