Skip to content

Python: A2UI (Agent-to-UI) support for the AG-UI adapter - #7423

Merged
Evan Mattson (moonbox3) merged 18 commits into
microsoft:mainfrom
ranst91:feat/a2ui-python-finish
Aug 21, 2026
Merged

Python: A2UI (Agent-to-UI) support for the AG-UI adapter#7423
Evan Mattson (moonbox3) merged 18 commits into
microsoft:mainfrom
ranst91:feat/a2ui-python-finish

Conversation

@ranst91

@ranst91 Ran Shemtov (ranst91) commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Motivation & Context

A2UI lets an agent generate rich, interactive UI (cards, forms, dashboards) that renders live in the client instead of plain text. It already shipped for .NET (#6494); this brings the same capability to the Python AG-UI adapter for parity. It reuses the shared, framework-agnostic ag-ui-a2ui-toolkit.

Description & Review Guide

  • What are the major changes? Adds an in-package _a2ui module to agent-framework-ag-ui, delivering:

    • Progressive streaming — the render_a2ui sub-agent's argument deltas reach the wire as the surface builds, so the client paints it incrementally.
    • Error recovery — a validate-and-retry loop. An invalid surface never paints, and exhaustion produces a failure envelope instead of throwing.
    • Sub-agent basedgenerate_a2ui delegates UI design to a forced render_a2ui structured-output sub-agent, run through the toolkit's recovery loop.

    Plus two small bridge fixes needed for A2UI to behave in a real client: strip unanswered tool calls from replayed history before the planner call (A2UI surfaces persist as activities, so a surface action like a card button would otherwise fail the next turn), and skip the terminal MESSAGES_SNAPSHOT for A2UI runs so the streamed transcript order stays stable (mirrors the existing predictive-tool snapshot suppression). Also adds example agents and a unit suite.

  • What is the impact of these changes? Purely additive. The toolkit is imported lazily behind an optional a2ui extra, so the base package is unchanged for anyone not using A2UI; the new a2ui_config parameters are optional with existing defaults. Verified end to end against a real model, not only fixtures.

  • What do you want reviewers to focus on? The auto-injection gate in _agent_run.py (nullish precedence between the runtime injectA2UITool flag and a backend inject_a2ui_tool opt-in) and the streaming coalescing in _a2ui/_agent.py.

Related Issue

No dedicated Python issue; this is the Python counterpart of the .NET A2UI work in #6494.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change.

Copilot AI review requested due to automatic review settings July 30, 2026 07:08
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Jul 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds Python-side A2UI (agent-to-UI) support to the AG-UI adapter for parity with the existing .NET implementation, enabling rich surface generation with progressive streaming and recovery semantics via the shared ag-ui-a2ui-toolkit.

Changes:

  • Introduces a new agent_framework_ag_ui._a2ui module (state plumbing, context replay wrapper, A2UI generation wrapper, and auto-injection factory) with lazy imports so the base package remains usable without the optional toolkit.
  • Updates the AG-UI hosting path to (a) stamp forwarded A2UI context into run options, (b) auto-inject the generate_a2ui tool when requested by the runtime, and (c) suppress the terminal snapshot for A2UI runs to preserve streamed ordering.
  • Adds an A2UI-focused test suite and expands the examples server with A2UI demo endpoints.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
python/uv.lock Adds ag-ui-a2ui-toolkit and registers the new a2ui extra for agent-framework-ag-ui.
python/packages/ag-ui/tests/ag_ui/test_a2ui.py New unit suite covering A2UI context plumbing, wrapper agents, streaming/non-streaming generation, and key regressions.
python/packages/ag-ui/pyproject.toml Defines the a2ui optional dependency and configures mypy overrides for the toolkit.
python/packages/ag-ui/agent_framework_ag_ui/_endpoint.py Adds a2ui_config parameter to the FastAPI endpoint helper and forwards it into config.
python/packages/ag-ui/agent_framework_ag_ui/_agent.py Extends agent configuration to carry a2ui_config for runtime auto-injection.
python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Implements A2UI auto-injection, AG-UI context stamping, and suppresses terminal snapshot for A2UI runs.
python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_state.py New toolkit-free state/context utilities (slice building, stamping, stripping, history mapping).
python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_factory.py New enable_a2ui helper and plan_a2ui_injection decision logic (Strands-parity rules).
python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_context_agent.py New wrapper to replay forwarded A2UI catalog/guidelines into the prompt via a system message.
python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py New A2UIAgent implementing streaming progressive paint + validate/retry and non-streaming tool execution.
python/packages/ag-ui/agent_framework_ag_ui/_a2ui/init.py New lazy-export package initializer for A2UI-related symbols.
python/packages/ag-ui/agent_framework_ag_ui/init.py Adds lazy exports for A2UI symbols at the package root.
python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py Updates examples server to support OpenAI Chat Completions and adds A2UI demo endpoints.
python/packages/ag-ui/agent_framework_ag_ui_examples/agents/a2ui_agents.py Adds A2UI demo agents (dynamic schema, advanced zero-config, recovery, fixed schema).
python/packages/ag-ui/agent_framework_ag_ui_examples/agents/init.py Exposes new A2UI demo agents/config via the examples agents package.
Comments suppressed due to low confidence (1)

python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py:88

  • After making python-dotenv optional, the .env load call also needs a guard; otherwise load_dotenv may be None and this will raise at runtime.
# Load the examples .env (OPENAI_API_KEY etc.) before constructing any chat client.
# override=True so a stale/empty exported OPENAI_API_KEY doesn't shadow the .env value.
load_dotenv(Path(__file__).resolve().parent.parent / ".env", override=True)

Comment thread python/packages/ag-ui/agent_framework_ag_ui_examples/server/main.py Outdated
Comment thread python/packages/ag-ui/pyproject.toml Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
@eavanvalkenburg

Copy link
Copy Markdown
Member

Ran Shemtov (@ranst91) please use the defined PR template, and there are a number of merge conflicts

@ranst91

Copy link
Copy Markdown
Contributor Author

Eduard van Valkenburg (@eavanvalkenburg) All comments (including about the PR body) seems to be addressed now. Lmk if there's anything else

@moonbox3 Evan Mattson (moonbox3) left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hi Ran Shemtov (@ranst91), thanks for taking this on. The core of this PR looks solid. A2UIAgent hides a lot of behavior (progressive streaming, the validate-and-retry recovery loop, fragment coalescing) behind a small surface, and the test suite mostly drives it through run() with real doubles rather than mocks. That's great, thank you.

My main feedback is about how it wires into the run loop. The existing extension points in this package (predictive state via predict_state_config, HITL via require_confirmation) are declarative config that the core run loop consumes. A2UI instead swaps the agent object mid-run: _agent_run.py rebinds agent to the wrapper stack after server tools are collected but before the protected-state-key computation, approval resolution, and continuation-state serialization. Since the wrappers don't mirror context_providers or client, those reads see the wrong agent. Concretely: provider source_ids drop out of the protected keys (client shared state can then clobber provider namespaces) and approved-tool execution loses function middleware. I'd suggest expressing A2UI as config on AgentConfig and letting the core honor it, no object swap. That would also fix two related spots:

  1. The terminal MESSAGES_SNAPSHOT skip matches the literal tool names "generate_a2ui"/"render_a2ui", so any user tool with that name silently loses its snapshot. Driving it off config, like _should_suppress_intermediate_snapshot already does for predictive tools, avoids that.
  2. If the toolkit import fails, the run continues with the injected render_a2ui tool still advertised but with no executor, which produces exactly the unanswered-tool-call state your sanitizer exists to repair. Failing with a clear install message when A2UI was explicitly requested seems safer.

One smaller thing: AGUIContextAgent ports a .NET shape that no longer exists in this repo (the .NET package moved to the external AG-UI SDK), and the stamp/strip channel through additional_properties exists only to replicate ChatOptions.AdditionalProperties. Python already has ContextProvider for per-run instruction injection, and using it would also remove the footgun where standalone AGUIContextAgent use leaks ag_ui_context to the provider. Happy to discuss any of this, and thanks again, the streaming core itself is in great shape.

Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_context_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_context_agent.py Outdated
@ranst91

Copy link
Copy Markdown
Contributor Author

Evan Mattson (@moonbox3) Thanks for the review. Comments should be addressed now. LMK if there's anything else

@moonbox3 Evan Mattson (moonbox3) added the ag-ui Usage: [Issues, PRs], Target: AG-UI protocol integration label Aug 5, 2026
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_context_agent.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_agent_run.py Outdated
Comment thread python/packages/ag-ui/agent_framework_ag_ui/_a2ui/_factory.py Outdated
@ranst91

Copy link
Copy Markdown
Contributor Author

Evan Mattson (@moonbox3) addressed the last round of comments. LMK if there's anything else

Adds an in-package _a2ui module to agent-framework-ag-ui delivering
progressive-streaming, error-recovery, and sub-agent-based A2UI surface
generation, reusing the shared ag-ui-a2ui-toolkit. Includes example
agents, a unit suite, and two bridge fixes (strip unanswered tool calls
from replayed history; suppress the terminal MESSAGES_SNAPSHOT for A2UI
runs to keep streamed order stable).

Signed-off-by: ran <ran@copilotkit.ai>
Reworks A2UI so it no longer swaps the agent object mid-run, and fixes the
issues that swap caused.

- Drive A2UI through a dedicated runner used only for the stream call; keep
  the original agent bound so protected-state-key computation, approval
  resolution, and continuation serialization still read its real
  context_providers and client (no more provider-namespace or approval
  middleware loss).
- Hand the forwarded AG-UI context to the runner directly instead of stamping
  it onto run-option additional_properties. That channel leaked the slice to
  the provider SDK on any run carrying AG-UI context, including non-A2UI runs
  where nothing stripped it back. Removes the stamp/strip/read helpers and the
  dead .NET-shaped path.
- Suppress the terminal MESSAGES_SNAPSHOT off whether A2UI actually drove the
  run, not the literal tool names, so an unrelated user tool named
  "generate_a2ui" keeps its snapshot.
- Fail loud with an install hint when A2UI is requested but the toolkit isn't
  installed, instead of advertising render_a2ui with no executor.
- Include the agent's own default tools in the no-double-injection check so an
  already-wired agent doesn't crash on a duplicate tool name.
- Execute ordinary developer tools called in the same turn as generate_a2ui
  (the declaration-only tool poisons the inner batch invocation), so a
  "look up data then render it" turn no longer skips the backend call.
- Attribute nameless streaming argument deltas by the provider tool-call index
  so interleaved parallel calls don't cross-contaminate; the OpenAI chat client
  preserves that index on the content.

Adds tests for the mixed-batch execution, index-based fragment attribution,
and the default-tool duplicate check.

Signed-off-by: ran <ran@copilotkit.ai>
…pshot

- Mixed-batch (a tool called in the same turn as generate_a2ui): execute
  server tools through the agent's real function-invocation pipeline (client
  function_middleware + config), the same path approval-resume uses, instead
  of a direct tool.invoke() that bypassed middleware/context/session.
- Look up mixed-batch tools across incoming AND the agent's own default tools,
  so a server tool wired only on the agent (no runtime tools=) still executes.
- Leave declaration-only client tools (func=None) as user-input requests
  instead of synthesizing a local result, preserving the resumable client-tool
  flow.
- Recognize a manually enable_a2ui()-wrapped agent when deciding to suppress
  the terminal MESSAGES_SNAPSHOT, so the ordering fix also covers that path.
- Remove .NET-specific comments from the Python module.

Adds tests: server-tool execution runs through middleware, default-tool
execution, client declaration-only tool left as user-input.

Signed-off-by: ran <ran@copilotkit.ai>
Consolidates A2UI wiring into one owner, per review:

- Fold the context-prepend (former AGUIContextAgent) into A2UIAgent, which now
  prepends the forwarded catalog + guidelines as a system message itself.
  Removes the extra agent type (matching the langgraph/strands adapters, which
  have no separate context agent).
- Make A2UIAgent the typed runner interface: it carries the render tool(s) to
  strip (drop_tool_names) and is recognized via is_a2ui_runner(). plan_a2ui_injection
  now returns the runner (or None) instead of a bare dict, so no private plan keys
  leak into the host and the host no longer tracks activation separately —
  is_a2ui_runner() covers both the auto-injected and manual enable_a2ui() paths.

Signed-off-by: ran <ran@copilotkit.ai>
End-to-end through run_agent_stream: a turn that calls a declaration-only client
tool alongside generate_a2ui surfaces the client tool as a resumable frontend
tool call (START/ARGS/END, no server-synthesized result) so the frontend
executes and resumes it, the A2UI surface still renders, the run finishes, and
no terminal MESSAGES_SNAPSHOT is emitted (manual enable_a2ui path). Confirms the
mixed-batch client-tool contract on the AG-UI wire, not just at the agent level.

Signed-off-by: ran <ran@copilotkit.ai>
This was referenced Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ag-ui Usage: [Issues, PRs], Target: AG-UI protocol integration python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants