zeroclaw-labs/zeroclaw · error · anyhow::Error

Ollama returned non-prompt-guided tools payload while native

Error message

Ollama returned non-prompt-guided tools payload while native tools are disabled

What it means

The Ollama provider hard-codes supports_native_tools() = false, so chat() injects tool specifications into the system prompt as text via with_prompt_guided_tool_instructions, which requires convert_tools to return ToolsPayload::PromptGuided. This bail fires when a non-empty tools slice converts to a native payload variant (OpenAI/Anthropic/Gemini) instead. Stock builds cannot reach it — OllamaModelProvider never overrides the trait's default PromptGuided conversion — so encountering it means a wrapper or fork changed tool conversion without enabling native tools.

Source

Thrown at crates/zeroclaw-providers/src/ollama.rs:604

                    images: None,
                    tool_calls: None,
                    tool_name: None,
                }
            })
            .collect()
    }

    fn with_prompt_guided_tool_instructions(
        &self,
        messages: &[ChatMessage],
        tools: Option<&[zeroclaw_api::tool::ToolSpec]>,
    ) -> anyhow::Result<Vec<ChatMessage>> {
        let Some(tools) = tools.filter(|items| !items.is_empty()) else {
            return Ok(messages.to_vec());
        };

        let ToolsPayload::PromptGuided { instructions } = self.convert_tools(tools) else {
            anyhow::bail!(
                "Ollama returned non-prompt-guided tools payload while native tools are disabled"
            );
        };
        let mut modified_messages = messages.to_vec();

        if let Some(system_message) = modified_messages.iter_mut().find(|m| m.role == "system") {
            if !system_message.content.is_empty() {
                system_message.content.push_str("\n\n");
            }
            system_message.content.push_str(&instructions);
        } else {
            modified_messages.insert(0, ChatMessage::system(instructions));
        }

        Ok(modified_messages)
    }

    fn response_to_chat_response(&self, response: ApiChatResponse, model: &str) -> ChatResponse {

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. If you maintain a wrapper: do not override convert_tools for Ollama, or keep returning ToolsPayload::PromptGuided while native tools are disabled
  2. If native Ollama tools are intended, implement the full path — supports_native_tools() = true plus tools serialization in the request body — instead of half-enabling it
  3. Otherwise report it upstream with the wrapper code, since the shipped provider cannot produce a native payload

Example fix

// before (fork/wrapper)
fn convert_tools(&self, tools: &[ToolSpec]) -> ToolsPayload {
    ToolsPayload::OpenAI { tools: to_openai(tools) }
}

// after: keep prompt-guided while native tools are disabled
fn convert_tools(&self, tools: &[ToolSpec]) -> ToolsPayload {
    ToolsPayload::PromptGuided { instructions: build_tool_instructions_text(tools) }
}
Defensive patterns

Strategy: type-guard

Validate before calling

fn ollama_tools_prompt_guided_safe(
    provider: &dyn ModelProvider,
    tools: &[zeroclaw_api::tool::ToolSpec],
) -> bool {
    if tools.is_empty() { return true; }
    !provider.supports_native_tools()
        && matches!(provider.convert_tools(tools), ToolsPayload::PromptGuided { .. })
}

Type guard

fn tools_payload_is_prompt_guided(payload: &ToolsPayload) -> bool {
    matches!(payload, ToolsPayload::PromptGuided { .. })
}

Try / catch

let messages = if !tools.is_empty() {
    match provider.convert_tools(tools) {
        ToolsPayload::PromptGuided { .. } => inject_instructions(messages, tools),
        _ => return Err(anyhow::anyhow!("wrapper overrides convert_tools with native tools disabled")),
    }
} else { messages.to_vec() };

Prevention

When it happens

Trigger: chat() with a non-empty request.tools on a modified Ollama provider whose convert_tools override returns a native ToolsPayload variant; downstream forks of ollama.rs enabling native conversion without also flipping supports_native_tools and serializing tools in send_request.

Common situations: Custom ModelProvider wrappers or vision/model-pin style decorators that alter convert_tools; forks porting native tool-calling support from another provider halfway; upstream refactors changing the default trait implementation.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/a4f888885593e77b. Report an issue: GitHub.