llmadapterevs 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- llmadapter/__init__.py +205 -0
- llmadapter/base.py +120 -0
- llmadapter/config.py +191 -0
- llmadapter/constants.py +13 -0
- llmadapter/core/__init__.py +32 -0
- llmadapter/core/async_adapter.py +876 -0
- llmadapter/core/circuit_breaker.py +140 -0
- llmadapter/core/execution.py +279 -0
- llmadapter/core/failover.py +110 -0
- llmadapter/core/preprocess.py +630 -0
- llmadapter/core/redaction.py +47 -0
- llmadapter/core/registry.py +80 -0
- llmadapter/core/responses_capabilities.py +313 -0
- llmadapter/core/responses_management.py +81 -0
- llmadapter/core/retry.py +83 -0
- llmadapter/core/streaming.py +549 -0
- llmadapter/core/sync_adapter.py +1075 -0
- llmadapter/errors.py +439 -0
- llmadapter/integrations/__init__.py +20 -0
- llmadapter/integrations/langchain.py +1174 -0
- llmadapter/models.py +1020 -0
- llmadapter/orchestration/__init__.py +62 -0
- llmadapter/orchestration/aggregators.py +215 -0
- llmadapter/orchestration/contracts.py +164 -0
- llmadapter/orchestration/routing.py +196 -0
- llmadapter/orchestration/runtime.py +1754 -0
- llmadapter/orchestration/tools.py +773 -0
- llmadapter/providers/__init__.py +58 -0
- llmadapter/providers/_responses_base.py +384 -0
- llmadapter/providers/_shared.py +411 -0
- llmadapter/providers/anthropic_provider.py +999 -0
- llmadapter/providers/azurefoundry_provider.py +749 -0
- llmadapter/providers/azureopenai_provider.py +681 -0
- llmadapter/providers/capabilities.py +137 -0
- llmadapter/providers/openai_provider.py +615 -0
- llmadapter/providers/vertexai_provider.py +817 -0
- llmadapter/ratelimit/__init__.py +24 -0
- llmadapter/ratelimit/batching.py +76 -0
- llmadapter/ratelimit/limiter.py +435 -0
- llmadapter/ratelimit/logging.py +29 -0
- llmadapter/ratelimit/tokenizer.py +217 -0
- llmadapter/responses_tools.py +95 -0
- llmadapter/structured.py +291 -0
- llmadapterevs-0.1.0.dist-info/METADATA +2983 -0
- llmadapterevs-0.1.0.dist-info/RECORD +47 -0
- llmadapterevs-0.1.0.dist-info/WHEEL +5 -0
- llmadapterevs-0.1.0.dist-info/top_level.txt +1 -0
llmadapter/__init__.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
|
5
|
+
|
|
6
|
+
for _lib in ("openai", "httpcore", "httpx"):
|
|
7
|
+
logging.getLogger(_lib).addHandler(logging.NullHandler())
|
|
8
|
+
del _lib
|
|
9
|
+
|
|
10
|
+
from .core import AsyncLLMAdapter, LLMAdapter
|
|
11
|
+
from .core.responses_capabilities import CapabilityStatus, ResponseCapabilities, ResponseCreateRequest
|
|
12
|
+
from .errors import (
|
|
13
|
+
APIConnectionError,
|
|
14
|
+
AuthenticationError,
|
|
15
|
+
ChatCompletionError,
|
|
16
|
+
ConfigurationError,
|
|
17
|
+
ContentFilterError,
|
|
18
|
+
ContextLengthExceededError,
|
|
19
|
+
EmbeddingGenerationError,
|
|
20
|
+
InvalidResponseError,
|
|
21
|
+
InvalidResponseRequestError,
|
|
22
|
+
LLMAdapterError,
|
|
23
|
+
ModelNotFoundError,
|
|
24
|
+
RateLimitBackendUnavailableError,
|
|
25
|
+
RateLimitExceededError,
|
|
26
|
+
ResponseNotFoundError,
|
|
27
|
+
ResponsesError,
|
|
28
|
+
TimeOutError,
|
|
29
|
+
UnsupportedCapabilityError,
|
|
30
|
+
)
|
|
31
|
+
from .models import (
|
|
32
|
+
CompactedResponse,
|
|
33
|
+
Conversation,
|
|
34
|
+
ConversationDeleted,
|
|
35
|
+
InputTokenCountResponse,
|
|
36
|
+
Response,
|
|
37
|
+
ResponseCodeInterpreterCallItem,
|
|
38
|
+
ResponseCompletedEvent,
|
|
39
|
+
ResponseComputerCallItem,
|
|
40
|
+
ResponseDeleted,
|
|
41
|
+
ResponseFileSearchCallItem,
|
|
42
|
+
ResponseFunctionCallArgumentsDeltaEvent,
|
|
43
|
+
ResponseFunctionCallItem,
|
|
44
|
+
ResponseImageGenerationCallItem,
|
|
45
|
+
ResponseItem,
|
|
46
|
+
ResponseItemList,
|
|
47
|
+
ResponseMcpApprovalRequestItem,
|
|
48
|
+
ResponseMcpCallItem,
|
|
49
|
+
ResponseMcpListToolsItem,
|
|
50
|
+
ResponseMessageItem,
|
|
51
|
+
ResponseOutputItem,
|
|
52
|
+
ResponseOutputTextDeltaEvent,
|
|
53
|
+
ProviderExtensions,
|
|
54
|
+
ResponseReasoningItem,
|
|
55
|
+
ResponseReasoningSummaryDeltaEvent,
|
|
56
|
+
ResponseStreamEvent,
|
|
57
|
+
ResponseUsage,
|
|
58
|
+
ResponseWebSearchCallItem,
|
|
59
|
+
FileSearchResult,
|
|
60
|
+
CodeInterpreterOutput,
|
|
61
|
+
GeneratedImage,
|
|
62
|
+
ComputerAction,
|
|
63
|
+
WebSearchSource,
|
|
64
|
+
)
|
|
65
|
+
from .responses_tools import CodeInterpreterTool, ComputerTool, ConnectorTool, FileSearchTool, ImageGenerationTool, McpApprovalDecision, McpTool, WebSearchTool
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"__version__",
|
|
69
|
+
# Adapters
|
|
70
|
+
"LLMAdapter",
|
|
71
|
+
"AsyncLLMAdapter",
|
|
72
|
+
"CapabilityStatus",
|
|
73
|
+
"ResponseCapabilities",
|
|
74
|
+
"ResponseCreateRequest",
|
|
75
|
+
# Errors
|
|
76
|
+
"LLMAdapterError",
|
|
77
|
+
"ConfigurationError",
|
|
78
|
+
"ContextLengthExceededError",
|
|
79
|
+
"InvalidResponseRequestError",
|
|
80
|
+
"UnsupportedCapabilityError",
|
|
81
|
+
"ModelNotFoundError",
|
|
82
|
+
"APIConnectionError",
|
|
83
|
+
"AuthenticationError",
|
|
84
|
+
"RateLimitExceededError",
|
|
85
|
+
"RateLimitBackendUnavailableError",
|
|
86
|
+
"ChatCompletionError",
|
|
87
|
+
"EmbeddingGenerationError",
|
|
88
|
+
"InvalidResponseError",
|
|
89
|
+
"ContentFilterError",
|
|
90
|
+
"TimeOutError",
|
|
91
|
+
"ResponsesError",
|
|
92
|
+
"ResponseNotFoundError",
|
|
93
|
+
# Responses API models
|
|
94
|
+
"Response",
|
|
95
|
+
"ResponseOutputItem",
|
|
96
|
+
"ResponseMessageItem",
|
|
97
|
+
"ResponseReasoningItem",
|
|
98
|
+
"ResponseFunctionCallItem",
|
|
99
|
+
"ResponseWebSearchCallItem",
|
|
100
|
+
"WebSearchSource",
|
|
101
|
+
"FileSearchResult",
|
|
102
|
+
"CodeInterpreterOutput",
|
|
103
|
+
"GeneratedImage",
|
|
104
|
+
"ComputerAction",
|
|
105
|
+
"WebSearchTool",
|
|
106
|
+
"FileSearchTool",
|
|
107
|
+
"CodeInterpreterTool",
|
|
108
|
+
"ImageGenerationTool",
|
|
109
|
+
"ComputerTool",
|
|
110
|
+
"McpTool",
|
|
111
|
+
"ConnectorTool",
|
|
112
|
+
"McpApprovalDecision",
|
|
113
|
+
"ResponseFileSearchCallItem",
|
|
114
|
+
"ResponseCodeInterpreterCallItem",
|
|
115
|
+
"ResponseImageGenerationCallItem",
|
|
116
|
+
"ResponseComputerCallItem",
|
|
117
|
+
"ResponseMcpListToolsItem",
|
|
118
|
+
"ResponseMcpApprovalRequestItem",
|
|
119
|
+
"ResponseMcpCallItem",
|
|
120
|
+
"ResponseUsage",
|
|
121
|
+
"ResponseItem",
|
|
122
|
+
"ResponseItemList",
|
|
123
|
+
"ResponseStreamEvent",
|
|
124
|
+
"ResponseCompletedEvent",
|
|
125
|
+
"ResponseOutputTextDeltaEvent",
|
|
126
|
+
"ResponseFunctionCallArgumentsDeltaEvent",
|
|
127
|
+
"ResponseReasoningSummaryDeltaEvent",
|
|
128
|
+
"ResponseDeleted",
|
|
129
|
+
"CompactedResponse",
|
|
130
|
+
"Conversation",
|
|
131
|
+
"ConversationDeleted",
|
|
132
|
+
"InputTokenCountResponse",
|
|
133
|
+
"ProviderExtensions",
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# LangChain compatibility — optional import (requires langchain-core)
|
|
137
|
+
try:
|
|
138
|
+
from .integrations.langchain import ChatLLMAdapter
|
|
139
|
+
|
|
140
|
+
__all__.append("ChatLLMAdapter")
|
|
141
|
+
except ModuleNotFoundError as exc:
|
|
142
|
+
if exc.name != "langchain_core":
|
|
143
|
+
raise
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
from .orchestration import (
|
|
147
|
+
AdvancedAgentConfig,
|
|
148
|
+
AggregatedResult,
|
|
149
|
+
BaseAggregator,
|
|
150
|
+
BaseRoutingPolicy,
|
|
151
|
+
aaggregate_specialists,
|
|
152
|
+
ainvoke_specialists,
|
|
153
|
+
CollectingAgentEventHandler,
|
|
154
|
+
CostAwareRoutingPolicy,
|
|
155
|
+
CheckpointStore,
|
|
156
|
+
EscalationRoutingPolicy,
|
|
157
|
+
JsonMergeAggregator,
|
|
158
|
+
LLMAdapterAgent,
|
|
159
|
+
InMemoryCheckpointStore,
|
|
160
|
+
FileCheckpointStore,
|
|
161
|
+
LoggingAgentEventHandler,
|
|
162
|
+
MajorityVoteAggregator,
|
|
163
|
+
RoundRobinRoutingPolicy,
|
|
164
|
+
SharedState,
|
|
165
|
+
SpecialistContract,
|
|
166
|
+
WeightedScoreAggregator,
|
|
167
|
+
aggregate_specialists,
|
|
168
|
+
create_llmadapter_agent,
|
|
169
|
+
invoke_specialists,
|
|
170
|
+
make_specialist_tools,
|
|
171
|
+
make_subagent_tool,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
__all__.extend(
|
|
175
|
+
[
|
|
176
|
+
"AdvancedAgentConfig",
|
|
177
|
+
"LLMAdapterAgent",
|
|
178
|
+
"create_llmadapter_agent",
|
|
179
|
+
"make_subagent_tool",
|
|
180
|
+
"make_specialist_tools",
|
|
181
|
+
"ainvoke_specialists",
|
|
182
|
+
"invoke_specialists",
|
|
183
|
+
"aaggregate_specialists",
|
|
184
|
+
"aggregate_specialists",
|
|
185
|
+
"AggregatedResult",
|
|
186
|
+
"BaseAggregator",
|
|
187
|
+
"JsonMergeAggregator",
|
|
188
|
+
"MajorityVoteAggregator",
|
|
189
|
+
"WeightedScoreAggregator",
|
|
190
|
+
"SharedState",
|
|
191
|
+
"SpecialistContract",
|
|
192
|
+
"CollectingAgentEventHandler",
|
|
193
|
+
"LoggingAgentEventHandler",
|
|
194
|
+
"BaseRoutingPolicy",
|
|
195
|
+
"RoundRobinRoutingPolicy",
|
|
196
|
+
"EscalationRoutingPolicy",
|
|
197
|
+
"CostAwareRoutingPolicy",
|
|
198
|
+
"CheckpointStore",
|
|
199
|
+
"InMemoryCheckpointStore",
|
|
200
|
+
"FileCheckpointStore",
|
|
201
|
+
]
|
|
202
|
+
)
|
|
203
|
+
except ModuleNotFoundError as exc:
|
|
204
|
+
if exc.name != "langchain_core":
|
|
205
|
+
raise
|
llmadapter/base.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class BaseProvider:
|
|
5
|
+
"""
|
|
6
|
+
Global base class for all providers (Chat, Embeddings, etc.).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseChatProvider(BaseProvider, ABC):
|
|
11
|
+
"""
|
|
12
|
+
Abstract base class for chat providers.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def create(self, messages, **kwargs):
|
|
17
|
+
"""
|
|
18
|
+
Create a chat completion.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
messages: List of message dicts or objects.
|
|
22
|
+
**kwargs: Additional parameters for provider.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
ChatCompletion or generator of ChatCompletionChunk.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseEmbeddingProvider(BaseProvider, ABC):
|
|
30
|
+
"""
|
|
31
|
+
Abstract base class for embedding providers.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def create(self, inputs, **kwargs):
|
|
36
|
+
"""
|
|
37
|
+
Create embeddings for the given inputs.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
inputs: List of input strings or objects.
|
|
41
|
+
**kwargs: Additional parameters for provider.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Embedding response object.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BaseResponsesProvider(BaseProvider, ABC):
|
|
49
|
+
"""
|
|
50
|
+
Abstract base class for Responses API providers.
|
|
51
|
+
|
|
52
|
+
Only ``create`` is required; the remaining verbs raise
|
|
53
|
+
:class:`~llmadapter.errors.ResponsesError` by default so a provider can
|
|
54
|
+
implement a partial surface without breaking the adapter.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def create(self, input, **kwargs): # noqa: A002
|
|
59
|
+
"""
|
|
60
|
+
Create a model response.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
input: A string or list of input items.
|
|
64
|
+
**kwargs: Additional parameters for provider (``stream``, ``tools``, …).
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Response, or a generator of ResponseStreamEvent when streaming.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def _unsupported(self, operation: str):
|
|
71
|
+
from .errors import ResponsesError
|
|
72
|
+
raise ResponsesError(
|
|
73
|
+
f"{type(self).__name__} does not support responses.{operation}",
|
|
74
|
+
status_code=501,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def retrieve(self, response_id, **kwargs):
|
|
78
|
+
"""Retrieve a previously created response by id."""
|
|
79
|
+
return self._unsupported("retrieve")
|
|
80
|
+
|
|
81
|
+
def delete(self, response_id, **kwargs):
|
|
82
|
+
"""Delete a stored response by id."""
|
|
83
|
+
return self._unsupported("delete")
|
|
84
|
+
|
|
85
|
+
def cancel(self, response_id, **kwargs):
|
|
86
|
+
"""Cancel an in-progress background response."""
|
|
87
|
+
return self._unsupported("cancel")
|
|
88
|
+
|
|
89
|
+
def compact(self, **kwargs):
|
|
90
|
+
"""Compact a response's conversation history."""
|
|
91
|
+
return self._unsupported("compact")
|
|
92
|
+
|
|
93
|
+
def list_input_items(self, response_id, **kwargs):
|
|
94
|
+
"""List the input items of a response."""
|
|
95
|
+
return self._unsupported("input_items.list")
|
|
96
|
+
|
|
97
|
+
def count_input_tokens(self, **kwargs):
|
|
98
|
+
"""Count the input tokens a request would consume."""
|
|
99
|
+
return self._unsupported("input_tokens.count")
|
|
100
|
+
|
|
101
|
+
def create_conversation(self, items=None, **kwargs):
|
|
102
|
+
return self._unsupported("conversations.create")
|
|
103
|
+
|
|
104
|
+
def retrieve_conversation(self, conversation_id, **kwargs):
|
|
105
|
+
return self._unsupported("conversations.retrieve")
|
|
106
|
+
|
|
107
|
+
def update_conversation(self, conversation_id, **kwargs):
|
|
108
|
+
return self._unsupported("conversations.update")
|
|
109
|
+
|
|
110
|
+
def delete_conversation(self, conversation_id, **kwargs):
|
|
111
|
+
return self._unsupported("conversations.delete")
|
|
112
|
+
|
|
113
|
+
def list_conversation_items(self, conversation_id, **kwargs):
|
|
114
|
+
return self._unsupported("conversations.items.list")
|
|
115
|
+
|
|
116
|
+
def retrieve_conversation_item(self, conversation_id, item_id, **kwargs):
|
|
117
|
+
return self._unsupported("conversations.items.retrieve")
|
|
118
|
+
|
|
119
|
+
def delete_conversation_item(self, conversation_id, item_id, **kwargs):
|
|
120
|
+
return self._unsupported("conversations.items.delete")
|
llmadapter/config.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""
|
|
2
|
+
config.py
|
|
3
|
+
|
|
4
|
+
This module defines configuration classes for different LLM providers using Pydantic models. These classes validate and store provider-specific settings such as API keys, endpoints, rate limits, and deployment information. The CONFIG_REGISTRY dictionary maps provider names to their configuration classes for easy lookup.
|
|
5
|
+
|
|
6
|
+
Classes:
|
|
7
|
+
BaseLLMConfig: Base configuration for all providers.
|
|
8
|
+
AzureOpenAIConfig, AzureFoundryConfig, VertexAIConfig, OpenAIConfig, AnthropicConfig:
|
|
9
|
+
Provider-specific configuration models.
|
|
10
|
+
|
|
11
|
+
Constants:
|
|
12
|
+
CONFIG_REGISTRY: Maps provider names to their config classes.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Any, Literal, Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import BaseModel, ConfigDict, Field, ValidationInfo, field_validator
|
|
18
|
+
|
|
19
|
+
from .constants import (
|
|
20
|
+
DEFAULT_COMPLETION_CONTEXT_WINDOW,
|
|
21
|
+
DEFAULT_EMBEDDING_RPM,
|
|
22
|
+
DEFAULT_EMBEDDING_TPM,
|
|
23
|
+
DEFAULT_MAX_RETRIES,
|
|
24
|
+
DEFAULT_RETRY_BASE_DELAY,
|
|
25
|
+
DEFAULT_RPM,
|
|
26
|
+
DEFAULT_TPM,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class BaseLLMConfig(BaseModel):
|
|
31
|
+
"""
|
|
32
|
+
Base configuration for LLM providers, including model, rate limits, and Redis settings.
|
|
33
|
+
All provider-specific configs should inherit from this class.
|
|
34
|
+
"""
|
|
35
|
+
# A unified adapter accepts the union of provider-specific constructor
|
|
36
|
+
# signatures. Providers consume their own fields and safely ignore the rest.
|
|
37
|
+
model_config = ConfigDict(extra="allow", str_strip_whitespace=True)
|
|
38
|
+
|
|
39
|
+
provider: str = Field(..., min_length=1, description="Provider name (openai, azure_openai, azure_foundry, etc.)")
|
|
40
|
+
model: Optional[str] = Field(default=None, description="Model name or ID")
|
|
41
|
+
deployment_name: Optional[str] = Field(
|
|
42
|
+
default=None,
|
|
43
|
+
description="Provider deployment/model alias; when supplied it is the default effective model",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def effective_model(self) -> Optional[str]:
|
|
48
|
+
"""Return the unified deployment-first default model identifier."""
|
|
49
|
+
return self.deployment_name or self.model
|
|
50
|
+
rpm: int = Field(default=DEFAULT_RPM, ge=0, description="Requests per minute limit (chat)")
|
|
51
|
+
tpm: int = Field(default=DEFAULT_TPM, ge=0, description="Tokens per minute limit (chat)")
|
|
52
|
+
embedding_rpm: int = Field(default=DEFAULT_EMBEDDING_RPM, ge=0,
|
|
53
|
+
description="Requests per minute limit (embeddings)")
|
|
54
|
+
embedding_tpm: int = Field(default=DEFAULT_EMBEDDING_TPM, ge=0,
|
|
55
|
+
description="Tokens per minute limit (embeddings)")
|
|
56
|
+
completion_context_window: int = Field(default=DEFAULT_COMPLETION_CONTEXT_WINDOW, gt=0,
|
|
57
|
+
description="Context window size for completion models")
|
|
58
|
+
context_output_reserve_tokens: int = Field(default=0, ge=0,
|
|
59
|
+
description="Output-token capacity reserved for every request")
|
|
60
|
+
context_overhead_tokens: int = Field(default=0, ge=0,
|
|
61
|
+
description="Additional caller-known protocol overhead tokens")
|
|
62
|
+
responses_capabilities: dict[str, bool] = Field(
|
|
63
|
+
default_factory=dict,
|
|
64
|
+
description="Per-target Responses capability overrides; False rejects a feature before dispatch",
|
|
65
|
+
)
|
|
66
|
+
mcp_policy: Optional[dict[str, Any]] = Field(
|
|
67
|
+
default=None, description="Explicit policy required before remote MCP or connector tools are sent"
|
|
68
|
+
)
|
|
69
|
+
redis_url: Optional[str] = Field(default=None,
|
|
70
|
+
description="Redis URL for distributed rate limiting; None uses process-local limits")
|
|
71
|
+
rate_limit_failure_policy: Literal["memory_fallback", "fail_open", "fail_closed"] = Field(
|
|
72
|
+
default="memory_fallback",
|
|
73
|
+
description="Behavior when a configured Redis rate-limit backend is unavailable",
|
|
74
|
+
)
|
|
75
|
+
redis_connect_timeout: float = Field(default=1.0, gt=0, allow_inf_nan=False,
|
|
76
|
+
description="Redis connect/read timeout in seconds")
|
|
77
|
+
user_id: str = Field(default="global", min_length=1, description="User identifier for rate limiting")
|
|
78
|
+
backup: Optional[dict[str, Any]] = Field(default=None, description="Backup provider config for failover")
|
|
79
|
+
timeout: Optional[float] = Field(default=None, gt=0, allow_inf_nan=False,
|
|
80
|
+
description="Request timeout in seconds (applies to all providers)")
|
|
81
|
+
max_retries: int = Field(default=DEFAULT_MAX_RETRIES, ge=0,
|
|
82
|
+
description="Number of retry attempts for transient errors (429, 5xx, connection). 0 = no retry.")
|
|
83
|
+
retry_base_delay: float = Field(default=DEFAULT_RETRY_BASE_DELAY, ge=0, allow_inf_nan=False,
|
|
84
|
+
description="Base delay in seconds for exponential back-off between retries.")
|
|
85
|
+
|
|
86
|
+
@field_validator("provider", "user_id", mode="before")
|
|
87
|
+
def required_string_fields(cls, v, info: ValidationInfo):
|
|
88
|
+
"""
|
|
89
|
+
Validate required string fields to ensure they are not empty.
|
|
90
|
+
"""
|
|
91
|
+
if v is None or (isinstance(v, str) and not v.strip()):
|
|
92
|
+
raise ValueError(f"{info.field_name} is required and cannot be empty")
|
|
93
|
+
return v
|
|
94
|
+
|
|
95
|
+
@field_validator("backup")
|
|
96
|
+
@classmethod
|
|
97
|
+
def validate_backup_shape(cls, value: Optional[dict[str, Any]]) -> Optional[dict[str, Any]]:
|
|
98
|
+
if value is None:
|
|
99
|
+
return None
|
|
100
|
+
if not value.get("provider") or not isinstance(value["provider"], str):
|
|
101
|
+
raise ValueError("backup.provider is required and must be a non-empty string")
|
|
102
|
+
return value
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AzureOpenAIConfig(BaseLLMConfig):
|
|
106
|
+
"""
|
|
107
|
+
Configuration for Azure OpenAI provider, including API key, endpoint, and deployment info.
|
|
108
|
+
Inherits from BaseLLMConfig.
|
|
109
|
+
"""
|
|
110
|
+
api_key: str = Field(..., min_length=1, description="API key for authentication")
|
|
111
|
+
endpoint: str = Field(..., min_length=1, description="Custom endpoint or base URL")
|
|
112
|
+
api_version: str = Field(..., min_length=1, description="API version (e.g., for Azure OpenAI)")
|
|
113
|
+
responses_api_version: Optional[str] = Field(
|
|
114
|
+
default=None, description="API version override for responses.* calls (Azure OpenAI)")
|
|
115
|
+
deployment_name: str = Field(..., min_length=1, description="Deployment name for Azure OpenAI")
|
|
116
|
+
region: Optional[str] = Field(default=None, description="Region for Azure services")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class AzureFoundryConfig(BaseLLMConfig):
|
|
120
|
+
"""
|
|
121
|
+
Configuration for Azure Foundry provider, including API key, endpoint, and deployment info.
|
|
122
|
+
Inherits from BaseLLMConfig.
|
|
123
|
+
"""
|
|
124
|
+
api_key: str = Field(..., min_length=1, description="API key for authentication")
|
|
125
|
+
endpoint: str = Field(..., min_length=1, description="Custom endpoint or base URL")
|
|
126
|
+
deployment_name: str = Field(..., min_length=1, description="Deployment Name or Model name for Azure Foundry")
|
|
127
|
+
api_version: str = Field(..., min_length=1, description="API version (e.g., for Azure Foundry)")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class VertexAIConfig(BaseLLMConfig):
|
|
131
|
+
"""
|
|
132
|
+
Configuration for VertexAI provider, including location, scopes, and model info.
|
|
133
|
+
Inherits from BaseLLMConfig.
|
|
134
|
+
"""
|
|
135
|
+
location: str = Field(..., min_length=1, description="GCP location (e.g., us-central1)")
|
|
136
|
+
credentials_path: str = Field(..., min_length=1, description="Path to GCP credentials JSON file")
|
|
137
|
+
scopes: list[str] = Field(default_factory=lambda: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
138
|
+
description="OAuth scopes for GCP authentication")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class OpenAIConfig(BaseLLMConfig):
|
|
142
|
+
"""
|
|
143
|
+
Configuration for native OpenAI provider, including API key and optional base URL.
|
|
144
|
+
Inherits from BaseLLMConfig.
|
|
145
|
+
"""
|
|
146
|
+
api_key: str = Field(..., min_length=1, description="API key for OpenAI authentication")
|
|
147
|
+
endpoint: Optional[str] = Field(default=None, description="Custom base URL (e.g., for proxies or compatible APIs)")
|
|
148
|
+
organization: Optional[str] = Field(default=None, description="OpenAI organization ID")
|
|
149
|
+
project: Optional[str] = Field(default=None, description="OpenAI project ID to scope requests")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class AnthropicConfig(BaseLLMConfig):
|
|
153
|
+
"""
|
|
154
|
+
Configuration for the Anthropic (Claude) provider.
|
|
155
|
+
|
|
156
|
+
Prompt caching is enabled by default (``enable_prompt_caching=True``).
|
|
157
|
+
When enabled, the provider automatically injects ``cache_control`` on
|
|
158
|
+
system messages and tool definitions so repeated calls reuse the cached
|
|
159
|
+
prefix instead of re-charging full input-token cost.
|
|
160
|
+
|
|
161
|
+
Inherits from BaseLLMConfig.
|
|
162
|
+
"""
|
|
163
|
+
api_key: str = Field(..., min_length=1, description="Anthropic API key")
|
|
164
|
+
deployment_name: Optional[str] = Field(
|
|
165
|
+
default=None,
|
|
166
|
+
description=(
|
|
167
|
+
"Preferred deployment/model identifier for Anthropic-compatible endpoints. "
|
|
168
|
+
"If provided with model, deployment_name takes precedence."
|
|
169
|
+
),
|
|
170
|
+
)
|
|
171
|
+
endpoint: Optional[str] = Field(
|
|
172
|
+
default=None,
|
|
173
|
+
description="Custom base URL (e.g., for an Anthropic-compatible proxy or gateway)",
|
|
174
|
+
)
|
|
175
|
+
enable_prompt_caching: bool = Field(
|
|
176
|
+
default=True,
|
|
177
|
+
description=(
|
|
178
|
+
"Auto-inject cache_control on system messages and tool definitions. "
|
|
179
|
+
"Requires ≥2048 cacheable tokens for Claude Sonnet/Opus, ≥1024 for Haiku."
|
|
180
|
+
),
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Register in config registry
|
|
185
|
+
CONFIG_REGISTRY = {
|
|
186
|
+
"azure_openai": AzureOpenAIConfig,
|
|
187
|
+
"azure_foundry": AzureFoundryConfig,
|
|
188
|
+
"vertexai": VertexAIConfig,
|
|
189
|
+
"openai": OpenAIConfig,
|
|
190
|
+
"anthropic": AnthropicConfig,
|
|
191
|
+
}
|
llmadapter/constants.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
DEFAULT_RPM = 10000
|
|
2
|
+
DEFAULT_TPM = 1000000
|
|
3
|
+
DEFAULT_EMBEDDING_RPM = 10000
|
|
4
|
+
DEFAULT_EMBEDDING_TPM = 1000000
|
|
5
|
+
DEFAULT_WINDOW_SECONDS = 60
|
|
6
|
+
DEFAULT_COMPLETION_CONTEXT_WINDOW = 123000 # (based on 124000 token limit minus some buffer)
|
|
7
|
+
|
|
8
|
+
# Retry / exponential back-off defaults
|
|
9
|
+
DEFAULT_MAX_RETRIES = 3 # number of *retries* (not total attempts)
|
|
10
|
+
DEFAULT_RETRY_BASE_DELAY = 1.0 # seconds — doubling each attempt
|
|
11
|
+
DEFAULT_RETRY_MAX_DELAY = 60.0 # seconds — cap on the backoff ceiling
|
|
12
|
+
|
|
13
|
+
DEFAULT_ANTHROPIC_MODEL = "claude-opus-4-5"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core
|
|
3
|
+
|
|
4
|
+
The adapter engine: public ``LLMAdapter`` / ``AsyncLLMAdapter`` entry points plus
|
|
5
|
+
the internal handlers, provider registries, retry/failover/preprocess helpers,
|
|
6
|
+
and log redaction.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .async_adapter import AsyncLLMAdapter
|
|
11
|
+
from .failover import merge_backup_config, prepare_backup_provider
|
|
12
|
+
from .preprocess import ProviderCache, prepare_messages
|
|
13
|
+
from .redaction import redact_sensitive
|
|
14
|
+
from .registry import (
|
|
15
|
+
ASYNC_CHAT_PROVIDER_REGISTRY,
|
|
16
|
+
CHAT_PROVIDER_REGISTRY,
|
|
17
|
+
EMBEDDING_PROVIDER_REGISTRY,
|
|
18
|
+
)
|
|
19
|
+
from .sync_adapter import LLMAdapter
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"LLMAdapter",
|
|
23
|
+
"AsyncLLMAdapter",
|
|
24
|
+
"CHAT_PROVIDER_REGISTRY",
|
|
25
|
+
"ASYNC_CHAT_PROVIDER_REGISTRY",
|
|
26
|
+
"EMBEDDING_PROVIDER_REGISTRY",
|
|
27
|
+
"redact_sensitive",
|
|
28
|
+
"prepare_messages",
|
|
29
|
+
"ProviderCache",
|
|
30
|
+
"prepare_backup_provider",
|
|
31
|
+
"merge_backup_config",
|
|
32
|
+
]
|