Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { openai } from '@ai-sdk/openai';
import type { OpenAIResponsesProviderOptions } from '@ai-sdk/openai/';
import { generateText, type ModelMessage, tool } from 'ai';
import { z } from 'zod';
import 'dotenv/config';

// Minimal reproduction example for GPT-5 invalid tool-call + looped agent
const extractTags = tool({
inputSchema: z.object({
tags: z.array(
z.string().describe('the keywords or tags referenced in the document'),
),
definitions: z
.record(z.string(), z.string())
.describe('The definitions that were extracted from the document'),
}),
async execute({ tags, definitions }) {
// Return the provided tags and definitions so the model sees a concrete result.
return { tags, definitions };
},
});

async function main() {
let _messages: ModelMessage[] = [
{
role: 'system',
content: 'You are a helpful assistant. Always use tools when requested.',
},
{
role: 'user',
content:
"Given the following document text, extract the 'tags' and their 'definitions'. Use the extractTags tool.\n\nDocument:\n\"Build a chatbot with React. The chatbot should support streaming and function calling. Also add unit tests and CI.\"\n\nReturn all tags you identify and a definition for each.",
},
];

for (let i = 0; i < 5; i++) {
console.log('==================== ITERATION START ====================');
console.log('Iteration:', i + 1);
const result = await generateText({
model: openai('gpt-5'),
messages: _messages,
tools: {
extractTags,
},
providerOptions: {
openai: {
reasoningEffort: 'medium',
// store: false,
} satisfies OpenAIResponsesProviderOptions,
},
});

console.log('--- RESULT SUMMARY ---');
console.log('Finish reason:', result.finishReason);
console.log('Text:', result.text);
console.log('--- RESULT CONTENT ---');
console.log('Content parts:', JSON.stringify(result.content, null, 2));

console.log('--- TOOL CALLS/RESULTS ---');
console.log('Tool calls:', JSON.stringify(result.toolCalls, null, 2));
console.log('Tool results:', JSON.stringify(result.toolResults, null, 2));

console.log('--- REQUEST/RESPONSE ---');
console.log('Request body:', JSON.stringify(result.request?.body, null, 2));
console.log(
'Response body:',
JSON.stringify(result.response?.body, null, 2),
);
console.log(
'Response messages:',
JSON.stringify(result.response?.messages, null, 2),
);

// Feed model responses back into the next turn (simulating the user's repro loop)
_messages.push(...(result.response.messages as ModelMessage[]));
console.log('OUTPUT messages (fed to next turn):');
console.log(JSON.stringify(_messages, null, 2));
console.log('==================== ITERATION END ======================');
}

console.log();
}

main().catch(console.error);
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { openai, OpenAIResponsesProviderOptions } from '@ai-sdk/openai';
import {
convertToModelMessages,
InferUITools,
streamText,
tool,
ToolSet,
UIDataTypes,
UIMessage,
validateUIMessages,
} from 'ai';
import { z } from 'zod';

const tools = {
web_search_preview: tool({
description: 'Search the web for information',
inputSchema: z.object({
query: z.string().describe('The search query'),
}),
execute: async (): Promise<{ results: string[] }> => {
throw new Error('Tool execution failed');
},
}),
} satisfies ToolSet;

// const tools = {
// web_search_preview: openai.tools.webSearch({}) as ToolSet[string],
// } satisfies ToolSet;

export type OpenAIInvalidToolcallReasoningMessage = UIMessage<
never,
UIDataTypes,
InferUITools<typeof tools>
>;

export async function POST(req: Request) {
const { messages } = await req.json();
const uiMessages = await validateUIMessages({ messages });

const result = streamText({
model: openai('gpt-5'),
tools,
messages: convertToModelMessages(uiMessages),
providerOptions: {
openai: {
reasoningEffort: 'medium',
} satisfies OpenAIResponsesProviderOptions,
},
onError: error => {
console.error(
'============ ERROR IN API ROUTE ==========================',
);
console.error('Error:', error);
console.error(
'================ STATE OF MESSAGES ==========================',
);
console.dir(uiMessages, { depth: null });
console.error(
'============ HERE FINISHES THE ERROR ==========================',
);
},
onFinish: event => {
console.log('Request body:', JSON.stringify(event.request.body, null, 2));
console.log('Messages body: ', JSON.stringify(uiMessages, null, 2));
},
});

return result.toUIMessageStreamResponse({
sendSources: true,
});
}
93 changes: 87 additions & 6 deletions examples/next-openai/app/chat-openai-web-search/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
'use client';

import { OpenAIWebSearchMessage } from '@/agent/openai-web-search-agent';
import { OpenAIInvalidToolcallReasoningMessage } from '@/app/api/chat-openai-invalid-toolcall-reasoning/route';
import { Response } from '@/components/ai-elements/response';
import ChatInput from '@/components/chat-input';
import { ReasoningView } from '@/components/reasoning-view';
import SourcesView from '@/components/sources-view';
import OpenAIWebSearchView from '@/components/tool/openai-web-search-view';
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';

export default function TestOpenAIWebSearch() {
const { error, status, sendMessage, messages, regenerate } =
useChat<OpenAIWebSearchMessage>({
useChat<OpenAIInvalidToolcallReasoningMessage>({
transport: new DefaultChatTransport({
api: '/api/chat-openai-web-search',
api: '/api/chat-openai-invalid-toolcall-reasoning',
}),
});

Expand All @@ -32,8 +31,90 @@ export default function TestOpenAIWebSearch() {
case 'reasoning': {
return <ReasoningView part={part} key={index} />;
}
case 'tool-web_search': {
return <OpenAIWebSearchView invocation={part} key={index} />;
case 'tool-web_search_preview': {
switch (part.state) {
case 'input-streaming':
case 'input-available':
return (
<div
key={index}
className="flex flex-col gap-2 p-3 bg-blue-50 rounded border-l-4 border-blue-400 shadow"
>
<div className="flex items-center font-semibold text-blue-700">
<span className="inline-block mr-2 bg-blue-200 text-blue-900 rounded px-2 py-0.5 text-xs font-mono tracking-wider">
TOOL
</span>
Calling web_search_preview...
</div>
{part.input && (
<div className="pl-5 text-sm text-blue-800">
<span className="font-semibold">Query:</span>{' '}
<span className="inline-block bg-white border border-blue-100 rounded px-2 py-0.5 font-mono">
{JSON.stringify(part.input)}
</span>
</div>
)}
</div>
);
case 'output-error':
return (
<div
key={index}
className="flex flex-col gap-2 p-3 bg-red-50 rounded border-l-4 border-red-400 shadow"
>
<div className="flex items-center font-semibold text-red-700">
<span className="inline-block mr-2 bg-red-200 text-red-900 rounded px-2 py-0.5 text-xs font-mono tracking-wider">
ERROR
</span>
Tool execution failed
</div>
{part.input && (
<div className="pl-5 text-sm text-red-800">
<span className="font-semibold">Input:</span>{' '}
<span className="inline-block bg-white border border-red-100 rounded px-2 py-0.5 font-mono">
{JSON.stringify(part.input)}
</span>
</div>
)}
{part.errorText && (
<div className="pl-5 text-sm text-red-800">
<span className="font-semibold">Error:</span>{' '}
{part.errorText}
</div>
)}
</div>
);
case 'output-available':
return (
<div
key={index}
className="flex flex-col gap-2 p-3 bg-green-50 rounded border-l-4 border-green-400 shadow"
>
<div className="flex items-center font-semibold text-green-700">
<span className="inline-block mr-2 bg-green-200 text-green-900 rounded px-2 py-0.5 text-xs font-mono tracking-wider">
SUCCESS
</span>
Tool executed successfully
</div>
{part.input && (
<div className="pl-5 text-sm text-green-800">
<span className="font-semibold">Input:</span>{' '}
<span className="inline-block bg-white border border-green-100 rounded px-2 py-0.5 font-mono">
{JSON.stringify(part.input)}
</span>
</div>
)}
{part.output && (
<div className="pl-5 text-sm text-green-800">
<span className="font-semibold">Output:</span>{' '}
<span className="inline-block bg-white border border-green-100 rounded px-2 py-0.5 font-mono">
{JSON.stringify(part.output)}
</span>
</div>
)}
</div>
);
}
}
}
})}
Expand Down
Loading