EU GPT logo
EU GPT

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.added for 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;
  }
}