Skip to content

Commit 943c7a8

Browse files
committed
move name formatting to model method
1 parent a132f26 commit 943c7a8

File tree

3 files changed

+43
-36
lines changed

3 files changed

+43
-36
lines changed

pydantic_ai_slim/pydantic_ai/models/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,11 @@ def model_name(self) -> str:
494494
"""The model name."""
495495
raise NotImplementedError()
496496

497+
@property
498+
def label(self) -> str:
499+
"""Human-friendly display label for the model."""
500+
return _format_model_label(self.model_name)
501+
497502
@classmethod
498503
def supported_builtin_tools(cls) -> frozenset[type[AbstractBuiltinTool]]:
499504
"""Return the set of builtin tool types this model class can handle.
@@ -728,6 +733,36 @@ def timestamp(self) -> datetime:
728733
"""
729734

730735

736+
def _format_model_label(model_name: str) -> str:
737+
"""Format model name for display in UI.
738+
739+
Handles common patterns:
740+
- gpt-5 -> GPT 5
741+
- claude-sonnet-4-5 -> Claude Sonnet 4.5
742+
- gemini-2.5-pro -> Gemini 2.5 Pro
743+
- meta-llama/llama-3-70b -> Llama 3 70b (OpenRouter style)
744+
"""
745+
# Handle OpenRouter-style names with / (e.g., meta-llama/llama-3-70b)
746+
if '/' in model_name:
747+
model_name = model_name.split('/')[-1]
748+
749+
parts = model_name.split('-')
750+
result: list[str] = []
751+
752+
for i, part in enumerate(parts):
753+
if i == 0 and part.lower() == 'gpt':
754+
result.append(part.upper())
755+
elif part.replace('.', '').isdigit():
756+
if result and result[-1].replace('.', '').isdigit():
757+
result[-1] = f'{result[-1]}.{part}'
758+
else:
759+
result.append(part)
760+
else:
761+
result.append(part.capitalize())
762+
763+
return ' '.join(result)
764+
765+
731766
def check_allow_model_requests() -> None:
732767
"""Check if model requests are allowed.
733768
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Web-based chat UI for Pydantic AI agents."""
22

3-
from .app import ModelsParam, create_web_app, format_model_display_name
3+
from .app import ModelsParam, create_web_app
44

55
__all__ = [
66
'create_web_app',
77
'ModelsParam',
8-
'format_model_display_name',
98
]

pydantic_ai_slim/pydantic_ai/ui/_web/app.py

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,40 +16,10 @@
1616
from pydantic_ai import Agent
1717
from pydantic_ai.builtin_tools import AbstractBuiltinTool
1818
from pydantic_ai.models import KnownModelName, Model, infer_model
19+
from pydantic_ai.toolsets import AbstractToolset
1920

2021
from .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-
5323
DEFAULT_UI_VERSION = '0.0.3'
5424
CDN_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

Comments
 (0)