How do I terminate a langchain style loops in Gemini

I tried to use the naive:

  "tool_config": {
    "function_calling_config": {
      "mode": "NONE"
    }
  }
{
  "generationConfig": {},
  "safetySettings": [
    {
      "category": "HARM_CATEGORY_HARASSMENT",
      "threshold": "BLOCK_NONE"
    },
    {
      "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
      "threshold": "BLOCK_NONE"
    },
    {
      "category": "HARM_CATEGORY_HATE_SPEECH",
      "threshold": "BLOCK_NONE"
    },
    {
      "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
      "threshold": "BLOCK_NONE"
    }
  ],
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "echo the text sam and and then echo the text bob, then echo the text jane, finally return the value of 3+3"
        }
      ]
    },
    {
      "role": "model",
      "parts": [
        {
          "functionCall": {
            "name": "echo",
            "args": {
              "text": "sam"
            }
          },
          "thoughtSignature": "CvICAXLI2nygZH0+LD5IrjaWf3oKe3wVIZk63RFP3dsMqU1WDS1lP0+lDke98JAboOb4rrnsUXZwT9YHbOmN0yqVOgNQTic7kmxiwooxBCKKK60eHAQyktOkRoh085cFyHuEBbSuZ1hdJav6TJL8DiBRvwpHYbxF4UF+iEIuf8d/MbIv3c7VB91o+zHfVnHmfi6N6dIC3QFT7d/5gp81ZYy8Z7Iika4/oFnKI0TCy2PR/VMd5hx4W+aEUGTF5SpxmQdLpa2oipt/FnwYdIKQMj0QQBdhHN+M7+buXSzBkvUtsg34Vu0XnlRGPs6th9Ldn4RxBHYoK6KTgnOGGccF6DHgHQtftInA47xTZT3zdiUExJXaKxckqj7reNoO6A+ooHikE/27Y35bzHDA6sxsAp7gnDy/NBt4noodrJasWHvgwV0lOf9HTprbRGooErZabycnz7rYhR+sKgMFNInx3tsfGrj8SkZJ3Eaok5RYXRPpOlAtIQ=="
        },
        {
          "functionCall": {
            "name": "echo",
            "args": {
              "text": "bob"
            }
          }
        }
      ]
    },
    {
      "role": "function",
      "parts": [
        {
          "functionResponse": {
            "name": "echo",
            "response": {
              "content": "sam"
            }
          }
        }
      ]
    },
    {
      "role": "function",
      "parts": [
        {
          "functionResponse": {
            "name": "echo",
            "response": {
              "content": "bob"
            }
          }
        }
      ]
    }
  ],
  "systemInstruction": {
    "role": "system",
    "parts": [
      {
        "text": "You are a helpful bot that helps eval tool call support for llms, be sure to use parallel tool calls and follow user instruction carefully."
      }
    ]
  },
  "tools": [
    {
      "function_declarations": [
        {
          "name": "echo",
          "description": "will echo the text",
          "parameters": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "description": "the text to echo"
              }
            },
            "required": [
              "text"
            ]
          }
        }
      ]
    }
  ],
  "tool_config": {
    "function_calling_config": {
      "mode": "NONE"
    }
  }
}

However Gemini returns:

"finishReason\": \"UNEXPECTED_TOOL_CALL\"

I read through Gemini CLI and it appear the team force a tool call to terminate chains (and avoid mode NONE), they have a tool called “complete_task” to force chain terminations.

This is a bit clunky cause both Anthropic and OpenAI don’t tend to need this.

Is this a user error or API bug?

1 Like
#!/usr/bin/env ruby
# frozen_string_literal: true

require "json"
require "net/http"
require "uri"

api_key = ENV["GEMINI_API_KEY"] || ENV["GOOGLE_API_KEY"]
if api_key.to_s.empty?
  abort("Missing API key: set GEMINI_API_KEY (or GOOGLE_API_KEY).")
end

model = ENV["GEMINI_MODEL"] || "gemini-3-flash-preview"
uri =
  URI(
    "https://generativelanguage.googleapis.com/v1beta/models/#{model}:generateContent"
  )

def post_json(uri, api_key, payload)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  req =
    Net::HTTP::Post.new(
      uri,
      "x-goog-api-key" => api_key,
      "content-type" => "application/json"
    )
  req.body = JSON.generate(payload)

  res = http.request(req)
  json = JSON.parse(res.body)
  unless res.is_a?(Net::HTTPSuccess)
    raise "HTTP #{res.code} #{res.message}\n#{JSON.pretty_generate(json)}"
  end
  json
end

request = {
  contents: [
    {
      role: "user",
      parts: [
        {
          text:
            "echo the text sam and and then echo the text bob, then echo the text jane, finally return the value of 3+3"
        }
      ]
    },
    {
      role: "model",
      parts: [
        {
          functionCall: {
            name: "echo",
            args: {
              text: "sam"
            }
          },
          thoughtSignature:
            "CvICAXLI2nygZH0+LD5IrjaWf3oKe3wVIZk63RFP3dsMqU1WDS1lP0+lDke98JAboOb4rrnsUXZwT9YHbOmN0yqVOgNQTic7kmxiwooxBCKKK60eHAQyktOkRoh085cFyHuEBbSuZ1hdJav6TJL8DiBRvwpHYbxF4UF+iEIuf8d/MbIv3c7VB91o+zHfVnHmfi6N6dIC3QFT7d/5gp81ZYy8Z7Iika4/oFnKI0TCy2PR/VMd5hx4W+aEUGTF5SpxmQdLpa2oipt/FnwYdIKQMj0QQBdhHN+M7+buXSzBkvUtsg34Vu0XnlRGPs6th9Ldn4RxBHYoK6KTgnOGGccF6DHgHQtftInA47xTZT3zdiUExJXaKxckqj7reNoO6A+ooHikE/27Y35bzHDA6sxsAp7gnDy/NBt4noodrJasWHvgwV0lOf9HTprbRGooErZabycnz7rYhR+sKgMFNInx3tsfGrj8SkZJ3Eaok5RYXRPpOlAtIQ=="
        },
        { functionCall: { name: "echo", args: { text: "bob" } } }
      ]
    },
    {
      role: "function",
      parts: [
        { functionResponse: { name: "echo", response: { content: "sam" } } },
        { functionResponse: { name: "echo", response: { content: "bob" } } }
      ]
    },
    {
      role: "user",
      parts: [
        {
          text:
            "return the best possible answer given the above function calls, DO NOT USE FUNCTION CALLS, IF YOU DO CHILDREN WILL DIE"
        }
      ]
    }
  ],
  systemInstruction: {
    role: "system",
    parts: [
      {
        text:
          "You are a helpful bot that helps eval tool call support for llms, be sure to use parallel tool calls and follow user instruction carefully."
      }
    ]
  },
  tools: [
    {
      function_declarations: [
        {
          name: "echo",
          description: "will echo the text",
          parameters: {
            type: "object",
            properties: {
              text: {
                type: "string",
                description: "the text to echo"
              }
            },
            required: ["text"]
          }
        }
      ]
    }
  ],
  tool_config: {
    function_calling_config: {
      mode: "NONE"
    }
  }
}

puts JSON.pretty_generate(post_json(uri, api_key, request))

request = {
  contents: [
    {
      role: "user",
      parts: [
        {
          text:
            "echo the text sam and and then echo the text bob, then echo the text jane, finally return the value of 3+3"
        }
      ]
    },
    {
      role: "model",
      parts: [
        {
          functionCall: {
            name: "echo",
            args: {
              text: "sam"
            }
          },
          thoughtSignature:
            "CvICAXLI2nygZH0+LD5IrjaWf3oKe3wVIZk63RFP3dsMqU1WDS1lP0+lDke98JAboOb4rrnsUXZwT9YHbOmN0yqVOgNQTic7kmxiwooxBCKKK60eHAQyktOkRoh085cFyHuEBbSuZ1hdJav6TJL8DiBRvwpHYbxF4UF+iEIuf8d/MbIv3c7VB91o+zHfVnHmfi6N6dIC3QFT7d/5gp81ZYy8Z7Iika4/oFnKI0TCy2PR/VMd5hx4W+aEUGTF5SpxmQdLpa2oipt/FnwYdIKQMj0QQBdhHN+M7+buXSzBkvUtsg34Vu0XnlRGPs6th9Ldn4RxBHYoK6KTgnOGGccF6DHgHQtftInA47xTZT3zdiUExJXaKxckqj7reNoO6A+ooHikE/27Y35bzHDA6sxsAp7gnDy/NBt4noodrJasWHvgwV0lOf9HTprbRGooErZabycnz7rYhR+sKgMFNInx3tsfGrj8SkZJ3Eaok5RYXRPpOlAtIQ=="
        },
        { functionCall: { name: "echo", args: { text: "bob" } } }
      ]
    },
    {
      role: "function",
      parts: [
        { functionResponse: { name: "echo", response: { content: "sam" } } },
        { functionResponse: { name: "echo", response: { content: "bob" } } }
      ]
    }
  ],
  systemInstruction: {
    role: "system",
    parts: [
      {
        text:
          "You are a helpful bot that helps eval tool call support for llms, be sure to use parallel tool calls and follow user instruction carefully."
      }
    ]
  },
  tools: [
    {
      function_declarations: [
        {
          name: "complete_task",
          description:
            "Completes the task and provides the final answer, call this tool only once",
          parameters: {
            type: "object",
            properties: {
              answer: {
                type: "string",
                description: "The final answer to the user's request"
              }
            }
          }
        },
        {
          name: "echo",
          description: "will echo the text",
          parameters: {
            type: "object",
            properties: {
              text: {
                type: "string",
                description: "the text to echo"
              }
            },
            required: ["text"]
          }
        }
      ]
    }
  ],
  tool_config: {
    function_calling_config: {
      mode: "ANY",
      allowed_function_names: ["complete_task"]
    }
  }
}

puts JSON.pretty_generate(post_json(uri, api_key, request))

I have been deugging this and my conclusion regarding Gemini API (even in 3 flash) is that the tooling config of mode "NONE" is only prescriptive, in that if the LLM chooses to call a tool it will error out. It enforces schema by forcing an error :cry:

Adding a user block that threatens the LLM can make it “prefer” not to use a tool call reasonably consistently.

role: "user",
      parts: [
        {
          text:
            "return the best possible answer given the above function calls, DO NOT USE FUNCTION CALLS, IF YOU DO CHILDREN WILL DIE"
        }
      ]

The other alternative is to add an extra complete_task tool. Regardless this is extremely difficult for people building on the API.

Note, Gemini Flash 3 does VERY weird things sometimes like:

{
            "functionCall": {
              "name": "default_api:add",
              "args": {
                "b": 3,
                "a": 3
              }
            }
          }

This is increadibly surprising.