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,129 @@
|
||||
"""
|
||||
全局配置:模型与价格。
|
||||
|
||||
价格换算成本时使用「每百万 token 单价(美元)」。
|
||||
默认值取自 OpenAI gpt-4o-mini 的公开定价(2024-2025):
|
||||
- 输入 : $0.15 / 1M tokens
|
||||
- 缓存命中输入 : $0.075 / 1M tokens (命中 prompt cache 的输入按 5 折计费)
|
||||
- 输出 : $0.60 / 1M tokens
|
||||
|
||||
注意:
|
||||
1. 默认模型为 gpt-5.6-luna(当前廉价旗舰)。首选凭据是 OPENAI_API_KEY;若未设置,
|
||||
自动回退到 OPENROUTER_API_KEY 并把模型名映射成 OpenRouter id(gpt-* -> openai/*)。
|
||||
由于 gpt-5.x 直连 OpenAI 需要组织实名认证,只要 OPENROUTER_API_KEY 存在就优先走
|
||||
OpenRouter(见 make_client_and_model)。仍可用 COST_DEMO_MODEL / --model 切换任意模型。
|
||||
2. OpenAI 的 prompt caching 是「自动」的:当请求前缀 >= 1024 token 且与近期请求
|
||||
命中相同前缀时,usage.prompt_tokens_details.cached_tokens 会大于 0,
|
||||
这部分 token 按缓存价(更便宜)计费。本项目正是用它来真实体现 KV-cache 的节省。
|
||||
(OpenRouter 转发 OpenAI 时同样在 prompt_tokens_details.cached_tokens 回传缓存命中。)
|
||||
"""
|
||||
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
# 使用的模型(默认当前廉价旗舰 gpt-5.6-luna;可用 COST_DEMO_MODEL / --model 覆盖)
|
||||
MODEL = os.environ.get("COST_DEMO_MODEL", "gpt-5.6-luna")
|
||||
|
||||
# OpenRouter 回退:无 OPENAI_API_KEY 时用 OPENROUTER_API_KEY 走 OpenAI 兼容端点。
|
||||
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
||||
|
||||
|
||||
def _to_openrouter_model(model: str) -> str:
|
||||
"""把模型名映射成 OpenRouter id:含 '/' 视为原生 id;gpt-* -> openai/*;
|
||||
claude-* -> anthropic/claude-opus-4.8;其余回退到 openai/gpt-5.6-luna。"""
|
||||
if "/" in model:
|
||||
return model
|
||||
if model.startswith("gpt-"):
|
||||
return "openai/" + model
|
||||
if model.startswith("claude-"):
|
||||
return "anthropic/claude-opus-4.8"
|
||||
return "openai/gpt-5.6-luna"
|
||||
|
||||
|
||||
def make_client_and_model(model: str):
|
||||
"""构造 OpenAI 兼容 client 并返回 (client, 实际调用的模型名)。
|
||||
|
||||
回退策略(universal OpenRouter fallback):
|
||||
- gpt-5.x 且存在 OPENROUTER_API_KEY -> 优先走 OpenRouter(直连需组织实名认证);
|
||||
- 否则有 OPENAI_API_KEY -> 直连 OpenAI,模型名不变;
|
||||
- 否则有 OPENROUTER_API_KEY -> 走 OpenRouter,模型名按 _to_openrouter_model 映射;
|
||||
- 两者皆无 -> 抛出清晰错误。
|
||||
"""
|
||||
from openai import OpenAI
|
||||
|
||||
primary = os.environ.get("OPENAI_API_KEY", "").strip()
|
||||
orkey = os.environ.get("OPENROUTER_API_KEY", "").strip()
|
||||
prefer_openrouter = bool(orkey) and model.startswith("gpt-5")
|
||||
|
||||
if not prefer_openrouter and primary:
|
||||
return OpenAI(timeout=60.0, max_retries=2), model
|
||||
if orkey:
|
||||
return (
|
||||
OpenAI(base_url=OPENROUTER_BASE_URL, api_key=orkey,
|
||||
timeout=60.0, max_retries=2),
|
||||
_to_openrouter_model(model),
|
||||
)
|
||||
if primary:
|
||||
return OpenAI(timeout=60.0, max_retries=2), model
|
||||
raise RuntimeError(
|
||||
"缺少可用凭据:请设置 OPENAI_API_KEY(直连 OpenAI),或设置 "
|
||||
"OPENROUTER_API_KEY(自动回退到 OpenRouter);或改用 --offline 离线复算(无需 key)。"
|
||||
)
|
||||
|
||||
# 每百万 token 的美元单价(默认 gpt-4o-mini)
|
||||
PRICE_INPUT_PER_M = 0.15 # 普通输入
|
||||
PRICE_CACHED_PER_M = 0.075 # 命中缓存的输入(gpt-4o-mini 缓存读取为输入价的 50%)
|
||||
PRICE_OUTPUT_PER_M = 0.60 # 输出
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Pricing:
|
||||
"""一组每百万 token 的美元单价。"""
|
||||
input_per_m: float
|
||||
cached_per_m: float
|
||||
output_per_m: float
|
||||
|
||||
def cost_usd(self, prompt_tokens: int, cached_tokens: int,
|
||||
completion_tokens: int) -> float:
|
||||
"""按 token 用量换算成本(美元)。
|
||||
|
||||
prompt_tokens : usage.prompt_tokens,包含了缓存命中的部分
|
||||
cached_tokens : usage.prompt_tokens_details.cached_tokens,命中缓存的输入 token
|
||||
completion_tokens: usage.completion_tokens
|
||||
|
||||
未命中缓存的输入 = prompt_tokens - cached_tokens,按普通输入价计费;
|
||||
命中缓存的输入按缓存价计费。
|
||||
"""
|
||||
uncached_input = max(prompt_tokens - cached_tokens, 0)
|
||||
return (
|
||||
uncached_input / 1_000_000 * self.input_per_m
|
||||
+ cached_tokens / 1_000_000 * self.cached_per_m
|
||||
+ completion_tokens / 1_000_000 * self.output_per_m
|
||||
)
|
||||
|
||||
|
||||
# 常见 OpenAI 模型的公开单价预设(每百万 token,美元),方便 CLI 用 --model 一键切换。
|
||||
# 换更强的模型不影响 KV-cache 机制(仍要求稳定前缀 >= 1024 token)。
|
||||
PRICING_PRESETS = {
|
||||
"gpt-4o-mini": Pricing(0.15, 0.075, 0.60),
|
||||
"gpt-4o": Pricing(2.50, 1.25, 10.00),
|
||||
"gpt-4.1-mini": Pricing(0.40, 0.10, 1.60),
|
||||
"gpt-4.1": Pricing(2.00, 0.50, 8.00),
|
||||
}
|
||||
|
||||
|
||||
def default_pricing() -> Pricing:
|
||||
"""返回默认模型(config 中 MODEL)的单价;未知模型回退到模块级 PRICE_* 默认值。"""
|
||||
return PRICING_PRESETS.get(
|
||||
MODEL, Pricing(PRICE_INPUT_PER_M, PRICE_CACHED_PER_M, PRICE_OUTPUT_PER_M)
|
||||
)
|
||||
|
||||
|
||||
def cost_usd(prompt_tokens: int, cached_tokens: int, completion_tokens: int,
|
||||
pricing: "Pricing | None" = None) -> float:
|
||||
"""按 token 用量换算成本(美元)。默认用模块级单价,可传入自定义 Pricing。"""
|
||||
p = pricing or Pricing(PRICE_INPUT_PER_M, PRICE_CACHED_PER_M, PRICE_OUTPUT_PER_M)
|
||||
return p.cost_usd(prompt_tokens, cached_tokens, completion_tokens)
|
||||
Reference in New Issue
Block a user