Skip to content

feat: GENERATE_CONVERSATIONAL_OUTPUT node for reliable structured-output extraction#965

Open
maxduu wants to merge 2 commits into
mainfrom
convo-agent-output-tool-call-auto
Open

feat: GENERATE_CONVERSATIONAL_OUTPUT node for reliable structured-output extraction#965
maxduu wants to merge 2 commits into
mainfrom
convo-agent-output-tool-call-auto

Conversation

@maxduu

@maxduu maxduu commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a dedicated graph node that runs after AGENT for conversational agents
with a custom outputSchema, producing the structured output via a focused,
forced tool call to `set_conversational_output`. Decouples conversational
quality from schema compliance — the main AGENT LLM stays focused on the
reply, and a second LLM call (tagged `TAG_NOSTREAM`, non-streamed) reliably
fills in the routing/handoff fields.

Motivation

Previously, conversational agents with custom output schemas would fail:

  • On old branches, they required the AGENT LLM to also call `end_execution()` in the same turn — models like gpt-4.1 don't reliably do this.
  • On `main`, they now fail at TERMINATE with a Pydantic `ValidationError` because required custom fields are never populated.

Changes

Graph structure (agent/react/)

  • New `AgentGraphNode.GENERATE_CONVERSATIONAL_OUTPUT` enum value.
  • New `conversational_output_node.py` — invokes a focused LLM call with only
    `set_conversational_output` bound (`tool_choice="any"`, `disable_streaming=True`,
    `TAG_NOSTREAM` tag). Reuses `state.messages` for full agent context and
    appends the framework instruction as a HumanMessage that never persists to state.
  • `agent.py` — conditionally inserts the new node between AGENT and TERMINATE
    when `config.is_conversational and config.conversational_outputs_enabled and has_custom_conversational_output_fields(output_schema)`.
  • `router_conversational.py` — routes AGENT-without-tool-calls to the new node
    (or straight to TERMINATE if the flag is off).

Config

  • New `AgentGraphConfig.conversational_outputs_enabled: bool = False` — the
    top-level feature flag. Defaults to False; existing callers unchanged.

Terminate node

  • `_handle_end_conversational` now best-effort extracts the tool call's args
    from `state.messages[-1]`. If the tool call is absent, custom fields stay
    empty and Pydantic surfaces a clear per-field error at schema validation.
  • The new node's AIMessage is sliced off before conversion so it doesn't leak
    into the response payload.

Utilities

  • New `has_custom_conversational_output_fields` +
    `build_conversational_output_args_schema` helpers in `utils.py` (strips
    `uipath__agent_response_messages` from the LLM-fillable args schema).
  • New `create_set_conversational_output_tool` factory in `agent/react/tools/tools.py`.
  • New shared `config_without_streaming` helper in `agent/tools/utils.py` —
    refactored out of `analyze_files_tool.py` since it's now used in two places.
  • New `UIPATH_CONVERSATIONAL_AGENT_RESPONSE_MESSAGES_FIELD` constant.

Tests: full topology + router + terminate + utils + node coverage added.

Related PRs

Part of a coordinated four-repo change. Each PR is independently reviewable, but they land together:

Test plan

  • `uv run pytest tests/agent tests/runtime tests/cli` (1567 tests pass)
  • `just lint` clean
  • Manual end-to-end against `convo-agent-web-search-file-attachments`

Copilot AI review requested due to automatic review settings July 2, 2026 05:55

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional (flagged) intermediate LangGraph node for conversational agents to reliably extract structured custom output fields via a forced set_conversational_output tool call, keeping the main AGENT LLM focused on the conversational reply while ensuring schema compliance at termination.

Changes:

  • Introduces GENERATE_CONVERSATIONAL_OUTPUT node + wiring/routing to run between AGENT and TERMINATE when conversational + flag enabled + custom output fields exist.
  • Adds utilities/factories (config_without_streaming, has_custom_conversational_output_fields, build_conversational_output_args_schema, create_set_conversational_output_tool) and updates termination to merge extracted custom fields.
  • Adds/updates tests covering the new node, routing behavior, termination behavior, and helper utilities.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/agent/tools/test_utils.py Adds unit tests for the new shared config_without_streaming helper.
tests/agent/tools/internal_tools/test_analyze_files_tool.py Removes the old private _config_without_streaming tests after refactor to shared helper.
tests/agent/react/test_utils.py Adds tests for has_custom_conversational_output_fields and build_conversational_output_args_schema.
tests/agent/react/test_terminate_node.py Adds termination tests for extracting/validating conversational custom output fields.
tests/agent/react/test_router_conversational.py Adds routing tests for the new conversational-output node branch.
tests/agent/react/test_flow_control_tools.py Adds tests for the new create_set_conversational_output_tool factory.
tests/agent/react/test_create_agent.py Adds topology tests ensuring the node is conditionally inserted by config/schema.
tests/agent/react/test_conversational_output_node.py Adds tests for the new node (tool binding, TAG_NOSTREAM config, streaming disabled, instruction handling).
src/uipath_langchain/agent/tools/utils.py Adds shared config_without_streaming helper used by multiple internal LLM calls.
src/uipath_langchain/agent/tools/internal_tools/analyze_files_tool.py Refactors to use config_without_streaming and keeps internal LLM call non-streamed.
src/uipath_langchain/agent/react/utils.py Adds helpers to detect custom conversational output fields and generate tool args schema.
src/uipath_langchain/agent/react/types.py Adds new graph node enum value, new config flag, and extends flow-control tool list.
src/uipath_langchain/agent/react/tools/tools.py Adds create_set_conversational_output_tool factory.
src/uipath_langchain/agent/react/terminate_node.py Extracts/merges set_conversational_output args into conversational termination output and improves validation errors.
src/uipath_langchain/agent/react/router_conversational.py Adds optional routing to GENERATE_CONVERSATIONAL_OUTPUT when enabled.
src/uipath_langchain/agent/react/conversational_output_node.py Implements the new focused LLM extraction node (non-streamed, TAG_NOSTREAM, forced tool call).
src/uipath_langchain/agent/react/constants.py Adds constant for the response-messages field name.
src/uipath_langchain/agent/react/agent.py Conditionally inserts the new node and passes routing flag based on config + schema analysis.

Comment on lines +98 to +100
custom_output_fields: dict[str, Any] = (
dict(set_output_call["args"]) if set_output_call is not None else {}
)
Comment on lines +83 to +86
detail=(
"The language model returned an unexpected response type."
"If you are using a BYOM configuration, verify your model deployment.",
),
@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants