实战 SOP
实战 SOP

大模型 API 成本优化 SOP:前缀缓存、模型路由与批量调用

大模型 API 成本优化六步法:量化基线、前缀缓存(Qwen3.8-Max 命中 ¥1/M 对未命中 ¥12 差 12 倍)、模型路由(贵模型做难题/便宜模型做易题)、batch API(延迟换折扣)、上下文裁剪与重复请求去重、监控迭代。每步附 Python 代码示例,含踩坑提示与 FAQ。

发布于 2026年8月3日12 分钟阅读
<!-- llm-api-cost-optimization-sop | sop | 大模型 API 成本优化 SOP:前缀缓存、模型路由与批量调用 -->

大模型 API 按 token 计费,单次调用不贵,但当一个 agent 任务要跑几十轮、一个产品日调用上百万次,成本就变成必须死盯的指标。阿里 Qwen3.8-Max 给了一个直观的数字:显式缓存命中输入 ¥1/百万 tokens,未命中 ¥12,差 12 倍(以官网为准)。同样 100 万 tokens 的输入,缓存命中花 1 块,没命中花 12 块。长程任务要跑起来,单次调用必须便宜到"敢让它反复试"。这篇 SOP 把成本优化拆成六步:量化基线 -> 前缀缓存 -> 模型路由 -> 批量 API -> 上下文优化 -> 监控迭代,每步给可复制的 Python 代码,最后附踩坑和 FAQ。核心思路四把刀:让贵的模型少干活、让便宜的模型多干活、让重复的输入走缓存、让不急的任务进批量队列。和本站《LLM 微调实战 SOP》讲怎么"训"不同,这篇讲怎么"省"——但两者有个共同前提:先量化,再动手。


一、量化当前成本基线

优化前先量。不知道每天花多少、花在哪、哪个模型吃掉大头,优化就是盲改。第一步不是砍成本,是把账算清。

每个 API 响应的 usage 字段都带 token 统计(prompt_tokenscompletion_tokens,部分支持 cached_tokens)。把这些数字记下来,按模型、按接口、按时段聚合,才能定位"钱花在哪"。

python
import time
from collections import defaultdict


class CostTracker:
    """轻量调用成本追踪器:记录每次调用的 token 用量与费用"""

    def __init__(self):
        self.logs = []
        self.cost_by_model = defaultdict(float)

    def log(self, model, input_tokens, output_tokens,
            cached_tokens=0, cost=0.0):
        self.logs.append({
            "ts": time.time(),
            "model": model,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cached_tokens": cached_tokens,
            "cost": cost,
        })
        self.cost_by_model[model] += cost

    def summary(self):
        total_cost = sum(l["cost"] for l in self.logs)
        total_input = sum(l["input_tokens"] for l in self.logs)
        total_cached = sum(l["cached_tokens"] for l in self.logs)
        hit_rate = total_cached / total_input if total_input else 0
        return {
            "total_cost": round(total_cost, 4),
            "total_input_tokens": total_input,
            "cache_hit_rate": f"{hit_rate:.1%}",
            "cost_by_model": dict(self.cost_by_model),
        }

跑一周,看 summary() 输出:总花费、各模型占比、缓存命中率。如果发现 80% 的调用其实只是翻译/分类这种简单任务,但全打在贵模型上,那路由优化空间就很大。如果缓存命中率不到 5%,那前缀缓存是第一优先级。没有这层数据,后面所有优化都是拍脑袋。


二、前缀缓存(prompt caching)

前缀缓存的核心原理:如果多次请求的前缀(system prompt + 长文档 + few-shot 示例)完全一致,服务商把这段前缀的 KV-cache 存下来,后续命中时按"缓存读取"价计费,远低于常规输入价。Qwen3.8-Max 缓存命中 ¥1/M 对未命中 ¥12/M 差 12 倍就是典型例子。

各家的实现方式不同:

服务商缓存机制接入方式
Anthropic显式缓存,需标记 cache_control在 system/content 块里加 cache_control
OpenAI自动缓存(prompt >1024 tokens 自动生效)无需改代码,把静态内容放前面即可
DeepSeek前缀缓存,自动命中把稳定内容放 prompt 开头
Qwen(阿里)显式/自动缓存以官方文档为准

Anthropic 的写法最清晰,以它为例:

python
import anthropic

client = anthropic.Anthropic()  # 环境变量 ANTHROPIC_API_KEY

# 稳定的长文档/规则/示例放进 system,标记 cache_control
LONG_DOC = "(这里放几千 token 的文档内容、规则、few-shot 示例)"

response = client.messages.create(
    model="claude-sonnet-4-5-20250929",  # 以官网为准
    max_tokens=1024,
    system=[
        {
            "type": "text",
            "text": f"你是文档助手。参考文档:\n{LONG_DOC}",
            "cache_control": {"type": "ephemeral"},
        }
    ],
    messages=[
        {"role": "user", "content": "总结文档要点。"}
    ],
)

# 响应 usage 里会返回缓存命中情况
print(f"缓存读取 tokens: {response.usage.cache_read_input_tokens}")
print(f"缓存写入 tokens: {response.usage.cache_creation_input_tokens}")
print(f"常规输入 tokens: {response.usage.input_tokens}")

关键约束:前缀必须逐 token 完全一致。system prompt 改一个字、工具列表增删一条、few-shot 顺序换一下,缓存全部失效。所以接入前缀缓存意味着把 prompt 结构固化:稳定段放最前面(system + 长文档 + 规则),变化段放最后面(用户当次输入)。

踩坑提醒:

  • 缓存有 TTL(Anthropic 默认 5 分钟,可用 beta header 延长到 1 小时;OpenAI/DeepSeek 各有窗口期,以官网为准),空闲太久自动过期。
  • 第一次请求必然是 cache write(部分服务商缓存写入价略高于常规输入价),第二次起才命中。高频调用场景收益最大。
  • 动态注入时间戳、随机 ID 到 prompt 开头会直接击穿缓存,这类变化内容必须放到 prompt 最末尾。

三、模型路由(分级调度)

不是所有任务都需要最强模型。翻译、分类、摘要、格式转换这类任务,便宜模型和贵模型差距很小,但价格差几倍到十几倍。模型路由的思路:按任务复杂度分流,贵模型只做难题,便宜模型做易题。

python
def route_model(query: str) -> str:
    """按任务复杂度路由到不同模型(模型名以官网为准)"""
    q = query.lower()

    # 复杂推理:代码、数学、长文分析、多步推理 -> 强模型
    hard_keywords = [
        "代码", "编程", "debug", "数学", "推理", "分析",
        "code", "math", "reason", "analyze", "essay",
    ]
    if any(kw in q for kw in hard_keywords):
        return "qwen-max"  # 强模型

    # 简单任务:翻译、分类、摘要、格式转换 -> 轻量模型
    return "qwen-turbo"  # 便宜模型


def call_with_fallback(query: str) -> str:
    """带 fallback 的路由调用"""
    from openai import OpenAI
    client = OpenAI()  # OpenAI 兼容接口(Qwen/DeepSeek 均支持)

    primary = route_model(query)
    fallback = "qwen-plus"  # 主模型不可用时降级

    for model in [primary, fallback]:
        try:
            resp = client.chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": query}],
                max_tokens=512,
            )
            return resp.choices[0].message.content
        except Exception as e:
            print(f"模型 {model} 调用失败: {e},尝试 fallback")
    raise RuntimeError("所有模型均不可用")

路由策略可以从简单到复杂逐步升级:

  1. 关键词匹配(上面代码):最简单,覆盖高频场景,几十行代码就能上线。
  2. 分类器路由:训一个轻量分类器(甚至用 embedding 相似度)判断难度,比关键词更准。
  3. 级联路由(cascade):先让便宜模型试,置信度低或结果不达标再升级到贵模型,最省但延迟翻倍。

不管用哪种,都要留 fallback:主模型限流或超时时自动降到备选模型,不能让用户看到 500 错误。


四、批量 API(batch API)

大量不急的任务(数据标注、批量翻译、内容生成、评估打分)不需要实时返回,可以走 batch API。OpenAI、Anthropic 都提供 batch 接口,把请求攒成 JSONL 文件提交,几小时内批量返回,价格通常有显著折扣(常见约五折,以官网为准)。

以 OpenAI Batch API 为例:

python
import json
from openai import OpenAI

client = OpenAI()  # 环境变量 OPENAI_API_KEY

# 1. 把请求写成 JSONL 文件
texts = ["你好", "谢谢", "再见", "请问", "没问题"]
requests = [
    {
        "custom_id": f"req-{i}",
        "method": "POST",
        "url": "/v1/chat/completions",
        "body": {
            "model": "gpt-4o-mini",
            "messages": [{"role": "user",
                          "content": f"翻译成英文:{text}"}],
            "max_tokens": 200,
        },
    }
    for i, text in enumerate(texts)
]

with open("batch_input.jsonl", "w", encoding="utf-8") as f:
    for r in requests:
        f.write(json.dumps(r, ensure_ascii=False) + "\n")

# 2. 上传文件 + 创建 batch 任务
batch_file = client.files.create(
    file=open("batch_input.jsonl", "rb"),
    purpose="batch",
)

batch = client.batches.create(
    input_file_id=batch_file.id,
    endpoint="/v1/chat/completions",
    completion_window="24h",
)

print(f"Batch ID: {batch.id}, 状态: {batch.status}")

# 3. 轮询状态,完成后下载结果
# batch = client.batches.retrieve(batch.id)
# if batch.status == "completed":
#     result = client.files.content(batch.output_file_id)

关键限制:

  • 延迟换价格:batch 任务在 24 小时窗口内完成,不适合实时场景。
  • 单文件上限:请求数和文件大小有上限(以官网为准),超量拆多个文件。
  • 不支持流式:batch 只返回完整结果,没有 streaming。

五、上下文与重复请求优化

长对话和多轮 agent 任务里,上下文长度是成本杀手。一个 20 轮的对话,如果不裁剪,每轮都把前面全部历史发一遍,token 数线性增长,成本也线性增长。上下文优化做两件事:裁剪历史、去重重复请求。

python
def trim_context(messages: list, max_messages: int = 20) -> list:
    """保留 system + 最近 N 条,裁掉中间历史"""
    if len(messages) <= max_messages:
        return messages

    system_msgs = [m for m in messages if m["role"] == "system"]
    dialog = [m for m in messages if m["role"] != "system"]
    keep = max_messages - len(system_msgs)
    recent = dialog[-keep:] if keep > 0 else []

    return system_msgs + recent


def compress_with_summary(messages: list, llm_call) -> list:
    """超长对话:早期消息压成摘要,保留近期原文"""
    if len(messages) <= 20:
        return messages

    system_msgs = [m for m in messages if m["role"] == "system"]
    dialog = [m for m in messages if m["role"] != "system"]

    to_summarize = dialog[:-10]
    recent = dialog[-10:]

    # 早期对话让便宜模型压成摘要
    summary_input = "\n".join(
        f"{m['role']}: {m['content']}" for m in to_summarize)
    summary = llm_call(f"把以下对话压成 200 字摘要:\n{summary_input}")

    summary_msg = {
        "role": "system",
        "content": f"早期对话摘要:{summary}",
    }
    return system_msgs + [summary_msg] + recent

除了裁剪,还要注意重复请求去重。同一个 prompt 在短时间内被多次调用(用户刷新、重试),可以加一层本地缓存:

python
import hashlib
import time


class ResponseCache:
    """简单的响应缓存(只适合幂等请求)"""

    def __init__(self, ttl_seconds=300):
        self.cache = {}
        self.ttl = ttl_seconds

    def _key(self, model, messages):
        raw = f"{model}:{str(messages)}"
        return hashlib.md5(raw.encode()).hexdigest()

    def get(self, model, messages):
        key = self._key(model, messages)
        if key in self.cache:
            ts, result = self.cache[key]
            if time.time() - ts < self.ttl:
                return result
        return None

    def set(self, model, messages, result):
        key = self._key(model, messages)
        self.cache[key] = (time.time(), result)

注意:只对幂等请求缓存(如"翻译这段文本"),对需要实时性的请求(如"今天天气")不要缓存,否则返回过期结果。


六、监控与迭代

成本优化不是一次性工程,上线后要持续监控这几个指标:

指标含义告警阈值(参考)
日总花费每日 API 成本日环比涨 50%
缓存命中率cached_tokens / input_tokens< 30%(有优化空间)
模型分布各模型调用占比贵模型占比 > 60%(查路由)
单请求均成本总花费 / 请求数周环比涨 20%
batch 占比batch 请求数 / 总请求数< 20%(有空间)

把第一步的 CostTracker 接到生产环境,每天跑一次 summary(),趋势看一周。如果缓存命中率从 60% 掉到 20%,大概率是 prompt 结构被改了导致前缀漂移;如果贵模型占比突然升高,检查是不是路由规则有漏洞让简单任务漏到了贵模型上。


七、踩坑记录

坑一:缓存命中需前缀完全一致。 system prompt 改一个标点、few-shot 顺序换一条、工具列表增删一个,缓存全失效。把稳定段和变化段严格分开,稳定段放最前。

坑二:batch API 有延迟不适合实时。 batch 是"几小时内出结果"的模式,用户等不了的实时对话、在线推理场景不能用,只适合离线批量任务。

坑三:路由分级不留 fallback 会炸。 主模型限流或超时,没有 fallback 直接 500。必须配备选模型,降级也比报错强。

坑四:动态内容注入 prompt 头部击穿缓存。 时间戳、随机 ID、用户昵称放在 prompt 开头,每次都不同,缓存永远不命中。这类内容放到 prompt 最末尾(用户消息部分)。

坑五:上下文裁太狠丢失关键信息。 max_messages 设太小,agent 丢失任务上下文,导致重复提问或答非所问。经验值:对话场景留 10-20 条,agent 场景用摘要压缩而非硬截断。

坑六:只看单价不看总账。 单次调用便宜了 50%,但因为便宜了所以调用频次翻倍,总花费反而涨了。优化要看总账,不是单次成本。


FAQ

Q1:前缀缓存能省多少?

取决于 prompt 结构和调用频率。如果 system prompt + 长文档占输入的 80%,且高频调用,缓存命中后那 80% 按缓存价计费。以 Qwen3.8-Max 为例,缓存命中价是未命中价的 1/12(以官网为准),命中率高时输入成本可降到原来的十几分之一。低频调用(一天几次)缓存容易过期,收益有限。

Q2:batch API 多久出结果?

OpenAI Batch API 的 completion_window 设为 24h,实际通常几小时内完成,但不保证实时。Anthropic 的 Message Batches 类似。batch 适合数据标注、批量翻译、离线评估这类不急的任务,实时场景不能用。

Q3:小团队先做哪一步?

先量基线(Step 1),再接前缀缓存(Step 2)。这两步投入最小、收益最直接:CostTracker 是几十行代码,前缀缓存对 Anthropic 来说就是加一行 cache_control。路由和 batch 是第二步,需要改调用逻辑。上下文裁剪和监控是第三步,持续迭代。

Q4:路由会不会掉质量?

简单任务不会。翻译、分类、摘要这类任务,便宜模型和贵模型差距很小,但价格差几倍。关键是路由规则要准确——如果把难题误判为易题发给便宜模型,质量会掉。建议从保守路由开始(只把明确简单的任务分流),逐步扩大。级联模式(先便宜模型试,不达标再升级)更稳但延迟翻倍。

Q5:缓存命中率怎么提?

三件事:(1)prompt 结构固化,稳定段(system + 文档 + 规则)放最前,变化段(用户输入)放最后;(2)避免在前缀里注入动态内容(时间戳、随机数、用户 ID),必须注入的放到用户消息部分;(3)调用频率要够高,保证 TTL 内有后续请求命中。如果调用频率低,考虑用更长 TTL 的缓存选项(如 Anthropic 的 1 小时缓存,以官网为准)。


看法

成本优化的本质不是"省钱",而是"让钱花在刀刃上"。当一个 agent 任务需要跑 50 轮才能收敛,如果每轮调用成本太高,你根本不敢让它跑——要么中途放弃,要么人为缩减迭代次数,牺牲质量。前缀缓存把单次调用成本压到原来的十几分之一,意味着同样的预算可以跑十几倍的迭代,或者同样的迭代次数只花十几分之一的钱。这才是成本优化的真正价值:它改变的是"敢不敢让模型充分试"的决策门槛。Qwen3.8-Max 把缓存命中和未命中的价差拉到 12 倍,本质上是在告诉开发者:长程任务可以跑了,只要你把前缀缓存用好。


参考来源

本文由 AI 辅助生成,经人工审核编辑。最后更新:2026-08-03

常见问题

前缀缓存能省多少?
取决于 prompt 结构和调用频率。如果 system prompt + 长文档占输入的 80% 且高频调用,那 80% 按缓存价计费。以 Qwen3.8-Max 为例,缓存命中价是未命中价的 1/12(以官网为准),命中率高时输入成本可降到原来的十几分之一。低频调用缓存容易过期,收益有限。
batch API 多久出结果?
OpenAI Batch API 的 completion_window 设 24h,实际通常几小时内完成,但不保证实时。Anthropic 的 Message Batches 类似。batch 适合数据标注、批量翻译、离线评估,实时场景不能用。
小团队先做哪一步?
先量基线(Step 1),再接前缀缓存(Step 2)。这两步投入最小、收益最直接:CostTracker 几十行代码,前缀缓存对 Anthropic 就是加一行 cache_control。路由和 batch 是第二步,上下文裁剪和监控是第三步,持续迭代。
路由会不会掉质量?
简单任务不会。翻译、分类、摘要这类任务便宜模型和贵模型差距很小但价格差几倍。关键是路由规则要准确,难题误判为易题发给便宜模型会掉质量。建议从保守路由开始,逐步扩大。级联模式(先便宜模型试,不达标再升级)更稳但延迟翻倍。
缓存命中率怎么提?
三件事:(1)prompt 结构固化,稳定段放最前,变化段放最后;(2)避免在前缀注入动态内容(时间戳、随机数、用户 ID),放到用户消息部分;(3)调用频率要够高,保证 TTL 内有后续请求命中。调用频率低时考虑更长 TTL 的缓存选项(以官网为准)。

相关文章

实战 SOP

AI 数字人制作实战 SOP:从脚本到成品的可复制流程

把 AI 数字人制作拆成六步可复制流程:明确用途选工具(HeyGen/D-ID/Synthesia/Colossyan/DeepBrain 及国内腾讯智影/硅基智能)、写口播脚本(附 prompt 模板)、选或定制形象、先定音色再生成口型、字幕剪辑与合规后处理、平台适配发布。附 5 个避坑(形象授权/口型对不齐/多语言音色/长视频成本/合规标识)和 5 条 FAQ。代表性流程,非单一工具实测,功能以官网为准。

2026年8月7日8 分钟阅读
实战 SOP

block/buzz 自托管部署 SOP:从 Docker 到 agent 入组

block/buzz 自托管部署完整 SOP(与 buzz-hive-mind 热点文成对):本地开发栈(just setup/build/dev)+ 生产单节点(deploy/compose Docker,Postgres/Redis/MinIO)+ 配置(.env:RELAY_URL/BUZZ_RELAY_PRIVATE_KEY/RELAY_OWNER_PUBKEY)+ agent 入组(Nostr keypair NIP-98 签名,buzz-admin 管成员)+ 闭门 relay + 5 FAQ。部署命令全据 README/compose/.env/CLI/ARCHITECTURE,未编造。

2026年8月6日9 分钟阅读
实战 SOP

n8n 搭建 AI agent 工作流实战 SOP:部署与避坑

在 n8n 画布里搭一个能自主调用工具的 AI agent 工作流的完整 SOP:Docker 自托管一条命令部署、AI Agent 节点四件套解剖(Language Model+Memory+Tools+System Prompt)、分步搭建(选触发器->配节点->加工具->输出->测试发布)、五个避坑(Memory 失忆/API Key 硬编码/过度设计/上下文漂移/数据格式不匹配)+5 FAQ。节点参数以 n8n 官方文档为准,给配置逻辑不伪造完整 JSON。

2026年8月6日9 分钟阅读