docs/web.md
Pydantic AI includes a built-in web chat interface that you can use to interact with your agents through a browser.
For CLI usage with clai web, see the CLI - Web Chat UI documentation.
!!! note The web UI is meant for local development and debugging. In production, you can use one of the UI Event Stream integrations to connect your agent to a custom frontend.
Install the web extra (installs Starlette and Uvicorn):
pip/uv-add 'pydantic-ai-slim[web]'
Create a web app from an agent instance using [Agent.to_web()][pydantic_ai.agent.Agent.to_web]:
from pydantic_ai import Agent
agent = Agent('openai:gpt-5.2', instructions='You are a helpful assistant.')
@agent.tool_plain
def get_weather(city: str) -> str:
return f'The weather in {city} is sunny'
app = agent.to_web()
Run the app with any ASGI server:
uvicorn my_module:app --host 127.0.0.1 --port 7932
You can specify additional models to make available in the UI. Models can be provided as a list of model names/instances or a dictionary mapping display labels to model names/instances.
from pydantic_ai import Agent
from pydantic_ai.models.anthropic import AnthropicModel
# Model with custom configuration
anthropic_model = AnthropicModel('claude-sonnet-4-5')
agent = Agent('openai:gpt-5.2')
app = agent.to_web(
models=['openai:gpt-5.2', anthropic_model],
)
# Or with custom display labels
app = agent.to_web(
models={'GPT 5.2': 'openai:gpt-5.2', 'Claude': anthropic_model},
)
Configure native tools on the agent with capabilities=[NativeTool(...)] to expose them as options in the UI (shown only for models that support each tool):
from pydantic_ai import Agent
from pydantic_ai.capabilities import NativeTool
from pydantic_ai.native_tools import CodeExecutionTool, WebSearchTool
agent = Agent(
'openai:gpt-5.2',
capabilities=[NativeTool(CodeExecutionTool()), NativeTool(WebSearchTool())],
)
app = agent.to_web(models=['anthropic:claude-sonnet-4-6'])
!!! note "Memory Tool"
The memory native tool is not supported via to_web() or clai web. If your agent needs memory, configure the [MemoryTool][pydantic_ai.native_tools.MemoryTool] directly on the agent at construction time.
You can pass extra instructions that will be included in each agent run:
from pydantic_ai import Agent
agent = Agent('openai:gpt-5.2')
app = agent.to_web(instructions='Always respond in a friendly tone.')
Tools that require approval are surfaced in the UI as approve/reject prompts: when the agent calls such a tool, the UI renders the pending call and lets you approve or deny it before the run continues. This works out of the box — no extra configuration is needed.
!!! warning
The chat endpoint executes tool approvals relayed by the client, including for tools marked requires_approval=True. The server trusts the approval decision it receives, so any client that can reach the endpoint can approve any pending call.
Binding to localhost is not on its own a security boundary here: a web page open in the same browser can also reach `http://127.0.0.1:7932`. The chat endpoint therefore only accepts `Content-Type: application/json`, which a browser cannot send cross-origin without a preflight that the server refuses. Treat approval prompts as a convenience for the developer driving the UI rather than an authorization control, and don't expose `to_web()` to untrusted clients without putting authentication in front of it.
The web UI app uses the following routes which should not be overwritten:
/ and /{id} - Serves the chat UI/api/chat - Chat endpoint (POST, OPTIONS). Requires Content-Type: application/json; other content types are rejected with 415./api/configure - Frontend configuration (GET)/api/health - Health check (GET)The app cannot currently be mounted at a subpath (e.g., /chat) because the UI expects these routes at the root. You can add additional routes to the app, but avoid conflicts with these reserved paths.
By default, the web UI is fetched from a CDN and cached locally. You can provide html_source to override this for offline usage or enterprise environments.
The default UI build is split across many files: index.html references a stylesheet and, at runtime,
lazily imports chunks for syntax highlighting, diagrams and math. Those references point back at the
CDN, so downloading index.html alone gives you a page that boots and then fails to render as soon as
a code block or an equation appears.
Use the offline build instead — a single self-contained file with every chunk, font and icon inlined, so it needs no network access beyond your own server:
from pydantic_ai.ui import OFFLINE_HTML_URL
print(OFFLINE_HTML_URL) # Use this URL to download the self-contained UI HTML file
#> https://cdn.jsdelivr.net/npm/@pydantic/[email protected]/offline/index.html
Download it once from a machine that has internet access, then move it into the air-gapped environment:
curl -o ~/pydantic-ai-ui.html <chat_ui_url>
Then use html_source to point to your local file or custom URL:
from pydantic_ai import Agent
agent = Agent('openai:gpt-5.2')
# Use a local file (e.g., for offline usage)
app = agent.to_web(html_source='~/pydantic-ai-ui.html')
# Or use a custom URL (e.g., for enterprise environments)
app = agent.to_web(html_source='https://cdn.example.com/ui/index.html')
The offline file is around 16 MB. That is not extra weight so much as relocated weight — the default
build ships the same assets across 400-odd files that the browser fetches from the CDN on demand,
where the offline build front-loads all of them into the first request. The default to_web() path
is unchanged and still uses the split build:
from pydantic_ai.ui import DEFAULT_HTML_URL
print(DEFAULT_HTML_URL)
#> https://cdn.jsdelivr.net/npm/@pydantic/[email protected]/dist/index.html