-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add Agent.to_web() method and web chat UI #3456
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
base: main
Are you sure you want to change the base?
Conversation
pydantic_ai_slim/pydantic_ai/_cli.py
Outdated
| args = parser.parse_args(args_list) | ||
|
|
||
| # Handle web subcommand | ||
| if args.command == 'web': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should be --web so it doesn't conflict with the prompt arg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeahp, changed that now
|
|
||
| self._get_toolset().apply(_set_sampling_model) | ||
|
|
||
| def to_web(self) -> Any: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're gonna need some args here -- have a look at the to_a2a and to_ag_ui methods. Not saying we need all of those args, but some may be useful
|
I just pushed an update to this removing the AST aspect and (hopefully) fixing the tests so they pass in CI haven't addressed the comments yet so it isn't reviewable yet |
- consolidate UI agent-options - fix cli commands - cache UI on client and server - offer choosable UI versions
- rename config vars to be nicer
pydantic_ai_slim/pydantic_ai/_cli.py
Outdated
| args = parser.parse_args(args_list) | ||
|
|
||
| # Handle web subcommand | ||
| if args.command == 'web': |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeahp, changed that now
|
|
||
| @app.get('/') | ||
| @app.get('/{id}') | ||
| async def index(request: Request, version: str | None = Query(None)): # pyright: ignore[reportUnusedFunction] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I understand the need for a version arg. An older version than the default is worse, and a newer version may not work with the API data model. I think they should develop in tandem, with a pinned version on this side.
What we could do is add a frontend_url argument to the to_web method to allow the entire thing to be overridden easily?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
frontend_url remains relevant, I haven't included logic for this
Regenerated uv.lock to resolve conflicts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
- resolve model aliases - test builtin id type sync
| with TestClient(app) as client: | ||
| response = client.get('/') | ||
| assert response.status_code == 200 | ||
| assert response.headers['content-type'] == 'text/html; charset=utf-8' | ||
| assert 'cache-control' in response.headers | ||
| assert response.headers['cache-control'] == 'public, max-age=3600' | ||
| assert len(response.content) > 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can stay as assertions right?
| model_id = f'{model.system}:{model.model_name}' | ||
| display_name = label or format_model_display_name(model.model_name) | ||
| model_supported_tools = model.supported_builtin_tools() | ||
| supported_tool_ids = list(model_supported_tools & builtin_tool_ids) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I suggested putting this in Model.profile so that we can always trust model.profile.supported_builtin_tools. Then we can also override it on OpenAIChatModel to remove web_search if appropriate
|
been having some fun testing the combinations, for anyone who wants to try them: combinations to try out the agent UI1. Generic agent with explicit modelsource .env && uv run --with . clai web -m openai:gpt-4.1-mini 2. Agent with model (uses agent's configured model)source .env && uv run --with . clai web -a clai.clai._test_agents:chat_agent 3. Agent without model + CLI modelsource .env && uv run --with . clai web -a clai.clai._test_agents:agent_no_model -m anthropic:claude-haiku-4-5 4. Multiple models (first is default, others are options in UI)source .env && uv run --with . clai web -m google-gla:gemini-2.5-flash-lite -m openai:gpt-4.1-mini -m anthropic:claude-haiku-4-5 5. Override agent's model with different onesource .env && uv run --with . clai web -a clai.clai._test_agents:chat_agent -m google-gla:gemini-2.5-flash-lite 6. Single tool - web search enabledsource .env && uv run --with . clai web -m openai:gpt-4.1-mini -t web_search 7. Multiple toolssource .env && uv run --with . clai web -m anthropic:claude-haiku-4-5 -t web_search -t code_execution 8. Agent with builtin_tools configuredsource .env && uv run --with . clai web -a clai.clai._test_agents:agent_with_tools 9. Roleplay waiter instructionssource .env && uv run --with . clai web -m openai:gpt-4.1-mini -i "You're a grumpy Parisian waiter at a trendy bistro frequented by American tourists. You're secretly proud of the food but act annoyed by every question. Pepper your 10. With MCP configsource .env && uv run --with . clai web -m google-gla:gemini-2.5-flash-lite --mcp mcp_servers.json 11. ERROR: No model, no agent (should error)source .env && uv run --with . clai web 12. ERROR: Agent without model, no CLI model (should error)source .env && uv run --with . clai web -a clai.clai._test_agents:agent_no_model 13. WARNING: Unknown tool (should warn)source .env && uv run --with . clai web -m openai:gpt-4.1-mini -t definitely_not_a_real_tool Some fun alternative instructions you could swap in for #9: Pirate customer servicesource .env && uv run --with . clai web -m anthropic:claude-haiku-4-5 -i "You're a pirate who somehow ended up working tech support. Answer questions helpfully but can't stop using nautical terms and saying 'arrr'." Overly enthusiastic fitness coachsource .env && uv run --with . clai web -m google-gla:gemini-2.5-flash-lite -i "You're an extremely enthusiastic fitness coach who relates EVERYTHING back to exercise and healthy living. Even coding questions get workout analogies." Noir detectivesource .env && uv run --with . clai web -m openai:gpt-4.1-mini -i "You're a 1940s noir detective narrating your investigation. Every question is a 'case' and every answer is delivered in hard-boiled prose with lots of rain metaphors."
When we publish it should naturally just run as |
web-based chat interface for Pydantic AI agents
pydantic_ai.ui.webAgent.to_web()fastapi
app = create_chat_app(agent)the following endpoints come preconfigured:
GET /and/:id- serve the chat UIPOST /api/chat- Main chat endpoint using VercelAIAdapterGET /api/configure- Returns available models and builtin toolsGET /api/health- Health checkoptions and example
NOTE: the module for options is currently
pydantic_ai.ui.web.pre-configured model options:
anthropic:claude-sonnet-4-5openai-responses:gpt-5google-gla:gemini-2.5-prosupported builtin tools:
web_searchcode_executionimage_generationtesting
tests/test_ui_web.pynotes
@pydantic/[email protected]clai webcommand to launch from the CLI (as inuvx pydantic-workwithout the whole URL magic)docs/ui/to_web.md? I'd also reference this indocs/ui/overview.mdanddocs/agents.mdEDIT: if you try it out it's worth noting that the current hosted UI doesn't handle
ErrorChunks, so you will get no spinner and no response when there's a model-level error and fastapi will return a 200 any way.This will happen for instance when you use a model for which you don't have a valid API key in your environment
I opened a PR for the error chunks here pydantic/ai-chat-ui#4.
Closes #3295