Gemini 2.5 Pro - Tool Call Looping Bug - Reason for v high bills?

I’ve been usinng Gemini 2.5 pro, however, it will sporadically go into a loop where it continues to call a tool for several hours.

This relates to the call that took 5000+ seconds in the trace log above. Each time it successfully calls the tool. But then continue again. In some cases it has gone on for 37 hours.

However, then the same prompt is run again a few minutes later, it is fine and completes within a few seconds (see trace logs above).

Here are the error logs

Here is my billing chart.

Yesterday my app made 83 calls the gemini pro endpoint and it cost more than £400!!!

I’d be grateful if someone can look into this urgently. I’ve moved my workloads over to open AI from this morning and I’m not seeing any errors.

Hello,

Just to clarify our understanding, you are facing an unnecessary loop during tool calling where you intentionally try to call a tool but the model keeps looping and returning the tool in a single call.
If that is correct, we would need to analyze this issue further. Could you please share your prompts and the relevant snippet of your code so that we can try to reproduce the issue and provide a more accurate solution?

Hello! Do you have any updates about this issue? I’m experiencing same issue almost in 70% interactions from 4 Nov 2025 (Gemini 2.5 flash)
The model seems to enter a loop, repeatedly invoking the same tool mostly with identical arguments — even after receiving a valid response.
I noticed it often happens when user sends similar instructoins more than once
Example:

  • The user sends an instruction, e.g.:
    add product 373992459 to cart, quantity 1
  • Gemini issues a function call
{
  "name": "shopping-cart-tool",
  "arguments": {
    "add_products": [{ "product_id": 123, "quantity": 1 }]
  }
}
  • The tool returns a valid response
{
  "output": "[{\"product_id\":123,\"quantity\":1}]"
}
  • The user sends an instruction, e.g.:
    add product 123 to cart, quantity 5

  • Gemini issues a function call

{
  "name": "shopping-cart-tool",
  "arguments": {
    "add_products": [{ "product_id": 123, "quantity": 5 }]
  }
}
  • The tool returns a valid response
{
  "output": "[{\"product_id\":123,\"quantity\":5}]"
}
  • Instead of proceeding with a normal text reply (e.g. “Item added to cart”), the model immediately issues the same function call again, with identical arguments.

  • Even when the tool responds with:

{ "error": "\"Multiple calls with the same arguments are not allowed.\"" }
  • the model continues to invoke the same function — resulting in a

functionCall → functionResponse → functionCall → ...

Treat this as an application-control failure, not something the next prompt can be trusted to stop. Google’s current function-calling documentation makes the boundary clear: the model proposes a function call, but your application executes it and returns the result. The loop watchdog therefore belongs outside the model.

A practical containment pattern:

  1. Before executing, compute a call signature from tool_name + canonical_json(args) + relevant_state_version.
  2. Store the last signature, its result hash, and whether application state actually advanced.
  3. If the same signature appears again after a successful result with no state change, do not execute it again. Trip a circuit breaker after one duplicate (or two for read-only tools).
  4. Enforce independent hard budgets for total steps, wall-clock time, cumulative tokens and estimated cost. Any one budget must move the run to a terminal NEEDS_REVIEW state.
  5. Give side-effecting tools an idempotency key and a durable execution ledger. A retry should return the recorded result rather than repeat the side effect.
  6. Separate action from formatting: run the tool phase with only the needed tools, then request the final structured response in a second phase with tools disabled.
  7. On a trip, persist the last calls/results and require an explicit human resume decision. Do not feed another “duplicate call” error back indefinitely; that can become part of the loop.

Minimal pseudocode:

sig = sha256(tool_name + canonical_json(args) + state_version)

if sig == last_sig and last_result_ok and not state_advanced:
    return halt("REPEATED_CALL_WITHOUT_PROGRESS")

if steps >= MAX_STEPS or elapsed >= MAX_SECONDS or cost >= MAX_COST:
    return halt("BUDGET_EXCEEDED")

result = execute_once(tool_name, args, idempotency_key=sig)
record(sig, hash(result), state_version_after())

Also verify that the complete model response and function result are preserved in history exactly as the current Gemini function-calling documentation requires. That prevents a history-construction bug from looking like a model-only loop, but it still does not replace the runtime breaker.

I help maintain LaoZhang AI Blog, and used AI assistance to edit this reply. We documented a longer vendor-neutral implementation checklist for [detecting and recovering from AI-agent tool loops]blog.laozhang.ai/en/posts/ai-agent-tool-loop). The answer above is complete without opening it.