Suggest common model providers in the pydanticai connection form (#76053)

The Default Model Provider field was a blank text box, so users had to know
pydantic-ai's provider names (`openai-chat` vs `openai`) by heart. The field's
schema now lists common ones as `examples`, which the connection form renders as
suggestions on a text input: any other provider pydantic-ai recognizes can still
be typed. An `enum` dropdown would reject providers pydantic-ai adds until a new
release of this provider. A test keeps every suggestion resolvable.

The suggestions ship with the provider, but the installed pydantic-ai can
recognize more. The field's help text and the connection docs now say the list
is common choices, not every provider that works. A test also keeps each
suggestion among the connection's declared external services, so the form never
suggests a vendor the docs and registry don't list.

* Keep the model provider help text accurate on the registry and share test lookups

The registry's connection builder shows a conn-field's description but not its
examples, so the help text now says any provider the installed pydantic-ai
recognizes works instead of describing suggestions. The docs paragraph no longer
repeats part of the list by hand. The suggestion checks reuse one connection-type
lookup and one prefix-to-module mapping, and the two pydantic-ai checks on this
field are one test.
This commit is contained in:
Kaxil Naik authored and GitHub committed 2026-11-04 01:41:02 +00:00
1 parent 1631eccfc2
commit f34cae8fa6
5 files changed
+71 -13

No files matched your search

@@ -91,6 +91,10 @@ Default Model Provider
``@task.llm_batch`` applies it the same way, and sends ``openai-chat`` models
to its OpenAI adapter.
The form suggests common providers as you type. The suggestions are not a full
list: any provider the installed pydantic-ai recognizes is accepted, including
ones a newer pydantic-ai adds.
Fallback Connections
Other connection IDs to fail over to, in order, while this provider is
unavailable. Stored in ``extra["fallback_conn_ids"]``. Entries may name any
+17 -1
View File
@@ -232,11 +232,27 @@ connection-types:
- 'null'
model_provider:
label: Default Model Provider
description: "Provider for model names without a provider: prefix (e.g. openai-chat)"
description: >
Provider for model names without a provider: prefix (e.g. openai-chat).
Any provider the installed pydantic-ai recognizes works.
schema:
type:
- string
- 'null'
# Suggestions on a free-text input, not an enum: an enum would reject providers a newer
# pydantic-ai adds until the next release of this provider.
examples:
- openai-chat
- openai
- anthropic
- ollama
- litellm
- openrouter
- groq
- mistral
- deepseek
- together
- fireworks
fallback_conn_ids:
label: Fallback Connections
description: "Connection IDs to fail over to, in order, while this provider is unavailable."
@@ -201,8 +201,23 @@ def get_provider_info():
},
"model_provider": {
"label": "Default Model Provider",
"description": "Provider for model names without a provider: prefix (e.g. openai-chat)",
"schema": {"type": ["string", "null"]},
"description": "Provider for model names without a provider: prefix (e.g. openai-chat). Any provider the installed pydantic-ai recognizes works.\n",
"schema": {
"type": ["string", "null"],
"examples": [
"openai-chat",
"openai",
"anthropic",
"ollama",
"litellm",
"openrouter",
"groq",
"mistral",
"deepseek",
"together",
"fireworks",
],
},
},
"fallback_conn_ids": {
"label": "Fallback Connections",
@@ -2325,16 +2325,18 @@ class TestPydanticAIVertexHook:
prefix = _extract_google_cloud_prefix(description)
_assert_prefix_is_known_provider(prefix)
def test_conn_fields_default_provider_description_example_is_valid_provider(self):
"""Drift tripwire for the example in the generic connection's Default Model Provider conn-field."""
def test_conn_fields_default_provider_example_and_suggestions_are_valid_providers(self):
"""Drift tripwire for the Default Model Provider description example and form suggestions."""
connection_types = get_provider_info()["connection-types"]
generic_conn_fields = next(
c["conn-fields"] for c in connection_types if c["connection-type"] == "pydanticai"
)
description = generic_conn_fields["model_provider"]["description"]
example = re.search(r"\(e\.g\. ([a-z0-9-]+)\)", description)
assert example is not None, description
_assert_prefix_is_known_provider(example.group(1))
field = generic_conn_fields["model_provider"]
example = re.search(r"\(e\.g\. ([a-z0-9-]+)\)", field["description"])
assert example is not None, field["description"]
assert field["schema"]["examples"]
for provider in [example.group(1), *field["schema"]["examples"]]:
_assert_prefix_is_known_provider(provider)
def test_conn_types_ui_field_behaviour_placeholder_prefix_is_valid_provider(self):
"""
@@ -267,10 +267,19 @@ def _provider_classes_in_module(mod) -> list[type]:
return provider_classes
def _find_pydanticai_external_services() -> list[str]:
def _find_pydanticai_connection_type() -> dict:
connection_types = get_provider_info()["connection-types"]
(pydanticai,) = (c for c in connection_types if c["connection-type"] == "pydanticai")
return pydanticai["external-services"]
return pydanticai
def _find_pydanticai_external_services() -> list[str]:
return _find_pydanticai_connection_type()["external-services"]
def _module_for_prefix(prefix: str) -> str:
"""The pydantic-ai provider module behind a model-name prefix (``openai-chat`` -> ``openai``)."""
return PREFIX_ALIASES.get(prefix, prefix.replace("-", "_"))
class TestConnectionTypeExternalServices:
@@ -281,6 +290,18 @@ class TestConnectionTypeExternalServices:
)
class TestModelProviderSuggestions:
def test_every_suggestion_is_a_declared_external_service(self):
"""The form must not suggest a vendor the docs and registry don't list for this connection."""
pydanticai = _find_pydanticai_connection_type()
suggestions = pydanticai["conn-fields"]["model_provider"]["schema"]["examples"]
declared = set(pydanticai["external-services"])
undeclared = [s for s in suggestions if LABELS.get(_module_for_prefix(s)) not in declared]
assert not undeclared, f"suggested providers missing from external-services: {undeclared}"
class TestPydanticAIExternalServicesDrift:
def test_every_installed_vendor_is_declared(self):
"""
@@ -381,7 +402,7 @@ class TestPydanticAIExternalServicesDrift:
if ":" not in name:
continue # the bare "test" entry, pydantic-ai's built-in keyless TestModel
prefix = name.removeprefix("gateway/").split(":", 1)[0]
module = PREFIX_ALIASES.get(prefix, prefix.replace("-", "_"))
module = _module_for_prefix(prefix)
assert module in labelled, f"model prefix {prefix!r} (from {name!r}) has no label"
def test_gateway_note_vendors_match_known_model_names(self):
@@ -396,7 +417,7 @@ class TestPydanticAIExternalServicesDrift:
for name in known_model_names()
if name.startswith("gateway/")
}
modules = {PREFIX_ALIASES.get(p, p.replace("-", "_")) for p in gateway_prefixes}
modules = {_module_for_prefix(p) for p in gateway_prefixes}
derived = {LABELS.get(m, f"<unlabelled:{m}>") for m in modules}
assert derived == GATEWAY_NOTE_VENDOR_LABELS, (