provider-sdk 0.1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 provider-sdk contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.4
2
+ Name: provider-sdk
3
+ Version: 0.1.0
4
+ Summary: Provider-V2 平台插件 SDK — 第三方平台适配器开发与加载
5
+ Author: provider-sdk
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/nichengfuben/provider-sdk
8
+ Project-URL: Documentation, https://github.com/nichengfuben/provider-sdk/blob/main/docs/guide.md
9
+ Project-URL: Repository, https://github.com/nichengfuben/provider-sdk
10
+ Project-URL: Issues, https://github.com/nichengfuben/provider-sdk/issues
11
+ Keywords: provider,llm,gateway,plugin,sdk,openai
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: aiohttp>=3.9.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
27
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # provider-sdk
31
+
32
+ Provider-V2 平台插件 SDK。对标 [maibot-plugin-sdk](https://github.com/Mai-with-u/maibot-plugin-sdk) 的插件契约,为第三方**平台适配器**提供独立开发与加载能力。
33
+
34
+ ## 安装
35
+
36
+ ```bash
37
+ pip install -e X:\Project\provider-sdk
38
+ ```
39
+
40
+ ## 快速开始
41
+
42
+ 在 Provider Host 的 `plugins/` 目录下创建插件文件夹:
43
+
44
+ ```
45
+ plugins/
46
+ my_platform/
47
+ _manifest.json
48
+ plugin.py
49
+ config.toml # 可选,由 Host 管理
50
+ ```
51
+
52
+ `plugin.py` 最小示例见 [`examples/echo_platform/plugin.py`](examples/echo_platform/plugin.py)。
53
+
54
+ ```python
55
+ from provider_sdk import ProviderPlugin, PlatformAdapter, Candidate, make_id
56
+
57
+ class MyPlugin(ProviderPlugin, PlatformAdapter):
58
+ @property
59
+ def name(self) -> str:
60
+ return "my_platform"
61
+
62
+ async def on_load(self) -> None:
63
+ self.ctx.logger.info("loaded")
64
+
65
+ async def init(self, session): ...
66
+ async def candidates(self) -> list[Candidate]: ...
67
+ async def ensure_candidates(self, count: int) -> int: ...
68
+ async def complete(self, candidate, messages, model, stream, **kw): ...
69
+ async def close(self) -> None: ...
70
+
71
+ def create_plugin() -> MyPlugin:
72
+ return MyPlugin()
73
+ ```
74
+
75
+ ## 与 MaiBot SDK 的对应关系
76
+
77
+ | MaiBot | provider-sdk |
78
+ |--------|----------------|
79
+ | `MaiBotPlugin` | `ProviderPlugin` |
80
+ | `create_plugin()` | `create_plugin()` |
81
+ | `_manifest.json` | `_manifest.json` |
82
+ | `on_load` / `on_unload` / `on_config_update` | 相同 |
83
+ | `self.ctx.send` / `gateway` 等 | `self.ctx.config` / `http` / `logger` |
84
+ | `@Tool` / `@Command` | 平台插件以 `PlatformAdapter` 能力为主 |
85
+
86
+ ## Host 集成
87
+
88
+ ```python
89
+ from pathlib import Path
90
+ from provider_sdk.integrate import load_platform_plugins, register_loaded_plugins
91
+
92
+ loaded = await load_platform_plugins(Path("plugins"), session)
93
+ register_loaded_plugins(registry, loaded)
94
+ ```
95
+
96
+ 完整说明见 [`docs/guide.md`](docs/guide.md)。
97
+
98
+ ## 开发
99
+
100
+ ```bash
101
+ pip install -e ".[dev]"
102
+ pytest -q
103
+ ```
@@ -0,0 +1,74 @@
1
+ # provider-sdk
2
+
3
+ Provider-V2 平台插件 SDK。对标 [maibot-plugin-sdk](https://github.com/Mai-with-u/maibot-plugin-sdk) 的插件契约,为第三方**平台适配器**提供独立开发与加载能力。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pip install -e X:\Project\provider-sdk
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ 在 Provider Host 的 `plugins/` 目录下创建插件文件夹:
14
+
15
+ ```
16
+ plugins/
17
+ my_platform/
18
+ _manifest.json
19
+ plugin.py
20
+ config.toml # 可选,由 Host 管理
21
+ ```
22
+
23
+ `plugin.py` 最小示例见 [`examples/echo_platform/plugin.py`](examples/echo_platform/plugin.py)。
24
+
25
+ ```python
26
+ from provider_sdk import ProviderPlugin, PlatformAdapter, Candidate, make_id
27
+
28
+ class MyPlugin(ProviderPlugin, PlatformAdapter):
29
+ @property
30
+ def name(self) -> str:
31
+ return "my_platform"
32
+
33
+ async def on_load(self) -> None:
34
+ self.ctx.logger.info("loaded")
35
+
36
+ async def init(self, session): ...
37
+ async def candidates(self) -> list[Candidate]: ...
38
+ async def ensure_candidates(self, count: int) -> int: ...
39
+ async def complete(self, candidate, messages, model, stream, **kw): ...
40
+ async def close(self) -> None: ...
41
+
42
+ def create_plugin() -> MyPlugin:
43
+ return MyPlugin()
44
+ ```
45
+
46
+ ## 与 MaiBot SDK 的对应关系
47
+
48
+ | MaiBot | provider-sdk |
49
+ |--------|----------------|
50
+ | `MaiBotPlugin` | `ProviderPlugin` |
51
+ | `create_plugin()` | `create_plugin()` |
52
+ | `_manifest.json` | `_manifest.json` |
53
+ | `on_load` / `on_unload` / `on_config_update` | 相同 |
54
+ | `self.ctx.send` / `gateway` 等 | `self.ctx.config` / `http` / `logger` |
55
+ | `@Tool` / `@Command` | 平台插件以 `PlatformAdapter` 能力为主 |
56
+
57
+ ## Host 集成
58
+
59
+ ```python
60
+ from pathlib import Path
61
+ from provider_sdk.integrate import load_platform_plugins, register_loaded_plugins
62
+
63
+ loaded = await load_platform_plugins(Path("plugins"), session)
64
+ register_loaded_plugins(registry, loaded)
65
+ ```
66
+
67
+ 完整说明见 [`docs/guide.md`](docs/guide.md)。
68
+
69
+ ## 开发
70
+
71
+ ```bash
72
+ pip install -e ".[dev]"
73
+ pytest -q
74
+ ```
@@ -0,0 +1,39 @@
1
+ """Provider-V2 平台插件 SDK。
2
+
3
+ 插件作者的唯一依赖入口。插件不得导入 ``src.*``,只能通过本 SDK 与 Host 交互。
4
+
5
+ 核心导出:
6
+
7
+ - :class:`ProviderPlugin` — 插件基类(生命周期 + 配置)
8
+ - :class:`PlatformAdapter` — 平台适配器抽象接口
9
+ - :class:`PluginContext` — 运行时上下文(日志 / 配置 / HTTP)
10
+ - :class:`PluginConfigBase` — 插件配置模型基类
11
+ - :class:`Candidate` — 网关候选项
12
+ """
13
+
14
+ from provider_sdk.config import Field, PluginConfigBase
15
+ from provider_sdk.context import PluginContext
16
+ from provider_sdk.platform import PlatformAdapter
17
+ from provider_sdk.plugin import ProviderPlugin
18
+ from provider_sdk.types import (
19
+ CONFIG_RELOAD_SCOPE_GLOBAL,
20
+ CONFIG_RELOAD_SCOPE_SELF,
21
+ ALL_CAPABILITIES,
22
+ )
23
+ from provider_sdk.types.candidate import Candidate, make_id
24
+
25
+ __version__ = "0.1.0"
26
+
27
+ __all__ = [
28
+ "ProviderPlugin",
29
+ "PlatformAdapter",
30
+ "PluginContext",
31
+ "PluginConfigBase",
32
+ "Field",
33
+ "Candidate",
34
+ "make_id",
35
+ "CONFIG_RELOAD_SCOPE_SELF",
36
+ "CONFIG_RELOAD_SCOPE_GLOBAL",
37
+ "ALL_CAPABILITIES",
38
+ "__version__",
39
+ ]
@@ -0,0 +1,73 @@
1
+ """能力代理:配置、HTTP、日志。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from typing import Any, Optional
8
+
9
+ import aiohttp
10
+
11
+ __all__ = ["ConfigCapability", "HttpCapability", "LoggingCapability"]
12
+
13
+ GetConfigFn = Callable[[str, str], Any]
14
+ GetGlobalConfigFn = Callable[[], Mapping[str, Any]]
15
+
16
+
17
+ class LoggingCapability:
18
+ """插件日志能力。"""
19
+
20
+ def __init__(self, logger: logging.Logger) -> None:
21
+ self._logger = logger
22
+
23
+ @property
24
+ def logger(self) -> logging.Logger:
25
+ return self._logger
26
+
27
+
28
+ class ConfigCapability:
29
+ """插件配置读取能力。"""
30
+
31
+ def __init__(
32
+ self,
33
+ *,
34
+ get_plugin_config: Callable[[], Mapping[str, Any]],
35
+ get_global_config: Optional[GetGlobalConfigFn] = None,
36
+ ) -> None:
37
+ self._get_plugin_config = get_plugin_config
38
+ self._get_global_config = get_global_config
39
+
40
+ def get_plugin(self) -> dict[str, Any]:
41
+ """返回当前插件配置字典副本。"""
42
+ raw = self._get_plugin_config()
43
+ return dict(raw) if isinstance(raw, Mapping) else {}
44
+
45
+ def get_global(self) -> dict[str, Any]:
46
+ """返回 Host 全局配置字典副本。"""
47
+ if self._get_global_config is None:
48
+ return {}
49
+ raw = self._get_global_config()
50
+ return dict(raw) if isinstance(raw, Mapping) else {}
51
+
52
+ def get(self, section: str, key: str, default: Any = None) -> Any:
53
+ """读取全局配置中的嵌套键。"""
54
+ data = self.get_global()
55
+ node = data.get(section)
56
+ if isinstance(node, Mapping):
57
+ return node.get(key, default)
58
+ return default
59
+
60
+
61
+ class HttpCapability:
62
+ """共享 HTTP 会话能力。"""
63
+
64
+ def __init__(self, session: aiohttp.ClientSession) -> None:
65
+ self._session = session
66
+
67
+ @property
68
+ def session(self) -> aiohttp.ClientSession:
69
+ return self._session
70
+
71
+ async def request(self, method: str, url: str, **kwargs: Any) -> aiohttp.ClientResponse:
72
+ """对共享会话发起请求。"""
73
+ return await self._session.request(method, url, **kwargs)
@@ -0,0 +1,78 @@
1
+ """插件配置模型与校验工具。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from collections.abc import Mapping
7
+ from typing import Any, ClassVar, TypeVar, cast
8
+
9
+ from pydantic import BaseModel, ConfigDict, Field
10
+
11
+ __all__ = [
12
+ "Field",
13
+ "PluginConfigBase",
14
+ "build_plugin_default_config",
15
+ "is_plugin_config_class",
16
+ "merge_plugin_config_data",
17
+ "validate_plugin_config",
18
+ ]
19
+
20
+ PluginConfigT = TypeVar("PluginConfigT", bound="PluginConfigBase")
21
+
22
+
23
+ class PluginConfigBase(BaseModel):
24
+ """插件配置模型基类。
25
+
26
+ 插件作者继承此类声明配置结构;Host 据此生成默认配置与 WebUI Schema。
27
+ """
28
+
29
+ model_config = ConfigDict(validate_assignment=True, extra="ignore")
30
+
31
+ __ui_label__: ClassVar[str] = ""
32
+ __ui_icon__: ClassVar[str] = ""
33
+ __ui_order__: ClassVar[int] = 0
34
+
35
+
36
+ def is_plugin_config_class(candidate: Any) -> bool:
37
+ """判断对象是否为插件配置模型类。"""
38
+ return bool(inspect.isclass(candidate) and issubclass(candidate, PluginConfigBase))
39
+
40
+
41
+ def build_plugin_default_config(config_class: type[PluginConfigT]) -> dict[str, Any]:
42
+ """根据配置模型构造默认配置字典。"""
43
+ try:
44
+ instance = config_class()
45
+ except Exception as exc:
46
+ raise ValueError(
47
+ f"插件配置模型 {config_class.__name__} 需要为所有字段提供默认值"
48
+ ) from exc
49
+ return instance.model_dump(mode="python")
50
+
51
+
52
+ def validate_plugin_config(
53
+ config_class: type[PluginConfigT],
54
+ config_data: Mapping[str, Any],
55
+ ) -> PluginConfigT:
56
+ """校验并构造强类型配置实例。"""
57
+ return config_class.model_validate(dict(config_data))
58
+
59
+
60
+ def merge_plugin_config_data(
61
+ default_config: Mapping[str, Any],
62
+ raw_config: Mapping[str, Any],
63
+ ) -> tuple[dict[str, Any], bool]:
64
+ """将用户配置递归合并到默认配置之上。"""
65
+ changed = False
66
+ merged: dict[str, Any] = dict(default_config)
67
+
68
+ for key, value in raw_config.items():
69
+ base_value = merged.get(key)
70
+ if isinstance(base_value, Mapping) and isinstance(value, Mapping):
71
+ nested, nested_changed = merge_plugin_config_data(base_value, value)
72
+ merged[key] = nested
73
+ changed = changed or nested_changed or nested != dict(base_value)
74
+ elif base_value != value:
75
+ merged[key] = value
76
+ changed = True
77
+
78
+ return merged, changed
@@ -0,0 +1,78 @@
1
+ """插件运行时上下文。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from typing import Any, Optional
9
+
10
+ import aiohttp
11
+
12
+ from provider_sdk.capabilities import ConfigCapability, HttpCapability, LoggingCapability
13
+
14
+ __all__ = ["HostServices", "PluginContext", "create_plugin_context"]
15
+
16
+
17
+ @dataclass
18
+ class HostServices:
19
+ """Host 注入给 SDK 的运行时服务集合。"""
20
+
21
+ session: aiohttp.ClientSession
22
+ plugin_id: str
23
+ plugin_dir: str = ""
24
+ get_plugin_config: Any = None
25
+ get_global_config: Any = None
26
+ logger: Optional[logging.Logger] = None
27
+
28
+
29
+ class PluginContext:
30
+ """插件运行时上下文。
31
+
32
+ 插件通过 ``self.ctx`` 访问日志、配置与 HTTP 会话。
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ *,
38
+ plugin_id: str,
39
+ plugin_dir: str,
40
+ logger: logging.Logger,
41
+ config: ConfigCapability,
42
+ http: HttpCapability,
43
+ ) -> None:
44
+ self.plugin_id = plugin_id
45
+ self.plugin_dir = plugin_dir
46
+ self.logger = logger
47
+ self.config = config
48
+ self.http = http
49
+
50
+ @property
51
+ def paths(self) -> Mapping[str, str]:
52
+ """插件相关路径。"""
53
+ return {"plugin_dir": self.plugin_dir}
54
+
55
+
56
+ def create_plugin_context(services: HostServices) -> PluginContext:
57
+ """根据 Host 服务构造插件上下文。"""
58
+ plugin_id = services.plugin_id
59
+ logger = services.logger or logging.getLogger(f"provider_sdk.plugin.{plugin_id}")
60
+
61
+ def _plugin_config() -> Mapping[str, Any]:
62
+ if callable(services.get_plugin_config):
63
+ raw = services.get_plugin_config()
64
+ return dict(raw) if isinstance(raw, Mapping) else {}
65
+ return {}
66
+
67
+ config_cap = ConfigCapability(
68
+ get_plugin_config=_plugin_config,
69
+ get_global_config=services.get_global_config if callable(services.get_global_config) else None,
70
+ )
71
+ http_cap = HttpCapability(services.session)
72
+ return PluginContext(
73
+ plugin_id=plugin_id,
74
+ plugin_dir=services.plugin_dir,
75
+ logger=logger,
76
+ config=config_cap,
77
+ http=http_cap,
78
+ )
@@ -0,0 +1,51 @@
1
+ """与 Provider Host 注册表集成的辅助函数。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Any, Callable, Mapping, Optional, Sequence
7
+
8
+ import aiohttp
9
+
10
+ from provider_sdk.runtime.loader import LoadedPlugin, PluginLoader
11
+
12
+ __all__ = ["load_platform_plugins", "register_loaded_plugins"]
13
+
14
+
15
+ async def load_platform_plugins(
16
+ plugins_root: Path,
17
+ session: aiohttp.ClientSession,
18
+ *,
19
+ host_version: str = "",
20
+ get_plugin_config: Optional[Callable[[str], Mapping[str, Any]]] = None,
21
+ get_global_config: Optional[Callable[[], Mapping[str, Any]]] = None,
22
+ whitelist: Optional[Sequence[str]] = None,
23
+ blacklist: Optional[Sequence[str]] = None,
24
+ ) -> list[LoadedPlugin]:
25
+ """发现并加载 ``plugins/`` 目录下的平台插件。"""
26
+ loader = PluginLoader(host_version=host_version, plugin_type_filter="platform")
27
+ return await loader.discover_and_load(
28
+ plugins_root,
29
+ session,
30
+ get_plugin_config=get_plugin_config,
31
+ get_global_config=get_global_config,
32
+ whitelist=whitelist,
33
+ blacklist=blacklist,
34
+ )
35
+
36
+
37
+ def register_loaded_plugins(registry: Any, loaded: Sequence[LoadedPlugin]) -> None:
38
+ """将已加载插件的适配器注册到 echotools 风格注册表。
39
+
40
+ ``registry`` 需实现 ``register(adapter)`` 或内部 ``_registry.register``。
41
+ """
42
+ for record in loaded:
43
+ adapter = record.adapter
44
+ if hasattr(registry, "register") and callable(registry.register):
45
+ registry.register(adapter)
46
+ continue
47
+ inner = getattr(registry, "_registry", None)
48
+ if inner is not None and hasattr(inner, "register"):
49
+ inner.register(adapter)
50
+ continue
51
+ raise TypeError("registry 不支持 register(adapter)")
@@ -0,0 +1,140 @@
1
+ """平台适配器抽象接口 — 与 Provider Host 内置适配器契约一致。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ import uuid
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, AsyncGenerator, Dict, List, Optional, Union
9
+
10
+ import aiohttp
11
+
12
+ from provider_sdk.types.candidate import Candidate
13
+
14
+ __all__ = ["PlatformAdapter", "DEFAULT_CONTEXT_LENGTH"]
15
+
16
+ DEFAULT_CONTEXT_LENGTH = 131072
17
+
18
+
19
+ class PlatformAdapter(ABC):
20
+ """所有平台适配器的抽象基类。
21
+
22
+ 设计原则(与 Host 一致):
23
+
24
+ - ``init()`` 必须立即返回,耗时操作放后台 Task
25
+ - ``candidates()`` 随时反映真实状态
26
+ - 可选能力方法有安全默认实现
27
+ """
28
+
29
+ @property
30
+ @abstractmethod
31
+ def name(self) -> str:
32
+ """平台标识名(小写,建议与目录名一致)。"""
33
+ ...
34
+
35
+ @property
36
+ def supported_models(self) -> List[str]:
37
+ """支持的模型列表。"""
38
+ return []
39
+
40
+ @property
41
+ def default_capabilities(self) -> Dict[str, bool]:
42
+ """默认能力字典,用于 ``/v1/models`` 输出。"""
43
+ return {"chat": True}
44
+
45
+ @property
46
+ def context_length(self) -> Optional[int]:
47
+ """默认上下文长度。"""
48
+ return DEFAULT_CONTEXT_LENGTH
49
+
50
+ @abstractmethod
51
+ async def init(self, session: aiohttp.ClientSession) -> None:
52
+ """初始化适配器,必须立即返回。"""
53
+ ...
54
+
55
+ @abstractmethod
56
+ async def candidates(self) -> List[Candidate]:
57
+ """返回当前可用候选项列表。"""
58
+ ...
59
+
60
+ @abstractmethod
61
+ async def ensure_candidates(self, count: int) -> int:
62
+ """确保至少有 ``count`` 个候选项可用。"""
63
+ ...
64
+
65
+ @abstractmethod
66
+ async def complete(
67
+ self,
68
+ candidate: Candidate,
69
+ messages: List[Dict[str, Any]],
70
+ model: str,
71
+ stream: bool,
72
+ *,
73
+ thinking: bool = False,
74
+ search: bool = False,
75
+ **kw: Any,
76
+ ) -> AsyncGenerator[Union[str, Dict[str, Any]], None]:
77
+ """聊天补全,yield 文本增量或结构化事件。"""
78
+ ...
79
+
80
+ @abstractmethod
81
+ async def close(self) -> None:
82
+ """关闭适配器并释放资源。"""
83
+ ...
84
+
85
+ async def fetch_remote_models(self) -> List[str]:
86
+ """拉取远程模型列表。"""
87
+ return []
88
+
89
+ async def create_embedding(
90
+ self,
91
+ candidate: Candidate,
92
+ input_data: Union[str, List[str]],
93
+ model: str,
94
+ **kw: Any,
95
+ ) -> Dict[str, Any]:
96
+ inputs = [input_data] if isinstance(input_data, str) else list(input_data)
97
+ return {
98
+ "object": "list",
99
+ "data": [
100
+ {"object": "embedding", "index": i, "embedding": []}
101
+ for i, _ in enumerate(inputs)
102
+ ],
103
+ "model": model,
104
+ "usage": {"prompt_tokens": 0, "total_tokens": 0},
105
+ }
106
+
107
+ async def create_image(
108
+ self,
109
+ candidate: Candidate,
110
+ prompt: str,
111
+ model: str,
112
+ **kw: Any,
113
+ ) -> Dict[str, Any]:
114
+ return {"created": int(time.time()), "data": []}
115
+
116
+ async def create_moderation(
117
+ self,
118
+ candidate: Candidate,
119
+ input_data: Union[str, List[str]],
120
+ model: str,
121
+ **kw: Any,
122
+ ) -> Dict[str, Any]:
123
+ inputs = [input_data] if isinstance(input_data, str) else list(input_data)
124
+ return {
125
+ "id": f"modr-{uuid.uuid4().hex[:24]}",
126
+ "model": model,
127
+ "results": [{"flagged": False, "categories": {}, "category_scores": {}} for _ in inputs],
128
+ }
129
+
130
+ def set_proxy_enabled(self, enabled: bool) -> None:
131
+ """设置代理覆盖开关(可选)。"""
132
+ del enabled
133
+
134
+ def is_proxy_allowed(self) -> bool:
135
+ """是否允许代理切换。"""
136
+ return False
137
+
138
+ def is_proxy_enabled(self) -> bool:
139
+ """当前是否启用代理。"""
140
+ return False