Skip to content

feat: AG-UI adapter (toolsets) #2101

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: toolsets
Choose a base branch
from

Conversation

stevenh
Copy link
Contributor

@stevenh stevenh commented Jun 30, 2025

AG-UI adapter enabling integration with the AG-UI protocol via the Agent.to_ag_ui() method.

This includes examples for all features in the AG-UI dojo.

Fixes: ag-ui-protocol/ag-ui#5

Copy link
Contributor

hyperlint-ai bot commented Jun 30, 2025

PR Change Summary

Implemented the AG-UI adapter for seamless integration with the AG-UI protocol, enhancing user interaction capabilities for PydanticAI agents.

  • Added comprehensive documentation for the AG-UI adapter and its features.
  • Introduced examples demonstrating the integration of PydanticAI agents with the AG-UI protocol.
  • Updated installation instructions to include AG-UI dependencies.

Modified Files

  • docs/install.md

Added Files

  • docs/ag-ui.md
  • docs/api/ag_ui.md
  • examples/pydantic_ai_ag_ui_examples/README.md

How can I customize these reviews?

Check out the Hyperlint AI Reviewer docs for more information on how to customize the review.

If you just want to ignore it on this PR, you can add the hyperlint-ignore label to the PR. Future changes won't trigger a Hyperlint review.

Note specifically for link checks, we only check the first 30 links in a file and we cache the results for several hours (for instance, if you just added a page, you might experience this). Our recommendation is to add hyperlint-ignore to the PR to ignore the link check for this PR.

@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch 2 times, most recently from caddcc0 to 6671164 Compare July 1, 2025 12:35
@DouweM DouweM self-assigned this Jul 1, 2025
@dorukgezici
Copy link
Contributor

dorukgezici commented Jul 2, 2025

Hey! I know this is still a draft, but have been following the development of ag-ui support since I wish to build my MVP with pydantic-ai & ag-ui. Did a test run today on this branch, dropping my findings in case they are helpful:

  • If there are mcp_servers defined in the agent, .to_ag_ui() doesn't handle running the servers, so any request errors out
  • I noticed if the model is trying to chain tool calls, it errors out with a similar error to this even when parallel_tool_calls is disabled:
    pydantic_ai.exceptions.ModelHTTPError: status_code: 400, model_name: gpt-4.1-mini, body: {'message': "Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.", 'type': 'invalid_request_error', 'param': 'messages.[13].role', 'code': None}

@stevenh
Copy link
Contributor Author

stevenh commented Jul 3, 2025

Great info, keep it coming!

@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch 3 times, most recently from a391a93 to 2995d59 Compare July 3, 2025 09:03
@stevenh
Copy link
Contributor Author

stevenh commented Jul 3, 2025

Hey! I know this is still a draft, but have been following the development of ag-ui support since I wish to build my MVP with pydantic-ai & ag-ui. Did a test run today on this branch, dropping my findings in case they are helpful:

  • If there are mcp_servers defined in the agent, .to_ag_ui() doesn't handle running the servers, so any request errors out
  • I noticed if the model is trying to chain tool calls, it errors out with a similar error to this even when parallel_tool_calls is disabled:
    pydantic_ai.exceptions.ModelHTTPError: status_code: 400, model_name: gpt-4.1-mini, body: {'message': "Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.", 'type': 'invalid_request_error', 'param': 'messages.[13].role', 'code': None}

There's been some changes on toolset PR, which this depends on and has been updated, can you retest to see if these are fixed now @dorukgezici

@stevenh stevenh mentioned this pull request Jul 3, 2025
@dorukgezici
Copy link
Contributor

@stevenh concurrent tool calls worked well now!
I also tried a streamable HTTP MCP server (used the new toolsets=[] param on the agent) and the agent worked, but wasn't aware of the tool so it was not registered. That's probably an issue that belongs to the ongoing toolsets PR you are basing off?

@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch from 2995d59 to 78da22f Compare July 4, 2025 18:46
@stevenh
Copy link
Contributor Author

stevenh commented Jul 4, 2025

@stevenh concurrent tool calls worked well now!

I also tried a streamable HTTP MCP server (used the new toolsets=[] param on the agent) and the agent worked, but wasn't aware of the tool so it was not registered. That's probably an issue that belongs to the ongoing toolsets PR you are basing off?

Could you clarify what you mean by wasn't aware of the tool? Do you mean an AG-UI tool wasn't available?

@DouweM DouweM force-pushed the feat/ag-ui-adapter-toolset branch from 78da22f to 1bad3e5 Compare July 4, 2025 22:00
Copy link
Contributor

@DouweM DouweM left a comment

Choose a reason for hiding this comment

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

@stevenh Looking good! Left some questions and suggestions

@@ -204,6 +204,7 @@ jobs:
enable-cache: true

- run: uv sync --package pydantic-ai-slim --only-dev
- run: rm coverage/.coverage.*-py3.9-* # Exclude 3.9 coverage as it gets the wrong line numbers, causing invalid failures.
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove this from this PR and perhaps add it in a separate one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to add that to get checks to complete, otherwise they abort early. If you're happy to merge with checks failing, I can extract.

@@ -221,6 +223,15 @@ include = [
]
omit = ["tests/test_live.py", "tests/example_modules/*.py"]
branch = true
disable_warnings = ["include-ignored"]

[tool.coverage.paths]
Copy link
Contributor

Choose a reason for hiding this comment

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

We should remove this before we merge this!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This prevents tests failing with coverage enabled in VS code, happy to move to a seperate PR?

# specifically, it logs every request and response including binary
# content in Cassette.append, which is causing log downloads from
# GitHub action to fail.
logging.getLogger('vcr.cassette').setLevel(logging.WARNING)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you give an example of the types of verbose logging we were seeing before, that we're now not anymore? Just to know the tradeoff here

Copy link
Contributor Author

@stevenh stevenh Jul 4, 2025

Choose a reason for hiding this comment

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

It logs the binary output of requests, including image data. This made logs so large I couldn't even download them from github to investigate a failure, possibly a github bug as no matter what I did the log zip download failed.

I did raise an issue with upstream, as it logs all requests at INFO, but it seems unmaintained.

yield ToolCallArgsEvent(
type=EventType.TOOL_CALL_ARGS,
tool_call_id=agent_event.delta.tool_call_id
or stream_ctx.last_tool_call_id
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need to store this last tool call ID? If there's an ID on the start event, but not on the delta, that'd be a Pydantic AI bug

Copy link
Contributor Author

@stevenh stevenh Jul 5, 2025

Choose a reason for hiding this comment

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

agent_event.delta.tool_call_id can be None, if that was a required field, which I suspect it should this wouldn't be needed.


# Enums.
# TODO(steve): Remove this and all uses once https://github.com/ag-ui-protocol/ag-ui/pull/49 is merged.
class Role(str, Enum):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can drop this and just hard-code string values, they're defined as Literals on the event classes so they're type-checked for validity anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could but that results in repeated values, which is less efficient. I was hoping we could get these eliminated before we merge.

stevenh added 11 commits July 7, 2025 15:04
AG-UI adapter enabling integration with the AG-UI protocol via
the Agent.to_ag_ui() method.

This includes a full example for all features in the AG-UI dojo.

Fixes: ag-ui-protocol/ag-ui/issues/5
Move pydantic_ai_ag_ui to pydantic_ai_slim as ag_ui, updating references
and documentation accordingly.
Refactor to_ag_ui so it now returns a ASGI compatible server based off
starlette. This makes it easier for users setup apps with minimal code.

Fix some invalid references missed in the package refactor for cli and
examples.

Made enums, and exceptions private to the package, so they are not
exposed in the public API.
Eliminate the use of match statement and dataclass(kw_only=True) to
ensure compatibility with Python 3.9.

Remove duplicate import of starlette outside try block, causing tests to
fail when it's not installed.
Disable the deprecation warning for model_fields in Mistral.
Remove invalid no cover comment from Mistral model flagged by
strict-no-cover check.
Add support for the new tool call returns and thinking parts to the
AG UI adapter. This eliminates the need to build a message snapshot
while sill providing the necessary information for the UI.
Allow the agent path to be specified when using to_ag_ui.
Reduce the log level for the request done message in the AG-UI adapter
to debug level.
Add missing `from __future__ import annotations` imports to ensure
ensure that only relevant type annotations are processed at runtime.

Fix incorrect import of `dataclass` in `args.py`.
Switch from additional tools to the new toolset system.
@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch 2 times, most recently from 22e65b1 to 1d132db Compare July 7, 2025 17:21
* Update examples to use `to_ag_ui`
* Use relative imports where appropriate
* Remove `path` and `logger` parameters from `to_ag_ui` and dependencies
* Clean up import failure messages
* Improve FastAGUI doc comment
* Use `PrefixedToolset` eliminating manual tool prefixing
* Use CallToolsNode to handle tool calls

Also make StateDeps more strict, to avoid runtime failures.
@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch from 1d132db to 58cbcb3 Compare July 7, 2025 17:31
Add some no cover pragmas in existing code to avoid coverage check
failure.

Correct raise to avoid resetting traceback in `AgentGraph`.
@stevenh stevenh force-pushed the feat/ag-ui-adapter-toolset branch from 1db1afb to 639d60b Compare July 7, 2025 18:04
@stevenh stevenh marked this pull request as ready for review July 7, 2025 18:27
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.

3 participants