agent-loop-tool-result-injection · EN · 2026-10-23

Injecting Tool Results Back Into an Agent Loop Across Six Backends: Where Formatting Breaks

Compare how six different LLM backends expect tool results to be formatted when returned to an agent loop, and learn how to build a normalization shim that seamlessly supports Claude, GPT, DeepSeek, Qwen, GLM, and Kimi through a single API aggregator.

The Problem with Hand-Rolled Agent Loops

When you build an agent loop manually, you typically call a model, parse its tool call request, execute the tool, and then feed the tool result back into the conversation. This last step—injecting the tool result—is where many implementations break when switching between backends.

Each model family has its own conventions for representing tool calls and results. The differences are subtle but critical: some expect a dedicated tool role message, others use a function role, some require the result to be wrapped in a specific object, and some embed it differently within the message history.

Backend-Specific Expectations

Here's how the six backends commonly exposed through an aggregator tend to differ when handling tool results:

  • Claude (Anthropic) – Expects tool results as a toolresult content block within a user message. The result must reference the tooluse_id from the original tool call. Multiple tool results can be batched in one user message.
  • GPT (OpenAI) – Uses a tool role message with a toolcallid field and the result as a string in content. This is the most common pattern but is not universal.
  • DeepSeek – Similar to OpenAI's format but may have slight variations in field naming or required fields depending on the API version. Often expects role: "tool" with toolcallid and content.
  • Qwen (Alibaba) – Generally follows OpenAI's convention but may require the tool result to be a JSON string or have additional metadata. Some versions expect a function role instead.
  • GLM (Zhipu) – Typically uses a tool role but might require the result to be wrapped in a function object or include a name field.
  • Kimi (Moonshot) – Often aligns with OpenAI's format but may have differences in how it handles multiple tool results or streaming responses.

These differences mean that code written for one backend will likely fail or produce incorrect behavior when pointed at another.

Building a Normalization Shim

To support all six backends with a single agent loop, you need a normalization layer that translates a canonical internal representation into each backend's expected format. Here's a conceptual approach:

  1. Define a canonical tool result structure – For example, an object with toolcallid, name, and result (string or JSON).
  2. Detect the target backend – Based on the model name or a configuration flag, determine which formatting rules to apply.
  3. Transform the canonical structure – Write backend-specific adapters that convert the canonical tool result into the exact message shape required.
  4. Inject into the conversation – Append the transformed message to the message history in the correct position (usually after the assistant's tool call message).

Example Adapter Logic

  • For Claude: Create a user message with a toolresult content block containing tooluse_id and content.
  • For GPT/DeepSeek/Kimi: Create a tool role message with toolcallid and content.
  • For Qwen/GLM: Check documentation for exact requirements; often a tool role with additional name field or a function role.

Handling Tool Call IDs

A critical detail is the tool call ID. When the model requests a tool call, it provides an ID that must be referenced in the result. Claude uses tooluseid; OpenAI uses toolcallid. Your shim must map between these consistently. If the backend does not provide an ID (rare), you may need to generate one and hope the backend accepts it.

Streaming and Partial Results

If you're streaming responses, tool results are usually injected after the stream completes. However, some backends may expect the tool result to be part of the same stream or have specific ordering requirements. Always inject tool results as a new message after the assistant's message that contains the tool call.

Testing Across Backends

The only reliable way to ensure your shim works is to test each backend with a simple tool (e.g., a calculator or echo function) and verify that the model correctly incorporates the result into its next response. Use the aggregator to switch models easily and compare behavior.

Cost Considerations

When using the aggregator, you pay the official price multiplied by 1.3 for each API call, including those that inject tool results. If you're a key contributor, you receive credits at official price × 1.1 (or × 1.2 for premium models) in USDC. This means that while testing across multiple backends, your effective cost per call is higher than official pricing, so optimize your loop to minimize unnecessary calls.

Conclusion

Injecting tool results correctly is essential for a functional agent loop. By understanding the formatting differences across backends and implementing a normalization shim, you can write once and run everywhere—without rewriting your agent logic for each model. Always refer to the latest API documentation for each backend, as formats can change.