Anyone else having issues with tool calling when u...
# koog-agentic-framework
j
Anyone else having issues with tool calling when using Gemma4? Have tried for example with 12b and 26b models and also tried different context length but seems to return some arbitrary value (have traced in to tool and see correct value being returned from that)
Copy code
LLModel(
        provider = LLMProvider.Ollama,
        id = "gemma4:12b-mlx",
        capabilities = listOf(
            LLMCapability.Temperature,
            LLMCapability.Schema.JSON.Standard,
            LLMCapability.Tools
        ),
        contextLength = 8192,
    )
This is what Junie with Fable5 thinks the issue is! The tool itself works fine. Running
AgentMain
shows
GetEmissionsTool
is called correctly and returns
"Emissions for DEU in 2025: 100 MtCO2e"
, yet the model answers things like "The tool found no emission data for Germany in 2025" (and often calls the tool twice). The LLM never actually "sees" the tool result. #### Root cause: a Koog 1.0.0 Ollama client bug I captured the exact HTTP request Koog sends to Ollama (via a logging proxy). On the second turn Koog sends:
Copy code
json
{"role":"assistant", "content":"", "thinking":"\n", "tool_calls":[ ...GetEmissionsTool... ]},
{"role":"user", "content":""},                       ← spurious empty user message
{"role":"tool", "content":"Emissions for DEU in 2025: 100 MtCO2e"}
That empty
user
message injected between the tool call and the tool result
breaks the Gemma chat template. I verified by replaying the conversation directly against `http://localhost:11434/api/chat`: • Without the empty user message → the model answers correctly: "The emissions for Germany in 2025 are estimated at 100 MtCO2e." • With the empty user message → the model's thinking literally says "I haven't received the tool result yet" and it re-calls the tool or claims no data exists. The bug is in Koog 1.0.0's
prompt-executor-ollama-client
,
OllamaConverters.kt
→ `toOllamaChatMessages()`:
Copy code
kotlin
is Message.User -> {
    add(message.toOllamaTextChatMessage(model))   // ALWAYS emits a user message, even if empty
    message.parts.filterIsInstance<MessagePart.Tool.Result>().forEach { part ->
        add(OllamaChatMessageDTO(role = "tool", content = part.output))
    }
}
Koog represents a tool result as a
Message.User
containing only a
Tool.Result
part, but the converter unconditionally emits the user-role message first — producing
{"role":"user","content":""}
before every
tool
message.
fyi @Vadim Briliantov (btw I had hardcoded tool in this case to return that 100 string as a test). This works fine with Gemini and also qwen local model
v
cc @Maria Tigina could you please check