llm-api-adapter 0.3.1__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.
Files changed (36) hide show
  1. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/PKG-INFO +6 -5
  2. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/README.md +5 -4
  3. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/pyproject.toml +1 -1
  4. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/anthropic_adapter.py +51 -42
  5. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.json +19 -16
  6. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/sync_client.py +15 -4
  7. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/LICENSE +0 -0
  8. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/MANIFEST.in +0 -0
  9. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/setup.cfg +0 -0
  10. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/__init__.py +0 -0
  11. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/__init__.py +0 -0
  12. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/base_adapter.py +0 -0
  13. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/google_adapter.py +0 -0
  14. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/adapters/openai_adapter.py +0 -0
  15. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/__init__.py +0 -0
  16. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/config_errors.py +0 -0
  17. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/errors/llm_api_error.py +0 -0
  18. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/__init__.py +0 -0
  19. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llm_registry/llm_registry.py +0 -0
  20. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/__init__.py +0 -0
  21. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/anthropic/__init__.py +0 -0
  22. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/google/__init__.py +0 -0
  23. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/google/sync_client.py +0 -0
  24. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/openai/__init__.py +0 -0
  25. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/llms/openai/sync_client.py +0 -0
  26. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/__init__.py +0 -0
  27. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/__init__.py +0 -0
  28. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/messages/chat_message.py +0 -0
  29. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/responses/__init__.py +0 -0
  30. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/responses/chat_response.py +0 -0
  31. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/__init__.py +0 -0
  32. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/tool_call.py +0 -0
  33. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/models/tools/tool_spec.py +0 -0
  34. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter/universal_adapter.py +0 -0
  35. {llm_api_adapter-0.3.1 → llm_api_adapter-0.3.2}/src/llm_api_adapter.egg-info/SOURCES.txt +0 -0
  36. {llm_api_adapter-0.3.1 → 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.1
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
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/llm-api-adapter?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/llm-api-adapter)
43
43
  [![Tests](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml/badge.svg)](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml)
44
+ [![codecov](https://codecov.io/github/Inozem/llm_api_adapter/graph/badge.svg?token=T83ZPH1F7Z)](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.1
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.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`.
187
- - **Anthropic**: Available models include `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `claude-opus-4-1`, `claude-opus-4-0`, `claude-sonnet-4-0`, `claude-3-haiku-20240307`.
188
- - **Google**: Models such as `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` can be used.
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
 
@@ -1,6 +1,7 @@
1
1
  # LLM API Adapter SDK for Python
2
2
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/llm-api-adapter?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/llm-api-adapter)
3
3
  [![Tests](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml/badge.svg)](https://github.com/Inozem/llm_api_adapter/actions/workflows/ci-main.yml)
4
+ [![codecov](https://codecov.io/github/Inozem/llm_api_adapter/graph/badge.svg?token=T83ZPH1F7Z)](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.1
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.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`.
147
- - **Anthropic**: Available models include `claude-opus-4-6`, `claude-sonnet-4-6`, `claude-opus-4-5`, `claude-sonnet-4-5`, `claude-haiku-4-5`, `claude-opus-4-1`, `claude-opus-4-0`, `claude-sonnet-4-0`, `claude-3-haiku-20240307`.
148
- - **Google**: Models such as `gemini-3.1-pro-preview`, `gemini-3.1-flash-lite-preview`, `gemini-3-flash-preview`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-lite` can be used.
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
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "llm-api-adapter"
7
- version = "0.3.1"
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"
@@ -34,18 +34,8 @@ class AnthropicAdapter(LLMAdapterBase):
34
34
  parallel_tool_calls: Optional[bool] = None,
35
35
  previous_response: Optional[ChatResponse] = None,
36
36
  ) -> ChatResponse:
37
- temperature = self._validate_parameter(
38
- name="temperature",
39
- value=temperature,
40
- min_value=0,
41
- max_value=2,
42
- )
43
- top_p = self._validate_parameter(
44
- name="top_p",
45
- value=top_p,
46
- min_value=0,
47
- max_value=1,
48
- )
37
+ temperature = self._validate_parameter("temperature", temperature, 0, 2)
38
+ top_p = self._validate_parameter("top_p", top_p, 0, 1)
49
39
  try:
50
40
  self._validate_tools(tools)
51
41
  validated_tools = tools
@@ -87,10 +77,11 @@ class AnthropicAdapter(LLMAdapterBase):
87
77
  reasoning_level=reasoning_level,
88
78
  normalized_reasoning_level=normalized_reasoning_level,
89
79
  )
90
- params["thinking"] = {
91
- "type": "enabled",
92
- "budget_tokens": normalized_reasoning_level,
93
- }
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
94
85
  params = {k: v for k, v in params.items() if v is not None}
95
86
  _ = previous_response
96
87
  client = ClaudeSyncClient(api_key=self.api_key)
@@ -130,42 +121,60 @@ class AnthropicAdapter(LLMAdapterBase):
130
121
  return {"type": tool_choice}
131
122
  return {"type": "tool", "name": tool_choice}
132
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
+
133
154
  def _normalize_reasoning_level(self, level: str | int) -> int | None:
134
155
  minimum_level = 1024
135
- normalized_level: int | None = None
136
- if level is not None and not self.is_reasoning:
156
+ if not self.is_reasoning:
137
157
  warning_message = (
138
158
  f"Model '{self.model}' does not support reasoning — reasoning disabled."
139
159
  )
140
160
  warnings.warn(warning_message, UserWarning)
141
161
  logger.info(warning_message)
142
162
  return None
143
- if isinstance(level, bool):
144
- raise ValueError("Invalid type for level: bool is not accepted")
163
+ self._validate_not_bool(level)
145
164
  if isinstance(level, str):
146
- if level in self.reasoning_levels:
147
- normalized_level = self.reasoning_levels[level]
148
- else:
149
- raise ValueError(
150
- f"Unknown reasoning level key: {level!r}. "
151
- f"Valid keys: {list(self.reasoning_levels.keys())}"
152
- )
153
- if isinstance(level, int):
154
- normalized_level = level
155
- if normalized_level is not None:
156
- if normalized_level >= minimum_level:
157
- return normalized_level
158
- warning_message = (
159
- f"Reasoning level '{level}' is below the minimum supported value "
160
- f"{minimum_level}; using {minimum_level} instead."
161
- )
162
- warnings.warn(warning_message, UserWarning)
163
- logger.info(warning_message)
164
- return minimum_level
165
- raise ValueError(
166
- "Invalid type for level: expected int or str, "
167
- 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."
168
174
  )
175
+ warnings.warn(warning_message, UserWarning)
176
+ logger.info(warning_message)
177
+ return minimum_level
169
178
 
170
179
  def validate_reasoning_and_tokens(
171
180
  self,
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "schema_version": 6,
3
- "effective_date": "2026-03-19",
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
@@ -47,6 +51,14 @@
47
51
  "anthropic": {
48
52
  "currency": "USD",
49
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
+ },
50
62
  "claude-opus-4-6": {
51
63
  "pricing": {"in_per_1m": 5.0, "out_per_1m": 25.0},
52
64
  "is_reasoning": true
@@ -70,26 +82,21 @@
70
82
  "claude-opus-4-1": {
71
83
  "pricing": {"in_per_1m": 15.0, "out_per_1m": 75.0},
72
84
  "is_reasoning": true
73
- },
74
- "claude-opus-4-0": {
75
- "pricing": {"in_per_1m": 15.0, "out_per_1m": 75.0},
76
- "is_reasoning": true
77
- },
78
- "claude-sonnet-4-0": {
79
- "pricing": {"in_per_1m": 3.0, "out_per_1m": 15.0},
80
- "is_reasoning": true
81
- },
82
- "claude-3-haiku-20240307": {"pricing": {"in_per_1m": 0.25, "out_per_1m": 1.25}}
85
+ }
83
86
  }
84
87
  },
85
88
  "google": {
86
89
  "currency": "USD",
87
90
  "models": {
91
+ "gemini-3.5-flash": {
92
+ "pricing": {"in_per_1m": 1.5, "out_per_1m": 9.0},
93
+ "is_reasoning": true
94
+ },
88
95
  "gemini-3.1-pro-preview": {
89
96
  "pricing": {"in_per_1m": 2.0, "out_per_1m": 12.0},
90
97
  "is_reasoning": true
91
98
  },
92
- "gemini-3.1-flash-lite-preview": {
99
+ "gemini-3.1-flash-lite": {
93
100
  "pricing": {"in_per_1m": 0.25, "out_per_1m": 1.5},
94
101
  "is_reasoning": true
95
102
  },
@@ -104,10 +111,6 @@
104
111
  "gemini-2.5-flash": {
105
112
  "pricing": {"in_per_1m": 0.3, "out_per_1m": 2.5},
106
113
  "is_reasoning": true
107
- },
108
- "gemini-2.5-flash-lite": {
109
- "pricing": {"in_per_1m": 0.1, "out_per_1m": 0.4},
110
- "is_reasoning": true
111
114
  }
112
115
  }
113
116
  }
@@ -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
- if model.startswith(
38
- ("claude-sonnet-4-5", "claude-opus-4-1", "claude-haiku-4-5", "claude-opus-4-5",
39
- "claude-opus-4-6", "claude-sonnet-4-6")
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):
File without changes