Skip to content

Python: [Bug]: Ollama connector rejects the approval-resume message AG-UI synthesizes #7451

Description

@antsok

Description

Summary

Answering any tool-approval prompt on an AG-UI surface backed by the Ollama connector ends the run with a hard error:

ChatClientInvalidRequestException: Ollama connector currently only supports user messages
with TextContent or DataContent.

agent_framework_ag_ui synthesizes the resume as a role="user" message whose only contents are function_approval_response (_agent_run._canonical_approval_resume_messages, appended at _agent_run.py:1988 with the log line Appending N synthesized approval resume message(s).). agent_framework_ollama then refuses exactly that shape (_chat_client.py:475):

def _format_user_message(self, message: Message) -> list[OllamaMessage]:
    if not any(c.type in {"text", "data"} for c in message.contents) and not message.text:
        raise ChatClientInvalidRequestException(
            "Ollama connector currently only supports user messages with TextContent or DataContent."
        )

Both sides are first-party and both are behaving as written; they simply disagree about whether a control-only user message is legal. The user has already answered the prompt at this point, so the failure lands after their input and the approved tool's turn produces nothing.

The part that suggests where the fix belongs

Every other connector tolerates the same message. Measured on the versions below, same Message, three converters:

Connector Result for a user message holding only function_approval_response
agent_framework.openai (chat completions) [] — dropped silently
agent_framework_gemini [] — dropped, with logger.debug("Skipping unsupported content type for Gemini: %s", …)
agent_framework_ollama raises ChatClientInvalidRequestException

So the framework already treats "a content this connector cannot represent" as skippable in two of three connectors, and Gemini's caller even handles the resulting empty message (if not parts: continue). Ollama is the outlier.

Reproduction

Offline, no daemon and no network:

from agent_framework import Content, Message
from agent_framework_ollama import OllamaChatClient

call = Content.from_function_call(call_id="c1", name="search", arguments={})
resume = Message(
    role="user",
    contents=[Content.from_function_approval_response(True, id="c1", function_call=call)],
)

OllamaChatClient._format_user_message(object.__new__(OllamaChatClient), resume)
# ChatClientInvalidRequestException: Ollama connector currently only supports user messages
# with TextContent or DataContent.

End to end: any AG-UI surface on an Ollama model, one approval-gated tool, click approve.

Code Sample

Error Messages / Stack Traces

Package Versions

`agent-framework-core: 1.12.1, agent-framework-ag-ui; 1.0.0, agent-framework-ollama: 1.0.0b260721

Python Version

No response

Additional Context

Suggested fix

Either side closes it; the connector side looks more consistent with the rest of the framework:

  • Preferred — _format_user_message skips what it cannot represent instead of raising, returning [] for a message with no representable content, and _prepare_messages_for_ollama drops empty results (Gemini's if not parts: continue is the existing precedent). A control-only message carries nothing the model needs: the approval is resolved in-process and its outcome travels as the role="tool" function_result for the same call_id, which is present in the same payload.
  • Alternatively — AG-UI does not emit a provider-bound user message that holds only approval controls, since no connector renders them.

Raising is defensible for a genuinely empty user-authored turn; it is the framework's own synthesized control message that makes it fire here.

Environment

  • agent-framework-core 1.12.1, agent-framework-ag-ui 1.0.0, agent-framework-ollama 1.0.0b260721
  • Pinned to main @ 143386fecc1e1c8ab02f1e3ee21b544ed5456d1c; also reproduces on 33bc9c06, so this is long-standing rather than a recent regression
  • Model glm-5.2 via the Ollama cloud API; surface: AG-UI + CopilotKit

Note for triage

Our own history had an approval-control leak in the same area (a resolved local function_approval_response replayed because _approval_controls_to_keep only clears one whose terminal result follows it). That leak makes this fire more often — two responses in one message rather than one — but it is not the trigger: the single-response shape above raises on its own, and we observed this on a pin that predates that filter's current form.

Metadata

Metadata

Assignees

No one assigned

    Labels

    likely-fixedpythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions