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.
Description
Summary
Answering any tool-approval prompt on an AG-UI surface backed by the Ollama connector ends the run with a hard error:
agent_framework_ag_uisynthesizes the resume as arole="user"message whose only contents arefunction_approval_response(_agent_run._canonical_approval_resume_messages, appended at_agent_run.py:1988with the log lineAppending N synthesized approval resume message(s).).agent_framework_ollamathen refuses exactly that shape (_chat_client.py:475):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:function_approval_responseagent_framework.openai(chat completions)[]— dropped silentlyagent_framework_gemini[]— dropped, withlogger.debug("Skipping unsupported content type for Gemini: %s", …)agent_framework_ollamaChatClientInvalidRequestExceptionSo 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:
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:
_format_user_messageskips what it cannot represent instead of raising, returning[]for a message with no representable content, and_prepare_messages_for_ollamadrops empty results (Gemini'sif not parts: continueis the existing precedent). A control-only message carries nothing the model needs: the approval is resolved in-process and its outcome travels as therole="tool"function_resultfor the samecall_id, which is present in the same payload.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-core1.12.1,agent-framework-ag-ui1.0.0,agent-framework-ollama1.0.0b260721main@143386fecc1e1c8ab02f1e3ee21b544ed5456d1c; also reproduces on33bc9c06, so this is long-standing rather than a recent regressionglm-5.2via the Ollama cloud API; surface: AG-UI + CopilotKitNote for triage
Our own history had an approval-control leak in the same area (a resolved local
function_approval_responsereplayed because_approval_controls_to_keeponly 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.