Skip to content

Commit c936450

Browse files
committed
update tests and docs
1 parent 8351ebe commit c936450

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
@@ -190,9 +190,12 @@ class OpenAIResponsesModelSettings(OpenAIChatModelSettings, total=False):
190190
`medium`, and `high`.
191191
"""
192192

193-
openai_previous_response_id: str
193+
openai_previous_response_id: Literal['auto'] | str
194194
"""The identifier of the most recent response to include in the API request.
195195
196+
When set to `auto`, the request automatically uses the most recent
197+
`provider_response_id` along with the latest request from the message history.
198+
196199
This enables the model to reference previous reasoning traces.
197200
See the [OpenAI Responses API documentation](https://platform.openai.com/docs/guides/reasoning#keeping-reasoning-items-in-context)
198201
for more information.
@@ -897,11 +900,14 @@ async def _responses_create(
897900
tool_choice = 'required'
898901
else:
899902
tool_choice = 'auto'
900-
903+
print(messages)
904+
print('-------')
901905
previous_response_id = model_settings.get('openai_previous_response_id')
902-
if not previous_response_id:
906+
if previous_response_id == 'auto':
903907
messages, previous_response_id = self._get_response_id_and_trim(messages)
904-
908+
print(messages)
909+
print(previous_response_id)
910+
print('==========')
905911
instructions, openai_messages = await self._map_messages(messages)
906912
reasoning = self._get_reasoning(model_settings)
907913

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

10981098

1099-
async def test_previous_response_id_mixed_model_history(allow_model_requests: None, openai_api_key: str):
1099+
@pytest.mark.vcr()
1100+
async def test_openai_previous_response_id_auto_mode(allow_model_requests: None, openai_api_key: str):
1101+
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
1102+
history = [
1103+
ModelRequest(
1104+
parts=[
1105+
UserPromptPart(
1106+
content='The first secret key is sesame',
1107+
),
1108+
],
1109+
),
1110+
ModelResponse(
1111+
parts=[
1112+
TextPart(content='Open sesame! What would you like to unlock?'),
1113+
],
1114+
model_name='gpt-5',
1115+
provider_name='openai',
1116+
provider_response_id='resp_68b9bd97025c8195b443af591ca2345c08cb6072affe6099',
1117+
),
1118+
ModelRequest(
1119+
parts=[
1120+
UserPromptPart(
1121+
content='The second secret key is olives',
1122+
),
1123+
],
1124+
),
1125+
ModelResponse(
1126+
parts=[
1127+
TextPart(content='Understood'),
1128+
],
1129+
model_name='gpt-5',
1130+
provider_name='openai',
1131+
provider_response_id='resp_68b9bda81f5c8197a5a51a20a9f4150a000497db2a4c777b',
1132+
),
1133+
]
1134+
1135+
model = OpenAIResponsesModel('gpt-5', provider=OpenAIProvider(api_key=openai_api_key))
1136+
agent = Agent(model=model)
1137+
settings = OpenAIResponsesModelSettings(openai_previous_response_id='auto')
1138+
result = await agent.run('what is the first secret key', message_history=history, model_settings=settings)
1139+
assert result.output == snapshot('sesame')
1140+
1141+
1142+
async def test_openai_previous_response_id_mixed_model_history(allow_model_requests: None, openai_api_key: str):
11001143
"""Test if invalid previous response id is ignored when history contains non-OpenAI responses"""
11011144
history = [
11021145
ModelRequest(
@@ -1166,7 +1209,7 @@ async def test_previous_response_id_mixed_model_history(allow_model_requests: No
11661209
)
11671210

11681211

1169-
async def test_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
1212+
async def test_openai_previous_response_id_same_model_history(allow_model_requests: None, openai_api_key: str):
11701213
"""Test if message history is trimmed when model responses are from same model"""
11711214
history = [
11721215
ModelRequest(

0 commit comments

Comments
 (0)