修改了nanobot,往Hermes agent的风格走,进度1/3

This commit is contained in:
2026-04-20 18:11:14 +08:00
parent cdfc222c9f
commit 36882a7d7b
261 changed files with 12659 additions and 604 deletions

View File

@ -0,0 +1,249 @@
"""Provider registry: 统一维护 provider 元数据与匹配规则。"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
@dataclass(frozen=True, slots=True)
class ProviderSpec:
"""单个 provider 的元数据定义。"""
name: str
keywords: tuple[str, ...]
env_key: str
display_name: str = ""
litellm_prefix: str = ""
skip_prefixes: tuple[str, ...] = ()
env_extras: tuple[tuple[str, str], ...] = ()
is_gateway: bool = False
is_local: bool = False
detect_by_key_prefix: str = ""
detect_by_base_keyword: str = ""
default_api_base: str = ""
strip_model_prefix: bool = False
model_overrides: tuple[tuple[str, dict[str, Any]], ...] = ()
is_oauth: bool = False
is_direct: bool = False
supports_prompt_caching: bool = False
api_mode: str = "chat_completions"
provider_impl: str = "litellm"
@property
def label(self) -> str:
return self.display_name or self.name.title()
PROVIDERS: tuple[ProviderSpec, ...] = (
ProviderSpec(
name="custom",
keywords=(),
env_key="",
display_name="Custom",
is_direct=True,
provider_impl="custom",
api_mode="chat_completions",
),
ProviderSpec(
name="openrouter",
keywords=("openrouter",),
env_key="OPENROUTER_API_KEY",
display_name="OpenRouter",
litellm_prefix="openrouter",
is_gateway=True,
detect_by_key_prefix="sk-or-",
detect_by_base_keyword="openrouter",
default_api_base="https://openrouter.ai/api/v1",
supports_prompt_caching=True,
),
ProviderSpec(
name="aihubmix",
keywords=("aihubmix",),
env_key="OPENAI_API_KEY",
display_name="AiHubMix",
litellm_prefix="openai",
is_gateway=True,
detect_by_base_keyword="aihubmix",
default_api_base="https://aihubmix.com/v1",
strip_model_prefix=True,
),
ProviderSpec(
name="siliconflow",
keywords=("siliconflow",),
env_key="OPENAI_API_KEY",
display_name="SiliconFlow",
litellm_prefix="openai",
is_gateway=True,
detect_by_base_keyword="siliconflow",
default_api_base="https://api.siliconflow.cn/v1",
),
ProviderSpec(
name="volcengine",
keywords=("volcengine", "volces", "ark"),
env_key="OPENAI_API_KEY",
display_name="VolcEngine",
litellm_prefix="volcengine",
is_gateway=True,
detect_by_base_keyword="volces",
default_api_base="https://ark.cn-beijing.volces.com/api/v3",
),
ProviderSpec(
name="anthropic",
keywords=("anthropic", "claude"),
env_key="ANTHROPIC_API_KEY",
display_name="Anthropic",
supports_prompt_caching=True,
api_mode="anthropic_messages",
provider_impl="anthropic",
),
ProviderSpec(
name="openai",
keywords=("openai", "gpt"),
env_key="OPENAI_API_KEY",
display_name="OpenAI",
),
ProviderSpec(
name="openai_codex",
keywords=("openai-codex", "codex"),
env_key="",
display_name="OpenAI Codex",
is_oauth=True,
detect_by_base_keyword="codex",
default_api_base="https://chatgpt.com/backend-api",
api_mode="codex_responses",
provider_impl="codex",
),
ProviderSpec(
name="github_copilot",
keywords=("github_copilot", "copilot"),
env_key="",
display_name="Github Copilot",
litellm_prefix="github_copilot",
skip_prefixes=("github_copilot/",),
is_oauth=True,
),
ProviderSpec(
name="deepseek",
keywords=("deepseek",),
env_key="DEEPSEEK_API_KEY",
display_name="DeepSeek",
litellm_prefix="deepseek",
skip_prefixes=("deepseek/",),
),
ProviderSpec(
name="gemini",
keywords=("gemini",),
env_key="GEMINI_API_KEY",
display_name="Gemini",
litellm_prefix="gemini",
skip_prefixes=("gemini/",),
),
ProviderSpec(
name="zhipu",
keywords=("zhipu", "glm", "zai"),
env_key="ZAI_API_KEY",
display_name="Zhipu AI",
litellm_prefix="zai",
skip_prefixes=("zhipu/", "zai/", "openrouter/", "hosted_vllm/"),
env_extras=(("ZHIPUAI_API_KEY", "{api_key}"),),
),
ProviderSpec(
name="dashscope",
keywords=("qwen", "dashscope"),
env_key="DASHSCOPE_API_KEY",
display_name="DashScope",
litellm_prefix="dashscope",
skip_prefixes=("dashscope/", "openrouter/"),
),
ProviderSpec(
name="moonshot",
keywords=("moonshot", "kimi"),
env_key="MOONSHOT_API_KEY",
display_name="Moonshot",
litellm_prefix="moonshot",
skip_prefixes=("moonshot/", "openrouter/"),
env_extras=(("MOONSHOT_API_BASE", "{api_base}"),),
default_api_base="https://api.moonshot.ai/v1",
model_overrides=(("kimi-k2.5", {"temperature": 1.0}),),
),
ProviderSpec(
name="minimax",
keywords=("minimax",),
env_key="MINIMAX_API_KEY",
display_name="MiniMax",
litellm_prefix="minimax",
skip_prefixes=("minimax/", "openrouter/"),
default_api_base="https://api.minimax.io/v1",
),
ProviderSpec(
name="vllm",
keywords=("vllm",),
env_key="HOSTED_VLLM_API_KEY",
display_name="vLLM/Local",
litellm_prefix="hosted_vllm",
is_local=True,
),
ProviderSpec(
name="groq",
keywords=("groq",),
env_key="GROQ_API_KEY",
display_name="Groq",
litellm_prefix="groq",
skip_prefixes=("groq/",),
),
)
def find_by_name(name: str) -> ProviderSpec | None:
for spec in PROVIDERS:
if spec.name == name:
return spec
return None
def find_by_model(model: str) -> ProviderSpec | None:
"""按模型名关键词匹配标准 provider。"""
model_lower = model.lower()
model_normalized = model_lower.replace("-", "_")
model_prefix = model_lower.split("/", 1)[0] if "/" in model_lower else ""
normalized_prefix = model_prefix.replace("-", "_")
standard_specs = [spec for spec in PROVIDERS if not spec.is_gateway and not spec.is_local]
# 显式前缀优先级最高。
# 这里不能只看 standard provider
# - `openrouter/...` 应该直接命中 openrouter
# - `hosted_vllm/...` 应该能回到 vllm 这个本地 provider
# - `github_copilot/...codex` 也不应被误判成 openai_codex
for spec in PROVIDERS:
aliases = {spec.name}
if spec.litellm_prefix:
aliases.add(spec.litellm_prefix.replace("-", "_"))
if model_prefix and normalized_prefix in aliases:
return spec
for spec in standard_specs:
if any(keyword in model_lower or keyword.replace("-", "_") in model_normalized for keyword in spec.keywords):
return spec
return None
def find_gateway(
provider_name: str | None = None,
api_key: str | None = None,
api_base: str | None = None,
) -> ProviderSpec | None:
"""按 config key / api_key / api_base 识别 gateway 或 local provider。"""
if provider_name:
spec = find_by_name(provider_name)
if spec and (spec.is_gateway or spec.is_local):
return spec
for spec in PROVIDERS:
if spec.detect_by_key_prefix and api_key and api_key.startswith(spec.detect_by_key_prefix):
return spec
if spec.detect_by_base_keyword and api_base and spec.detect_by_base_keyword in api_base:
return spec
return None