1616from pydantic_ai import Agent
1717from pydantic_ai .builtin_tools import AbstractBuiltinTool
1818from pydantic_ai .models import KnownModelName , Model , infer_model
19+ from pydantic_ai .toolsets import AbstractToolset
1920
2021from .api import ModelInfo , add_api_routes
2122
22-
23- def format_model_display_name (model_name : str ) -> str :
24- """Format model name for display in UI.
25-
26- Handles common patterns:
27- - gpt-5 -> GPT 5
28- - claude-sonnet-4-5 -> Claude Sonnet 4.5
29- - gemini-2.5-pro -> Gemini 2.5 Pro
30- - meta-llama/llama-3-70b -> Llama 3 70b (OpenRouter style)
31- """
32- # Handle OpenRouter-style names with / (e.g., meta-llama/llama-3-70b)
33- if '/' in model_name :
34- model_name = model_name .split ('/' )[- 1 ]
35-
36- parts = model_name .split ('-' )
37- result : list [str ] = []
38-
39- for i , part in enumerate (parts ):
40- if i == 0 and part .lower () == 'gpt' :
41- result .append (part .upper ())
42- elif part .replace ('.' , '' ).isdigit ():
43- if result and result [- 1 ].replace ('.' , '' ).isdigit ():
44- result [- 1 ] = f'{ result [- 1 ]} .{ part } '
45- else :
46- result .append (part )
47- else :
48- result .append (part .capitalize ())
49-
50- return ' ' .join (result )
51-
52-
5323DEFAULT_UI_VERSION = '0.0.3'
5424CDN_URL_TEMPLATE = 'https://cdn.jsdelivr.net/npm/@pydantic/ai-chat-ui@{version}/dist/index.html'
5525
@@ -87,7 +57,7 @@ def _resolve_models(
8757 for label , model_ref in items :
8858 model = infer_model (model_ref )
8959 model_id = f'{ model .system } :{ model .model_name } '
90- display_name = label or format_model_display_name ( model .model_name )
60+ display_name = label or model .label
9161 model_supported_tools = model .supported_builtin_tools ()
9262 supported_tool_ids = [t .kind for t in (model_supported_tools & builtin_tool_types )]
9363 result .append (ModelInfo (id = model_id , name = display_name , builtin_tools = supported_tool_ids ))
@@ -137,6 +107,7 @@ def create_web_app(
137107 agent : Agent [AgentDepsT , OutputDataT ],
138108 models : ModelsParam = None ,
139109 builtin_tools : list [AbstractBuiltinTool ] | None = None ,
110+ toolsets : Sequence [AbstractToolset [AgentDepsT ]] | None = None ,
140111) -> Starlette :
141112 """Create a Starlette app that serves a web chat UI for the given agent.
142113
@@ -145,9 +116,11 @@ def create_web_app(
145116 models: Models to make available in the UI. Can be:
146117 - A sequence of model names/instances (e.g., `['openai:gpt-5', 'anthropic:claude-sonnet-4-5']`)
147118 - A dict mapping display labels to model names/instances
148- (e.g., `{'GPT 5': 'openai:gpt-5', 'Claude': 'anthropic:claude-sonnet-4-5'}`)
119+ (e.g., `{'GPT 5': 'openai:gpt-5', 'Claude': 'anthropic:claude-sonnet-4-5'}`)
149120 If not provided, the UI will have no model options.
150121 builtin_tools: Optional list of builtin tools. If not provided, no tools will be available.
122+ toolsets: Optional list of toolsets (e.g., MCP servers). These provide additional tools
123+ that work with any model.
151124
152125 Returns:
153126 A configured Starlette application ready to be served
@@ -158,7 +131,7 @@ def create_web_app(
158131
159132 app .state .agent = agent
160133
161- add_api_routes (app , models = resolved_models , builtin_tools = builtin_tools )
134+ add_api_routes (app , models = resolved_models , builtin_tools = builtin_tools , toolsets = toolsets )
162135
163136 async def index (request : Request ) -> Response :
164137 """Serve the chat UI from filesystem cache or CDN."""
0 commit comments