Public preview — This API is in public preview. Endpoints, schemas, and limits may change before general availability.
API
Tool use
An end-to-end example of the model reaching for web_search and how to render that in your UI.
Tools run server-side and surface through the stream. There is nothing to configure per request — the model decides, the platform runs, you watch.
This guide walks through one realistic example: a question that requires the model to search the web and cite sources.
The request#
const stream = await client.responses.create({
model: "auto",
input: "What was Apple's revenue in Q4 2025? Cite your source.",
stream: true,
});
The model has no internal knowledge of that quarter’s results. It will call web_search.
The event sequence#
You will receive events in roughly this order (sequence numbers in parentheses are illustrative):
(0) response.created
(1) response.output_item.added function_call "web_search" begins
(2) response.output_item.done function_call "web_search" completes with results
(3) response.content_part.added assistant text begins
(4) response.output_text.delta "Apple"
(5) response.output_text.delta " reported"
(...) more deltas
(N) response.output_text.done full text assembled
(N+1) response.completed
Multiple tool calls can interleave — the model might also call web_fetch on a result URL before answering. Use output_index to keep them separate.
Rendering it#
A reasonable chat UI pattern:
const state = {
tools: new Map(), // call_id -> { name, status, summary }
text: "",
};
for await (const event of stream) {
switch (event.type) {
case "response.output_item.added":
if (event.item.type === "function_call") {
state.tools.set(event.item.call_id, {
name: event.item.name,
status: "running",
});
render();
}
break;
case "response.output_item.done":
if (event.item.type === "function_call") {
const tool = state.tools.get(event.item.call_id);
if (tool) {
tool.status = event.item.status;
tool.summary = summarise(event.item.output);
}
render();
}
break;
case "response.output_text.delta":
state.text += event.delta;
render();
break;
case "response.completed":
// done
break;
}
}
Render the tool pills (one per call_id) above or alongside the text. Update them in place when they transition from running to completed.
The tool output shape#
The output field on output_item.done is a string. For web_search and web_fetch it is a JSON-encoded structure. You can parse it if you want to surface the results visually:
const tool = event.item;
if (tool.name === "web_search" && tool.status === "completed") {
const hits = JSON.parse(tool.output); // [{ title, url, snippet }, ...]
renderHits(hits);
}
For calculator it is the numeric result as a string. For current_datetime it is an ISO 8601 timestamp.
Disabling tools for a request#
There is no per-request “no tools” flag today. The best lever is the instructions field:
instructions: "Answer from your own training data only. Do not use web_search or web_fetch."
The model usually complies. For absolute control, run prompts that obviously cannot benefit from tools (e.g. summarisation of input the user provided) — the model will not invoke tools speculatively.
Tool failures#
If a tool fails mid-execution, you receive response.output_item.done with status: "failed" and a description in output. The model handles this — it will either:
- Retry the tool with different arguments (you will see another
output_item.addedfor the same tool name). - Apologise and answer without the tool.
- Pick a different tool.
Your UI should surface “search failed” gracefully — most users prefer “I couldn’t search the web, so here’s what I know” over a silent absence of citations.
Putting it all together#
A minimal end-to-end script that streams to the terminal and prints tool activity inline:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.EUGPT_API_KEY,
baseURL: "https://chat.eugpt.ai/v1",
});
const stream = await client.responses.create({
model: "auto",
input: "What's the latest news on the EU AI Act enforcement?",
stream: true,
});
for await (const event of stream) {
switch (event.type) {
case "response.output_item.added":
if (event.item.type === "function_call") {
process.stderr.write(`\n[tool] ${event.item.name} …`);
}
break;
case "response.output_item.done":
if (event.item.type === "function_call") {
process.stderr.write(` (${event.item.status})\n`);
}
break;
case "response.output_text.delta":
process.stdout.write(event.delta);
break;
case "response.completed":
process.stdout.write("\n");
break;
}
}