fix: set_conversational_output leaking CAS event, phantom CAS events for OpenAI envelope chunk#979
Open
maxduu wants to merge 6 commits into
Open
fix: set_conversational_output leaking CAS event, phantom CAS events for OpenAI envelope chunk#979maxduu wants to merge 6 commits into
maxduu wants to merge 6 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Prevents the runtime from emitting phantom CAS events for (1) OpenAI Responses API “envelope-only” chunks and (2) the internal set_conversational_output tool call used for conversational structured output extraction.
Changes:
- Add a shape-guard to skip envelope-only
AIMessageChunks (no content / no tool calls / notchunk_position="last") to avoid phantommessageStartevents. - Route
set_conversational_outputtool-call args viainner_state.conversational_output(instead of themessageschannel) so LangGraph’s messages-stream sweep can’t leak synthetic tool calls to CAS. - Update/extend tests to cover envelope-chunk skipping and the new conversational output plumbing + error path.
Reviewed changes
Copilot reviewed 10 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Bumps package version to 0.14.4. |
| pyproject.toml | Bumps project version to 0.14.4. |
| src/uipath_langchain/runtime/messages.py | Skips envelope-only chunks to prevent phantom CAS message start/content-part start events. |
| tests/runtime/test_chat_message_mapper.py | Updates existing mapper tests and adds coverage for envelope-only chunk skipping + empty-last-chunk behavior. |
| src/uipath_langchain/agent/react/types.py | Adds InnerAgentGraphState.conversational_output to carry extracted structured output args. |
| src/uipath_langchain/agent/react/conversational_output_node.py | Extracts set_conversational_output args from the internal LLM response and stores them in inner_state; raises LLM_INVALID_RESPONSE if the tool call is missing. |
| src/uipath_langchain/agent/react/terminate_node.py | Reads structured custom output fields from inner_state.conversational_output and removes graph-shape-dependent logic. |
| src/uipath_langchain/agent/react/agent.py | Simplifies terminate node creation (no longer passes with_conversational_output_node). |
| tests/agent/react/test_conversational_output_node.py | Updates expectations to assert inner_state updates (no messages) and adds test for missing tool call -> structured error. |
| tests/agent/react/test_terminate_node.py | Updates termination behavior tests to supply conversational output via inner_state. |
| tests/agent/react/test_create_agent.py | Updates mocks/assertions for the new create_terminate_node signature usage. |
| async def test_returns_response_with_tool_call(self): | ||
| async def test_returns_extracted_args_via_inner_state(self): | ||
| """The extracted set_conversational_output args land on | ||
| inner_state.conversational_output_args, not on `messages` — that |
Contributor
Author
There was a problem hiding this comment.
Removed outdated comment.
|
andrewwan-uipath
approved these changes
Jul 10, 2026
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.



Summary
Two independent fixes that stop the runtime from emitting phantom
messageStart/toolCallevents to CAS for internal machinery that shouldn't be user-visible.1. OpenAI Responses API envelope chunk
Issue: OpenAI's Responses API (e.g. gpt-5.4) fronts a stream with an empty envelope chunk (id=
resp_..., from the SSEresponse.createdevent), then streams the actual content under a different id (lc_run--...). The mapper treated the envelope as a new message and emitted a phantommessageStart+contentPartStartpair to CAS.Fix:
src/uipath_langchain/runtime/messages.py— shape-guard that skips streaming message-start/content-part-start events for chunks with no content, no tool calls, andchunk_position != "last".Before:

After:
(With temporary prints). Note the
"Skipping phantom message start..."print.2.
set_conversational_outputtool call leaking to CAScc @andrewwan-uipath
Why:
GENERATE_CONVERSATIONAL_OUTPUTruns an internal LLM call withtool_choice="any"bound only to theset_conversational_outputtool.TAG_NOSTREAMon the invoke config correctly suppressed the LLM callback path, but LangGraph's messages-stream mode also streams message-outputs from the return of{"messages": [response]}. So the chat-mapper emittedtoolCallStart+executingToolCallevents to CAS for the synthetic tool call.Fix: Extract the tool call args inside the node and route them via
inner_state.conversational_outputinstead ofmessages. The AIMessage never enters the messages channel, so there's nothing for LangGraph's sweep to pick up. This refactors and simplifies a lot of the terminate_node logic since it simply just obtains the output from theinner_stateof the graph rather than parsing the last message.Before:

Note the temporary log
Mapping AIMessage to events: resp_...After:

Test plan
test_returns_extracted_args_via_inner_stateandtest_raises_when_llm_omits_set_conversational_output_callintest_conversational_output_node.pytest_conversational_extracts_custom_output_fieldsto pass args viainner_statetest_chat_message_mapper.py🤖 Generated with Claude Code