zylon-ai/private-gpt · error · ValueError

Expected tool calls in response but found none. Message: {re

Error message

Expected tool calls in response but found none. Message: {response.message}

What it means

ValueError raised while extracting tool calls from an OpenAI Responses LLM result when no ToolCallBlock is present in message.blocks nor in additional_kwargs['tool_calls'], and error_on_no_tool_call is enabled. It means the model answered with plain content instead of invoking a function/tool, and the caller declared that a tool call was mandatory.

Source

Thrown at private_gpt/components/llm/custom/openairesponses.py:224

        never accumulates blocks — it stores ToolCallBlock objects in
        message.additional_kwargs["tool_calls"] instead.  We check both locations.
        """
        from llama_index.core.llms.llm import ToolSelection
        from llama_index.core.llms.utils import parse_partial_json

        # Non-streaming / ResponseCompletedEvent path: blocks populated directly
        tool_call_blocks = [
            b for b in response.message.blocks if isinstance(b, ToolCallBlock)
        ]

        # Streaming accumulation path: _handle_stream_chunk stores them here
        if not tool_call_blocks:
            raw = response.message.additional_kwargs.get("tool_calls", [])
            tool_call_blocks = [tc for tc in raw if isinstance(tc, ToolCallBlock)]

        if not tool_call_blocks:
            if error_on_no_tool_call:
                raise ValueError(
                    "Expected tool calls in response but found none. "
                    f"Message: {response.message}"
                )
            return []

        tool_selections = []
        for b in tool_call_blocks:
            # tool_kwargs may be a JSON string (Responses API) or already a dict
            raw_kwargs = b.tool_kwargs
            if isinstance(raw_kwargs, str):
                try:
                    argument_dict = parse_partial_json(raw_kwargs) or {}
                except Exception:
                    argument_dict = {}
            else:
                argument_dict = raw_kwargs or {}
            tool_selections.append(
                ToolSelection(

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Log response.message to see what the model said instead of calling the tool — often a clarification or refusal you can address in the prompt.
  2. Ensure tools are actually attached to the request and the schema is valid JSON Schema the model supports.
  3. Use a model with reliable function calling (per the Responses API docs) for tool-driven flows.
  4. If a text answer is acceptable in some cases, disable error_on_no_tool_call and handle the empty selection list.
Defensive patterns

Strategy: try-catch

Try / catch

try:
    selections = extract_tool_selections(response, error_on_no_tool_call=True)
except ValueError as e:
    if 'Expected tool calls' in str(e):
        logger.warning('model declined tool call: %s', response.message.content)
        selections = []  # fall back to plain-answer handling

Prevention

When it happens

Trigger: Using OpenAIResponses-based agents/structured output where the prompt or function-calling config expects a tool call, but the model returns a text response — weak function-calling models, missing tools in the request, schema the model rejects, or the model asking a clarifying question.

Common situations: Switching to a model with poor function-calling support; tool definitions omitted from the chat request; overly complex JSON schema the model refuses; temperature/max_tokens settings truncating the response before the tool call is emitted.

Related errors


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