Skip to content

Commit 4c2bb77

Browse files
authored
fix (provider/openai): send sources action as include (#8559)
## Background `web_search_call.action.sources` was not sent to the OpenAI API. ## Summary * check if tool is `web_search` or `web_search_preview` and use correct name in tool calls and results * automatically add `web_search_call.action.sources` include when either web search tool is present * remove redundant tests ## Manual Verification - [x] run `examples/ai-core/src/generate-text/openai-web-search-tool.ts` ## Tasks - [x] add include flag automatically - [x] investigate why there are no tool calls / results in the generate case - [x] update generate test (outputs, check flag) - [x] add easy way to record raw chunks and use them in tests - [x] investigate why stream does not send sources - [x] update stream test (outputs, check flag) - [x] update examples - [x] changeset ## Related Issues Closes #8528
1 parent 561e8b0 commit 4c2bb77

File tree

8 files changed

+924
-751
lines changed

8 files changed

+924
-751
lines changed

.changeset/eighty-buses-raise.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+
fix (provider/openai): send sources action as include
Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { openai } from '@ai-sdk/openai';
22
import { generateText } from 'ai';
3-
import 'dotenv/config';
3+
import { run } from '../lib/run';
44

5-
async function main() {
5+
run(async () => {
66
const result = await generateText({
7-
model: openai.responses('gpt-5'),
7+
model: openai.responses('gpt-5-mini'),
88
prompt: 'What happened in tech news today?',
99
tools: {
1010
web_search: openai.tools.webSearch({
@@ -13,13 +13,9 @@ async function main() {
1313
},
1414
});
1515

16-
for (const toolCall of result.toolCalls) {
17-
if (toolCall.toolName === 'web_search') {
18-
console.log('Search query:', toolCall.input);
19-
}
20-
}
21-
16+
console.dir(result.response.body, { depth: Infinity });
17+
console.dir(result.toolCalls, { depth: Infinity });
18+
console.dir(result.toolResults, { depth: Infinity });
19+
console.dir(result.sources, { depth: Infinity });
2220
console.log(result.text);
23-
}
24-
25-
main().catch(console.error);
21+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { StreamTextResult } from 'ai';
2+
import fs from 'fs';
3+
4+
export async function saveRawChunks({
5+
result,
6+
filename,
7+
}: {
8+
result: StreamTextResult<any, any>;
9+
filename: string;
10+
}) {
11+
const rawChunks: unknown[] = [];
12+
for await (const chunk of result.fullStream) {
13+
if (chunk.type === 'raw') {
14+
rawChunks.push(chunk.rawValue);
15+
}
16+
}
17+
18+
fs.writeFileSync(
19+
filename,
20+
rawChunks.map(chunk => JSON.stringify(chunk)).join('\n'),
21+
);
22+
}
Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { openai } from '@ai-sdk/openai';
2-
import { stepCountIs, streamText } from 'ai';
3-
import 'dotenv/config';
2+
import { streamText } from 'ai';
3+
import { run } from '../lib/run';
44

5-
async function main() {
5+
run(async () => {
66
const result = streamText({
7-
model: openai.responses('gpt-4o-mini'),
7+
model: openai.responses('gpt-5-mini'),
8+
prompt: 'What happened in tech news today?',
89
tools: {
910
web_search: openai.tools.webSearch({
10-
searchContextSize: 'high',
11+
searchContextSize: 'medium',
1112
}),
1213
},
13-
prompt: 'Look up the company that owns Sonny Angel',
14-
stopWhen: stepCountIs(5), // note: should stop after a single step
1514
});
1615

1716
for await (const chunk of result.fullStream) {
@@ -22,36 +21,30 @@ async function main() {
2221
}
2322

2423
case 'tool-call': {
25-
console.log('Tool call:', JSON.stringify(chunk, null, 2));
24+
console.log(
25+
`\x1b[32m\x1b[1mTool call:\x1b[22m ${JSON.stringify(chunk, null, 2)}\x1b[0m`,
26+
);
2627
break;
2728
}
2829

2930
case 'tool-result': {
30-
console.log('Tool result:', JSON.stringify(chunk, null, 2));
31+
console.log(
32+
`\x1b[32m\x1b[1mTool result:\x1b[22m ${JSON.stringify(chunk, null, 2)}\x1b[0m`,
33+
);
3134
break;
3235
}
3336

3437
case 'source': {
3538
if (chunk.sourceType === 'url') {
36-
process.stdout.write(`\n\n Source: ${chunk.title} (${chunk.url})`);
37-
} else {
38-
process.stdout.write(`\n\n Document: ${chunk.title}`);
39+
process.stdout.write(
40+
`\n\n\x1b[36mSource: ${chunk.title} (${chunk.url})\x1b[0m\n\n`,
41+
);
3942
}
4043
break;
4144
}
4245

43-
case 'finish-step': {
44-
console.log();
45-
console.log();
46-
console.log('STEP FINISH');
47-
console.log('Finish reason:', chunk.finishReason);
48-
console.log('Usage:', chunk.usage);
49-
console.log();
50-
break;
51-
}
52-
5346
case 'finish': {
54-
console.log('FINISH');
47+
console.log('\n\nFINISH');
5548
console.log('Finish reason:', chunk.finishReason);
5649
console.log('Total Usage:', chunk.totalUsage);
5750
break;
@@ -62,6 +55,4 @@ async function main() {
6255
break;
6356
}
6457
}
65-
}
66-
67-
main().catch(console.error);
58+
});

packages/openai/src/responses/openai-responses-api-types.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ export type OpenAIResponsesMessage =
1313
| OpenAIFileSearchCall
1414
| OpenAIResponsesReasoning;
1515

16+
export type OpenAIResponsesIncludeOptions =
17+
| Array<
18+
| 'web_search_call.action.sources'
19+
| 'code_interpreter_call.outputs'
20+
| 'computer_call_output.output.image_url'
21+
| 'file_search_call.results'
22+
| 'message.input_image.image_url'
23+
| 'message.output_text.logprobs'
24+
| 'reasoning.encrypted_content'
25+
>
26+
| undefined
27+
| null;
28+
1629
export type OpenAIResponsesSystemMessage = {
1730
role: 'system' | 'developer';
1831
content: string;

0 commit comments

Comments
 (0)