Skip to content

Follow-up turn + previousResponseId + client tool call → "No tool call found for function call output with call_id" #18537

Description

@ahmedharis994

Description

Using @ai-sdk/openai@4.0.29 with ai@7.0.51. A multi-turn conversation via the OpenAI Responses API, chaining turns with providerOptions.openai.previousResponseId, and using a plain client-executed tool (tool({ inputSchema, execute }), not a provider-defined tool like local_shell/shell/apply_patch/computer).

First turn (no previousResponseId): the model calls the tool, everything works, response streams normally.

Any follow-up turn (a previousResponseId from the prior turn is set) where the model makes a new tool call: the request fails with

AI_APICallError: No tool call found for function call output with call_id call_XXXXXXXX.

Minimal repro shape:

import { streamText, tool, stepCountIs } from 'ai';
import { openai } from '@ai-sdk/openai';
import { z } from 'zod';

const myTool = tool({
  description: 'a plain client-executed tool',
  inputSchema: z.object({ query: z.string() }),
  execute: async ({ query }) => ({ result: `results for ${query}` }),
});

// Turn 1 — works
const r1 = await streamText({
  model: openai.responses('gpt-4.1-mini'), // or a reasoning model, e.g. gpt-5.x
  prompt: 'search for red shoes',
  tools: { myTool },
  stopWhen: stepCountIs(3),
});
for await (const _ of r1.fullStream) {}
const responseId = (await r1.finalStep).providerMetadata?.openai?.responseId;

// Turn 2 — FAILS: "No tool call found for function call output with call_id ..."
const r2 = await streamText({
  model: openai.responses('gpt-4.1-mini'),
  prompt: 'now search for blue shoes',
  tools: { myTool },
  stopWhen: stepCountIs(3),
  providerOptions: { openai: { previousResponseId: responseId, store: true } },
});
for await (const part of r2.fullStream) {
  if (part.type === 'error') console.error(part.error); // <-- the 400 surfaces here
}

What I found tracing the root cause

In packages/openai/src/responses/convert-to-openai-responses-input.ts, case 'tool-call':, there's an early break:

if (hasPreviousResponseId && store && id != null) {
  break;
}

This drops the assistant's function_call item on a follow-up turn whenever the tool call carries a provider itemId (i.e. id != null) — which includes plain client-executed tool calls, not just provider-defined ones. The matching function_call_output for that call is still sent (it's only referenced by call_id, unconditionally), so the request ends up with a function_call_output whose function_call was never included → OpenAI 400s.

Below that break, there's already a guard for provider-defined tools:

const isProviderDefinedToolCall = /* local_shell | shell | apply_patch | computer | custom */;
if (store && id != null && isProviderDefinedToolCall) {
  input.push({ type: 'item_reference', id });
  break;
}

It looks like the intent (per #15795's own description — "Plain client-executed function calls must NOT be [sent as item_reference]… the matching function_call_output can only reference the call by call_id") was that plain client tool calls should fall through to the unconditional function_call push further down, so they pair with their output by call_id regardless of store/previousResponseId. But the earlier hasPreviousResponseId && store && id != null break fires first and drops them before that fallthrough is reached.

Guarding that break with the same isProviderDefinedToolCall check (and hoisting that const above it) seems to resolve it in local testing — plain client tool calls then fall through and pair correctly.

Environment

  • ai: 7.0.51
  • @ai-sdk/openai: 4.0.29 (also reproduces on 4.0.33, latest — confirmed same guard ordering on main)
  • Node: 24
  • Model: reproduces with both gpt-4.1-mini and reasoning models (gpt-5.x)

Related

Impact

Any multi-turn conversation that chains via previousResponseId (rather than resending full message history) and uses a plain client-executed tool cannot make a new tool call on any turn after the first. This affects any app using server-side conversation chaining + tools with the Responses API — a fairly standard combination.

Reproduction

No response

AI SDK Version

  • ai: 7.0.51
  • @ai-sdk/openai: 4.0.29 (also reproduces on 4.0.33, latest — confirmed same guard ordering on main)

Code of Conduct

  • I agree to follow this project's Code of Conduct

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions