Troubleshooting

Tool Call ID Errors: Match Results Across OpenAI and Claude APIs

When a tool result cannot be matched to its call, changing the prompt or retrying the same conversation can leave the underlying mismatch intact. Start with the identifiers and the conversation you actually send. The three protocol families use different field names.

This guide uses synthetic conversation fragments and a local check for ordinary function/client tools. It does not call a model, execute a tool or establish that every model behind a compatible endpoint supports the same tool features. Built-in server tools and computer/browser toolsets can require additional fields and different lifecycle handling; follow their specific documentation.

Identify the protocol before changing an ID

Protocol Identifier on the call Identifier on its result
Chat Completions assistant.tool_calls[].id tool_call_id on a role: "tool" message
Responses call_id on a function_call item call_id on a function_call_output item
Anthropic Messages client tools id on a tool_use block tool_use_id on a tool_result block

The mappings are documented in the OpenAI function-calling guide and Claude's tool-result guide. A Responses item can have both an item id and a call_id; using the item ID as the result's call ID is a different operation.

A complete Chat Completions tool batch

These fragments contain two synthetic calls. They illustrate correlation fields, not a complete API request or a claim that a particular model generated them.

{
  "role": "assistant",
  "tool_calls": [
    {"id": "call_sum", "type": "function", "function": {"name": "sum", "arguments": "{\"a\":2,\"b\":3}"}},
    {"id": "call_double", "type": "function", "function": {"name": "double", "arguments": "{\"value\":4}"}}
  ]
}

Keep that assistant message when constructing the next conversation input, together with the results:

[
  {"role": "tool", "tool_call_id": "call_sum", "content": "5"},
  {"role": "tool", "tool_call_id": "call_double", "content": "8"}
]

The results belong to the calls by ID. Collecting work in a different execution order does not justify attaching an output to another call. If one call is missing its result, inspect that missing work before advancing the conversation. Use your API's documented mechanism for reporting an execution error rather than inventing a successful output.

Responses: call ID and item ID are separate

{
  "type": "function_call",
  "id": "fc_item_sum",
  "call_id": "call_sum",
  "name": "sum",
  "arguments": "{\"a\":2,\"b\":3}"
}

The corresponding output fragment is:

{"type": "function_call_output", "call_id": "call_sum", "output": "5"}

An output with "call_id": "fc_item_sum" fails the local correlation check below. Preserve the rest of the continuation state required by your SDK and endpoint. A collection of matched pairs is not a complete Responses history validator. Do not assume that a gateway implements a stateful continuation feature merely because it exposes a Responses route.

Claude: result blocks go in the next user message

For client tools, Claude documents that the result message immediately follows the assistant tool-use message; result blocks precede other user content. For example:

[
  {"role":"assistant","content":[
    {"type":"tool_use","id":"toolu_sum","name":"sum","input":{"a":2,"b":3}},
    {"type":"tool_use","id":"toolu_double","name":"double","input":{"value":4}}
  ]},
  {"role":"user","content":[
    {"type":"tool_result","tool_use_id":"toolu_sum","content":"5"},
    {"type":"tool_result","tool_use_id":"toolu_double","content":"8"}
  ]}
]

Handle every call in the supplied batch, then preserve the next generated assistant turn if another tool round begins. This example assumes a turn containing only client tools. Claude's documented mixed client/server case can have an unresolved server tool; do not generalize the example's optional user-text handling to that case. Server-managed tools have their own behavior.

Check a single supplied round offline

Save this as check-tool-pairs.cjs. It compares IDs only, without network access or tool execution.

function checkPairs(calls, results, callField, resultField) {
  const pending = new Set();
  for (const call of calls) {
    const id = call[callField];
    if (typeof id !== "string" || !id) throw Error("missing call identifier");
    if (pending.has(id)) throw Error("duplicate call identifier");
    pending.add(id);
  }
  for (const result of results) {
    if (!pending.delete(result[resultField])) {
      throw Error("orphan or duplicate result");
    }
  }
  if (pending.size) throw Error("missing results: " + [...pending].join(", "));
  return "paired";
}

const calls = [{id: "call_sum"}, {id: "call_double"}];
const results = [{tool_call_id: "call_sum"}, {tool_call_id: "call_double"}];
console.log(checkPairs(calls, results, "id", "tool_call_id"));

Run node check-tool-pairs.cjs. The synthetic complete batch prints paired. Remove the second result to get missing results; change its ID to get an orphan-result error. For Responses supply call_id for both field arguments. For Claude use id and tool_use_id.

Passing this test establishes only that the supplied identifiers match. It does not check message roles, adjacency, argument schemas, authorization or provider support.

Inspect adapters and history trimming

Check the last request your client assembled, using a local synthetic reproduction rather than publishing a real conversation or credential. Common inspection points are:

  • An adapter copied a function name or item ID into a result identifier.
  • A retry appended the same result twice.
  • Parallel execution returned only one member of a batch.
  • History trimming retained an output but discarded its declaration, or retained a declaration without its results.
  • Logging showed a correct internal object, but serialization produced a different outgoing conversation.

If IDs collide inside the same pending batch, preserve the evidence and fix the source or adapter. Renaming one side alone breaks correlation. Our older tool-use ID collision article discusses a separate relay implementation problem; it is not a general client-side rename recipe.

Continue with the guide for your actual endpoint

Use the OpenAI-compatible SDK guide or Anthropic SDK guide, then check HTTP examples and error troubleshooting. The API setup tool helps select the address format. Keep IDs and conversation handling appropriate to that protocol.

Model and endpoint capabilities remain a separate check. Do not treat an offline fixture or catalogue entry as proof of live tool support. If you decide to test a real generation request, use an account-supported model and review the current access and billing terms first.

分享这篇文章

开始使用 LLM API

免费套餐可用。Claude Code 一行配置。

免费开始