Skip to content

Commit 8493141

Browse files
lgrammelshaper
andauthored
feat (providers/openai): add support for reasoning summaries (#5906) (#5909)
Co-authored-by: Walter Korman <shaper@vercel.com>
1 parent 07599dd commit 8493141

6 files changed

Lines changed: 390 additions & 2 deletions

File tree

.changeset/mean-monkeys-sip.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@ai-sdk/openai': patch
3+
---
4+
5+
feat (providers/openai): add support for reasoning summaries

content/providers/01-ai-sdk-providers/02-openai.mdx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,9 @@ The following provider options are available:
563563
- **reasoningEffort** _'low' | 'medium' | 'high'_
564564
Reasoning effort for reasoning models. Defaults to `medium`. If you use `providerOptions` to set the `reasoningEffort` option, this model setting will be ignored.
565565

566+
- **reasoningSummary** _'auto' | 'detailed'_
567+
Controls whether the model returns its reasoning process. Set to `'auto'` for a condensed summary, `'detailed'` for more comprehensive reasoning. Defaults to `undefined` (no reasoning summaries). When enabled, reasoning summaries appear in the stream as events with type `'reasoning'` and in non-streaming responses within the `reasoning` field.
568+
566569
- **strictSchemas** _boolean_
567570
Whether to use strict JSON schemas in tools and when generating JSON outputs. Defaults to `true`.
568571

@@ -616,6 +619,53 @@ const result = await generateText({
616619
const sources = result.sources;
617620
```
618621

622+
#### Reasoning Summaries
623+
624+
For reasoning models like `o3-mini`, `o3`, and `o4-mini`, you can enable reasoning summaries to see the model's thought process:
625+
626+
```ts highlight="8-9,16"
627+
import { openai } from '@ai-sdk/openai';
628+
import { streamText } from 'ai';
629+
630+
const result = streamText({
631+
model: openai.responses('o3-mini'),
632+
prompt: 'Tell me about the Mission burrito debate in San Francisco.',
633+
providerOptions: {
634+
openai: {
635+
reasoningSummary: 'detailed', // 'auto' for condensed or 'detailed' for comprehensive
636+
},
637+
},
638+
});
639+
640+
for await (const part of result.fullStream) {
641+
if (part.type === 'reasoning') {
642+
console.log(`Reasoning: ${part.textDelta}`);
643+
} else if (part.type === 'text-delta') {
644+
process.stdout.write(part.textDelta);
645+
}
646+
}
647+
```
648+
649+
For non-streaming calls with `generateText`, the reasoning summaries are available in the `reasoning` field of the response:
650+
651+
```ts highlight="8-9,13"
652+
import { openai } from '@ai-sdk/openai';
653+
import { generateText } from 'ai';
654+
655+
const result = await generateText({
656+
model: openai.responses('o3-mini'),
657+
prompt: 'Tell me about the Mission burrito debate in San Francisco.',
658+
providerOptions: {
659+
openai: {
660+
reasoningSummary: 'detailed',
661+
},
662+
},
663+
});
664+
console.log('Reasoning:', result.reasoning);
665+
```
666+
667+
Learn more about reasoning summaries in the [OpenAI documentation](https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries).
668+
619669
#### PDF support
620670

621671
The OpenAI Responses API supports reading PDF files.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { openai, OpenAIResponsesProviderOptions } from '@ai-sdk/openai';
2+
import { generateText } from 'ai';
3+
import 'dotenv/config';
4+
5+
async function main() {
6+
const result = await generateText({
7+
// supported: o4-mini, o3, o3-mini and o1
8+
model: openai.responses('o3-mini'),
9+
prompt:
10+
'Tell me about the debate over Taqueria La Cumbre and El Farolito and who created the San Francisco Mission-style burrito.',
11+
providerOptions: {
12+
openai: {
13+
// https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
14+
reasoningSummary: 'auto', // 'detailed'
15+
} satisfies OpenAIResponsesProviderOptions,
16+
},
17+
});
18+
19+
process.stdout.write('\x1b[34m');
20+
console.log(result.reasoning);
21+
process.stdout.write('\x1b[0m');
22+
console.log(result.text);
23+
console.log();
24+
console.log('Finish reason:', result.finishReason);
25+
console.log('Usage:', {
26+
...result.usage,
27+
reasoningTokens: result.providerMetadata?.openai?.reasoningTokens,
28+
});
29+
console.log();
30+
console.log('Request:', JSON.stringify(result.request, null, 2));
31+
console.log('Response:', JSON.stringify(result.response, null, 2));
32+
}
33+
34+
main().catch(console.error);
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'dotenv/config';
2+
import { openai } from '@ai-sdk/openai';
3+
import { streamText } from 'ai';
4+
5+
async function main() {
6+
const result = streamText({
7+
// supported: o4-mini, o3, o3-mini and o1
8+
model: openai.responses('o3-mini'),
9+
system: 'You are a helpful assistant.',
10+
prompt:
11+
'Tell me about the debate over Taqueria La Cumbre and El Farolito and who created the San Francisco Mission-style burrito.',
12+
providerOptions: {
13+
openai: {
14+
// https://platform.openai.com/docs/guides/reasoning?api-mode=responses#reasoning-summaries
15+
// reasoningSummary: 'auto', // 'detailed'
16+
reasoningSummary: 'detailed',
17+
},
18+
},
19+
});
20+
21+
for await (const part of result.fullStream) {
22+
if (part.type === 'reasoning' && part.reasoningType === 'text') {
23+
process.stdout.write('\x1b[34m' + part.text + '\x1b[0m');
24+
} else if (part.type === 'text') {
25+
process.stdout.write(part.text);
26+
}
27+
}
28+
29+
console.log();
30+
console.log('Finish reason:', await result.finishReason);
31+
console.log('Usage:', await result.usage);
32+
console.log('Provider metadata:', await result.providerMetadata);
33+
}
34+
35+
main().catch(console.error);

packages/openai/src/responses/openai-responses-language-model.test.ts

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,83 @@ describe('OpenAIResponsesLanguageModel', () => {
5050
'https://api.openai.com/v1/responses': {},
5151
});
5252

53+
const prepareReasoningResponse = () => {
54+
server.urls['https://api.openai.com/v1/responses'].response = {
55+
type: 'json-value',
56+
body: {
57+
id: 'resp_67c97c0203188190a025beb4a75242bc',
58+
object: 'response',
59+
created_at: 1741257730,
60+
status: 'completed',
61+
error: null,
62+
incomplete_details: null,
63+
input: [],
64+
instructions: null,
65+
max_output_tokens: null,
66+
model: 'o3-mini-2025-01-31',
67+
output: [
68+
{
69+
id: 'rs_6808709f6fcc8191ad2e2fdd784017b3',
70+
type: 'reasoning',
71+
summary: [
72+
{
73+
type: 'summary_text',
74+
text: '**Exploring burrito origins**\n\nThe user is curious about the debate regarding Taqueria La Cumbre and El Farolito.',
75+
},
76+
{
77+
type: 'summary_text',
78+
text: "**Investigating burrito origins**\n\nThere's a fascinating debate about who created the Mission burrito.",
79+
},
80+
],
81+
},
82+
{
83+
id: 'msg_67c97c02656c81908e080dfdf4a03cd1',
84+
type: 'message',
85+
status: 'completed',
86+
role: 'assistant',
87+
content: [
88+
{
89+
type: 'output_text',
90+
text: 'answer text',
91+
annotations: [],
92+
},
93+
],
94+
},
95+
],
96+
parallel_tool_calls: true,
97+
previous_response_id: null,
98+
reasoning: {
99+
effort: 'low',
100+
summary: 'auto',
101+
},
102+
store: true,
103+
temperature: 1,
104+
text: {
105+
format: {
106+
type: 'text',
107+
},
108+
},
109+
tool_choice: 'auto',
110+
tools: [],
111+
top_p: 1,
112+
truncation: 'disabled',
113+
usage: {
114+
input_tokens: 34,
115+
input_tokens_details: {
116+
cached_tokens: 0,
117+
},
118+
output_tokens: 538,
119+
output_tokens_details: {
120+
reasoning_tokens: 320,
121+
},
122+
total_tokens: 572,
123+
},
124+
user: null,
125+
metadata: {},
126+
},
127+
};
128+
};
129+
53130
describe('doGenerate', () => {
54131
describe('basic text response', () => {
55132
beforeEach(() => {
@@ -695,6 +772,55 @@ describe('OpenAIResponsesLanguageModel', () => {
695772
{ type: 'unsupported-setting', setting: 'stopSequences' },
696773
]);
697774
});
775+
776+
it('should extract reasoning summary', async () => {
777+
prepareReasoningResponse();
778+
779+
const result = await createModel('o3-mini').doGenerate({
780+
prompt: TEST_PROMPT,
781+
inputFormat: 'prompt',
782+
providerOptions: {
783+
openai: {
784+
reasoningEffort: 'low',
785+
reasoningSummary: 'auto',
786+
},
787+
},
788+
});
789+
790+
expect(result.content).toMatchInlineSnapshot(`
791+
[
792+
{
793+
"reasoningType": "text",
794+
"text": "**Exploring burrito origins**
795+
796+
The user is curious about the debate regarding Taqueria La Cumbre and El Farolito.,**Investigating burrito origins**
797+
798+
There's a fascinating debate about who created the Mission burrito.",
799+
"type": "reasoning",
800+
},
801+
{
802+
"text": "answer text",
803+
"type": "text",
804+
},
805+
]
806+
`);
807+
808+
expect(result.providerMetadata).toStrictEqual({
809+
openai: {
810+
responseId: 'resp_67c97c0203188190a025beb4a75242bc',
811+
cachedPromptTokens: 0,
812+
reasoningTokens: 320,
813+
},
814+
});
815+
816+
expect(await server.calls[0].requestBody).toMatchObject({
817+
model: 'o3-mini',
818+
reasoning: {
819+
effort: 'low',
820+
summary: 'auto',
821+
},
822+
});
823+
});
698824
});
699825

700826
describe('tool calls', () => {
@@ -1377,5 +1503,98 @@ describe('OpenAIResponsesLanguageModel', () => {
13771503
]
13781504
`);
13791505
});
1506+
1507+
it('should stream reasoning summary', async () => {
1508+
prepareReasoningResponse();
1509+
1510+
server.urls['https://api.openai.com/v1/responses'].response = {
1511+
type: 'stream-chunks',
1512+
chunks: [
1513+
`data:{"type":"response.created","response":{"id":"resp_67c9a81b6a048190a9ee441c5755a4e8","object":"response","created_at":1741269019,"status":"in_progress","error":null,"incomplete_details":null,"input":[],"instructions":null,"max_output_tokens":null,"model":"o3-mini-2025-01-31","output":[],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":"low","summary":"auto"},"store":true,"temperature":null,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":null,"truncation":"disabled","usage":null,"user":null,"metadata":{}}}\n\n`,
1514+
`data:{"type":"response.reasoning_summary_text.delta","item_id":"rs_68082c0556348191af675cee0453109b","output_index":0,"summary_index":0,"delta":"**Exploring burrito origins**\\n\\nThe user is"}\n\n`,
1515+
`data:{"type":"response.reasoning_summary_text.delta","item_id":"rs_68082c0556348191af675cee0453109b","output_index":0,"summary_index":0,"delta":" curious about the debate regarding Taqueria La Cumbre and El Farolito."}\n\n`,
1516+
`data:{"type":"response.reasoning_summary_text.done","item_id":"rs_68082c0556348191af675cee0453109b","output_index":0,"summary_index":0,"text":"**Exploring burrito origins**\\n\\nThe user is curious about the debate regarding Taqueria La Cumbre and El Farolito."}\n\n`,
1517+
`data:{"type":"response.reasoning_summary_text.delta","item_id":"rs_68082c0556348191af675cee0453109b","output_index":0,"summary_index":1,"delta":"**Investigating burrito origins**\\n\\nThere's a fascinating debate about who created the Mission burrito."}\n\n`,
1518+
`data:{"type":"response.reasoning_summary_part.done","item_id":"rs_68082c0556348191af675cee0453109b","output_index":0,"summary_index":1,"part":{"type":"summary_text","text":"**Investigating burrito origins**\\n\\nThere's a fascinating debate about who created the Mission burrito."}}\n\n`,
1519+
`data:{"type":"response.output_item.added","output_index":1,"item":{"id":"msg_67c9a81dea8c8190b79651a2b3adf91e","type":"message","status":"in_progress","role":"assistant","content":[]}}\n\n`,
1520+
`data:{"type":"response.content_part.added","item_id":"msg_67c9a81dea8c8190b79651a2b3adf91e","output_index":1,"content_index":0,"part":{"type":"output_text","text":"","annotations":[]}}\n\n`,
1521+
`data:{"type":"response.output_text.delta","item_id":"msg_67c9a81dea8c8190b79651a2b3adf91e","output_index":1,"content_index":0,"delta":"Taqueria La Cumbre"}\n\n`,
1522+
`data:{"type":"response.completed","response":{"id":"resp_67c9a81b6a048190a9ee441c5755a4e8","object":"response","created_at":1741269019,"status":"completed","error":null,"incomplete_details":null,"input":[],"instructions":null,"max_output_tokens":null,"model":"o3-mini-2025-01-31","output":[{"id":"rs_68082c0556348191af675cee0453109b","type":"reasoning","summary":[{"type":"summary_text","text":"**Exploring burrito origins**\\n\\nThe user is curious about the debate regarding Taqueria La Cumbre and El Farolito."},{"type":"summary_text","text":"**Investigating burrito origins**\\n\\nThere's a fascinating debate about who created the Mission burrito."}]},{"id":"msg_67c9a81dea8c8190b79651a2b3adf91e","type":"message","status":"completed","role":"assistant","content":[{"type":"output_text","text":"Taqueria La Cumbre","annotations":[]}]}],"parallel_tool_calls":true,"previous_response_id":null,"reasoning":{"effort":"low","summary":"auto"},"store":true,"temperature":null,"text":{"format":{"type":"text"}},"tool_choice":"auto","tools":[],"top_p":null,"truncation":"disabled","usage":{"input_tokens":543,"input_tokens_details":{"cached_tokens":234},"output_tokens":478,"output_tokens_details":{"reasoning_tokens":350},"total_tokens":1021},"user":null,"metadata":{}}}\n\n`,
1523+
],
1524+
};
1525+
1526+
const { stream } = await createModel('o3-mini').doStream({
1527+
inputFormat: 'prompt',
1528+
prompt: TEST_PROMPT,
1529+
providerOptions: {
1530+
openai: {
1531+
reasoningEffort: 'low',
1532+
reasoningSummary: 'auto',
1533+
},
1534+
},
1535+
});
1536+
1537+
expect(await convertReadableStreamToArray(stream)).toMatchInlineSnapshot(`
1538+
[
1539+
{
1540+
"type": "stream-start",
1541+
"warnings": [],
1542+
},
1543+
{
1544+
"id": "resp_67c9a81b6a048190a9ee441c5755a4e8",
1545+
"modelId": "o3-mini-2025-01-31",
1546+
"timestamp": 2025-03-06T13:50:19.000Z,
1547+
"type": "response-metadata",
1548+
},
1549+
{
1550+
"reasoningType": "text",
1551+
"text": "**Exploring burrito origins**
1552+
1553+
The user is",
1554+
"type": "reasoning",
1555+
},
1556+
{
1557+
"reasoningType": "text",
1558+
"text": " curious about the debate regarding Taqueria La Cumbre and El Farolito.",
1559+
"type": "reasoning",
1560+
},
1561+
{
1562+
"reasoningType": "text",
1563+
"text": "**Investigating burrito origins**
1564+
1565+
There's a fascinating debate about who created the Mission burrito.",
1566+
"type": "reasoning",
1567+
},
1568+
{
1569+
"text": "Taqueria La Cumbre",
1570+
"type": "text",
1571+
},
1572+
{
1573+
"finishReason": "stop",
1574+
"providerMetadata": {
1575+
"openai": {
1576+
"cachedPromptTokens": 234,
1577+
"reasoningTokens": 350,
1578+
"responseId": "resp_67c9a81b6a048190a9ee441c5755a4e8",
1579+
},
1580+
},
1581+
"type": "finish",
1582+
"usage": {
1583+
"inputTokens": 543,
1584+
"outputTokens": 478,
1585+
},
1586+
},
1587+
]
1588+
`);
1589+
1590+
expect(await server.calls[0].requestBody).toMatchObject({
1591+
model: 'o3-mini',
1592+
reasoning: {
1593+
effort: 'low',
1594+
summary: 'auto',
1595+
},
1596+
stream: true,
1597+
});
1598+
});
13801599
});
13811600
});

0 commit comments

Comments
 (0)