1244 lines
42 KiB
Python
1244 lines
42 KiB
Python
from __future__ import annotations
|
|
from typing import Any, Union, Literal, overload
|
|
from .core.client import AsyncInstructor, Instructor
|
|
import instructor
|
|
from instructor import __version__
|
|
from instructor.models import KnownModelName
|
|
from instructor.cache import BaseCache
|
|
import warnings
|
|
import logging
|
|
|
|
# Type alias for the return type
|
|
InstructorType = Union[Instructor, AsyncInstructor]
|
|
|
|
logger = logging.getLogger("instructor.auto_client")
|
|
|
|
|
|
# List of supported providers
|
|
supported_providers = [
|
|
"openai",
|
|
"azure_openai",
|
|
"databricks",
|
|
"anthropic",
|
|
"google",
|
|
"generative-ai",
|
|
"vertexai",
|
|
"mistral",
|
|
"cohere",
|
|
"perplexity",
|
|
"groq",
|
|
"writer",
|
|
"bedrock",
|
|
"cerebras",
|
|
"deepseek",
|
|
"fireworks",
|
|
"ollama",
|
|
"openrouter",
|
|
"xai",
|
|
"litellm",
|
|
]
|
|
|
|
|
|
@overload
|
|
def from_provider(
|
|
model: KnownModelName,
|
|
async_client: Literal[True] = True,
|
|
cache: BaseCache | None = None, # noqa: ARG001
|
|
**kwargs: Any,
|
|
) -> AsyncInstructor: ...
|
|
|
|
|
|
@overload
|
|
def from_provider(
|
|
model: KnownModelName,
|
|
async_client: Literal[False] = False,
|
|
cache: BaseCache | None = None, # noqa: ARG001
|
|
**kwargs: Any,
|
|
) -> Instructor: ...
|
|
|
|
|
|
@overload
|
|
def from_provider(
|
|
model: str,
|
|
async_client: Literal[True] = True,
|
|
cache: BaseCache | None = None, # noqa: ARG001
|
|
**kwargs: Any,
|
|
) -> AsyncInstructor: ...
|
|
|
|
|
|
@overload
|
|
def from_provider(
|
|
model: str,
|
|
async_client: Literal[False] = False,
|
|
cache: BaseCache | None = None, # noqa: ARG001
|
|
**kwargs: Any,
|
|
) -> Instructor: ...
|
|
|
|
|
|
def from_provider(
|
|
model: Union[str, KnownModelName], # noqa: UP007
|
|
async_client: bool = False,
|
|
cache: BaseCache | None = None,
|
|
mode: Union[instructor.Mode, None] = None, # noqa: ARG001, UP007
|
|
**kwargs: Any,
|
|
) -> Union[Instructor, AsyncInstructor]: # noqa: UP007
|
|
"""Create an Instructor client from a model string.
|
|
|
|
Args:
|
|
model: String in format "provider/model-name"
|
|
(e.g., "openai/gpt-4", "anthropic/claude-3-sonnet", "google/gemini-pro")
|
|
async_client: Whether to return an async client
|
|
cache: Optional cache adapter (e.g., ``AutoCache`` or ``RedisCache``)
|
|
to enable transparent response caching. Automatically flows through
|
|
**kwargs to all provider implementations.
|
|
mode: Override the default mode for the provider. If not specified, uses the
|
|
recommended default mode for each provider.
|
|
**kwargs: Additional arguments passed to the provider client functions.
|
|
This includes the cache parameter and any provider-specific options.
|
|
|
|
Returns:
|
|
Instructor or AsyncInstructor instance
|
|
|
|
Raises:
|
|
ValueError: If provider is not supported or model string is invalid
|
|
ImportError: If required package for provider is not installed
|
|
|
|
Examples:
|
|
>>> import instructor
|
|
>>> from instructor.cache import AutoCache
|
|
>>>
|
|
>>> # Basic usage
|
|
>>> client = instructor.from_provider("openai/gpt-4")
|
|
>>> client = instructor.from_provider("anthropic/claude-3-sonnet")
|
|
>>>
|
|
>>> # With caching
|
|
>>> cache = AutoCache(maxsize=1000)
|
|
>>> client = instructor.from_provider("openai/gpt-4", cache=cache)
|
|
>>>
|
|
>>> # Async clients
|
|
>>> async_client = instructor.from_provider("openai/gpt-4", async_client=True)
|
|
"""
|
|
# Add cache to kwargs if provided so it flows through to provider functions
|
|
if cache is not None:
|
|
kwargs["cache"] = cache
|
|
|
|
try:
|
|
provider, model_name = model.split("/", 1)
|
|
except ValueError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
'Model string must be in format "provider/model-name" '
|
|
'(e.g. "openai/gpt-4" or "anthropic/claude-3-sonnet")'
|
|
) from None
|
|
|
|
provider_info = {"provider": provider, "operation": "initialize"}
|
|
logger.info(
|
|
"Initializing %s provider with model %s",
|
|
provider,
|
|
model_name,
|
|
extra=provider_info,
|
|
)
|
|
logger.debug(
|
|
"Provider configuration: async_client=%s, mode=%s",
|
|
async_client,
|
|
mode,
|
|
extra=provider_info,
|
|
)
|
|
api_key = None
|
|
if "api_key" in kwargs:
|
|
api_key = kwargs.pop("api_key")
|
|
if api_key:
|
|
logger.debug(
|
|
"API key provided for %s provider (length: %d characters)",
|
|
provider,
|
|
len(api_key),
|
|
extra=provider_info,
|
|
)
|
|
|
|
if provider == "openai":
|
|
try:
|
|
import openai
|
|
import httpx
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
from openai import DEFAULT_MAX_RETRIES, NotGiven, Timeout, not_given
|
|
from collections.abc import Mapping
|
|
from typing import cast
|
|
|
|
# Extract base_url and other OpenAI client parameters from kwargs
|
|
base_url = kwargs.pop("base_url", None)
|
|
organization = cast(str | None, kwargs.pop("organization", None))
|
|
|
|
timeout_raw = kwargs.pop("timeout", not_given)
|
|
timeout: float | Timeout | None | NotGiven
|
|
timeout = (
|
|
not_given
|
|
if timeout_raw is not_given
|
|
else cast(float | Timeout | None, timeout_raw)
|
|
)
|
|
|
|
max_retries_raw = kwargs.pop("max_retries", None)
|
|
max_retries = (
|
|
DEFAULT_MAX_RETRIES
|
|
if max_retries_raw is None
|
|
else int(cast(int, max_retries_raw))
|
|
)
|
|
|
|
default_headers = cast(
|
|
Mapping[str, str] | None, kwargs.pop("default_headers", None)
|
|
)
|
|
default_query = cast(
|
|
Mapping[str, object] | None, kwargs.pop("default_query", None)
|
|
)
|
|
http_client_raw = kwargs.pop("http_client", None)
|
|
strict_response_validation = bool(
|
|
kwargs.pop("_strict_response_validation", False)
|
|
)
|
|
|
|
if async_client:
|
|
http_client = cast(httpx.AsyncClient | None, http_client_raw)
|
|
client = openai.AsyncOpenAI(
|
|
api_key=api_key,
|
|
base_url=base_url,
|
|
organization=organization,
|
|
timeout=timeout,
|
|
max_retries=max_retries,
|
|
default_headers=default_headers,
|
|
default_query=default_query,
|
|
http_client=http_client,
|
|
_strict_response_validation=strict_response_validation,
|
|
)
|
|
else:
|
|
http_client = cast(httpx.Client | None, http_client_raw)
|
|
client = openai.OpenAI(
|
|
api_key=api_key,
|
|
base_url=base_url,
|
|
organization=organization,
|
|
timeout=timeout,
|
|
max_retries=max_retries,
|
|
default_headers=default_headers,
|
|
default_query=default_query,
|
|
http_client=http_client,
|
|
_strict_response_validation=strict_response_validation,
|
|
)
|
|
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the OpenAI provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "azure_openai":
|
|
try:
|
|
import os
|
|
from openai import AzureOpenAI, AsyncAzureOpenAI
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
|
|
# Get required Azure OpenAI configuration from environment
|
|
api_key = api_key or os.environ.get("AZURE_OPENAI_API_KEY")
|
|
azure_endpoint = kwargs.pop(
|
|
"azure_endpoint", os.environ.get("AZURE_OPENAI_ENDPOINT")
|
|
)
|
|
api_version = kwargs.pop("api_version", "2024-02-01")
|
|
|
|
if not api_key:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"AZURE_OPENAI_API_KEY is not set. "
|
|
"Set it with `export AZURE_OPENAI_API_KEY=<your-api-key>` or pass it as kwarg api_key=<your-api-key>"
|
|
)
|
|
|
|
if not azure_endpoint:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"AZURE_OPENAI_ENDPOINT is not set. "
|
|
"Set it with `export AZURE_OPENAI_ENDPOINT=<your-endpoint>` or pass it as kwarg azure_endpoint=<your-endpoint>"
|
|
)
|
|
|
|
client = (
|
|
AsyncAzureOpenAI(
|
|
api_key=api_key,
|
|
api_version=api_version,
|
|
azure_endpoint=azure_endpoint,
|
|
)
|
|
if async_client
|
|
else AzureOpenAI(
|
|
api_key=api_key,
|
|
api_version=api_version,
|
|
azure_endpoint=azure_endpoint,
|
|
)
|
|
)
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the Azure OpenAI provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "databricks":
|
|
try:
|
|
import os
|
|
import openai
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
|
|
api_key = (
|
|
api_key
|
|
or os.environ.get("DATABRICKS_TOKEN")
|
|
or os.environ.get("DATABRICKS_API_KEY")
|
|
)
|
|
if not api_key:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"DATABRICKS_TOKEN is not set. "
|
|
"Set it with `export DATABRICKS_TOKEN=<your-token>` or `export DATABRICKS_API_KEY=<your-token>` "
|
|
"or pass it as kwarg `api_key=<your-token>`."
|
|
)
|
|
|
|
base_url = kwargs.pop("base_url", None)
|
|
if base_url is None:
|
|
base_url = (
|
|
os.environ.get("DATABRICKS_BASE_URL")
|
|
or os.environ.get("DATABRICKS_HOST")
|
|
or os.environ.get("DATABRICKS_WORKSPACE_URL")
|
|
)
|
|
|
|
if not base_url:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"DATABRICKS_HOST is not set. "
|
|
"Set it with `export DATABRICKS_HOST=<your-workspace-url>` or `export DATABRICKS_WORKSPACE_URL=<your-workspace-url>` "
|
|
"or pass `base_url=<your-workspace-url>`."
|
|
)
|
|
|
|
base_url = str(base_url).rstrip("/")
|
|
if not base_url.endswith("/serving-endpoints"):
|
|
base_url = f"{base_url}/serving-endpoints"
|
|
|
|
openai_client_kwargs = {}
|
|
for key in (
|
|
"organization",
|
|
"timeout",
|
|
"max_retries",
|
|
"default_headers",
|
|
"http_client",
|
|
"app_info",
|
|
):
|
|
if key in kwargs:
|
|
openai_client_kwargs[key] = kwargs.pop(key)
|
|
|
|
client = (
|
|
openai.AsyncOpenAI(
|
|
api_key=api_key, base_url=base_url, **openai_client_kwargs
|
|
)
|
|
if async_client
|
|
else openai.OpenAI(
|
|
api_key=api_key, base_url=base_url, **openai_client_kwargs
|
|
)
|
|
)
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the Databricks provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
elif provider == "anthropic":
|
|
try:
|
|
import anthropic
|
|
from instructor import from_anthropic # type: ignore[attr-defined] # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
anthropic.AsyncAnthropic(
|
|
api_key=api_key,
|
|
default_headers={"User-Agent": f"instructor/{__version__}"},
|
|
)
|
|
if async_client
|
|
else anthropic.Anthropic(
|
|
api_key=api_key,
|
|
default_headers={"User-Agent": f"instructor/{__version__}"},
|
|
)
|
|
)
|
|
max_tokens = kwargs.pop("max_tokens", 4096)
|
|
result = from_anthropic(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.ANTHROPIC_TOOLS,
|
|
max_tokens=max_tokens,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The anthropic package is required to use the Anthropic provider. "
|
|
"Install it with `pip install anthropic`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "google":
|
|
# Import google-genai package - catch ImportError only for actual imports
|
|
try:
|
|
import google.genai as genai
|
|
from instructor import from_genai # type: ignore[attr-defined]
|
|
except ImportError as e:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The google-genai package is required to use the Google provider. "
|
|
"Install it with `pip install google-genai`."
|
|
) from e
|
|
|
|
try:
|
|
import os
|
|
|
|
# Remove vertexai from kwargs if present to avoid passing it twice
|
|
vertexai_flag = kwargs.pop("vertexai", False)
|
|
|
|
# Get API key from kwargs or environment
|
|
api_key = api_key or os.environ.get("GOOGLE_API_KEY")
|
|
|
|
# Extract client-specific parameters
|
|
client_kwargs = {}
|
|
for key in [
|
|
"debug_config",
|
|
"http_options",
|
|
"credentials",
|
|
"project",
|
|
"location",
|
|
]:
|
|
if key in kwargs:
|
|
client_kwargs[key] = kwargs.pop(key)
|
|
|
|
client = genai.Client(
|
|
vertexai=vertexai_flag,
|
|
api_key=api_key,
|
|
**client_kwargs,
|
|
) # type: ignore
|
|
if async_client:
|
|
result = from_genai(
|
|
client,
|
|
use_async=True,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.GENAI_TOOLS,
|
|
**kwargs,
|
|
) # type: ignore
|
|
else:
|
|
result = from_genai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.GENAI_TOOLS,
|
|
**kwargs,
|
|
) # type: ignore
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "mistral":
|
|
try:
|
|
from mistralai import Mistral
|
|
from instructor import from_mistral # type: ignore[attr-defined]
|
|
import os
|
|
|
|
api_key = api_key or os.environ.get("MISTRAL_API_KEY")
|
|
|
|
if api_key:
|
|
client = Mistral(api_key=api_key)
|
|
else:
|
|
raise ValueError(
|
|
"MISTRAL_API_KEY is not set. "
|
|
"Set it with `export MISTRAL_API_KEY=<your-api-key>`."
|
|
)
|
|
|
|
if async_client:
|
|
result = from_mistral(
|
|
client, model=model_name, use_async=True, **kwargs
|
|
)
|
|
else:
|
|
result = from_mistral(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The mistralai package is required to use the Mistral provider. "
|
|
"Install it with `pip install mistralai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "cohere":
|
|
try:
|
|
import cohere
|
|
from instructor import from_cohere # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
cohere.AsyncClientV2(api_key=api_key)
|
|
if async_client
|
|
else cohere.ClientV2(api_key=api_key)
|
|
)
|
|
result = from_cohere(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The cohere package is required to use the Cohere provider. "
|
|
"Install it with `pip install cohere`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "perplexity":
|
|
try:
|
|
import openai
|
|
from instructor import from_perplexity # type: ignore[attr-defined]
|
|
import os
|
|
|
|
api_key = api_key or os.environ.get("PERPLEXITY_API_KEY")
|
|
if not api_key:
|
|
raise ValueError(
|
|
"PERPLEXITY_API_KEY is not set. "
|
|
"Set it with `export PERPLEXITY_API_KEY=<your-api-key>` or pass it as a kwarg api_key=<your-api-key>"
|
|
)
|
|
|
|
client = (
|
|
openai.AsyncOpenAI(
|
|
api_key=api_key, base_url="https://api.perplexity.ai"
|
|
)
|
|
if async_client
|
|
else openai.OpenAI(
|
|
api_key=api_key, base_url="https://api.perplexity.ai"
|
|
)
|
|
)
|
|
result = from_perplexity(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the Perplexity provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "groq":
|
|
try:
|
|
import groq
|
|
from instructor import from_groq # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
groq.AsyncGroq(api_key=api_key)
|
|
if async_client
|
|
else groq.Groq(api_key=api_key)
|
|
)
|
|
result = from_groq(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The groq package is required to use the Groq provider. "
|
|
"Install it with `pip install groq`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "writer":
|
|
try:
|
|
from writerai import AsyncWriter, Writer
|
|
from instructor import from_writer # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
AsyncWriter(api_key=api_key)
|
|
if async_client
|
|
else Writer(api_key=api_key)
|
|
)
|
|
result = from_writer(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The writerai package is required to use the Writer provider. "
|
|
"Install it with `pip install writer-sdk`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "bedrock":
|
|
try:
|
|
import os
|
|
import boto3
|
|
from instructor import from_bedrock # type: ignore[attr-defined]
|
|
|
|
# Get AWS configuration from environment or kwargs
|
|
if "region" in kwargs:
|
|
region = kwargs.pop("region")
|
|
else:
|
|
logger.debug(
|
|
"AWS_DEFAULT_REGION is not set. Using default region us-east-1"
|
|
)
|
|
region = os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
|
|
|
|
# Extract AWS-specific parameters
|
|
# Dictionary to collect AWS credentials and session parameters for boto3 client
|
|
aws_kwargs = {}
|
|
for key in [
|
|
"aws_access_key_id",
|
|
"aws_secret_access_key",
|
|
"aws_session_token",
|
|
]:
|
|
if key in kwargs:
|
|
aws_kwargs[key] = kwargs.pop(key)
|
|
elif key.upper() in os.environ:
|
|
logger.debug(f"Using {key.upper()} from environment variable")
|
|
aws_kwargs[key] = os.environ[key.upper()]
|
|
|
|
# Add region to client configuration
|
|
aws_kwargs["region_name"] = region
|
|
|
|
# Create bedrock-runtime client
|
|
client = boto3.client("bedrock-runtime", **aws_kwargs)
|
|
|
|
# Determine default mode based on model
|
|
if mode is None:
|
|
# Anthropic models (Claude) support tools, others use JSON
|
|
if model_name and (
|
|
"anthropic" in model_name.lower() or "claude" in model_name.lower()
|
|
):
|
|
default_mode = instructor.Mode.BEDROCK_TOOLS
|
|
else:
|
|
default_mode = instructor.Mode.BEDROCK_JSON
|
|
else:
|
|
default_mode = mode
|
|
|
|
result = from_bedrock(
|
|
client,
|
|
mode=default_mode,
|
|
async_client=async_client,
|
|
_async=async_client, # for backward compatibility
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The boto3 package is required to use the AWS Bedrock provider. "
|
|
"Install it with `pip install boto3`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "cerebras":
|
|
try:
|
|
from cerebras.cloud.sdk import AsyncCerebras, Cerebras
|
|
from instructor import from_cerebras # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
AsyncCerebras(api_key=api_key)
|
|
if async_client
|
|
else Cerebras(api_key=api_key)
|
|
)
|
|
result = from_cerebras(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The cerebras package is required to use the Cerebras provider. "
|
|
"Install it with `pip install cerebras`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "fireworks":
|
|
try:
|
|
from fireworks.client import AsyncFireworks, Fireworks
|
|
from instructor import from_fireworks # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
AsyncFireworks(api_key=api_key)
|
|
if async_client
|
|
else Fireworks(api_key=api_key)
|
|
)
|
|
result = from_fireworks(client, model=model_name, **kwargs)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The fireworks-ai package is required to use the Fireworks provider. "
|
|
"Install it with `pip install fireworks-ai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "vertexai":
|
|
warnings.warn(
|
|
"The 'vertexai' provider is deprecated. Use 'google' provider with vertexai=True instead. "
|
|
"Example: instructor.from_provider('google/gemini-pro', vertexai=True)",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
# Import google-genai package - catch ImportError only for actual imports
|
|
try:
|
|
import google.genai as genai # type: ignore
|
|
from instructor import from_genai # type: ignore[attr-defined]
|
|
except ImportError as e:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The google-genai package is required to use the VertexAI provider. "
|
|
"Install it with `pip install google-genai`."
|
|
) from e
|
|
|
|
try:
|
|
import os
|
|
|
|
# Get project and location from kwargs or environment
|
|
project = kwargs.pop("project", os.environ.get("GOOGLE_CLOUD_PROJECT"))
|
|
location = kwargs.pop(
|
|
"location", os.environ.get("GOOGLE_CLOUD_LOCATION", "us-central1")
|
|
)
|
|
|
|
if not project:
|
|
raise ValueError(
|
|
"Project ID is required for Vertex AI. "
|
|
"Set it with `export GOOGLE_CLOUD_PROJECT=<your-project-id>` "
|
|
"or pass it as kwarg project=<your-project-id>"
|
|
)
|
|
|
|
client = genai.Client(
|
|
vertexai=True,
|
|
project=project,
|
|
location=location,
|
|
**kwargs,
|
|
) # type: ignore
|
|
kwargs["model"] = model_name # Pass model as part of kwargs
|
|
if async_client:
|
|
result = from_genai(
|
|
client,
|
|
use_async=True,
|
|
mode=mode if mode else instructor.Mode.GENAI_TOOLS,
|
|
**kwargs,
|
|
) # type: ignore
|
|
else:
|
|
result = from_genai(
|
|
client, mode=mode if mode else instructor.Mode.GENAI_TOOLS, **kwargs
|
|
) # type: ignore
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "generative-ai":
|
|
warnings.warn(
|
|
"The 'generative-ai' provider is deprecated. Use 'google' provider instead. "
|
|
"Example: instructor.from_provider('google/gemini-pro')",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
# Import google-genai package - catch ImportError only for actual imports
|
|
try:
|
|
from google import genai
|
|
from instructor import from_genai # type: ignore[attr-defined]
|
|
except ImportError as e:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The google-genai package is required to use the Google GenAI provider. "
|
|
"Install it with `pip install google-genai`."
|
|
) from e
|
|
|
|
try:
|
|
import os
|
|
|
|
# Get API key from kwargs or environment
|
|
api_key = api_key or os.environ.get("GOOGLE_API_KEY")
|
|
|
|
client = genai.Client(vertexai=False, api_key=api_key)
|
|
if async_client:
|
|
result = from_genai(
|
|
client,
|
|
use_async=True,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.GENAI_TOOLS,
|
|
**kwargs,
|
|
) # type: ignore
|
|
else:
|
|
result = from_genai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.GENAI_TOOLS,
|
|
**kwargs,
|
|
) # type: ignore
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "ollama":
|
|
try:
|
|
import openai
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
|
|
# Get base_url from kwargs or use default
|
|
base_url = kwargs.pop("base_url", "http://localhost:11434/v1")
|
|
api_key = kwargs.pop("api_key", "ollama") # required but unused
|
|
|
|
client = (
|
|
openai.AsyncOpenAI(base_url=base_url, api_key=api_key)
|
|
if async_client
|
|
else openai.OpenAI(base_url=base_url, api_key=api_key)
|
|
)
|
|
|
|
# Models that support function calling (tools mode)
|
|
tool_capable_models = {
|
|
"llama3.1",
|
|
"llama3.2",
|
|
"llama4",
|
|
"mistral-nemo",
|
|
"firefunction-v2",
|
|
"command-a",
|
|
"command-r",
|
|
"command-r-plus",
|
|
"command-r7b",
|
|
"qwen2.5",
|
|
"qwen2.5-coder",
|
|
"qwen3",
|
|
"devstral",
|
|
}
|
|
|
|
# Check if model supports tools by looking at model name
|
|
supports_tools = any(
|
|
capable_model in model_name.lower()
|
|
for capable_model in tool_capable_models
|
|
)
|
|
|
|
default_mode = (
|
|
instructor.Mode.TOOLS if supports_tools else instructor.Mode.JSON
|
|
)
|
|
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else default_mode,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the Ollama provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "deepseek":
|
|
try:
|
|
import openai
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
import os
|
|
|
|
# Get API key from kwargs or environment
|
|
api_key = api_key or os.environ.get("DEEPSEEK_API_KEY")
|
|
|
|
if not api_key:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"DEEPSEEK_API_KEY is not set. "
|
|
"Set it with `export DEEPSEEK_API_KEY=<your-api-key>` or pass it as kwarg api_key=<your-api-key>"
|
|
)
|
|
|
|
# DeepSeek uses OpenAI-compatible API
|
|
base_url = kwargs.pop("base_url", "https://api.deepseek.com")
|
|
|
|
client = (
|
|
openai.AsyncOpenAI(api_key=api_key, base_url=base_url)
|
|
if async_client
|
|
else openai.OpenAI(api_key=api_key, base_url=base_url)
|
|
)
|
|
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the DeepSeek provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "xai":
|
|
try:
|
|
from xai_sdk.sync.client import Client as SyncClient
|
|
from xai_sdk.aio.client import Client as AsyncClient
|
|
from instructor import from_xai # type: ignore[attr-defined]
|
|
|
|
client = (
|
|
AsyncClient(api_key=api_key)
|
|
if async_client
|
|
else SyncClient(api_key=api_key)
|
|
)
|
|
result = from_xai(
|
|
client,
|
|
mode=mode if mode else instructor.Mode.XAI_JSON,
|
|
model=model_name,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The xAI provider needs the optional dependency `xai-sdk`. "
|
|
'Install it with `uv pip install "instructor[xai]"` (or `pip install "instructor[xai]"`). '
|
|
"Note: xai-sdk requires Python 3.10+."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "openrouter":
|
|
try:
|
|
import openai
|
|
from instructor import from_openai # type: ignore[attr-defined]
|
|
import os
|
|
|
|
# Get API key from kwargs or environment
|
|
api_key = api_key or os.environ.get("OPENROUTER_API_KEY")
|
|
|
|
if not api_key:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"OPENROUTER_API_KEY is not set. "
|
|
"Set it with `export OPENROUTER_API_KEY=<your-api-key>` or pass it as kwarg api_key=<your-api-key>"
|
|
)
|
|
|
|
# OpenRouter uses OpenAI-compatible API
|
|
base_url = kwargs.pop("base_url", "https://openrouter.ai/api/v1")
|
|
|
|
client = (
|
|
openai.AsyncOpenAI(api_key=api_key, base_url=base_url)
|
|
if async_client
|
|
else openai.OpenAI(api_key=api_key, base_url=base_url)
|
|
)
|
|
|
|
result = from_openai(
|
|
client,
|
|
model=model_name,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The openai package is required to use the OpenRouter provider. "
|
|
"Install it with `pip install openai`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
elif provider == "litellm":
|
|
try:
|
|
from litellm import completion, acompletion
|
|
from instructor import from_litellm
|
|
|
|
completion_func = acompletion if async_client else completion
|
|
result = from_litellm(
|
|
completion_func,
|
|
mode=mode if mode else instructor.Mode.TOOLS,
|
|
**kwargs,
|
|
)
|
|
logger.info(
|
|
"Client initialized",
|
|
extra={**provider_info, "status": "success"},
|
|
)
|
|
return result
|
|
except ImportError:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
raise ConfigurationError(
|
|
"The litellm package is required to use the LiteLLM provider. "
|
|
"Install it with `pip install litellm`."
|
|
) from None
|
|
except Exception as e:
|
|
logger.error(
|
|
"Error initializing %s client: %s",
|
|
provider,
|
|
e,
|
|
exc_info=True,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise
|
|
|
|
else:
|
|
from .core.exceptions import ConfigurationError
|
|
|
|
logger.error(
|
|
"Error initializing %s client: unsupported provider",
|
|
provider,
|
|
extra={**provider_info, "status": "error"},
|
|
)
|
|
raise ConfigurationError(
|
|
f"Unsupported provider: {provider}. "
|
|
f"Supported providers are: {supported_providers}"
|
|
)
|