AI API 成本优化实战:缓存、模型路由、Token 控制、Batch 与监控配置

AI API 账单变高,通常不是某一次请求突然很贵,而是多个小问题叠加出来的结果:

  • 请求量上涨;
  • 上下文越来越长;
  • 输出内容不受控制;
  • 简单任务也使用高能力模型;
  • 失败后不断重试;
  • Agent / 工具调用链路过长;
  • 离线任务被当成实时任务逐条调用。

很多团队第一反应是"换一个更便宜的模型"。这个方向可以做,但不建议作为唯一手段。更稳妥的做法是先把调用链路拆开,看清楚成本花在哪里,然后再分别处理:

  1. 哪些请求根本不该发;
  2. 哪些 token 可以省;
  3. 哪些任务可以走低成本模型;
  4. 哪些非实时任务可以异步或批量处理;
  5. 哪些异常需要通过监控、预算和告警提前拦住。

本文按工程落地视角整理一套 AI API 成本优化方案,重点放在可配置、可验证、可排错的实现方式上。


1. 成本排查:先把 AI API 调用数据打全

在做缓存、模型路由、上下文压缩之前,建议先补齐调用日志。否则很容易优化错方向。

例如:

  • 以为贵在请求次数,实际贵在输出 token;
  • 以为贵在模型单价,实际是失败重试过多;
  • 以为压缩 prompt 有用,实际是 RAG 召回片段过多;
  • 以为 batch 能省钱,实际单条任务本身 prompt 就很浪费。

1.1 建议记录的调用字段

生产环境中,建议每次 AI API 调用至少记录以下字段:

字段 作用
request_id 追踪单次调用链路
user_id / tenant_id 统计用户或租户成本
business_type 区分客服、摘要、审核、分类等业务
model 统计不同模型使用占比
input_tokens 分析输入 token 消耗
output_tokens 分析输出 token 消耗
latency_ms 观察延迟
success 统计成功率
error_code 统计失败类型
retry_count 统计重试成本
cache_hit 评估缓存收益
estimated_cost 估算单次与总成本

注意:不同平台的 token 统计方式、价格和计费规则可能不同,具体应以各平台最新文档为准。这里重点是日志字段设计,不涉及具体价格承诺。

1.2 一个通用的调用日志结构

可以先用类似下面的结构记录:

复制代码
{
  "request_id": "req_20250101_000001",
  "user_id": "u_10001",
  "tenant_id": "t_001",
  "business_type": "customer_service_qa",
  "model": "default-model",
  "input_tokens": 1200,
  "output_tokens": 300,
  "latency_ms": 1820,
  "success": true,
  "error_code": null,
  "retry_count": 0,
  "cache_hit": false,
  "estimated_cost": null,
  "created_at": "2025-01-01T10:00:00Z"
}

如果暂时没有接入精确成本计算,estimated_cost 可以先为空,至少先把 token、模型、业务线和失败率记录下来。

后续再按模型单价、输入 token、输出 token 做成本估算。


2. 环境变量配置:把 Endpoint、模型名和鉴权信息统一管理

AI API 成本优化通常不应该写死在业务代码里。建议把 API 地址、模型名、超时时间、重试次数、输出长度等都做成配置项。

2.1 推荐的环境变量结构

复制代码
# API Endpoint
AI_API_BASE_URL=https://api.example.com/v1

# 鉴权 Key
AI_API_KEY=your_api_key_here

# 默认模型
AI_MODEL_DEFAULT=default-model

# 小模型 / 低成本模型
AI_MODEL_SMALL=small-model

# 强模型 / 高能力模型
AI_MODEL_STRONG=strong-model

# 请求超时时间,单位:秒
AI_REQUEST_TIMEOUT=30

# 最大重试次数
AI_MAX_RETRY=2

# 默认最大输出长度
AI_MAX_OUTPUT_TOKENS=512

# 是否启用缓存
AI_CACHE_ENABLED=true

# 缓存过期时间,单位:秒
AI_CACHE_TTL=3600

这样做有几个好处:

  • 不同环境可以使用不同 Endpoint;
  • 模型切换不需要改代码;
  • 可以按业务逐步灰度模型路由;
  • 可以快速调整超时、重试、输出长度;
  • 排错时更容易定位是鉴权、Endpoint、模型名还是参数问题。

2.2 基础客户端封装示例

下面是一个偏通用的 Python 伪实现,用于说明封装思路:

复制代码
import os
import time
import uuid
import requests

AI_API_BASE_URL = os.getenv("AI_API_BASE_URL")
AI_API_KEY = os.getenv("AI_API_KEY")
AI_MODEL_DEFAULT = os.getenv("AI_MODEL_DEFAULT", "default-model")
AI_REQUEST_TIMEOUT = int(os.getenv("AI_REQUEST_TIMEOUT", "30"))
AI_MAX_RETRY = int(os.getenv("AI_MAX_RETRY", "2"))
AI_MAX_OUTPUT_TOKENS = int(os.getenv("AI_MAX_OUTPUT_TOKENS", "512"))


def call_ai_api(messages, model=None, max_tokens=None, business_type="default"):
    request_id = str(uuid.uuid4())
    model = model or AI_MODEL_DEFAULT
    max_tokens = max_tokens or AI_MAX_OUTPUT_TOKENS

    url = f"{AI_API_BASE_URL}/chat/completions"

    headers = {
        "Authorization": f"Bearer {AI_API_KEY}",
        "Content-Type": "application/json"
    }

    payload = {
        "model": model,
        "messages": messages,
        "max_tokens": max_tokens
    }

    retry_count = 0
    start_time = time.time()

    while True:
        try:
            resp = requests.post(
                url,
                headers=headers,
                json=payload,
                timeout=AI_REQUEST_TIMEOUT
            )

            latency_ms = int((time.time() - start_time) * 1000)

            if resp.status_code == 200:
                data = resp.json()

                # 这里根据具体平台返回字段做适配
                usage = data.get("usage", {})
                input_tokens = usage.get("prompt_tokens")
                output_tokens = usage.get("completion_tokens")

                log_ai_call(
                    request_id=request_id,
                    business_type=business_type,
                    model=model,
                    input_tokens=input_tokens,
                    output_tokens=output_tokens,
                    latency_ms=latency_ms,
                    success=True,
                    error_code=None,
                    retry_count=retry_count,
                    cache_hit=False
                )

                return data

            if should_retry(resp.status_code) and retry_count < AI_MAX_RETRY:
                retry_count += 1
                time.sleep(0.5 * retry_count)
                continue

            log_ai_call(
                request_id=request_id,
                business_type=business_type,
                model=model,
                input_tokens=None,
                output_tokens=None,
                latency_ms=latency_ms,
                success=False,
                error_code=str(resp.status_code),
                retry_count=retry_count,
                cache_hit=False
            )

            raise RuntimeError(f"AI API error: {resp.status_code}, {resp.text}")

        except requests.Timeout:
            latency_ms = int((time.time() - start_time) * 1000)

            if retry_count < AI_MAX_RETRY:
                retry_count += 1
                time.sleep(0.5 * retry_count)
                continue

            log_ai_call(
                request_id=request_id,
                business_type=business_type,
                model=model,
                input_tokens=None,
                output_tokens=None,
                latency_ms=latency_ms,
                success=False,
                error_code="timeout",
                retry_count=retry_count,
                cache_hit=False
            )

            raise


def should_retry(status_code):
    return status_code in [429, 500, 502, 503, 504]


def log_ai_call(**kwargs):
    # 生产环境建议写入日志系统、数据库或监控平台
    print(kwargs)

这段代码不是绑定某个具体平台的 SDK,而是展示封装思路:

  • Endpoint 从环境变量读取;
  • API Key 统一管理;
  • 模型名可配置;
  • 最大输出长度可配置;
  • 请求超时可配置;
  • 重试次数可控;
  • 每次调用都记录日志。

3. 方法一:缓存与去重,先减少不必要的调用

缓存是 AI API 成本优化中 ROI 很高的一类方案,因为它不是让单次调用变便宜,而是直接避免重复调用。

3.1 适合缓存的场景

适合缓存的请求一般有两个特点:

  1. 输入相对稳定;
  2. 输出可以复用。

常见场景包括:

  • FAQ 客服问答;
  • 文档问答中的高频问题;
  • 文本分类;
  • 内容安全审核;
  • 翻译;
  • 改写;
  • 摘要。

不适合强缓存的场景包括:

  • 实时数据查询;
  • 个性化推荐;
  • 高度依赖最新上下文的多轮对话;
  • 复杂推理任务;
  • 需要严格时效性的业务。

3.2 缓存 Key 不要只用用户输入

很多人会直接用用户输入做缓存 Key,例如:

复制代码
用户问题 -> 模型回答

这种方式实现简单,但风险比较大。

更稳妥的缓存 Key 至少应包含:

  • 用户输入;
  • system prompt 版本;
  • 模型名称;
  • 温度等关键参数;
  • 业务配置版本。

否则会出现一种隐蔽问题:

prompt 已经改了,模型也换了,但缓存仍然命中了旧答案。

3.3 缓存 Key 示例

复制代码
import hashlib
import json


def build_cache_key(
    user_input,
    system_prompt_version,
    model,
    temperature,
    business_config_version
):
    raw = {
        "user_input": user_input,
        "system_prompt_version": system_prompt_version,
        "model": model,
        "temperature": temperature,
        "business_config_version": business_config_version
    }

    text = json.dumps(raw, ensure_ascii=False, sort_keys=True)
    return hashlib.sha256(text.encode("utf-8")).hexdigest()

3.4 完全输入缓存示例

复制代码
def ask_with_cache(user_input):
    cache_key = build_cache_key(
        user_input=user_input,
        system_prompt_version="v3",
        model=os.getenv("AI_MODEL_DEFAULT"),
        temperature=0.2,
        business_config_version="faq_2025_01"
    )

    cached = cache_get(cache_key)
    if cached:
        log_cache_hit(cache_key)
        return cached

    messages = [
        {"role": "system", "content": "你是客服助手,请基于知识库回答用户问题。"},
        {"role": "user", "content": user_input}
    ]

    result = call_ai_api(
        messages=messages,
        model=os.getenv("AI_MODEL_DEFAULT"),
        max_tokens=512,
        business_type="customer_service_qa"
    )

    answer = extract_answer(result)
    cache_set(cache_key, answer, ttl=3600)

    return answer

3.5 缓存的边界

缓存不是万能的,主要风险包括:

  • 缓存过期;
  • 知识库更新后仍返回旧答案;
  • 相似问题误命中;
  • 多租户数据隔离不严;
  • 不同模型或 prompt 版本混用旧结果。

建议顺序是:

  1. 先做完全相同输入缓存;
  2. 再评估语义相似缓存;
  3. 严肃业务中要设置 TTL 和版本号;
  4. 知识库更新时要能批量失效缓存。

4. 方法二:模型分层与路由,简单任务不要占用强模型

很多 AI API 费用偏高,是因为所有任务都使用了同一个高能力模型。

但实际业务里,不同任务的难度差别很大:

  • 文本分类;
  • 标签提取;
  • 固定格式改写;
  • 简单摘要;
  • JSON 信息抽取;

这些任务通常不一定需要最高能力模型。

更合理的方式是做模型分层:

  • 简单任务:优先小模型或低成本模型;
  • 中等任务:使用通用模型;
  • 复杂推理、长文理解、高风险任务:使用强模型;
  • 小模型失败或置信度不足时,再升级到强模型。

4.1 模型路由策略

任务条件 推荐策略
文本分类、标签提取、固定格式改写 优先小模型
命中 FAQ 或 RAG 片段很明确 小模型 + RAG
输入很长、问题复杂、需要多步推理 使用强模型
小模型输出格式错误 升级模型重试
小模型置信度低 升级模型重试
涉及合规、金融、医疗等高风险判断 提高模型等级,并保留人工审核

重点不是"永远选便宜模型",而是按任务难度和质量要求分配模型。

4.2 模型路由代码示例

复制代码
AI_MODEL_SMALL = os.getenv("AI_MODEL_SMALL", "small-model")
AI_MODEL_DEFAULT = os.getenv("AI_MODEL_DEFAULT", "default-model")
AI_MODEL_STRONG = os.getenv("AI_MODEL_STRONG", "strong-model")


def route_model(task_type, input_text, risk_level="normal"):
    text_len = len(input_text)

    if risk_level == "high":
        return AI_MODEL_STRONG

    if task_type in ["classification", "tag_extract", "format_rewrite"]:
        return AI_MODEL_SMALL

    if task_type in ["summary"] and text_len < 3000:
        return AI_MODEL_DEFAULT

    if task_type in ["complex_qa", "multi_step_reasoning", "long_context"]:
        return AI_MODEL_STRONG

    return AI_MODEL_DEFAULT

4.3 小模型失败后升级

复制代码
def classify_text(text):
    model = route_model(
        task_type="classification",
        input_text=text,
        risk_level="normal"
    )

    messages = [
        {
            "role": "system",
            "content": "你是文本分类器,只输出 JSON,不要输出额外解释。"
        },
        {
            "role": "user",
            "content": text
        }
    ]

    result = call_ai_api(
        messages=messages,
        model=model,
        max_tokens=128,
        business_type="classification"
    )

    parsed = try_parse_json(result)

    if not parsed or parsed.get("confidence", 0) < 0.7:
        result = call_ai_api(
            messages=messages,
            model=AI_MODEL_STRONG,
            max_tokens=128,
            business_type="classification_fallback"
        )
        parsed = try_parse_json(result)

    return parsed

这里的关键是:

  • 小模型不是无条件使用;
  • 要有质量校验;
  • 要有失败回退;
  • 要监控升级比例;
  • 要观察人工介入率是否上升。

如果模型路由导致人工纠错、用户投诉或业务失败增加,那只是把 API 成本转移到了其他地方。


5. 方法三:压缩上下文与控制输出长度,减少 token 浪费

AI API 的输入 token 和输出 token 都会影响成本。很多系统刚上线时费用正常,运行一段时间后变贵,通常和以下问题有关:

  • 多轮对话历史越来越长;
  • RAG 检索片段越塞越多;
  • system prompt 不断追加规则;
  • 输出不限制长度;
  • 模型返回大量无用解释。

5.1 输入端:控制上下文长度

输入侧可以从这些地方优化:

  1. 删除无关历史消息;
  2. 多轮对话使用摘要记忆;
  3. RAG 控制分块大小;
  4. RAG 控制召回数量;
  5. 清理重复、空泛、冲突的 prompt;
  6. 长文任务先切分、筛选,再送入模型。

尤其是 RAG 场景,不是召回越多越好。

召回片段过多会带来两个问题:

  • 输入 token 增加;
  • 噪声变多,模型可能被干扰。

更合理的方式是:

  1. 优化文档分块;
  2. 优化检索排序;
  3. 必要时做重排;
  4. 只把最相关片段送给模型。

5.2 RAG 上下文拼接示例

复制代码
def build_rag_messages(question, retrieved_chunks):
    # 只保留排序靠前的片段
    top_chunks = retrieved_chunks[:5]

    context = "\n\n".join([
        f"文档片段 {idx + 1}:\n{chunk['content']}"
        for idx, chunk in enumerate(top_chunks)
    ])

    messages = [
        {
            "role": "system",
            "content": (
                "你是知识库问答助手。"
                "请只基于提供的文档片段回答。"
                "如果文档中没有答案,请说明无法从现有资料确定。"
            )
        },
        {
            "role": "user",
            "content": f"文档片段:\n{context}\n\n用户问题:{question}"
        }
    ]

    return messages

这里的 retrieved_chunks[:5] 只是示例,实际数量要结合业务效果评估,不能简单固定。

5.3 多轮对话摘要记忆

多轮对话中,不建议每次都把完整历史传给模型。

可以把历史对话压缩成摘要,再结合最近几轮对话:

复制代码
def build_chat_messages(summary_memory, recent_messages, current_question):
    messages = [
        {
            "role": "system",
            "content": "你是客服助手,请结合对话摘要和最近消息回答用户。"
        }
    ]

    if summary_memory:
        messages.append({
            "role": "system",
            "content": f"历史对话摘要:{summary_memory}"
        })

    messages.extend(recent_messages[-4:])

    messages.append({
        "role": "user",
        "content": current_question
    })

    return messages

需要注意:上下文不能压缩过度。压缩后要观察:

  • 平均输入 token 是否下降;
  • 回答命中率是否稳定;
  • 用户追问率是否上升;
  • 人工介入率是否增加。

如果用户追问明显变多,可能说明关键上下文被删掉了。

5.4 输出端:限制模型自由发挥

输出 token 同样会带来成本。

建议在三个层面控制:

  1. API 参数设置最大输出长度;
  2. prompt 中明确输出格式;
  3. 对分类、抽取、摘要任务使用固定 schema。

例如分类任务:

复制代码
messages = [
    {
        "role": "system",
        "content": (
            "你是文本分类器。"
            "请只输出 JSON,不要输出 Markdown,不要解释。"
            "JSON 字段包括:category、confidence、reason。"
            "reason 不超过 30 个字。"
        )
    },
    {
        "role": "user",
        "content": "用户输入文本..."
    }
]

result = call_ai_api(
    messages=messages,
    model=AI_MODEL_SMALL,
    max_tokens=128,
    business_type="classification"
)

对于分类、标签、信息抽取等任务,没有必要让模型输出很长的解释。


6. 方法四:批量处理与异步化,非实时任务不要逐条实时跑

不是所有 AI API 调用都必须同步返回。

适合批量处理的任务包括:

  • 批量摘要历史文章;
  • 批量生成商品标签;
  • 批量清洗用户评论;
  • 批量评估问答质量;
  • 离线构建知识库问答对。

这些任务如果全部使用实时接口逐条调用,会带来几个问题:

  • 并发峰值难控制;
  • 失败重试分散;
  • 成本统计困难;
  • 实时链路压力大。

更好的方式是放入队列,异步执行。

如果平台支持 Batch API,也可以结合使用。不同平台的 Batch 规则、限制和价格可能不同,具体以各自最新文档为准。

6.1 适合与不适合 batch 的场景

适合批量处理 不适合批量处理
用户不需要立即看到结果 在线客服即时回复
可以延迟几分钟到几小时 实时搜索问答
输入输出格式统一 高度个性化多轮对话
可以失败后统一重跑 强交互式 Agent

6.2 队列化处理示例

复制代码
def submit_offline_task(task_type, payload):
    task = {
        "task_type": task_type,
        "payload": payload,
        "status": "pending",
        "retry_count": 0
    }

    task_id = save_task_to_db(task)
    push_to_queue(task_id)

    return task_id

消费者处理:

复制代码
def consume_task(task_id):
    task = get_task_from_db(task_id)

    try:
        if task["task_type"] == "article_summary":
            result = run_article_summary(task["payload"])
        elif task["task_type"] == "tag_generation":
            result = run_tag_generation(task["payload"])
        else:
            raise ValueError("unknown task type")

        mark_task_success(task_id, result)

    except Exception as e:
        if task["retry_count"] < 2:
            increase_retry_count(task_id)
            push_to_queue(task_id)
        else:
            mark_task_failed(task_id, str(e))

6.3 批量化之前先优化单条调用

批量处理不等于一定省钱。

如果单条任务存在这些问题:

  • prompt 过长;
  • RAG 片段过多;
  • 输出不限制;
  • 失败重试失控;

那么批量处理只是把浪费放大。

建议顺序是:

  1. 先优化单条 prompt;
  2. 控制输出长度;
  3. 做好失败重试;
  4. 再放入异步队列或 batch 流程。

7. 方法五:监控、预算与告警,形成成本闭环

没有监控的成本优化,很容易停留在"感觉省了"。

建议从三个维度做预算:

  1. 日预算:防止单日异常峰值;
  2. 月预算:控制整体支出;
  3. 用户 / 租户预算:避免少数用户打爆成本。

7.1 建议监控指标

指标 排查方向
每千次请求成本 判断整体优化效果
平均输入 token 判断上下文是否膨胀
平均输出 token 判断输出是否失控
失败率 判断接口或参数问题
重试率 判断异常重试成本
缓存命中率 判断缓存是否有效
模型升级比例 判断模型路由是否合理
平均延迟 判断链路性能
单用户成本 判断是否存在异常用户
单业务线成本 判断哪个业务最烧钱

7.2 告警规则示例

告警不一定一开始就很复杂,可以先做简单规则:

  • 当日成本超过过去 7 天均值一定比例;
  • 某个接口失败率突然升高;
  • 平均输出 token 明显上涨;
  • 缓存命中率快速下降;
  • 某个用户或租户成本异常增加;
  • 重试次数突然增多;
  • 强模型调用比例异常升高。

这些告警不会直接降低单次调用费用,但可以防止成本继续失控。


8. 常见报错与排查方法

下面整理一些 AI API 成本优化和接入过程中常见的问题。

8.1 401 / Unauthorized

常见原因:

  • API Key 没配置;
  • API Key 配错;
  • 环境变量没有生效;
  • Header 格式错误;
  • 使用了错误的 Endpoint。

排查步骤:

复制代码
echo $AI_API_BASE_URL
echo $AI_API_KEY

检查请求头是否类似:

复制代码
Authorization: Bearer your_api_key_here
Content-Type: application/json

注意不要把不同环境的 Key 混用,例如测试环境和生产环境。


8.2 404 / model not found

常见原因:

  • 模型名写错;
  • 当前 Endpoint 不支持该模型;
  • 使用了错误的接口路径;
  • 模型配置没有在环境变量中更新。

排查:

复制代码
echo $AI_MODEL_DEFAULT
echo $AI_MODEL_SMALL
echo $AI_MODEL_STRONG

如果做了模型路由,要检查实际请求中传入的模型名,而不是只看默认模型。


8.3 429 / rate limit

常见原因:

  • 并发过高;
  • 短时间请求过多;
  • 重试策略过激;
  • 队列没有限速;
  • Agent 链路重复调用。

处理建议:

  • 加指数退避;
  • 限制最大重试次数;
  • 对非实时任务改为队列;
  • 对重复问题做缓存;
  • 监控 retry_counterror_code=429 的比例。

不要无限重试,否则会进一步放大成本。


8.4 context length exceeded

常见原因:

  • 多轮历史太长;
  • RAG 召回片段太多;
  • 文档没有切分;
  • system prompt 过长;
  • 把无关内容全部塞进上下文。

处理建议:

  • 只保留相关历史;
  • 长对话做摘要;
  • 控制 RAG 分块大小;
  • 控制召回数量;
  • 删除重复 prompt;
  • 长文先筛选再调用模型。

8.5 JSON 解析失败

常见原因:

  • prompt 没有限制输出格式;
  • 输出长度被截断;
  • 模型返回了 Markdown;
  • 小模型格式遵循能力不足;
  • schema 太复杂。

处理建议:

  • 明确要求"只输出 JSON";
  • 限制字段;
  • 减少无用解释;
  • 对 JSON 做校验;
  • 失败后升级模型重试;
  • 记录格式错误率。

示例 prompt:

复制代码
请只输出 JSON,不要输出 Markdown,不要添加解释。
字段包括:
- category: 字符串
- confidence: 0 到 1 之间的小数
- reason: 不超过 30 个字

8.6 timeout

常见原因:

  • 输入太长;
  • 输出太长;
  • 模型响应慢;
  • 网络不稳定;
  • 并发过高。

处理建议:

  • 设置合理超时时间;
  • 控制 max_tokens
  • 对非实时任务异步化;
  • 对超时请求限制重试次数;
  • 记录超时对应的输入 token 和模型。

9. 一个完整示例:客服问答系统如何降低 AI API 成本

假设有一个客服问答系统,最初实现如下:

  1. 用户每问一个问题;
  2. 系统取完整历史对话;
  3. 检索知识库片段;
  4. 拼接固定 prompt;
  5. 统一发送给强模型。

这个方案实现简单,但成本容易高在三个地方:

  • 重复问题多;
  • 上下文太长;
  • 所有请求都走强模型。

9.1 第一步:补调用日志

先记录:

  • 用户 ID;
  • 业务类型;
  • 模型;
  • 输入 token;
  • 输出 token;
  • 是否命中缓存;
  • 是否命中知识库;
  • 是否升级模型;
  • 失败率;
  • 重试次数。

没有这些数据,后续无法判断优化是否有效。


9.2 第二步:对高频 FAQ 做缓存

例如:

  • 退换货规则;
  • 发票说明;
  • 物流时效;
  • 售后流程。

这些问题重复率通常较高,没有必要每次都调用模型。

处理流程:

复制代码
用户问题
  -> 生成缓存 Key
  -> 查询缓存
      -> 命中:直接返回
      -> 未命中:调用 AI API
          -> 写入缓存
          -> 返回结果

9.3 第三步:增加意图识别和模型路由

先用小模型判断用户问题类型:

  • 是否是 FAQ;
  • 是否命中标准答案;
  • 是否需要人工;
  • 是否是复杂投诉;
  • 是否需要跨多个文档推理。

简单问题直接返回标准答案或让小模型轻微润色。

复杂问题再升级到强模型。

复制代码
用户问题
  -> 小模型意图识别
      -> 明确 FAQ:返回标准答案
      -> 简单知识库问答:小模型 + RAG
      -> 复杂投诉 / 多文档推理:强模型
      -> 低置信度:强模型兜底

9.4 第四步:压缩多轮上下文

没有必要每次都传完整历史。

可以保留:

  • 历史摘要;
  • 最近几轮对话;
  • 当前问题相关的知识库片段。

这样能降低输入 token,同时减少无关信息干扰。


9.5 第五步:离线分析未解决问题

夜间或低峰期,可以批量分析:

  • 哪些问题未解决;
  • 哪些问题重复出现;
  • 哪些问题需要补充 FAQ;
  • 哪些知识库片段质量较差。

这些任务不需要放在实时链路里,可以异步处理。


10. 常见误区

10.1 只看模型单价

便宜模型如果需要更多轮对话才能解决问题,总成本未必更低。

应该看:

  • 每千次请求总成本;
  • 成功率;
  • 升级模型比例;
  • 人工介入率;
  • 用户追问率。

10.2 过度压缩上下文

上下文删得太狠,模型可能拿不到必要信息。

结果可能是:

  • 回答不完整;
  • 准确率下降;
  • 用户追问增加;
  • 总调用次数反而上升。

10.3 缓存不做失效

知识库更新后仍返回旧答案,在客服、合规、金融等场景中风险较高。

缓存必须考虑:

  • TTL;
  • prompt 版本;
  • 知识库版本;
  • 模型版本;
  • 多租户隔离。

10.4 盲目追求最便宜模型

高风险任务更应该关注:

  • 稳定性;
  • 可解释性;
  • 回退机制;
  • 人工审核;
  • 业务风险。

不能只看 API 单价。

10.5 没有监控就上线

没有 token、失败率、重试率、缓存命中率这些数据,就无法证明优化是否有效。

10.6 把 batch 当万能方案

实时交互任务如果强行异步化,会影响用户体验。

Batch 更适合:

  • 离线任务;
  • 后台分析;
  • 批量生成;
  • 数据清洗。

11. 推荐落地顺序

比较稳妥的 AI API 成本优化顺序是:

  1. 先做监控

    记录 token、模型、业务线、用户、失败率、重试次数和预估成本。

  2. 再做缓存与去重

    对重复问题、固定任务、可复用结果建立缓存。

  3. 做模型分层与路由

    小模型能解决的任务不要默认走强模型,小模型失败再升级。

  4. 压缩输入与输出 token

    清理无效历史上下文,控制 RAG 片段数量,限制输出长度。

  5. 将非实时任务批量化或异步化

    后台任务放入队列,不要全部挤在实时链路上。

AI API 成本控制不是一次调参,而是一套持续优化闭环。核心不是简单换成更便宜的模型,而是让每一次调用都有必要,让每一个 token 都有价值,让每一种任务匹配合适的模型。

如果需要第三方 Claude API 兼容接入服务