mirror of
https://github.com/apache/airflow.git
synced 2026-11-04 07:12:48 +00:00
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:
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
|
||||
|
||||
@@ -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, (
|
||||
|
||||
Reference in new issue
Block a user