Reject unknown chat model providers and rename gemini to google
This commit is contained in:
parent
d0c66eef8f
commit
ad100ecd4d
5 changed files with 75 additions and 12 deletions
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
### Changed
|
||||
|
||||
- Chat model provider `gemini` renamed to `google`, matching pydantic-ai. Update `provider: gemini` to `provider: google`.
|
||||
|
||||
### Added
|
||||
|
||||
- `lancedb.databases` configures a named set of local or remote databases.
|
||||
|
|
@ -33,6 +37,7 @@
|
|||
- Inspector search results mark truncated previews with an ellipsis.
|
||||
- Document titles, URIs, headings and database names render as text, not Rich
|
||||
markup, in `search` output, chat citations and the chat document filter.
|
||||
- An unrecognized chat model provider raises `Unknown model provider '<name>'` instead of reaching pydantic-ai as a `provider:name` string, and outranks the `api_key` check, so an unusable provider is no longer reported as a missing vendor environment variable.
|
||||
|
||||
## [0.78.0] - 2026-08-24
|
||||
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ qa:
|
|||
|
||||
Same mechanism, opposite direction. Without `extra_body` the Gemma-4 chat template defaults to non-thinking and dumps a verbose answer straight into `content`. With it on, vLLM (started with `--reasoning-parser`) populates the parsed `reasoning` field and leaves `content` as the concise final answer.
|
||||
|
||||
**Provider support:** honored by openai, ollama, anthropic, and groq via pydantic-ai's `ModelSettings.extra_body`. Silently ignored by gemini and bedrock.
|
||||
**Provider support:** honored by openai, ollama, anthropic, and groq via pydantic-ai's `ModelSettings.extra_body`. Silently ignored by google and bedrock.
|
||||
|
||||
## Embedding Providers
|
||||
|
||||
|
|
@ -393,7 +393,7 @@ Any provider supported by Pydantic AI can be used. Examples:
|
|||
# Google Gemini
|
||||
qa:
|
||||
model:
|
||||
provider: gemini
|
||||
provider: google
|
||||
name: gemini-1.5-flash
|
||||
|
||||
# Groq
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class ModelConfig(ConfigModel):
|
|||
`ModelSettings.extra_body`. Provider-side escape hatch for
|
||||
keys haiku.rag doesn't model explicitly (e.g. vLLM's
|
||||
`chat_template_kwargs.enable_thinking: false` for Qwen3).
|
||||
Honored by openai/ollama/anthropic/groq; ignored by gemini/bedrock.
|
||||
Honored by openai/ollama/anthropic/groq; ignored by google/bedrock.
|
||||
"""
|
||||
|
||||
provider: str = "ollama"
|
||||
|
|
|
|||
|
|
@ -62,6 +62,32 @@ def check_api_key_supported(
|
|||
)
|
||||
|
||||
|
||||
def _check_provider_known(provider: str) -> None:
|
||||
"""Reject a chat provider pydantic-ai cannot resolve.
|
||||
|
||||
Providers we do not branch on reach pydantic-ai as a `provider:name` string,
|
||||
so without this an unusable name fails deep inside pydantic-ai with nothing
|
||||
naming the config key it came from. Asking pydantic-ai's own resolver rather
|
||||
than keeping a list here leaves a newly added provider working with no
|
||||
release of ours.
|
||||
"""
|
||||
from pydantic_ai.providers import infer_provider_class
|
||||
|
||||
try:
|
||||
infer_provider_class(provider)
|
||||
except ImportError:
|
||||
# pydantic-ai knows the name, its SDK is just not installed here. That
|
||||
# failure names the extra to install, so leave it to be raised in place.
|
||||
return
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
f"Unknown model provider '{provider}'. See "
|
||||
"https://ai.pydantic.dev/models/ for the providers pydantic-ai "
|
||||
"supports. An OpenAI-compatible server (vLLM, sglang, LM Studio) "
|
||||
"uses provider 'openai' with base_url."
|
||||
) from None
|
||||
|
||||
|
||||
def cosine_similarity(vec1: list[float], vec2: list[float]) -> float:
|
||||
"""Compute cosine similarity between two vectors."""
|
||||
dot_product = sum(a * b for a, b in zip(vec1, vec2))
|
||||
|
|
@ -169,6 +195,7 @@ def get_model(
|
|||
|
||||
provider = model_config.provider
|
||||
model = model_config.name
|
||||
_check_provider_known(provider)
|
||||
check_api_key_supported(model_config, {"openai", "ollama"})
|
||||
|
||||
if provider == "ollama":
|
||||
|
|
@ -261,7 +288,7 @@ def get_model(
|
|||
|
||||
return AnthropicModel(model_name=model, settings=anthropic_settings)
|
||||
|
||||
elif provider == "gemini":
|
||||
elif provider == "google":
|
||||
from pydantic_ai.models.google import GoogleModel
|
||||
|
||||
return GoogleModel(
|
||||
|
|
|
|||
|
|
@ -390,23 +390,23 @@ def test_get_model_anthropic_thinking_off_disables_adaptive_models():
|
|||
|
||||
|
||||
@pytest.mark.skipif(not HAS_GOOGLE, reason="Google not installed")
|
||||
def test_get_model_gemini():
|
||||
"""Test get_model returns GoogleModel for Gemini."""
|
||||
def test_get_model_google():
|
||||
"""Test get_model returns GoogleModel for Google."""
|
||||
from pydantic_ai.models.google import GoogleModel
|
||||
|
||||
model_config = ModelConfig(provider="gemini", name="gemini-2.0-flash-exp")
|
||||
model_config = ModelConfig(provider="google", name="gemini-2.0-flash-exp")
|
||||
result = get_model(model_config)
|
||||
assert isinstance(result, GoogleModel)
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_GOOGLE, reason="Google not installed")
|
||||
@pytest.mark.parametrize("enable_thinking", [True, False])
|
||||
def test_get_model_gemini_with_thinking(enable_thinking):
|
||||
"""Test get_model configures thinking for Gemini."""
|
||||
def test_get_model_google_with_thinking(enable_thinking):
|
||||
"""Test get_model configures thinking for Google."""
|
||||
from pydantic_ai.models.google import GoogleModel
|
||||
|
||||
model_config = ModelConfig(
|
||||
provider="gemini",
|
||||
provider="google",
|
||||
name="gemini-2.0-flash-thinking-exp",
|
||||
enable_thinking=enable_thinking,
|
||||
)
|
||||
|
|
@ -536,14 +536,45 @@ def test_get_model_bedrock_rejects_mantle_only_model():
|
|||
get_model(model_config)
|
||||
|
||||
|
||||
def test_get_model_unknown_provider():
|
||||
"""Test get_model returns string format for unknown providers."""
|
||||
def test_get_model_passthrough_for_unbranched_provider():
|
||||
"""A provider pydantic-ai knows but we do not branch on passes through as a
|
||||
string, so a new pydantic-ai provider needs no haiku.rag release."""
|
||||
model_config = ModelConfig(provider="mistral", name="mistral-large-latest")
|
||||
result = get_model(model_config)
|
||||
assert isinstance(result, str)
|
||||
assert result == "mistral:mistral-large-latest"
|
||||
|
||||
|
||||
def test_get_model_accepts_provider_whose_sdk_is_missing(monkeypatch):
|
||||
"""A missing vendor SDK is not an unknown provider: that ImportError names
|
||||
the extra to install, so it must reach the caller unchanged.
|
||||
|
||||
Uses a provider whose SDK *is* installed, so the patch is what produces the
|
||||
ImportError rather than the environment.
|
||||
"""
|
||||
import pydantic_ai.providers
|
||||
|
||||
def _missing_sdk(provider: str):
|
||||
raise ImportError("Please install the `cohere` package")
|
||||
|
||||
monkeypatch.setattr(pydantic_ai.providers, "infer_provider_class", _missing_sdk)
|
||||
result = get_model(ModelConfig(provider="cohere", name="command-r"))
|
||||
|
||||
assert result == "cohere:command-r"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("provider", ["nonsense", "vllm", "gemini"])
|
||||
def test_get_model_rejects_unknown_provider(provider):
|
||||
"""An unknown provider is named here rather than passed through to fail
|
||||
inside pydantic-ai, where nothing identifies the config it came from.
|
||||
|
||||
`vllm` and `gemini` get no special case: both were haiku.rag's own
|
||||
vocabulary, and both fail the same way as a typo.
|
||||
"""
|
||||
with pytest.raises(ValueError, match=provider):
|
||||
get_model(ModelConfig(provider=provider, name="whatever"))
|
||||
|
||||
|
||||
def test_get_package_versions():
|
||||
"""Test get_package_versions returns expected keys."""
|
||||
from haiku.rag.utils import get_package_versions
|
||||
|
|
|
|||
Loading…
Reference in a new issue