zylon-ai/private-gpt · error · ValueError

Tool choice '{self.tool_choice}' is not in the provided tool

Error message

Tool choice '{self.tool_choice}' is not in the provided tools.

What it means

ChatBody rejects a request where tool_choice.type == 'tool' (forcing a specific function) but tool_choice.name does not match any name in the tools list. The API cannot force a function it was not given, so validation fails with the offending tool_choice printed in the message.

Source

Thrown at private_gpt/server/chat/chat_models.py:333

        for message in self.messages:
            if not message.content:
                raise ValueError(f"Message content cannot be empty: {message}")
            if isinstance(message.content, list):
                for block in message.content:
                    if block is None:
                        raise ValueError(f"Block cannot be None: {message}")

        if self.messages[-1].role not in self._valid_last_message_roles:
            raise ValueError(
                f"Last message role must be one of {self._valid_last_message_roles}, but got {self.messages[-1].role}"
            )

        # Check tools and tool choice
        if self.tools and self.tool_choice and self.tool_choice.type == "tool":
            if not self.tools:
                raise ValueError("Tool choice is set, but no tools are provided.")
            if self.tool_choice.name not in [tool.name for tool in self.tools]:
                raise ValueError(
                    f"Tool choice '{self.tool_choice}' is not in the provided tools."
                )

        if not self.tools and self.tool_context:
            raise ValueError(
                "Tool context is provided, but no tools are specified. "
                "Please provide tools to use with the tool context."
            )

        # Apply global tool context to tools without specific context
        if self.tools is not None:
            global_tool_context = self.tool_context or []
            if global_tool_context:
                for tool in self.tools:
                    if tool.context is None:
                        tool.context = global_tool_context

        has_structured_output = bool(self.output_config and self.output_config.format)

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Make tool_choice.name exactly match one of the tool names in the same request's tools array.
  2. Derive tool_choice from the tools list programmatically instead of hardcoding the name.
  3. If the tool was deliberately removed, switch tool_choice to {"type":"auto"} or another available tool.

Example fix

# before
{"tools":[{"name":"search","type":"..."}],"tool_choice":{"type":"tool","name":"lookup"}}

# after
{"tools":[{"name":"search","type":"..."}],"tool_choice":{"type":"tool","name":"search"}}
Defensive patterns

Strategy: validation

Validate before calling

if tool_choice and tool_choice.get('type') == 'tool':
    assert tool_choice['name'] in {t['name'] for t in tools}, 'forced tool must be in tools'

Type guard

def tool_choice_is_valid(tools: list[dict], tc: dict | None) -> bool:
    return tc is None or tc.get('type') != 'tool' or tc.get('name') in {t['name'] for t in tools}

Try / catch

except ValidationError as e:
    if 'not in the provided tools' in str(e):
        tool_choice = {'type': 'auto'}; retry()
    else:
        raise

Prevention

When it happens

Trigger: tool_choice={"type":"tool","name":"lookup"} while tools only defines e.g. 'search'; renaming a tool in the tools registry without updating the client's tool_choice; dynamic tool filtering that removes the forced tool.

Common situations: Version skew between client tool names and server-registered tools; feature-flagged tool sets where the forced tool is disabled; typos in the tool name string.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/4e6a54342a09fdea. Report an issue: GitHub.