Files
2026-07-13 13:39:38 +08:00

1060 lines
38 KiB
Python

# Copyright 2023 LiveKit, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import os
from dataclasses import asdict, dataclass
from typing import Any, Literal
from urllib.parse import urlparse
import httpx
import openai
from livekit.agents import llm
from livekit.agents.inference.llm import LLMStream as _LLMStream
from livekit.agents.llm import (
ChatContext,
ToolChoice,
utils as llm_utils,
)
from livekit.agents.types import (
DEFAULT_API_CONNECT_OPTIONS,
NOT_GIVEN,
APIConnectOptions,
NotGivenOr,
)
from livekit.agents.utils import is_given
from openai.types import ReasoningEffort
from openai.types.chat import ChatCompletionToolChoiceOptionParam, completion_create_params
from .models import (
CerebrasChatModels,
ChatModels,
CometAPIChatModels,
DeepSeekChatModels,
NebiusChatModels,
OctoChatModels,
OpenRouterProviderPreferences,
OpenRouterWebPlugin,
PerplexityChatModels,
SambaNovaChatModels,
TelnyxChatModels,
TogetherChatModels,
XAIChatModels,
_supports_reasoning_effort,
)
from .utils import AsyncAzureADTokenProvider
lk_oai_debug = int(os.getenv("LK_OPENAI_DEBUG", 0))
Verbosity = Literal["low", "medium", "high"]
PromptCacheRetention = Literal["in_memory", "24h"]
@dataclass
class _LLMOptions:
model: str | ChatModels
user: NotGivenOr[str]
safety_identifier: NotGivenOr[str]
prompt_cache_key: NotGivenOr[str]
temperature: NotGivenOr[float]
top_p: NotGivenOr[float]
parallel_tool_calls: NotGivenOr[bool]
tool_choice: NotGivenOr[ToolChoice]
store: NotGivenOr[bool]
metadata: NotGivenOr[dict[str, str]]
max_completion_tokens: NotGivenOr[int]
service_tier: NotGivenOr[str]
reasoning_effort: NotGivenOr[ReasoningEffort]
verbosity: NotGivenOr[Verbosity]
prompt_cache_retention: NotGivenOr[PromptCacheRetention]
extra_body: NotGivenOr[dict[str, Any]]
extra_headers: NotGivenOr[dict[str, str]]
extra_query: NotGivenOr[dict[str, str]]
class LLM(llm.LLM):
def __init__(
self,
*,
model: str | ChatModels = "gpt-4.1",
api_key: NotGivenOr[str] = NOT_GIVEN,
base_url: NotGivenOr[str] = NOT_GIVEN,
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
store: NotGivenOr[bool] = NOT_GIVEN,
metadata: NotGivenOr[dict[str, str]] = NOT_GIVEN,
max_completion_tokens: NotGivenOr[int] = NOT_GIVEN,
timeout: httpx.Timeout | None = None,
max_retries: NotGivenOr[int] = NOT_GIVEN,
service_tier: NotGivenOr[str] = NOT_GIVEN,
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
verbosity: NotGivenOr[Verbosity] = NOT_GIVEN,
prompt_cache_retention: NotGivenOr[PromptCacheRetention] = NOT_GIVEN,
extra_body: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
extra_headers: NotGivenOr[dict[str, str]] = NOT_GIVEN,
extra_query: NotGivenOr[dict[str, str]] = NOT_GIVEN,
_provider_fmt: NotGivenOr[str] = NOT_GIVEN,
_strict_tool_schema: bool = True,
) -> None:
"""
Create a new instance of OpenAI LLM.
``api_key`` must be set to your OpenAI API key, either using the argument or by setting the
``OPENAI_API_KEY`` environmental variable.
"""
super().__init__()
if not is_given(reasoning_effort) and _supports_reasoning_effort(model):
if model in ["gpt-5.1", "gpt-5.2", "gpt-5.4", "gpt-5.4-mini"]:
reasoning_effort = "none"
else:
reasoning_effort = "minimal"
self._opts = _LLMOptions(
model=model,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
store=store,
metadata=metadata,
max_completion_tokens=max_completion_tokens,
service_tier=service_tier,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
verbosity=verbosity,
prompt_cache_retention=prompt_cache_retention,
extra_body=extra_body,
extra_headers=extra_headers,
extra_query=extra_query,
)
if is_given(api_key) and not api_key:
raise ValueError(
"OpenAI API key is required, either as argument or set"
" OPENAI_API_KEY environment variable"
)
self._provider_fmt = _provider_fmt or "openai"
self._strict_tool_schema = _strict_tool_schema
self._owns_client = client is None
self._client = client or openai.AsyncClient(
api_key=api_key if is_given(api_key) else None,
base_url=base_url if is_given(base_url) else None,
max_retries=max_retries if is_given(max_retries) else 0,
http_client=httpx.AsyncClient(
timeout=timeout
if timeout
else httpx.Timeout(connect=15.0, read=5.0, write=5.0, pool=5.0),
follow_redirects=True,
limits=httpx.Limits(
max_connections=50,
max_keepalive_connections=50,
keepalive_expiry=120,
),
),
)
async def aclose(self) -> None:
if self._owns_client:
await self._client.close()
@property
def model(self) -> str:
return self._opts.model
@property
def provider(self) -> str:
return self._client._base_url.netloc.decode("utf-8")
@staticmethod
def with_azure(
*,
model: str | ChatModels = "gpt-4o",
azure_endpoint: str | None = None,
azure_deployment: str | None = None,
api_version: str | None = None,
api_key: str | None = None,
azure_ad_token: str | None = None,
azure_ad_token_provider: AsyncAzureADTokenProvider | None = None,
organization: str | None = None,
project: str | None = None,
base_url: str | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
timeout: httpx.Timeout | None = None,
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
verbosity: NotGivenOr[Verbosity] = NOT_GIVEN,
max_completion_tokens: NotGivenOr[int] = NOT_GIVEN,
) -> LLM:
"""
This automatically infers the following arguments from their corresponding environment variables if they are not provided:
- `api_key` from `AZURE_OPENAI_API_KEY`
- `organization` from `OPENAI_ORG_ID`
- `project` from `OPENAI_PROJECT_ID`
- `azure_ad_token` from `AZURE_OPENAI_AD_TOKEN`
- `api_version` from `OPENAI_API_VERSION`
- `azure_endpoint` from `AZURE_OPENAI_ENDPOINT`
""" # noqa: E501
azure_client = openai.AsyncAzureOpenAI(
max_retries=0,
azure_endpoint=azure_endpoint,
azure_deployment=azure_deployment,
api_version=api_version,
api_key=api_key,
azure_ad_token=azure_ad_token,
azure_ad_token_provider=azure_ad_token_provider,
organization=organization,
project=project,
base_url=base_url,
timeout=timeout
if timeout
else httpx.Timeout(connect=15.0, read=5.0, write=5.0, pool=5.0),
) # type: ignore
llm = LLM(
model=model,
client=azure_client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
verbosity=verbosity,
max_completion_tokens=max_completion_tokens,
)
llm._owns_client = True
return llm
@staticmethod
def with_cerebras(
*,
model: str | CerebrasChatModels = "llama-4-scout-17b-16e-instruct",
api_key: str | None = None,
base_url: str = "https://api.cerebras.ai/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of Cerebras LLM.
``api_key`` must be set to your Cerebras API key, either using the argument or by setting
the ``CEREBRAS_API_KEY`` environment variable.
"""
api_key = api_key or os.environ.get("CEREBRAS_API_KEY")
if api_key is None:
raise ValueError(
"Cerebras API key is required, either as argument or set CEREBRAS_API_KEY environment variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
_strict_tool_schema=False,
)
@staticmethod
def with_sambanova(
*,
model: str | SambaNovaChatModels = "DeepSeek-R1-0528",
api_key: str | None = None,
base_url: str = "https://api.sambanova.ai/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of SambaNova LLM (OpenAI-compatible).
``api_key`` must be set to your SambaNova API key, either using the argument or by setting
the ``SAMBANOVA_API_KEY`` environment variable.
"""
api_key = api_key or os.environ.get("SAMBANOVA_API_KEY")
if api_key is None:
raise ValueError(
"SambaNova API key is required, either as argument or set SAMBANOVA_API_KEY environment variable"
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
_strict_tool_schema=False,
)
@staticmethod
def with_fireworks(
*,
model: str = "accounts/fireworks/models/llama-v3p3-70b-instruct",
api_key: str | None = None,
base_url: str = "https://api.fireworks.ai/inference/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of Fireworks LLM.
``api_key`` must be set to your Fireworks API key, either using the argument or by setting
the ``FIREWORKS_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("FIREWORKS_API_KEY")
if api_key is None:
raise ValueError(
"Fireworks API key is required, either as argument or set FIREWORKS_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_x_ai(
*,
model: str | XAIChatModels = "grok-3-fast",
api_key: str | None = None,
base_url: str = "https://api.x.ai/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of XAI LLM.
``api_key`` must be set to your XAI API key, either using the argument or by setting
the ``XAI_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("XAI_API_KEY")
if api_key is None:
raise ValueError(
"XAI API key is required, either as argument or set XAI_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
# TODO(long): add provider fmt for grok
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_openrouter(
*,
model: str = "auto",
api_key: str | None = None,
base_url: str = "https://openrouter.ai/api/v1",
client: openai.AsyncClient | None = None,
site_url: str | None = None,
app_name: str | None = None,
fallback_models: list[str] | None = None,
provider: OpenRouterProviderPreferences | None = None,
plugins: list[OpenRouterWebPlugin] | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
timeout: httpx.Timeout | None = None,
) -> LLM:
"""
Create a new instance of OpenRouter LLM.
``api_key`` must be set to your OpenRouter API key, either using the argument or by setting
the ``OPENROUTER_API_KEY`` environment variable.
"""
api_key = api_key or os.environ.get("OPENROUTER_API_KEY")
if api_key is None:
raise ValueError(
"OpenRouter API key is required, either as argument or set OPENROUTER_API_KEY environment variable"
)
# Set up analytics headers for OpenRouter
default_headers: dict[str, str] = {}
if site_url:
default_headers["HTTP-Referer"] = site_url
if app_name:
default_headers["X-Title"] = app_name
# Build OpenRouter-specific request body
or_body: dict[str, Any] = {}
if provider:
or_body["provider"] = provider
if fallback_models:
# Set fallback models for routing
or_body["models"] = [model, *fallback_models]
if plugins:
or_body["plugins"] = [
{k: v for k, v in asdict(p).items() if v is not None} for p in plugins
]
return LLM(
model=model,
api_key=api_key,
client=client,
base_url=base_url,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
extra_body=or_body,
extra_headers=default_headers,
timeout=timeout,
)
@staticmethod
def with_deepseek(
*,
model: str | DeepSeekChatModels = "deepseek-chat",
api_key: str | None = None,
base_url: str = "https://api.deepseek.com/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of DeepSeek LLM.
``api_key`` must be set to your DeepSeek API key, either using the argument or by setting
the ``DEEPSEEK_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("DEEPSEEK_API_KEY")
if api_key is None:
raise ValueError(
"DeepSeek API key is required, either as argument or set DEEPSEEK_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_cometapi(
*,
model: str | CometAPIChatModels = "gpt-5-chat-latest",
api_key: str | None = None,
base_url: str = "https://api.cometapi.com/v1/",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of CometAPI LLM.
``api_key`` must be set to your CometAPI API key, either using the argument or by setting
the ``COMETAPI_API_KEY`` environmental variable.
CometAPI provides access to 500+ AI models from multiple providers including OpenAI,
Anthropic, Google, xAI, DeepSeek, and Qwen through a unified API.
Get your API key at: https://api.cometapi.com/console/token
Learn more: https://www.cometapi.com/?utm_source=livekit&utm_campaign=integration&utm_medium=integration&utm_content=integration
"""
api_key = api_key or os.environ.get("COMETAPI_API_KEY")
if api_key is None:
raise ValueError(
"CometAPI API key is required, either as argument or set COMETAPI_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_octo(
*,
model: str | OctoChatModels = "llama-2-13b-chat",
api_key: str | None = None,
base_url: str = "https://text.octoai.run/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of OctoAI LLM.
``api_key`` must be set to your OctoAI API key, either using the argument or by setting
the ``OCTOAI_TOKEN`` environmental variable.
"""
api_key = api_key or os.environ.get("OCTOAI_TOKEN")
if api_key is None:
raise ValueError(
"OctoAI API key is required, either as argument or set OCTOAI_TOKEN environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_ollama(
*,
model: str = "llama3.1",
base_url: str = "http://localhost:11434/v1",
client: openai.AsyncClient | None = None,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of Ollama LLM.
"""
return LLM(
model=model,
api_key="ollama",
base_url=base_url,
client=client,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_ovhcloud(
*,
model: str = "gpt-oss-120b",
api_key: str | None = None,
base_url: str = "https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of OVHcloud AI Endpoints LLM.
``api_key`` must be set to your OVHcloud AI Endpoints API key, either using the argument or by setting
the ``OVHCLOUD_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("OVHCLOUD_API_KEY")
if api_key is None:
raise ValueError(
"OVHcloud AI Endpoints API key is required, either as argument or set OVHCLOUD_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_perplexity(
*,
model: str | PerplexityChatModels = "llama-3.1-sonar-small-128k-chat",
api_key: str | None = None,
base_url: str = "https://api.perplexity.ai",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of PerplexityAI LLM.
``api_key`` must be set to your TogetherAI API key, either using the argument or by setting
the ``PERPLEXITY_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("PERPLEXITY_API_KEY")
if api_key is None:
raise ValueError(
"Perplexity AI API key is required, either as argument or set PERPLEXITY_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_together(
*,
model: str | TogetherChatModels = "meta-llama/Meta-Llama-3.1-8B-Instruct-Turbo",
api_key: str | None = None,
base_url: str = "https://api.together.xyz/v1",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of TogetherAI LLM.
``api_key`` must be set to your TogetherAI API key, either using the argument or by setting
the ``TOGETHER_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("TOGETHER_API_KEY")
if api_key is None:
raise ValueError(
"Together AI API key is required, either as argument or set TOGETHER_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_telnyx(
*,
model: str | TelnyxChatModels = "meta-llama/Meta-Llama-3.1-70B-Instruct",
api_key: str | None = None,
base_url: str = "https://api.telnyx.com/v2/ai",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of Telnyx LLM.
``api_key`` must be set to your Telnyx API key, either using the argument or by setting
the ``TELNYX_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("TELNYX_API_KEY")
if api_key is None:
raise ValueError(
"Telnyx AI API key is required, either as argument or set TELNYX_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_nebius(
*,
model: str | NebiusChatModels = "meta-llama/Meta-Llama-3.1-70B-Instruct",
api_key: str | None = None,
base_url: str = "https://api.studio.nebius.com/v1/",
client: openai.AsyncClient | None = None,
user: NotGivenOr[str] = NOT_GIVEN,
temperature: NotGivenOr[float] = NOT_GIVEN,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: ToolChoice = "auto",
reasoning_effort: NotGivenOr[ReasoningEffort] = NOT_GIVEN,
safety_identifier: NotGivenOr[str] = NOT_GIVEN,
prompt_cache_key: NotGivenOr[str] = NOT_GIVEN,
top_p: NotGivenOr[float] = NOT_GIVEN,
) -> LLM:
"""
Create a new instance of Nebius LLM.
``api_key`` must be set to your Nebius API key, either using the argument or by setting
the ``NEBIUS_API_KEY`` environmental variable.
"""
api_key = api_key or os.environ.get("NEBIUS_API_KEY")
if api_key is None:
raise ValueError(
"Nebius API key is required, either as argument or set NEBIUS_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=model,
api_key=api_key,
base_url=base_url,
client=client,
user=user,
temperature=temperature,
parallel_tool_calls=parallel_tool_calls,
tool_choice=tool_choice,
reasoning_effort=reasoning_effort,
safety_identifier=safety_identifier,
prompt_cache_key=prompt_cache_key,
top_p=top_p,
)
@staticmethod
def with_letta(
*,
agent_id: str,
base_url: str = "https://api.letta.com/v1/chat/completions",
api_key: str | None = None,
) -> LLM:
"""
Create a new Letta-backed LLM instance connected to the specified Letta agent.
Args:
agent_id (str): The Letta agent ID (must be prefixed with 'agent-').
base_url (str): The URL of the Letta server (e.g., http://localhost:8283/v1/chat/completions for local or https://api.letta.com/v1/chat/completions for cloud).
api_key (str | None, optional): Optional API key for authentication, required if
the Letta server enforces auth.
Returns:
LLM: A configured LLM instance for interacting with the given Letta agent.
"""
parsed = urlparse(base_url)
if parsed.scheme not in {"http", "https"}:
raise ValueError(f"Invalid URL scheme: '{parsed.scheme}'. Must be 'http' or 'https'.")
if not parsed.netloc:
raise ValueError(f"URL '{base_url}' is missing a network location (e.g., domain name).")
api_key = api_key or os.environ.get("LETTA_API_KEY")
if api_key is None:
raise ValueError(
"Letta API key is required, either as argument or set LETTA_API_KEY environmental variable" # noqa: E501
)
return LLM(
model=agent_id,
api_key=api_key,
base_url=base_url,
client=None,
user=NOT_GIVEN,
temperature=NOT_GIVEN,
parallel_tool_calls=NOT_GIVEN,
tool_choice=NOT_GIVEN,
)
def chat(
self,
*,
chat_ctx: ChatContext,
tools: list[llm.Tool] | None = None,
conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
parallel_tool_calls: NotGivenOr[bool] = NOT_GIVEN,
tool_choice: NotGivenOr[ToolChoice] = NOT_GIVEN,
response_format: NotGivenOr[
completion_create_params.ResponseFormat | type[llm_utils.ResponseFormatT]
] = NOT_GIVEN,
extra_kwargs: NotGivenOr[dict[str, Any]] = NOT_GIVEN,
) -> LLMStream:
extra = {}
if is_given(extra_kwargs):
extra.update(extra_kwargs)
if is_given(self._opts.extra_body):
extra["extra_body"] = self._opts.extra_body
if is_given(self._opts.extra_headers):
extra["extra_headers"] = self._opts.extra_headers
if is_given(self._opts.extra_query):
extra["extra_query"] = self._opts.extra_query
if is_given(self._opts.metadata):
extra["metadata"] = self._opts.metadata
if is_given(self._opts.user):
extra["user"] = self._opts.user
if is_given(self._opts.max_completion_tokens):
extra["max_completion_tokens"] = self._opts.max_completion_tokens
if is_given(self._opts.temperature):
extra["temperature"] = self._opts.temperature
if is_given(self._opts.service_tier):
extra["service_tier"] = self._opts.service_tier
if is_given(self._opts.reasoning_effort):
extra["reasoning_effort"] = self._opts.reasoning_effort
if is_given(self._opts.safety_identifier):
extra["safety_identifier"] = self._opts.safety_identifier
if is_given(self._opts.prompt_cache_key):
extra["prompt_cache_key"] = self._opts.prompt_cache_key
if is_given(self._opts.top_p):
extra["top_p"] = self._opts.top_p
if is_given(self._opts.verbosity):
extra["verbosity"] = self._opts.verbosity
if is_given(self._opts.prompt_cache_retention):
extra["prompt_cache_retention"] = self._opts.prompt_cache_retention
parallel_tool_calls = (
parallel_tool_calls if is_given(parallel_tool_calls) else self._opts.parallel_tool_calls
)
if is_given(parallel_tool_calls):
extra["parallel_tool_calls"] = parallel_tool_calls
tool_choice = tool_choice if is_given(tool_choice) else self._opts.tool_choice
if is_given(tool_choice):
oai_tool_choice: ChatCompletionToolChoiceOptionParam
if isinstance(tool_choice, dict):
oai_tool_choice = {
"type": "function",
"function": {"name": tool_choice["function"]["name"]},
}
extra["tool_choice"] = oai_tool_choice
elif tool_choice in ("auto", "required", "none"):
oai_tool_choice = tool_choice
extra["tool_choice"] = oai_tool_choice
if is_given(response_format):
extra["response_format"] = llm_utils.to_openai_response_format(response_format) # type: ignore
return LLMStream(
self,
model=self._opts.model,
provider_fmt=self._provider_fmt,
strict_tool_schema=self._strict_tool_schema,
client=self._client,
chat_ctx=chat_ctx,
tools=tools or [],
conn_options=conn_options,
extra_kwargs=extra,
)
class LLMStream(_LLMStream):
def __init__(
self,
llm: LLM,
*,
model: str | ChatModels,
provider_fmt: str,
strict_tool_schema: bool,
client: openai.AsyncClient,
chat_ctx: llm.ChatContext,
tools: list[llm.Tool],
conn_options: APIConnectOptions,
extra_kwargs: dict[str, Any],
) -> None:
super().__init__(
llm,
model=model,
provider_fmt=provider_fmt,
strict_tool_schema=strict_tool_schema,
client=client,
chat_ctx=chat_ctx,
tools=tools,
conn_options=conn_options,
extra_kwargs=extra_kwargs,
)