Repository navigation
Conversation
A non-streaming ChatResponse reaches _convert_chat_response_to_event as a single chunk, where all text blocks were glued into one TextBlockDeltaEvent and tool calls were emitted after the text. The message rebuilt from the replyStream events therefore differed from the message persisted to the context and returned by reply(): two text parts became one "Helloworld" block, and [text, tool_call, text] kept neither the split nor the order. Emit the text and tool call blocks in their original order instead, and let only the first text block of a chunk continue the stream of the previous chunk so that further text blocks open separate ones. Streaming chunks keep their behavior, as each of them carries at most one text block per logical stream.
Contributor
|
Thanks for the pull request. One thing worth sorting out before it is reviewed:
If you think it belongs here anyway, say so and a maintainer will take a look. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3236
PR Title Format
fix(agent): preserve block order of non-streaming responses in events— conventional commit, lowercase scope, lowercase description.AgentScope Version
2.0.10dev (main)
Description
Summary
Agent._convert_chat_response_to_eventflattened everyTextBlockof a non-streaming response into one glued delta and emitted tool calls after all the text. The message rebuilt fromreply_streamevents therefore diverged from the assistant message the agent persists to the context and returns fromreply(). Text and tool call blocks are now emitted in their original order, one text stream per block.Why
A non-streaming
ChatResponsereaches_convert_chat_response_to_event(src/agentscope/agent/_agent.py:3762) as a single chunk carrying the whole response, and the old code:TextBlocks of the chunk into oneTextBlockDeltaEventvia"".join(...)with no separator, andToolCallBlocks after all text, dropping their position inchunk.content.At the same time
_save_to_context(src/agentscope/agent/_agent.py:3428) persistscompleted_response.contentverbatim, andreply()(src/agentscope/agent/_agent.py:1811) returns those blocks unchanged — so for the same response the event stream said[TextBlock('Helloworld')]while the context said[TextBlock('Hello'), TextBlock('world')], and[text, tool_call, text]lost both the split and the order.This is reachable with real models: Gemini's non-streaming parser builds one
TextBlockper part (src/agentscope/model/_gemini/_model.py), Anthropic's non-streaming parser preserves[text, tool_use, text](src/agentscope/model/_anthropic/_model.py), andstream=Falseis a supportedChatModelBaseconfig. The streaming branch of the same function keeps the block order naturally (a tool-call chunk closes the open text stream), so only the non-streaming branch diverged.Changes
src/agentscope/agent/_agent.py:_convert_chat_response_to_eventnow walkschunk.contentand emitsTextBlockandToolCallBlockevents in original order. Only the first text block of a chunk continues the stream of the previous chunk; every further text block closes the current stream and opens a separate one. The close-the-stream-if-no-text condition is unchanged.ChatResponse.append_text(src/agentscope/model/_model_response.py:68) merges same-id text within a chunk, so streaming chunks carry at most one text block per logical stream and take the "first text block continues" path unchanged.tests/agent_basic_test.py: two regression tests — two adjacent text blocks stay separate blocks in the event stream, and the message rebuilt viaMsg.append_eventfrom the events of an interleaved[text, tool_call, text]response matches the message saved to the context.No public API change, no breaking change.
Testing
New regression tests on the unpatched tree (both fail with the glued/reordered blocks):
with the interleaved case asserting the rebuilt signature against the saved one:
With the fix, the same command:
Existing agent module tests:
Full suite comparison (
PYTHONPATH=src python3 -m pytest tests -q -p no:xdist, with the four test files that need uninstalled extras ignored): 161 failed / 2457 passed before and after the change, with identical failure lists — the only difference is the two new tests, which fail before and pass after. The pre-existing failures are local environment issues (missing optional services/deps: SQL storage, Ollama, MongoDB, Qdrant, docx parsers) and CI runscoverage run -m pytest tests -vwith the fulldevextra installed.Lint and type checks (same hooks CI runs):
Checklist