polyllm-core 0.2.0__tar.gz → 0.3.0__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.
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/PKG-INFO +1 -1
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/pyproject.toml +1 -1
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/client.py +10 -3
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/types.py +3 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core.egg-info/PKG-INFO +1 -1
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/tests/test_core.py +21 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/LICENSE +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/README.md +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/setup.cfg +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/__init__.py +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/config.py +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/errors.py +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/params.py +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/py.typed +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core/registry.py +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core.egg-info/SOURCES.txt +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core.egg-info/dependency_links.txt +0 -0
- {polyllm_core-0.2.0 → polyllm_core-0.3.0}/src/polyllm_core.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from dataclasses import replace
|
|
3
4
|
from typing import Any, AsyncIterator, Mapping
|
|
4
5
|
|
|
5
6
|
from .config import resolve_provider_runtime
|
|
@@ -148,7 +149,8 @@ def create_llm(config: ClientConfig | Mapping[str, Any] | None = None, **kwargs:
|
|
|
148
149
|
if not isinstance(config, ClientConfig):
|
|
149
150
|
raise PolyLLMError("INVALID_CONFIG", "create_llm 配置必须是 ClientConfig 或 dict")
|
|
150
151
|
config = replace_provider_configs(config)
|
|
151
|
-
resolve_runtime_configs(config)
|
|
152
|
+
resolved = resolve_runtime_configs(config)
|
|
153
|
+
config = replace(config, providers=resolved)
|
|
152
154
|
return PolyLLM(config)
|
|
153
155
|
|
|
154
156
|
|
|
@@ -162,8 +164,6 @@ def _merge_request(
|
|
|
162
164
|
|
|
163
165
|
|
|
164
166
|
def replace_provider_configs(config: ClientConfig) -> ClientConfig:
|
|
165
|
-
from dataclasses import replace
|
|
166
|
-
|
|
167
167
|
providers = {
|
|
168
168
|
provider: value if isinstance(value, ProviderConfig) else ProviderConfig(**value)
|
|
169
169
|
for provider, value in config.providers.items()
|
|
@@ -226,8 +226,15 @@ def normalize_tool_call(value: Any) -> UnifiedToolCall:
|
|
|
226
226
|
|
|
227
227
|
|
|
228
228
|
def resolve_runtime_configs(config: ClientConfig) -> dict[str, ProviderConfig]:
|
|
229
|
+
plugins_by_provider = {plugin.provider: plugin for plugin in config.plugins}
|
|
229
230
|
resolved: dict[str, ProviderConfig] = {}
|
|
230
231
|
for provider, provider_config in config.providers.items():
|
|
232
|
+
plugin = plugins_by_provider.get(provider)
|
|
233
|
+
if plugin is not None and plugin.allow_missing_api_key and not (
|
|
234
|
+
provider_config.api_key or provider_config.api_key_env
|
|
235
|
+
):
|
|
236
|
+
resolved[provider] = provider_config
|
|
237
|
+
continue
|
|
231
238
|
runtime = resolve_provider_runtime(provider, provider_config)
|
|
232
239
|
resolved[provider] = ProviderConfig(
|
|
233
240
|
api_key=runtime.api_key,
|
|
@@ -151,6 +151,9 @@ class ChatContext:
|
|
|
151
151
|
class LLMPlugin(ABC):
|
|
152
152
|
provider: str
|
|
153
153
|
models: tuple[ModelSpec, ...]
|
|
154
|
+
#: 插件声明"无 API Key 也能工作"(例如本地服务,如 Ollama)。
|
|
155
|
+
#: 设置为 True 时,`create_llm` 在构造阶段不为该 provider 强制校验 API Key。
|
|
156
|
+
allow_missing_api_key: bool = False
|
|
154
157
|
|
|
155
158
|
@abstractmethod
|
|
156
159
|
async def chat(
|
|
@@ -142,6 +142,27 @@ class CoreTests(unittest.TestCase):
|
|
|
142
142
|
response = asyncio.run(client.chat("test:mini", messages=[{"role": "user", "content": "hi"}]))
|
|
143
143
|
self.assertEqual(response.model, "test-model")
|
|
144
144
|
|
|
145
|
+
def test_runtime_resolved_at_construction_not_request_time(self):
|
|
146
|
+
import os
|
|
147
|
+
os.environ["POLYLLM_TEST_KEY"] = "from-env"
|
|
148
|
+
client = create_llm(
|
|
149
|
+
plugins=[TestPlugin()],
|
|
150
|
+
providers={"test": {"api_key_env": "POLYLLM_TEST_KEY"}},
|
|
151
|
+
)
|
|
152
|
+
os.environ.pop("POLYLLM_TEST_KEY")
|
|
153
|
+
# 构造时已把环境变量解析写回 provider config,请求时不再读环境变量
|
|
154
|
+
self.assertEqual(client._config.providers["test"].api_key, "from-env")
|
|
155
|
+
captured = {}
|
|
156
|
+
|
|
157
|
+
class CapturingPlugin(TestPlugin):
|
|
158
|
+
async def chat(self, request, config, context):
|
|
159
|
+
captured["api_key"] = config.api_key
|
|
160
|
+
return await super().chat(request, config, context)
|
|
161
|
+
|
|
162
|
+
client3 = create_llm(plugins=[CapturingPlugin()], providers={"test": {"api_key": "k"}})
|
|
163
|
+
asyncio.run(client3.chat("test-model", messages=[{"role": "user", "content": "hi"}]))
|
|
164
|
+
self.assertEqual(captured["api_key"], "k")
|
|
165
|
+
|
|
145
166
|
def test_unlisted_model_resolution(self):
|
|
146
167
|
client = create_llm(
|
|
147
168
|
plugins=[TestPlugin()],
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|