Files
567-labs--instructor/tests/coverage/test_auto_client_tail_coverage.py
wehub-resource-sync 97e91a83f3
Ruff / Ruff (push) Has been cancelled
Test / Core Tests (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.10) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.11) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.12) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.13) (push) Has been cancelled
Test / Offline Coverage Tests (Python 3.9) (push) Has been cancelled
Test / Full Coverage (Python 3.11) (push) Has been cancelled
Test / Core Provider Tests (OpenAI) (push) Has been cancelled
Test / Core Provider Tests (Anthropic) (push) Has been cancelled
Test / Core Provider Tests (Google) (push) Has been cancelled
Test / Core Provider Tests (Other) (push) Has been cancelled
Test / Anthropic Tests (push) Has been cancelled
Test / Gemini Tests (push) Has been cancelled
Test / Google GenAI Tests (push) Has been cancelled
Test / Vertex AI Tests (push) Has been cancelled
Test / OpenAI Tests (push) Has been cancelled
Test / Writer Tests (push) Has been cancelled
Test / Auto Client Tests (push) Has been cancelled
ty / type-check (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:38 +08:00

661 lines
21 KiB
Python

from __future__ import annotations
import builtins
import importlib
import logging
import sys
from collections.abc import Iterator
from contextlib import AbstractContextManager, nullcontext
from functools import partial
from typing import Any
import pytest
from instructor import Mode
from instructor.v2 import auto_client
from instructor.v2.core.errors import ConfigurationError
from tests.coverage.client_cleanup import (
clear_proxy_environment,
close_idle_event_loop,
close_provider_client,
ignore_fireworks_pydantic_warning,
)
def provider_info(provider: str) -> dict[str, str]:
return {"provider": provider, "operation": "initialize"}
def expected_provider_deprecation(provider: str) -> AbstractContextManager[Any]:
if provider in {"vertexai", "generative-ai"}:
return pytest.warns(
DeprecationWarning, match=rf"The '{provider}' provider is deprecated\."
)
return nullcontext()
@pytest.fixture(autouse=True)
def isolated_provider_environment(monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
clear_proxy_environment(monkeypatch)
yield
close_idle_event_loop()
@pytest.mark.parametrize(
"provider,builder,sdk_module,sync_name,async_name,factory_module,factory_name",
[
(
"cerebras",
auto_client._build_cerebras,
"cerebras.cloud.sdk",
"Cerebras",
"AsyncCerebras",
"instructor.v2.providers.cerebras.client",
"from_cerebras",
),
(
"fireworks",
auto_client._build_fireworks,
"fireworks.client",
"Fireworks",
"AsyncFireworks",
"instructor.v2.providers.fireworks.client",
"from_fireworks",
),
],
)
@pytest.mark.parametrize("async_client", [False, True])
def test_cloud_sdk_builders_forward_real_client_model_and_options(
monkeypatch: pytest.MonkeyPatch,
request: pytest.FixtureRequest,
provider: str,
builder: Any,
sdk_module: str,
sync_name: str,
async_name: str,
factory_module: str,
factory_name: str,
async_client: bool,
) -> None:
with ignore_fireworks_pydantic_warning():
sdk = importlib.import_module(sdk_module)
factory = importlib.import_module(factory_module)
seen: dict[str, Any] = {}
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
request.addfinalizer(
partial(close_provider_client, client, async_client=async_client)
)
seen["client"] = client
seen["kwargs"] = kwargs
return {"provider": provider, **kwargs}
monkeypatch.setattr(factory, factory_name, capture)
result = builder(
provider=provider,
model_name="llama-test",
async_client=async_client,
mode=Mode.JSON,
api_key="test-key",
kwargs={"max_tokens": 17},
provider_info=provider_info(provider),
)
expected_type = getattr(sdk, async_name if async_client else sync_name)
assert isinstance(seen["client"], expected_type)
if provider == "fireworks":
assert seen["client"]._client_v1.api_key == "test-key"
else:
assert seen["client"].api_key == "test-key"
assert seen["kwargs"] == {"model": "llama-test", "max_tokens": 17}
assert result == {"provider": provider, "model": "llama-test", "max_tokens": 17}
@pytest.mark.parametrize("async_client", [False, True])
def test_vertexai_builder_initializes_project_and_forwards_mode(
monkeypatch: pytest.MonkeyPatch, async_client: bool
) -> None:
import instructor
import vertexai
import vertexai.generative_models as generative_models
from google.auth.credentials import AnonymousCredentials
credentials = AnonymousCredentials()
initialized: dict[str, Any] = {}
seen: dict[str, Any] = {}
vertex_model = object()
model_names: list[str] = []
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
seen["client"] = client
seen["kwargs"] = kwargs
return kwargs
def initialize(**kwargs: Any) -> None:
initialized.update(kwargs)
def create_model(model_name: str) -> object:
model_names.append(model_name)
return vertex_model
monkeypatch.setattr(vertexai, "init", initialize)
monkeypatch.setattr(generative_models, "GenerativeModel", create_model)
monkeypatch.setattr(instructor, "from_vertexai", capture)
with expected_provider_deprecation("vertexai"):
result = auto_client._build_vertexai(
provider="vertexai",
model_name="gemini-test",
async_client=async_client,
mode=Mode.JSON,
api_key=None,
kwargs={
"project": "project-test",
"location": "europe-west1",
"credentials": credentials,
"max_tokens": 23,
},
provider_info=provider_info("vertexai"),
)
assert initialized == {
"project": "project-test",
"location": "europe-west1",
"credentials": credentials,
}
assert model_names == ["gemini-test"]
assert seen["client"] is vertex_model
assert result == {"use_async": async_client, "mode": Mode.JSON, "max_tokens": 23}
def test_vertexai_builder_requires_project(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.delenv("GOOGLE_CLOUD_PROJECT", raising=False)
with expected_provider_deprecation("vertexai"):
with pytest.raises(ValueError, match="Project ID is required for Vertex AI"):
auto_client._build_vertexai(
provider="vertexai",
model_name="gemini-test",
async_client=False,
mode=None,
api_key=None,
kwargs={},
provider_info=provider_info("vertexai"),
)
@pytest.mark.parametrize("async_client", [False, True])
def test_generative_ai_builder_uses_environment_key_and_forwards_options(
monkeypatch: pytest.MonkeyPatch, async_client: bool
) -> None:
from google import genai
import instructor.v2.providers.genai.client as genai_client
seen: dict[str, Any] = {}
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
seen["client"] = client
seen["kwargs"] = kwargs
return kwargs
monkeypatch.setenv("GOOGLE_API_KEY", "environment-key")
monkeypatch.setattr(genai_client, "from_genai", capture)
with expected_provider_deprecation("generative-ai"):
result = auto_client._build_generative_ai(
provider="generative-ai",
model_name="gemini-test",
async_client=async_client,
mode=Mode.JSON,
api_key=None,
kwargs={"max_tokens": 31},
provider_info=provider_info("generative-ai"),
)
assert isinstance(seen["client"], genai.Client)
assert seen["client"]._api_client.api_key == "environment-key"
expected = {"model": "gemini-test", "mode": Mode.JSON, "max_tokens": 31}
if async_client:
expected["use_async"] = True
assert result == expected
@pytest.mark.parametrize(
"provider,builder,factory_module,factory_name,environment_key,default_url",
[
(
"deepseek",
auto_client._build_deepseek,
"instructor.v2.providers.openai.client",
"from_deepseek",
"DEEPSEEK_API_KEY",
"https://api.deepseek.com",
),
(
"openrouter",
auto_client._build_openrouter,
"instructor.v2.providers.openrouter.client",
"from_openrouter",
"OPENROUTER_API_KEY",
"https://openrouter.ai/api/v1/",
),
],
)
@pytest.mark.parametrize("async_client", [False, True])
def test_openai_compatible_tail_builders_use_environment_and_custom_url(
monkeypatch: pytest.MonkeyPatch,
request: pytest.FixtureRequest,
provider: str,
builder: Any,
factory_module: str,
factory_name: str,
environment_key: str,
default_url: str,
async_client: bool,
) -> None:
import openai
factory = importlib.import_module(factory_module)
calls: list[tuple[object, dict[str, Any]]] = []
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
request.addfinalizer(
partial(close_provider_client, client, async_client=async_client)
)
calls.append((client, kwargs))
return kwargs
monkeypatch.setenv(environment_key, "environment-key")
monkeypatch.setattr(factory, factory_name, capture)
default_result = builder(
provider=provider,
model_name="model-test",
async_client=async_client,
mode=None,
api_key=None,
kwargs={"max_tokens": 41},
provider_info=provider_info(provider),
)
custom_result = builder(
provider=provider,
model_name="model-test",
async_client=async_client,
mode=Mode.JSON,
api_key="explicit-key",
kwargs={"base_url": "https://compatible.invalid/v1", "max_tokens": 43},
provider_info=provider_info(provider),
)
expected_type = openai.AsyncOpenAI if async_client else openai.OpenAI
assert isinstance(calls[0][0], expected_type)
assert calls[0][0].api_key == "environment-key"
assert str(calls[0][0].base_url) == default_url
assert default_result == {
"model": "model-test",
"mode": Mode.TOOLS,
"max_tokens": 41,
}
assert isinstance(calls[1][0], expected_type)
assert calls[1][0].api_key == "explicit-key"
assert str(calls[1][0].base_url) == "https://compatible.invalid/v1/"
assert custom_result == {
"model": "model-test",
"mode": Mode.JSON,
"max_tokens": 43,
}
@pytest.mark.parametrize(
"provider,builder,environment_key",
[
("deepseek", auto_client._build_deepseek, "DEEPSEEK_API_KEY"),
("openrouter", auto_client._build_openrouter, "OPENROUTER_API_KEY"),
],
)
def test_openai_compatible_tail_builders_require_api_key(
monkeypatch: pytest.MonkeyPatch, provider: str, builder: Any, environment_key: str
) -> None:
monkeypatch.delenv(environment_key, raising=False)
with pytest.raises(ConfigurationError, match=f"{environment_key} is not set"):
builder(
provider=provider,
model_name="model-test",
async_client=False,
mode=None,
api_key=None,
kwargs={},
provider_info=provider_info(provider),
)
@pytest.mark.parametrize("async_client", [False, True])
def test_ollama_builder_forwards_mode_url_and_real_client(
monkeypatch: pytest.MonkeyPatch,
request: pytest.FixtureRequest,
async_client: bool,
) -> None:
import openai
import instructor.v2.providers.openai.client as openai_client
calls: list[tuple[object, dict[str, Any]]] = []
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
request.addfinalizer(
partial(close_provider_client, client, async_client=async_client)
)
calls.append((client, kwargs))
return kwargs
monkeypatch.setattr(openai_client, "from_openai", capture)
tools_result = auto_client._build_ollama(
provider="ollama",
model_name="llama3.1:8b",
async_client=async_client,
mode=None,
api_key=None,
kwargs={"base_url": "http://localhost:22444/v1", "max_tokens": 47},
provider_info=provider_info("ollama"),
)
json_result = auto_client._build_ollama(
provider="ollama",
model_name="phi-mini",
async_client=async_client,
mode=None,
api_key=None,
kwargs={},
provider_info=provider_info("ollama"),
)
expected_type = openai.AsyncOpenAI if async_client else openai.OpenAI
assert isinstance(calls[0][0], expected_type)
assert str(calls[0][0].base_url) == "http://localhost:22444/v1/"
assert calls[0][0].api_key == "ollama"
assert tools_result == {
"model": "llama3.1:8b",
"mode": Mode.TOOLS,
"max_tokens": 47,
}
assert isinstance(calls[1][0], expected_type)
assert str(calls[1][0].base_url) == "http://localhost:11434/v1/"
assert json_result == {"model": "phi-mini", "mode": Mode.JSON}
@pytest.mark.parametrize("async_client", [False, True])
@pytest.mark.asyncio
@pytest.mark.skipif(sys.version_info < (3, 10), reason="xai-sdk requires Python 3.10+")
async def test_xai_builder_forwards_real_client_and_mode(
monkeypatch: pytest.MonkeyPatch, async_client: bool
) -> None:
import grpc
from xai_sdk.aio.client import Client as AsyncClient
from xai_sdk.sync.client import Client as SyncClient
import instructor.v2.providers.xai.client as xai_client
seen: dict[str, Any] = {}
channels: list[Any] = []
if async_client:
create_channel = grpc.aio.secure_channel
def track_channel(*args: Any, **kwargs: Any) -> Any:
channel = create_channel(*args, **kwargs)
channels.append(channel)
return channel
monkeypatch.setattr(grpc.aio, "secure_channel", track_channel)
else:
create_channel = grpc.secure_channel
def track_channel(*args: Any, **kwargs: Any) -> Any:
channel = create_channel(*args, **kwargs)
channels.append(channel)
return channel
monkeypatch.setattr(grpc, "secure_channel", track_channel)
def capture(client: object, **kwargs: Any) -> dict[str, Any]:
seen["client"] = client
return kwargs
monkeypatch.setattr(xai_client, "from_xai", capture)
try:
result = auto_client._build_xai(
provider="xai",
model_name="grok-test",
async_client=async_client,
mode=Mode.JSON,
api_key="test-key",
kwargs={"max_tokens": 53},
provider_info=provider_info("xai"),
)
assert isinstance(seen["client"], AsyncClient if async_client else SyncClient)
assert result == {"model": "grok-test", "mode": Mode.JSON, "max_tokens": 53}
assert len(channels) == 1
finally:
for channel in channels:
if async_client:
await channel.close()
else:
channel.close()
@pytest.mark.skipif(sys.version_info < (3, 10), reason="xai-sdk requires Python 3.10+")
def test_xai_builder_reports_unavailable_factory(
monkeypatch: pytest.MonkeyPatch,
) -> None:
import instructor.v2.providers.xai.client as xai_client
monkeypatch.setattr(xai_client, "from_xai", None)
with pytest.raises(ConfigurationError, match="Failed to import xAI provider"):
auto_client._build_xai(
provider="xai",
model_name="grok-test",
async_client=False,
mode=None,
api_key="test-key",
kwargs={},
provider_info=provider_info("xai"),
)
@pytest.mark.parametrize("async_client", [False, True])
def test_litellm_builder_selects_completion_and_forwards_mode(
monkeypatch: pytest.MonkeyPatch, async_client: bool
) -> None:
import litellm
import instructor.v2.providers.litellm.client as litellm_client
seen: dict[str, Any] = {}
def capture(completion: object, **kwargs: Any) -> dict[str, Any]:
seen["completion"] = completion
return kwargs
monkeypatch.setattr(litellm_client, "from_litellm", capture)
result = auto_client._build_litellm(
provider="litellm",
model_name="ignored-model",
async_client=async_client,
mode=Mode.JSON,
api_key=None,
kwargs={"max_tokens": 59},
provider_info=provider_info("litellm"),
)
assert seen["completion"] is (
litellm.acompletion if async_client else litellm.completion
)
assert result == {"mode": Mode.JSON, "max_tokens": 59}
MISSING_IMPORTS = [
(
"cerebras",
auto_client._build_cerebras,
"cerebras.cloud.sdk",
"Cerebras provider",
),
(
"fireworks",
auto_client._build_fireworks,
"fireworks.client",
"Fireworks provider",
),
("vertexai", auto_client._build_vertexai, "vertexai", "VertexAI provider"),
(
"generative-ai",
auto_client._build_generative_ai,
"google",
"Google GenAI provider",
),
("ollama", auto_client._build_ollama, "openai", "Ollama provider"),
("deepseek", auto_client._build_deepseek, "openai", "DeepSeek provider"),
(
"xai",
auto_client._build_xai,
"xai_sdk.sync.client",
"optional dependency `xai-sdk`",
),
("openrouter", auto_client._build_openrouter, "openai", "OpenRouter provider"),
("litellm", auto_client._build_litellm, "litellm", "LiteLLM provider"),
]
@pytest.mark.parametrize("provider,builder,blocked_import,message", MISSING_IMPORTS)
def test_tail_builder_missing_dependency_has_actionable_error(
monkeypatch: pytest.MonkeyPatch,
provider: str,
builder: Any,
blocked_import: str,
message: str,
) -> None:
real_import = builtins.__import__
def guarded_import(
name: str,
globals_: dict[str, Any] | None = None,
locals_: dict[str, Any] | None = None,
fromlist: tuple[str, ...] = (),
level: int = 0,
) -> Any:
if name == blocked_import or name.startswith(f"{blocked_import}."):
raise ModuleNotFoundError(
f"No module named '{blocked_import}'", name=blocked_import
)
return real_import(name, globals_, locals_, fromlist, level)
monkeypatch.setattr(builtins, "__import__", guarded_import)
with expected_provider_deprecation(provider):
with pytest.raises(ConfigurationError, match=message):
builder(
provider=provider,
model_name="model-test",
async_client=False,
mode=None,
api_key="test-key",
kwargs={},
provider_info=provider_info(provider),
)
FACTORY_FAILURES = [
(
"cerebras",
auto_client._build_cerebras,
"instructor.v2.providers.cerebras.client",
"from_cerebras",
),
(
"fireworks",
auto_client._build_fireworks,
"instructor.v2.providers.fireworks.client",
"from_fireworks",
),
("vertexai", auto_client._build_vertexai, "instructor", "from_vertexai"),
(
"generative-ai",
auto_client._build_generative_ai,
"instructor.v2.providers.genai.client",
"from_genai",
),
(
"ollama",
auto_client._build_ollama,
"instructor.v2.providers.openai.client",
"from_openai",
),
(
"deepseek",
auto_client._build_deepseek,
"instructor.v2.providers.openai.client",
"from_deepseek",
),
pytest.param(
"xai",
auto_client._build_xai,
"instructor.v2.providers.xai.client",
"from_xai",
marks=pytest.mark.skipif(
sys.version_info < (3, 10), reason="xai-sdk requires Python 3.10+"
),
),
(
"openrouter",
auto_client._build_openrouter,
"instructor.v2.providers.openrouter.client",
"from_openrouter",
),
(
"litellm",
auto_client._build_litellm,
"instructor.v2.providers.litellm.client",
"from_litellm",
),
]
@pytest.mark.parametrize("provider,builder,module_name,factory_name", FACTORY_FAILURES)
def test_tail_builder_factory_failure_is_logged_and_propagated(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
provider: str,
builder: Any,
module_name: str,
factory_name: str,
) -> None:
with ignore_fireworks_pydantic_warning():
module = importlib.import_module(module_name)
if provider == "vertexai":
import vertexai
import vertexai.generative_models as generative_models
monkeypatch.setattr(vertexai, "init", lambda **_kwargs: None)
monkeypatch.setattr(
generative_models, "GenerativeModel", lambda _name: object()
)
def fail(client: object, **_kwargs: Any) -> None:
close_provider_client(client)
raise RuntimeError("provider factory failed")
monkeypatch.setattr(module, factory_name, fail)
with expected_provider_deprecation(provider):
with caplog.at_level(logging.ERROR, logger="instructor.auto_client"):
with pytest.raises(RuntimeError, match="provider factory failed"):
builder(
provider=provider,
model_name="model-test",
async_client=False,
mode=None,
api_key="test-key",
kwargs={"project": "project-test"}
if provider == "vertexai"
else {},
provider_info=provider_info(provider),
)
assert f"Error initializing {provider} client" in caplog.text