agentscope-ai / agentscope-ai/agentscope-runtime

[Bug] AGUIAdapterUtils does not distinguish MessageType.REASONING from MessageType.MESSAGE, causing thinking content to be emitted as regular TEXT_MESSAGE_* events in AG-UI stream

Aberta
#504 0 comentários 0 reações 0 responsáveis Ver no GitHub
bug
Linguagem predominante
Python
Estrelas
863
Forks
168
Métricas de merge de PRs
Nenhum PR com merge em 30d

Descrição

Bug Description
When using agentscope-runtime with the AG-UI adapter (/hiagent/a2h endpoint), both the model's reasoning/thinking content and its final answer are streamed as identical TEXT_MESSAGE_START / TEXT_MESSAGE_CONTENT / TEXT_MESSAGE_END events with role: "assistant". There is no way for the frontend to distinguish between the two.
Steps to Reproduce
Configure a ReActAgent with an OpenAIChatModel that has extended thinking enabled (or any model that emits thinking content blocks).
Register the agent via AgentApp with AGUIAdaptorConfig.
Send a request to the AG-UI endpoint and observe the SSE stream.
Observed stream:
data: {"type": "TEXT_MESSAGE_START", "messageId": "msg_aaa...", "role": "assistant"}
data: {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_aaa...", "delta": "用户只是打招呼,无需创建计划..."}
data: {"type": "TEXT_MESSAGE_END", "messageId": "msg_aaa..."}

data: {"type": "TEXT_MESSAGE_START", "messageId": "msg_bbb...", "role": "assistant"}
data: {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_bbb...", "delta": "你好!我是 hi-coder..."}
data: {"type": "TEXT_MESSAGE_END", "messageId": "msg_bbb..."}
Both msg_aaa (thinking) and msg_bbb (answer) are indistinguishable at the AG-UI protocol level.
Expected Behavior
Reasoning/thinking content should be emitted using the dedicated AG-UI event types that the protocol already defines:
data: {"type": "THINKING_TEXT_MESSAGE_START"}
data: {"type": "THINKING_TEXT_MESSAGE_CONTENT", "delta": "用户只是打招呼,无需创建计划..."}
data: {"type": "THINKING_TEXT_MESSAGE_END"}

data: {"type": "TEXT_MESSAGE_START", "messageId": "msg_bbb...", "role": "assistant"}
data: {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg_bbb...", "delta": "你好!我是 hi-coder..."}
data: {"type": "TEXT_MESSAGE_END", "messageId": "msg_bbb..."}
Or alternatively using REASONING_MESSAGE_START / REASONING_MESSAGE_CONTENT / REASONING_MESSAGE_END (with role: "reasoning").
Root Cause
The pipeline has three stages, and the bug is isolated to the third:Stage 1 – Model layer ✅
OpenAIChatModel correctly parses streaming LLM output into two distinct content block types:
{"type": "thinking", "thinking": "..."} for reasoning tokens
{"type": "text", "text": "..."} for answer tokens
Stage 2 – AgentScope stream adapter ✅
adapt_agentscope_message_stream in agentscope_runtime/adapters/agentscope/stream.py already correctly routes:
type == "thinking" blocks → Message(type=MessageType.REASONING, role="assistant")
type == "text" blocks → Message(type=MessageType.MESSAGE, role="assistant")
Two distinct MessageType values flow into the AG-UI adapter.Stage 3 – AG-UI adapter ❌ (bug is here)
AGUIAdapterUtils._convert_content_event in agui_adapter_utils.py checks only isinstance(content, TextContent) without inspecting the parent Message.type. As a result, TextContent from both MessageType.MESSAGE and MessageType.REASONING messages is unconditionally mapped to TextMessageContentEvent, discarding the type distinction.Relevant code (simplified):
# agui_adapter_utils.py, _convert_content_event()
if isinstance(content, TextContent):
events.extend(_ensure_agui_text_message_started(agui_msg_id)) # always TEXT_MESSAGE_START
...
events.append(TextMessageContentEvent(...)) # always TEXT_MESSAGE_CONTENT
Proposed Fix
The adapter needs to track whether a given agui_msg_id belongs to a REASONING message, and emit the corresponding event family accordingly.One approach: when _convert_message_event receives a Message with type == MessageType.REASONING, record its ID in a separate set (e.g. self._reasoning_message_ids). Then in _convert_content_event, branch on whether agui_msg_id is in that set:
if agui_msg_id in self._reasoning_message_ids:
# emit THINKING_TEXT_MESSAGE_* or REASONING_MESSAGE_* events
else:
# emit TEXT_MESSAGE_* events (current behavior)
The AG-UI protocol already supports THINKING_TEXT_MESSAGE_START/CONTENT/END and REASONING_MESSAGE_START/CONTENT/END — they just aren't being used.
Environment
agentscope-runtime >= 1.1.0
ag-ui (python SDK, whichever version is pulled transitively)
Model: any model that emits extended thinking / reasoning tokens (e.g. DeepSeek-R1, Claude 3.7 Sonnet with extended thinking, Qwen3 thinking mode, etc.)
Additional Context
This affects any downstream frontend (including Qoder's Canvas, CopilotKit, etc.) that relies on the AG-UI event type to render thinking and answer content differently. With the current behavior, there is no reliable way for a frontend to distinguish reasoning from the final answer without resorting to fragile heuristics.

Guia de contribuição

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.