Files
ai-agent-book/agentbook/providers/registry.py
T
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

175 lines
6.2 KiB
Python

"""The provider registry: which backends exist and what they are called.
This module is pure data plus lookup. Adding a provider means adding one entry
to :data:`PROVIDERS` and nothing else -- chapter CLIs build their
``--provider`` choices from :data:`SUPPORTED_PROVIDERS`, so a new entry becomes
selectable without touching any argparse code.
Resolution *policy* -- which provider wins, when to fall back -- lives in
:mod:`agentbook.providers.resolution`, not here.
"""
from __future__ import annotations
from .models import Provider
from .openrouter import OPENROUTER_BASE_URL, OPENROUTER_DEFAULT_MODEL
__all__ = [
"PROVIDERS",
"SUPPORTED_PROVIDERS",
"canonical_provider",
"lookup",
"supported_providers",
]
PROVIDERS: dict[str, Provider] = {
"dashscope": Provider(
name="dashscope",
# Alibaba Cloud Model Studio (Bailian) keys are region-bound. Default
# to the mainland endpoint for this Chinese-first project; readers
# using an international-region key can set DASHSCOPE_BASE_URL to the
# Singapore endpoint documented in the experiment README.
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
default_model="qwen3.7-plus",
key_vars=("DASHSCOPE_API_KEY",),
base_url_var="DASHSCOPE_BASE_URL",
),
"siliconflow": Provider(
name="siliconflow",
base_url="https://api.siliconflow.cn/v1",
default_model="Qwen/Qwen3.5-397B-A17B",
key_vars=("SILICONFLOW_API_KEY",),
),
"doubao": Provider(
name="doubao",
base_url="https://ark.cn-beijing.volces.com/api/v3",
default_model="doubao-seed-1-6-thinking-250715",
key_vars=("ARK_API_KEY",),
),
"kimi": Provider(
name="kimi",
base_url="https://api.moonshot.cn/v1",
default_model="kimi-k3",
# KIMI_API_KEY kept for backwards compatibility.
key_vars=("MOONSHOT_API_KEY", "KIMI_API_KEY"),
base_url_var="KIMI_BASE_URL",
),
"deepseek": Provider(
name="deepseek",
base_url="https://api.deepseek.com",
# V4 Flash is OpenAI-compatible with tool calling + thinking mode.
# Legacy deepseek-chat / deepseek-reasoner aliases deprecated 2026-07-24.
default_model="deepseek-v4-flash",
key_vars=("DEEPSEEK_API_KEY",),
base_url_var="DEEPSEEK_BASE_URL",
),
"zhipu": Provider(
name="zhipu",
base_url="https://open.bigmodel.cn/api/paas/v4",
default_model="glm-5.2",
key_vars=("ZHIPU_API_KEY",),
),
"openrouter": Provider(
name="openrouter",
base_url=OPENROUTER_BASE_URL,
default_model=OPENROUTER_DEFAULT_MODEL,
key_vars=("OPENROUTER_API_KEY",),
base_url_var="OPENROUTER_BASE_URL",
# Resells many vendors' models, so ids must be namespaced.
namespaces_models=True,
),
"openai": Provider(
name="openai",
base_url="https://api.openai.com/v1",
default_model="gpt-4o",
key_vars=("OPENAI_API_KEY",),
base_url_var="OPENAI_BASE_URL",
),
"gemini": Provider(
name="gemini",
# Google exposes an OpenAI-compatible endpoint; the free tier is
# generous enough for most chapter experiments.
base_url="https://generativelanguage.googleapis.com/v1beta/openai",
default_model="gemini-2.5-flash",
key_vars=("GEMINI_API_KEY", "GOOGLE_API_KEY"),
),
"ollama": Provider(
name="ollama",
base_url="http://localhost:11434/v1",
default_model="qwen3:8b",
# Ollama ignores the key but the OpenAI client requires a non-empty one.
key_vars=("OLLAMA_API_KEY",),
base_url_var="OLLAMA_BASE_URL",
requires_key=False,
),
}
# Provider names used interchangeably in the chapters, mapped to canonical ones.
_ALIASES = {
"moonshot": "kimi",
"ark": "doubao",
"google": "gemini",
# "Qwen" is the model family and "Bailian" is the product name; both
# select Alibaba's DashScope-compatible endpoint rather than SiliconFlow.
"qwen": "dashscope",
"bailian": "dashscope",
}
# Every accepted name, canonical plus aliases. Chapter CLIs use this for their
# --provider choices so a new registry entry is immediately selectable instead
# of being rejected by argparse.
#
# Computed once at import: PROVIDERS is a module-level table edited in source,
# not registered at runtime. Anything mutating PROVIDERS after import (tests
# do, to exercise hypothetical providers) must read supported_providers()
# instead, which recomputes.
SUPPORTED_PROVIDERS: tuple[str, ...] = tuple(sorted(set(PROVIDERS) | set(_ALIASES)))
def supported_providers() -> tuple[str, ...]:
"""Return every accepted provider name, canonical plus aliases.
Prefer the :data:`SUPPORTED_PROVIDERS` constant unless
:data:`PROVIDERS` may have been modified since import.
Returns:
Sorted provider names and aliases, recomputed from the live table.
"""
return tuple(sorted(set(PROVIDERS) | set(_ALIASES)))
def canonical_provider(provider: str) -> str:
"""Normalise a provider name, resolving aliases.
Args:
provider: A provider name or alias, e.g. ``"moonshot"`` or ``"Kimi"``.
Case and surrounding whitespace are ignored. ``None`` is tolerated.
Returns:
The canonical name, e.g. ``"kimi"``. Names that are not known aliases
are returned lowercased but otherwise unchanged, so callers can still
look them up and get a sensible error for genuinely unknown providers.
"""
key = (provider or "").strip().lower()
return _ALIASES.get(key, key)
def lookup(provider: str) -> Provider:
"""Find the :class:`~agentbook.providers.models.Provider` for a name.
Args:
provider: A provider name or alias.
Returns:
The registered provider specification.
Raises:
ValueError: If the name matches no registry entry or alias. The message
lists the supported names.
"""
key = canonical_provider(provider)
if key not in PROVIDERS:
supported = ", ".join(sorted(PROVIDERS))
raise ValueError(f"Unsupported provider: {provider!r}. Supported: {supported}")
return PROVIDERS[key]