llm-api-adapter 0.3.0__tar.gz → 0.3.2__tar.gz
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.
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/PKG-INFO +12 -6
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/README.md +11 -5
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/pyproject.toml +1 -1
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/anthropic_adapter.py +76 -39
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/google_adapter.py +40 -21
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/openai_adapter.py +21 -12
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.json +28 -17
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/sync_client.py +15 -4
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/chat_message.py +14 -7
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/responses/chat_response.py +7 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/tool_call.py +1 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/LICENSE +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/MANIFEST.in +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/setup.cfg +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/base_adapter.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/config_errors.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/llm_api_error.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/google/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/google/sync_client.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/openai/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/openai/sync_client.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/responses/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/__init__.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/tool_spec.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/universal_adapter.py +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter.egg-info/SOURCES.txt +0 -0
- {llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/tests/tests_runner.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: llm-api-adapter
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Lightweight, pluggable adapter for multiple LLM APIs (OpenAI, Anthropic, Google)
|
|
5
5
|
Author: Sergey Inozemtsev
|
|
6
6
|
License: MIT License
|
|
@@ -41,6 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|
# LLM API Adapter SDK for Python
|
|
42
42
|
[](https://pepy.tech/projects/llm-api-adapter)
|
|
43
43
|
[](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml)
|
|
44
|
+
[](https://codecov.io/github/Inozem/llm_api_adapter)
|
|
44
45
|
|
|
45
46
|
## Overview
|
|
46
47
|
|
|
@@ -50,7 +51,7 @@ Currently, the project supports OpenAI, Anthropic, and Google with a consistent
|
|
|
50
51
|
|
|
51
52
|
### Version
|
|
52
53
|
|
|
53
|
-
Current version: 0.3.
|
|
54
|
+
Current version: 0.3.2
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
## Features
|
|
@@ -183,9 +184,9 @@ The SDK provides a set of standardized errors for easier debugging and integrati
|
|
|
183
184
|
|
|
184
185
|
The SDK allows you to easily switch between LLM providers and specify the model you want to use. Currently supported providers are OpenAI, Anthropic, and Google.
|
|
185
186
|
|
|
186
|
-
- **OpenAI**: You can use models like `gpt-5.4`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`.
|
|
187
|
-
- **Anthropic**: Available models include `claude-opus-4-
|
|
188
|
-
- **Google**: Models such as `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite
|
|
187
|
+
- **OpenAI**: You can use models like `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`.
|
|
188
|
+
- **Anthropic**: Available models include `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `claude-opus-4-1`.
|
|
189
|
+
- **Google**: Models such as `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash` can be used.
|
|
189
190
|
|
|
190
191
|
Example:
|
|
191
192
|
|
|
@@ -487,6 +488,7 @@ first = adapter.chat(
|
|
|
487
488
|
messages=messages,
|
|
488
489
|
tools=tools,
|
|
489
490
|
tool_choice="auto",
|
|
491
|
+
max_tokens=1000,
|
|
490
492
|
)
|
|
491
493
|
|
|
492
494
|
if first.tool_calls:
|
|
@@ -501,7 +503,11 @@ if first.tool_calls:
|
|
|
501
503
|
)
|
|
502
504
|
)
|
|
503
505
|
|
|
504
|
-
final = adapter.chat(
|
|
506
|
+
final = adapter.chat(
|
|
507
|
+
messages=messages,
|
|
508
|
+
previous_response=first,
|
|
509
|
+
max_tokens=1000
|
|
510
|
+
)
|
|
505
511
|
print(final.content)
|
|
506
512
|
```
|
|
507
513
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# LLM API Adapter SDK for Python
|
|
2
2
|
[](https://pepy.tech/projects/llm-api-adapter)
|
|
3
3
|
[](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml)
|
|
4
|
+
[](https://codecov.io/github/Inozem/llm_api_adapter)
|
|
4
5
|
|
|
5
6
|
## Overview
|
|
6
7
|
|
|
@@ -10,7 +11,7 @@ Currently, the project supports OpenAI, Anthropic, and Google with a consistent
|
|
|
10
11
|
|
|
11
12
|
### Version
|
|
12
13
|
|
|
13
|
-
Current version: 0.3.
|
|
14
|
+
Current version: 0.3.2
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
## Features
|
|
@@ -143,9 +144,9 @@ The SDK provides a set of standardized errors for easier debugging and integrati
|
|
|
143
144
|
|
|
144
145
|
The SDK allows you to easily switch between LLM providers and specify the model you want to use. Currently supported providers are OpenAI, Anthropic, and Google.
|
|
145
146
|
|
|
146
|
-
- **OpenAI**: You can use models like `gpt-5.4`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`.
|
|
147
|
-
- **Anthropic**: Available models include `claude-opus-4-
|
|
148
|
-
- **Google**: Models such as `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite
|
|
147
|
+
- **OpenAI**: You can use models like `gpt-5.5`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`, `gpt-5.2`, `gpt-5.1`, `gpt-5`, `gpt-5-mini`, `gpt-5-nano`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-4o`, `gpt-4o-mini`.
|
|
148
|
+
- **Anthropic**: Available models include `claude-opus-4-8`, `claude-opus-4-7`, `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `claude-opus-4-1`.
|
|
149
|
+
- **Google**: Models such as `gemini-3.5-flash`, `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash` can be used.
|
|
149
150
|
|
|
150
151
|
Example:
|
|
151
152
|
|
|
@@ -447,6 +448,7 @@ first = adapter.chat(
|
|
|
447
448
|
messages=messages,
|
|
448
449
|
tools=tools,
|
|
449
450
|
tool_choice="auto",
|
|
451
|
+
max_tokens=1000,
|
|
450
452
|
)
|
|
451
453
|
|
|
452
454
|
if first.tool_calls:
|
|
@@ -461,7 +463,11 @@ if first.tool_calls:
|
|
|
461
463
|
)
|
|
462
464
|
)
|
|
463
465
|
|
|
464
|
-
final = adapter.chat(
|
|
466
|
+
final = adapter.chat(
|
|
467
|
+
messages=messages,
|
|
468
|
+
previous_response=first,
|
|
469
|
+
max_tokens=1000
|
|
470
|
+
)
|
|
465
471
|
print(final.content)
|
|
466
472
|
```
|
|
467
473
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "llm-api-adapter"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "Lightweight, pluggable adapter for multiple LLM APIs (OpenAI, Anthropic, Google)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/anthropic_adapter.py
RENAMED
|
@@ -32,19 +32,19 @@ class AnthropicAdapter(LLMAdapterBase):
|
|
|
32
32
|
tools: Optional[List[ToolSpec]] = None,
|
|
33
33
|
tool_choice: Optional[str | dict] = None,
|
|
34
34
|
parallel_tool_calls: Optional[bool] = None,
|
|
35
|
-
|
|
35
|
+
previous_response: Optional[ChatResponse] = None,
|
|
36
36
|
) -> ChatResponse:
|
|
37
|
-
temperature = self._validate_parameter(
|
|
38
|
-
|
|
39
|
-
)
|
|
40
|
-
top_p = self._validate_parameter(name="top_p", value=top_p, min_value=0, max_value=1)
|
|
37
|
+
temperature = self._validate_parameter("temperature", temperature, 0, 2)
|
|
38
|
+
top_p = self._validate_parameter("top_p", top_p, 0, 1)
|
|
41
39
|
try:
|
|
42
40
|
self._validate_tools(tools)
|
|
43
41
|
validated_tools = tools
|
|
44
|
-
normalized_tool_choice = self._normalize_tool_choice(
|
|
42
|
+
normalized_tool_choice = self._normalize_tool_choice(
|
|
43
|
+
tool_choice,
|
|
44
|
+
validated_tools,
|
|
45
|
+
)
|
|
45
46
|
normalized_messages = self._normalize_messages(messages)
|
|
46
47
|
system_prompt, transformed_messages = normalized_messages.to_anthropic()
|
|
47
|
-
client = ClaudeSyncClient(api_key=self.api_key)
|
|
48
48
|
params: Dict[str, Any] = {
|
|
49
49
|
"model": self.model,
|
|
50
50
|
"messages": transformed_messages,
|
|
@@ -55,23 +55,36 @@ class AnthropicAdapter(LLMAdapterBase):
|
|
|
55
55
|
"timeout_s": timeout_s,
|
|
56
56
|
}
|
|
57
57
|
if validated_tools:
|
|
58
|
-
params["tools"] = [
|
|
58
|
+
params["tools"] = [
|
|
59
|
+
self._to_anthropic_tool(tool)
|
|
60
|
+
for tool in validated_tools
|
|
61
|
+
]
|
|
59
62
|
if normalized_tool_choice is not None:
|
|
60
|
-
params["tool_choice"] = self._to_anthropic_tool_choice(
|
|
63
|
+
params["tool_choice"] = self._to_anthropic_tool_choice(
|
|
64
|
+
normalized_tool_choice
|
|
65
|
+
)
|
|
61
66
|
if parallel_tool_calls is False:
|
|
62
67
|
params["disable_parallel_tool_use"] = True
|
|
63
68
|
elif parallel_tool_calls is True:
|
|
64
69
|
params["disable_parallel_tool_use"] = False
|
|
65
70
|
if reasoning_level:
|
|
66
|
-
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
71
|
+
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
72
|
+
reasoning_level
|
|
73
|
+
)
|
|
67
74
|
if normalized_reasoning_level:
|
|
68
75
|
self.validate_reasoning_and_tokens(
|
|
69
76
|
max_tokens=max_tokens,
|
|
70
77
|
reasoning_level=reasoning_level,
|
|
71
78
|
normalized_reasoning_level=normalized_reasoning_level,
|
|
72
79
|
)
|
|
73
|
-
params["
|
|
80
|
+
params["budget_tokens"] = normalized_reasoning_level
|
|
81
|
+
if self.is_reasoning:
|
|
82
|
+
effort = self._reasoning_level_to_effort(reasoning_level)
|
|
83
|
+
if effort:
|
|
84
|
+
params["effort"] = effort
|
|
74
85
|
params = {k: v for k, v in params.items() if v is not None}
|
|
86
|
+
_ = previous_response
|
|
87
|
+
client = ClaudeSyncClient(api_key=self.api_key)
|
|
75
88
|
response = client.chat_completion(**params)
|
|
76
89
|
chat_response = ChatResponse.from_anthropic_response(response)
|
|
77
90
|
if self.pricing:
|
|
@@ -88,12 +101,18 @@ class AnthropicAdapter(LLMAdapterBase):
|
|
|
88
101
|
self.handle_error(error=e, error_message=error_message)
|
|
89
102
|
|
|
90
103
|
def _to_anthropic_tool(self, tool: ToolSpec) -> Dict[str, Any]:
|
|
91
|
-
payload: Dict[str, Any] = {
|
|
104
|
+
payload: Dict[str, Any] = {
|
|
105
|
+
"name": tool.name,
|
|
106
|
+
"input_schema": tool.json_schema,
|
|
107
|
+
}
|
|
92
108
|
if tool.description:
|
|
93
109
|
payload["description"] = tool.description
|
|
94
110
|
return payload
|
|
95
111
|
|
|
96
|
-
def _to_anthropic_tool_choice(
|
|
112
|
+
def _to_anthropic_tool_choice(
|
|
113
|
+
self,
|
|
114
|
+
tool_choice: Optional[str],
|
|
115
|
+
) -> Optional[Dict[str, Any]]:
|
|
97
116
|
if tool_choice is None:
|
|
98
117
|
return None
|
|
99
118
|
if tool_choice == "none":
|
|
@@ -102,42 +121,60 @@ class AnthropicAdapter(LLMAdapterBase):
|
|
|
102
121
|
return {"type": tool_choice}
|
|
103
122
|
return {"type": "tool", "name": tool_choice}
|
|
104
123
|
|
|
124
|
+
def _validate_not_bool(self, level) -> None:
|
|
125
|
+
if isinstance(level, bool):
|
|
126
|
+
raise ValueError("Invalid type for level: bool is not accepted")
|
|
127
|
+
|
|
128
|
+
def _validate_reasoning_str(self, level: str) -> None:
|
|
129
|
+
if level not in self.reasoning_levels:
|
|
130
|
+
raise ValueError(
|
|
131
|
+
f"Unknown reasoning level key: {level!r}. "
|
|
132
|
+
f"Valid keys: {list(self.reasoning_levels.keys())}"
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
def _resolve_reasoning_int(self, level: int) -> int:
|
|
136
|
+
if isinstance(level, int):
|
|
137
|
+
return level
|
|
138
|
+
raise ValueError(
|
|
139
|
+
"Invalid type for level: expected int or str, "
|
|
140
|
+
f"got {type(level).__name__!r}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def _reasoning_level_to_effort(self, level: str | int) -> str | None:
|
|
144
|
+
self._validate_not_bool(level)
|
|
145
|
+
if isinstance(level, str):
|
|
146
|
+
self._validate_reasoning_str(level)
|
|
147
|
+
return None if level == "none" else level
|
|
148
|
+
numeric = self._resolve_reasoning_int(level)
|
|
149
|
+
for key, threshold in self.reasoning_levels.items():
|
|
150
|
+
if threshold > 0 and numeric <= threshold:
|
|
151
|
+
return key
|
|
152
|
+
return list(self.reasoning_levels)[-1]
|
|
153
|
+
|
|
105
154
|
def _normalize_reasoning_level(self, level: str | int) -> int | None:
|
|
106
155
|
minimum_level = 1024
|
|
107
|
-
|
|
108
|
-
if level is not None and not self.is_reasoning:
|
|
156
|
+
if not self.is_reasoning:
|
|
109
157
|
warning_message = (
|
|
110
158
|
f"Model '{self.model}' does not support reasoning — reasoning disabled."
|
|
111
159
|
)
|
|
112
160
|
warnings.warn(warning_message, UserWarning)
|
|
113
161
|
logger.info(warning_message)
|
|
114
162
|
return None
|
|
115
|
-
|
|
116
|
-
raise ValueError("Invalid type for level: bool is not accepted")
|
|
163
|
+
self._validate_not_bool(level)
|
|
117
164
|
if isinstance(level, str):
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if normalized_level is not None:
|
|
128
|
-
if normalized_level >= minimum_level:
|
|
129
|
-
return normalized_level
|
|
130
|
-
warning_message = (
|
|
131
|
-
f"Reasoning level '{level}' is below the minimum supported value {minimum_level}; "
|
|
132
|
-
f"using {minimum_level} instead."
|
|
133
|
-
)
|
|
134
|
-
warnings.warn(warning_message, UserWarning)
|
|
135
|
-
logger.info(warning_message)
|
|
136
|
-
return minimum_level
|
|
137
|
-
raise ValueError(
|
|
138
|
-
"Invalid type for level: expected int or str, "
|
|
139
|
-
f"got {type(level).__name__!r}"
|
|
165
|
+
self._validate_reasoning_str(level)
|
|
166
|
+
numeric = self.reasoning_levels[level]
|
|
167
|
+
else:
|
|
168
|
+
numeric = self._resolve_reasoning_int(level)
|
|
169
|
+
if numeric >= minimum_level:
|
|
170
|
+
return numeric
|
|
171
|
+
warning_message = (
|
|
172
|
+
f"Reasoning level '{level}' is below the minimum supported value "
|
|
173
|
+
f"{minimum_level}; using {minimum_level} instead."
|
|
140
174
|
)
|
|
175
|
+
warnings.warn(warning_message, UserWarning)
|
|
176
|
+
logger.info(warning_message)
|
|
177
|
+
return minimum_level
|
|
141
178
|
|
|
142
179
|
def validate_reasoning_and_tokens(
|
|
143
180
|
self,
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/google_adapter.py
RENAMED
|
@@ -31,29 +31,39 @@ class GoogleAdapter(LLMAdapterBase):
|
|
|
31
31
|
tools: Optional[List[ToolSpec]] = None,
|
|
32
32
|
tool_choice: Optional[str | dict] = None,
|
|
33
33
|
parallel_tool_calls: Optional[bool] = None,
|
|
34
|
-
|
|
34
|
+
previous_response: Optional[ChatResponse] = None,
|
|
35
35
|
) -> ChatResponse:
|
|
36
36
|
temperature = self._validate_parameter(
|
|
37
|
-
name="temperature",
|
|
37
|
+
name="temperature",
|
|
38
|
+
value=temperature,
|
|
39
|
+
min_value=0,
|
|
40
|
+
max_value=2,
|
|
38
41
|
)
|
|
39
42
|
top_p = self._validate_parameter(
|
|
40
|
-
name="top_p",
|
|
43
|
+
name="top_p",
|
|
44
|
+
value=top_p,
|
|
45
|
+
min_value=0,
|
|
46
|
+
max_value=1,
|
|
41
47
|
)
|
|
42
48
|
try:
|
|
43
49
|
self._validate_tools(tools)
|
|
44
50
|
validated_tools = tools
|
|
45
51
|
normalized_tool_choice = self._normalize_tool_choice(
|
|
46
|
-
tool_choice,
|
|
52
|
+
tool_choice,
|
|
53
|
+
validated_tools,
|
|
47
54
|
)
|
|
48
55
|
normalized_messages = self._normalize_messages(messages)
|
|
56
|
+
_ = previous_response
|
|
49
57
|
system_prompt, transformed_messages = normalized_messages.to_google()
|
|
50
|
-
generation_config = {
|
|
58
|
+
generation_config: Dict[str, Any] = {
|
|
51
59
|
"maxOutputTokens": max_tokens,
|
|
52
60
|
"temperature": temperature,
|
|
53
61
|
"topP": top_p,
|
|
54
62
|
}
|
|
55
63
|
if reasoning_level:
|
|
56
|
-
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
64
|
+
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
65
|
+
reasoning_level
|
|
66
|
+
)
|
|
57
67
|
if normalized_reasoning_level is not None:
|
|
58
68
|
generation_config["thinkingConfig"] = {
|
|
59
69
|
"thinkingBudget": normalized_reasoning_level,
|
|
@@ -64,10 +74,17 @@ class GoogleAdapter(LLMAdapterBase):
|
|
|
64
74
|
"generationConfig": generation_config,
|
|
65
75
|
}
|
|
66
76
|
if system_prompt:
|
|
67
|
-
payload["system_instruction"] = {
|
|
77
|
+
payload["system_instruction"] = {
|
|
78
|
+
"parts": [{"text": system_prompt}]
|
|
79
|
+
}
|
|
68
80
|
if validated_tools:
|
|
69
81
|
payload["tools"] = [
|
|
70
|
-
{
|
|
82
|
+
{
|
|
83
|
+
"functionDeclarations": [
|
|
84
|
+
self._to_google_function_declaration(tool)
|
|
85
|
+
for tool in validated_tools
|
|
86
|
+
]
|
|
87
|
+
}
|
|
71
88
|
]
|
|
72
89
|
tool_config = self._to_google_tool_config(normalized_tool_choice)
|
|
73
90
|
if tool_config is not None:
|
|
@@ -94,34 +111,36 @@ class GoogleAdapter(LLMAdapterBase):
|
|
|
94
111
|
self.handle_error(error=e, error_message=error_message)
|
|
95
112
|
|
|
96
113
|
def _to_google_function_declaration(self, tool: ToolSpec) -> Dict[str, Any]:
|
|
97
|
-
|
|
114
|
+
declaration: Dict[str, Any] = {
|
|
98
115
|
"name": tool.name,
|
|
99
116
|
"parametersJsonSchema": tool.json_schema,
|
|
100
117
|
}
|
|
101
118
|
if tool.description:
|
|
102
|
-
|
|
103
|
-
return
|
|
119
|
+
declaration["description"] = tool.description
|
|
120
|
+
return declaration
|
|
104
121
|
|
|
105
|
-
def _to_google_tool_config(
|
|
122
|
+
def _to_google_tool_config(
|
|
123
|
+
self,
|
|
124
|
+
tool_choice: Optional[str],
|
|
125
|
+
) -> Optional[Dict[str, Any]]:
|
|
106
126
|
if tool_choice is None:
|
|
107
127
|
return None
|
|
108
128
|
if tool_choice == "none":
|
|
109
129
|
mode = "NONE"
|
|
110
|
-
|
|
130
|
+
allowed_function_names = None
|
|
111
131
|
elif tool_choice == "auto":
|
|
112
132
|
mode = "AUTO"
|
|
113
|
-
|
|
133
|
+
allowed_function_names = None
|
|
114
134
|
elif tool_choice == "any":
|
|
115
135
|
mode = "ANY"
|
|
116
|
-
|
|
136
|
+
allowed_function_names = None
|
|
117
137
|
else:
|
|
118
138
|
mode = "ANY"
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return {"functionCallingConfig": cfg}
|
|
139
|
+
allowed_function_names = [tool_choice]
|
|
140
|
+
function_calling_config: Dict[str, Any] = {"mode": mode}
|
|
141
|
+
if allowed_function_names:
|
|
142
|
+
function_calling_config["allowedFunctionNames"] = allowed_function_names
|
|
143
|
+
return {"functionCallingConfig": function_calling_config}
|
|
125
144
|
|
|
126
145
|
def _normalize_reasoning_level(self, level: str | int | None) -> int | None:
|
|
127
146
|
minimum_level = 0
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/openai_adapter.py
RENAMED
|
@@ -30,7 +30,7 @@ class OpenAIAdapter(LLMAdapterBase):
|
|
|
30
30
|
tools: Optional[List[ToolSpec]] = None,
|
|
31
31
|
tool_choice: Any = None,
|
|
32
32
|
parallel_tool_calls: Optional[bool] = None,
|
|
33
|
-
|
|
33
|
+
previous_response: Optional[ChatResponse] = None,
|
|
34
34
|
) -> ChatResponse:
|
|
35
35
|
temperature = self._validate_parameter(
|
|
36
36
|
name="temperature",
|
|
@@ -51,7 +51,13 @@ class OpenAIAdapter(LLMAdapterBase):
|
|
|
51
51
|
client = OpenAISyncClient(api_key=self.api_key)
|
|
52
52
|
normalized_messages = self._normalize_messages(messages)
|
|
53
53
|
use_responses_api = client._should_use_responses_api(self.model)
|
|
54
|
-
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
54
|
+
normalized_reasoning_level = self._normalize_reasoning_level(
|
|
55
|
+
reasoning_level
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
previous_response_id: Optional[str] = None
|
|
59
|
+
if previous_response is not None:
|
|
60
|
+
previous_response_id = previous_response.response_id
|
|
55
61
|
|
|
56
62
|
if use_responses_api:
|
|
57
63
|
transformed_messages = normalized_messages.to_openai_responses_input()
|
|
@@ -111,21 +117,24 @@ class OpenAIAdapter(LLMAdapterBase):
|
|
|
111
117
|
error_message = getattr(e, "text", None) or str(e)
|
|
112
118
|
self.handle_error(error=e, error_message=error_message)
|
|
113
119
|
|
|
114
|
-
def _map_tools_to_openai(
|
|
120
|
+
def _map_tools_to_openai(
|
|
121
|
+
self,
|
|
122
|
+
tools: Optional[List[ToolSpec]],
|
|
123
|
+
) -> Optional[List[Dict[str, Any]]]:
|
|
115
124
|
if not tools:
|
|
116
125
|
return None
|
|
117
126
|
mapped: List[Dict[str, Any]] = []
|
|
118
|
-
for
|
|
119
|
-
|
|
120
|
-
"name":
|
|
121
|
-
"parameters":
|
|
127
|
+
for tool in tools:
|
|
128
|
+
function_payload: Dict[str, Any] = {
|
|
129
|
+
"name": tool.name,
|
|
130
|
+
"parameters": tool.json_schema,
|
|
122
131
|
}
|
|
123
|
-
if
|
|
124
|
-
|
|
132
|
+
if tool.description:
|
|
133
|
+
function_payload["description"] = tool.description
|
|
125
134
|
mapped.append(
|
|
126
135
|
{
|
|
127
136
|
"type": "function",
|
|
128
|
-
"function":
|
|
137
|
+
"function": function_payload,
|
|
129
138
|
}
|
|
130
139
|
)
|
|
131
140
|
return mapped
|
|
@@ -167,7 +176,7 @@ class OpenAIAdapter(LLMAdapterBase):
|
|
|
167
176
|
"Invalid type for level: expected int or str, "
|
|
168
177
|
f"got {type(level).__name__!r}"
|
|
169
178
|
)
|
|
170
|
-
|
|
179
|
+
|
|
171
180
|
def _map_tools_to_openai_responses(
|
|
172
181
|
self,
|
|
173
182
|
tools: Optional[List[ToolSpec]],
|
|
@@ -186,7 +195,7 @@ class OpenAIAdapter(LLMAdapterBase):
|
|
|
186
195
|
}
|
|
187
196
|
)
|
|
188
197
|
return result
|
|
189
|
-
|
|
198
|
+
|
|
190
199
|
def _map_tool_choice_to_openai_responses(self, tool_choice: Any) -> Any:
|
|
191
200
|
if tool_choice is None:
|
|
192
201
|
return None
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.json
RENAMED
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
{
|
|
2
|
-
"schema_version":
|
|
3
|
-
"effective_date": "2026-
|
|
2
|
+
"schema_version": 6,
|
|
3
|
+
"effective_date": "2026-06-22",
|
|
4
4
|
"providers": {
|
|
5
5
|
"openai": {
|
|
6
6
|
"currency": "USD",
|
|
7
7
|
"models": {
|
|
8
|
+
"gpt-5.5": {
|
|
9
|
+
"pricing": {"in_per_1m": 5.0, "out_per_1m": 30.0},
|
|
10
|
+
"is_reasoning": true
|
|
11
|
+
},
|
|
8
12
|
"gpt-5.4": {
|
|
9
13
|
"pricing": {"in_per_1m": 2.5, "out_per_1m": 15.0},
|
|
10
14
|
"is_reasoning": true
|
|
11
15
|
},
|
|
16
|
+
"gpt-5.4-mini": {
|
|
17
|
+
"pricing": {"in_per_1m": 0.75, "out_per_1m": 4.5},
|
|
18
|
+
"is_reasoning": true
|
|
19
|
+
},
|
|
20
|
+
"gpt-5.4-nano": {
|
|
21
|
+
"pricing": {"in_per_1m": 0.2, "out_per_1m": 1.25},
|
|
22
|
+
"is_reasoning": true
|
|
23
|
+
},
|
|
12
24
|
"gpt-5.2": {
|
|
13
25
|
"pricing": {"in_per_1m": 1.75, "out_per_1m": 14.0},
|
|
14
26
|
"is_reasoning": true
|
|
@@ -39,6 +51,14 @@
|
|
|
39
51
|
"anthropic": {
|
|
40
52
|
"currency": "USD",
|
|
41
53
|
"models": {
|
|
54
|
+
"claude-opus-4-8": {
|
|
55
|
+
"pricing": {"in_per_1m": 5.0, "out_per_1m": 25.0},
|
|
56
|
+
"is_reasoning": true
|
|
57
|
+
},
|
|
58
|
+
"claude-opus-4-7": {
|
|
59
|
+
"pricing": {"in_per_1m": 5.0, "out_per_1m": 25.0},
|
|
60
|
+
"is_reasoning": true
|
|
61
|
+
},
|
|
42
62
|
"claude-opus-4-6": {
|
|
43
63
|
"pricing": {"in_per_1m": 5.0, "out_per_1m": 25.0},
|
|
44
64
|
"is_reasoning": true
|
|
@@ -62,26 +82,21 @@
|
|
|
62
82
|
"claude-opus-4-1": {
|
|
63
83
|
"pricing": {"in_per_1m": 15.0, "out_per_1m": 75.0},
|
|
64
84
|
"is_reasoning": true
|
|
65
|
-
}
|
|
66
|
-
"claude-opus-4-0": {
|
|
67
|
-
"pricing": {"in_per_1m": 15.0, "out_per_1m": 75.0},
|
|
68
|
-
"is_reasoning": true
|
|
69
|
-
},
|
|
70
|
-
"claude-sonnet-4-0": {
|
|
71
|
-
"pricing": {"in_per_1m": 3.0, "out_per_1m": 15.0},
|
|
72
|
-
"is_reasoning": true
|
|
73
|
-
},
|
|
74
|
-
"claude-3-haiku-20240307": {"pricing": {"in_per_1m": 0.25, "out_per_1m": 1.25}}
|
|
85
|
+
}
|
|
75
86
|
}
|
|
76
87
|
},
|
|
77
88
|
"google": {
|
|
78
89
|
"currency": "USD",
|
|
79
90
|
"models": {
|
|
91
|
+
"gemini-3.5-flash": {
|
|
92
|
+
"pricing": {"in_per_1m": 1.5, "out_per_1m": 9.0},
|
|
93
|
+
"is_reasoning": true
|
|
94
|
+
},
|
|
80
95
|
"gemini-3.1-pro-preview": {
|
|
81
96
|
"pricing": {"in_per_1m": 2.0, "out_per_1m": 12.0},
|
|
82
97
|
"is_reasoning": true
|
|
83
98
|
},
|
|
84
|
-
"gemini-3.1-flash-lite
|
|
99
|
+
"gemini-3.1-flash-lite": {
|
|
85
100
|
"pricing": {"in_per_1m": 0.25, "out_per_1m": 1.5},
|
|
86
101
|
"is_reasoning": true
|
|
87
102
|
},
|
|
@@ -96,10 +111,6 @@
|
|
|
96
111
|
"gemini-2.5-flash": {
|
|
97
112
|
"pricing": {"in_per_1m": 0.3, "out_per_1m": 2.5},
|
|
98
113
|
"is_reasoning": true
|
|
99
|
-
},
|
|
100
|
-
"gemini-2.5-flash-lite": {
|
|
101
|
-
"pricing": {"in_per_1m": 0.1, "out_per_1m": 0.4},
|
|
102
|
-
"is_reasoning": true
|
|
103
114
|
}
|
|
104
115
|
}
|
|
105
116
|
}
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/sync_client.py
RENAMED
|
@@ -14,6 +14,8 @@ from ...errors.llm_api_error import (
|
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
|
+
_ADAPTIVE_THINKING_MODELS = ("claude-opus-4-6", "claude-sonnet-4-6", "claude-opus-4-7", "claude-opus-4-8")
|
|
18
|
+
|
|
17
19
|
@dataclass
|
|
18
20
|
class ClaudeSyncClient:
|
|
19
21
|
api_key: str
|
|
@@ -34,11 +36,20 @@ class ClaudeSyncClient:
|
|
|
34
36
|
return response.json()
|
|
35
37
|
|
|
36
38
|
def _prepare_chat_payload_for_model(self, model: str, kwargs: dict) -> dict:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
):
|
|
39
|
+
budget_tokens = kwargs.pop("budget_tokens", None)
|
|
40
|
+
effort = kwargs.pop("effort", None)
|
|
41
|
+
if model.startswith(_ADAPTIVE_THINKING_MODELS):
|
|
41
42
|
kwargs.pop("top_p", None)
|
|
43
|
+
if effort:
|
|
44
|
+
kwargs["thinking"] = {"type": "adaptive"}
|
|
45
|
+
kwargs["output_config"] = {"effort": effort}
|
|
46
|
+
else:
|
|
47
|
+
if model.startswith(
|
|
48
|
+
("claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-4-5", "claude-opus-4-5")
|
|
49
|
+
):
|
|
50
|
+
kwargs.pop("top_p", None)
|
|
51
|
+
if budget_tokens:
|
|
52
|
+
kwargs["thinking"] = {"type": "enabled", "budget_tokens": budget_tokens}
|
|
42
53
|
return {"model": model, **kwargs}
|
|
43
54
|
|
|
44
55
|
def _send_request(self, url: str, payload: dict, timeout_s: float | None = None):
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/chat_message.py
RENAMED
|
@@ -105,14 +105,17 @@ class AIMessage(Message):
|
|
|
105
105
|
parts.append({"text": self.content})
|
|
106
106
|
if self.tool_calls:
|
|
107
107
|
for tc in self.tool_calls:
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
"
|
|
111
|
-
|
|
112
|
-
"args": tc.arguments or {},
|
|
113
|
-
}
|
|
108
|
+
part: Dict[str, Any] = {
|
|
109
|
+
"functionCall": {
|
|
110
|
+
"name": tc.name,
|
|
111
|
+
"args": tc.arguments or {},
|
|
114
112
|
}
|
|
115
|
-
|
|
113
|
+
}
|
|
114
|
+
if tc.provider_data:
|
|
115
|
+
thought_signature = tc.provider_data.get("thoughtSignature")
|
|
116
|
+
if thought_signature is not None:
|
|
117
|
+
part["thoughtSignature"] = thought_signature
|
|
118
|
+
parts.append(part)
|
|
116
119
|
if not parts:
|
|
117
120
|
parts = [{"text": ""}]
|
|
118
121
|
return {"role": "model", "parts": parts}
|
|
@@ -262,10 +265,14 @@ class Messages:
|
|
|
262
265
|
if not isinstance(arguments, dict):
|
|
263
266
|
raise ValueError("assistant.tool_calls.arguments must be dict")
|
|
264
267
|
call_id = tool_call_raw.get("call_id")
|
|
268
|
+
provider_data = tool_call_raw.get("provider_data")
|
|
269
|
+
if provider_data is not None and not isinstance(provider_data, dict):
|
|
270
|
+
raise ValueError("assistant.tool_calls.provider_data must be dict")
|
|
265
271
|
return ToolCall(
|
|
266
272
|
name=name,
|
|
267
273
|
arguments=arguments,
|
|
268
274
|
call_id=call_id,
|
|
275
|
+
provider_data=provider_data,
|
|
269
276
|
)
|
|
270
277
|
|
|
271
278
|
def _parse_legacy_openai_tool_call(self, tool_call_raw: Dict[str, Any]) -> ToolCall:
|
|
@@ -253,11 +253,18 @@ class ChatResponse:
|
|
|
253
253
|
raise InvalidToolArgumentsError(
|
|
254
254
|
detail=f"Google functionCall args must be dict for tool={name!r}"
|
|
255
255
|
)
|
|
256
|
+
thought_signature = part.get("thoughtSignature")
|
|
257
|
+
provider_data = None
|
|
258
|
+
if thought_signature is not None:
|
|
259
|
+
provider_data = {
|
|
260
|
+
"thoughtSignature": thought_signature,
|
|
261
|
+
}
|
|
256
262
|
parsed_tool_calls.append(
|
|
257
263
|
ToolCall(
|
|
258
264
|
name=name,
|
|
259
265
|
arguments=args,
|
|
260
266
|
call_id=name,
|
|
267
|
+
provider_data=provider_data,
|
|
261
268
|
)
|
|
262
269
|
)
|
|
263
270
|
return cls(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/base_adapter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/__init__.py
RENAMED
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/google/sync_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/openai/sync_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/__init__.py
RENAMED
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/responses/__init__.py
RENAMED
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/__init__.py
RENAMED
|
File without changes
|
{llm_api_adapter-0.3.0 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/tool_spec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|