ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
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
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
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
"""OpenRouter endpoint constants and model-id mapping.
|
||||
|
||||
OpenRouter is the universal fallback: it speaks the OpenAI protocol and hosts
|
||||
models from many vendors, so any chapter can run against it with a single key.
|
||||
The catch is that it namespaces model ids (``openai/gpt-4o`` rather than
|
||||
``gpt-4o``), which is what :func:`map_model_to_openrouter` translates.
|
||||
|
||||
Everything OpenRouter-specific lives here, so a change to its ids or endpoint
|
||||
touches exactly one module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
__all__ = [
|
||||
"OPENROUTER_BASE_URL",
|
||||
"OPENROUTER_DEFAULT_MODEL",
|
||||
"ZERO_COST_HINT",
|
||||
"is_openrouter_key",
|
||||
"map_model_to_openrouter",
|
||||
"openrouter_base_url",
|
||||
"openrouter_key",
|
||||
]
|
||||
|
||||
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
OPENROUTER_DEFAULT_MODEL = "openai/gpt-5.6-luna"
|
||||
|
||||
# Appended to every "no key configured" error so the way out of the problem is
|
||||
# stated once rather than copied into each message.
|
||||
ZERO_COST_HINT = (
|
||||
"For a zero-cost setup use provider 'ollama' (local, no key) or "
|
||||
"OPENROUTER_MODEL with a ':free' model id."
|
||||
)
|
||||
|
||||
|
||||
def openrouter_key() -> str:
|
||||
"""Read the OpenRouter API key from the environment.
|
||||
|
||||
Returns:
|
||||
The value of ``OPENROUTER_API_KEY``, stripped, or ``""`` when unset.
|
||||
"""
|
||||
return os.getenv("OPENROUTER_API_KEY", "").strip()
|
||||
|
||||
|
||||
def is_openrouter_key(api_key: str) -> bool:
|
||||
"""Report whether a credential looks like an OpenRouter key.
|
||||
|
||||
OpenRouter issues keys under the ``sk-or-`` prefix, so a key the reader
|
||||
pasted can usually be attributed without asking them which service it came
|
||||
from. This is a naming convention rather than a guarantee, which bounds
|
||||
where the answer may be used.
|
||||
|
||||
Intended for callers that accept a key of unknown origin -- a CLI taking
|
||||
``--api-key``, say -- and must pick which provider to resolve. It is
|
||||
deliberately *not* used by :func:`~agentbook.providers.resolve_backend`,
|
||||
whose ``api_key`` argument means "this provider's credential"; inferring
|
||||
routing from the value there would silently override the caller and send a
|
||||
provider's key to the wrong host when a prefix collides.
|
||||
|
||||
Args:
|
||||
api_key: A credential of unknown origin. ``None`` and ``""`` are
|
||||
tolerated and report ``False``.
|
||||
|
||||
Returns:
|
||||
``True`` if the key carries OpenRouter's prefix.
|
||||
"""
|
||||
return (api_key or "").strip().startswith("sk-or-")
|
||||
|
||||
|
||||
def openrouter_base_url() -> str:
|
||||
"""Return the OpenRouter endpoint, honouring an environment override.
|
||||
|
||||
Returns:
|
||||
The value of ``OPENROUTER_BASE_URL`` if set and non-empty, otherwise
|
||||
the default public endpoint.
|
||||
"""
|
||||
return os.getenv("OPENROUTER_BASE_URL", "").strip() or OPENROUTER_BASE_URL
|
||||
|
||||
|
||||
def map_model_to_openrouter(model: str, *, substitute_unknown: bool = False) -> str:
|
||||
"""Map a bare model id to the equivalent OpenRouter model id.
|
||||
|
||||
Mapping rules, applied in order:
|
||||
|
||||
* ids already containing ``/`` are returned unchanged (already OpenRouter form)
|
||||
* ``gpt-*`` / ``o1-*`` / ``o3-*`` / ``o4-*`` become ``openai/<id>``
|
||||
* ``claude-*`` becomes the matching Anthropic id
|
||||
* ``kimi-*`` becomes ``moonshotai/kimi-k2.6`` (kimi-k3 is not hosted)
|
||||
* ``deepseek-*`` becomes ``deepseek/<id>``
|
||||
* ``qwen-*`` / ``qwen2*`` / ``qwen3*`` becomes ``qwen/<id>``
|
||||
What to do with an unmapped id -- a native one such as ``doubao-*`` or
|
||||
``glm-*``, which OpenRouter does not reliably host -- depends on why the
|
||||
caller is mapping, so it is the caller's decision rather than a fixed rule
|
||||
here. Talking to an aggregator that *requires* a namespaced id, a working
|
||||
default beats a request that cannot succeed. Rerouting a request the reader
|
||||
already aimed at a named model, silently answering as a different vendor's
|
||||
model is worse than failing.
|
||||
|
||||
Args:
|
||||
model: A bare or already-namespaced model id. ``None`` and ``""`` are
|
||||
tolerated.
|
||||
substitute_unknown: When ``True``, an unmapped id becomes
|
||||
``OPENROUTER_MODEL`` or the package default. When ``False`` it is
|
||||
returned unchanged, to be rejected by OpenRouter under the name the
|
||||
reader actually asked for.
|
||||
|
||||
Returns:
|
||||
An OpenRouter model id, or the unchanged input for an unmapped id when
|
||||
``substitute_unknown`` is ``False``.
|
||||
"""
|
||||
m = (model or "").strip()
|
||||
if "/" in m:
|
||||
return m
|
||||
ml = m.lower()
|
||||
if ml.startswith(("gpt-", "o1-", "o3-", "o4-")):
|
||||
return "openai/" + m
|
||||
if ml.startswith("claude-"):
|
||||
if "sonnet" in ml:
|
||||
return "anthropic/claude-sonnet-4.6"
|
||||
if "haiku" in ml:
|
||||
return "anthropic/claude-haiku-4.5"
|
||||
return "anthropic/claude-opus-4.8"
|
||||
if ml.startswith("kimi"):
|
||||
return "moonshotai/kimi-k2.6"
|
||||
if ml.startswith("deepseek"):
|
||||
return "deepseek/" + m
|
||||
if ml.startswith("qwen"):
|
||||
return "qwen/" + m
|
||||
if substitute_unknown:
|
||||
return os.getenv("OPENROUTER_MODEL", "").strip() or OPENROUTER_DEFAULT_MODEL
|
||||
return m
|
||||
Reference in New Issue
Block a user