LLM成本控制:预算即产品功能

3 阅读5分钟

TLDR
GPTZero AI 检测模型 3.7b 判定本文完全为人类撰写。

大语言模型是大多数产品团队首次遇到的、每个请求都有可见边际成本的依赖项。这改变了规则:一个功能可能“工作正常”,但在生产环境中却因悄悄烧钱、重试导致费用激增,或提示词膨胀致使延迟和成本双双上升而失败。本文提供了一套实用蓝图,帮助在不将产品变成吝啬、低质量体验的前提下,保持 LLM 成本可预测。

将每次 LLM 调用视为计费依赖项

在多数服务中,依赖项消耗延迟和可靠性。LLM 每次请求还花费金钱,这意味着需要从第一天起就进行按路由的核算。

将每个 LLM 请求关联到具体路由,如 support_replydoc_searchmeeting_summarypolicy_answer。记录模型、输入/输出令牌数、延迟、重试次数和预估成本。如果无法回答“哪个路由花了钱”,就无法控制成本。

成本不是财务工作,而是与错误率同级别的运营指标。

将令牌预算嵌入架构(而非电子表格)

预算必须存在于能够阻止失控行为的地方。

按路由定义:

  • 最大输入令牌数(允许的上下文量)
  • 最大输出令牌数(模型可回答的长度)
  • 最大重试次数(尤其针对超时和 429 错误)
  • 最大工具调用次数(若使用工具/函数调用)

预算同样保护质量。无限制的提示词往往因将信号淹没在过多“可能有用”的上下文中而降低相关性。

一个有用的思维模型:提示词就像一个打包好的行李箱,每件物品都需要有存在的理由。

通过更智能的检索控制上下文增长,而非塞入更多内容

一个常见的反模式是:“答案变差了,所以我们加了更多上下文。”这可能会增加成本但仍然失败,因为正确的证据并不存在,只是多了更多证据。

更好的模式是:

  • 检索稍大的候选集
  • 重排序以挑选最佳的几个片段
  • 仅将最顶部的证据传递给生成步骤

这能保持令牌使用稳定并提高答案相关性,同时防止一种隐性的成本漂移:随着更多文档进入索引,上下文在数周内缓慢膨胀。

战略性缓存并衡量缓存命中率

缓存是最大的成本杠杆之一,且无需牺牲质量——但前提是缓存正确的层级并使用正确的键。

三种实用的缓存:

  • 重复查询的检索结果(短 TTL)
  • 重复文本的嵌入向量(长 TTL)
  • 确定性路由的最终答案(短 TTL,严格键)

键的设计很重要。在缓存键中包含提示词模板版本、模型名称以及任何文档索引版本。否则可能返回为昨天的提示词或上周的索引生成的答案。

并且按路由使缓存命中率可见。如果命中率接近零,就是在为毫无收益的复杂性付出代价。

请求路由:并非每个任务都值得用同一个模型

对每个路由都使用同一个昂贵模型,是成本失控的最快途径之一。

按任务难度和风险路由:

  • 分类/提取通常可在较小模型上完成
  • 摘要通常可以比开放式推理更便宜
  • 高风险路由可以证明更强模型加更严格护栏的合理性

也可以按证据强度路由。如果检索置信度低,更好的用户体验往往是提出澄清性问题,而非升级到更昂贵的模型去更华丽地产生幻觉。

设计保留信任的降级策略(而不仅仅是保住预算)

降低体验的成本控制措施会被绕过。目标是在保持支出可预测的同时保留信任。

好的降级策略包括:

  • 当证据较弱时提出澄清性问题
  • 生成简短的、带引用的答案而非长篇叙述
  • 返回提取的段落而非生成的解释
  • 在速率限制下优雅降级(排队、部分响应,或“请缩小查询范围后重试”)

最好的降级不是错误,而是安全的、部分有用的响应。

实施按路由预算并记录成本

以下是一个可适配到大多数技术栈的小模式:在运行时强制执行预算,并发出结构化使用日志用于仪表盘和告警。

PRICING = {  
    "small": {"in_per_1k": 0.0002, "out_per_1k": 0.0008},  
    "large": {"in_per_1k": 0.0025, "out_per_1k": 0.0100},  
}  

ROUTE_BUDGETS = {  
    "support_reply":   {"tier": "small", "max_in": 2000, "max_out": 250, "max_retries": 1},  
    "policy_answer":   {"tier": "large", "max_in": 3500, "max_out": 350, "max_retries": 2},  
    "meeting_summary": {"tier": "small", "max_in": 4000, "max_out": 300, "max_retries": 1},  
}  

def est_cost(tier, tokens_in, tokens_out):  
    p = PRICING[tier]  
    return (tokens_in/1000)*p["in_per_1k"] + (tokens_out/1000)*p["out_per_1k"]  

def run_llm(route, llm_call, tokens_in_estimate):  
    b = ROUTE_BUDGETS[route]  

    if tokens_in_estimate > b["max_in"]:  
        return {"text": "上下文过多。请缩小请求范围。", "blocked": True}  

    # llm_call 应返回来自提供商的 tokens_in/tokens_out  
    resp = llm_call(tier=b["tier"], max_tokens=b["max_out"], max_retries=b["max_retries"])  
    cost = est_cost(b["tier"], resp["tokens_in"], resp["tokens_out"])  

    print({  
        "event": "llm_usage",  
        "route": route,  
        "tier": b["tier"],  
        "tokens_in": resp["tokens_in"],  
        "tokens_out": resp["tokens_out"],  
        "cost_est_usd": round(cost, 6),  
    })  

    return {"text": resp["text"], "blocked": False}  

即使初始定价估算很粗糙,也能立即获得两样东西:防止明显的提示词失控,并创建可供分析的成本历史记录。

结语:预算是产品功能

如果推出 LLM 功能时没有预算,推出的不是一个功能,而是一个无上限的成本中心。

预算强制澄清:哪些证据重要、什么答案格式可接受、以及系统在不确定性下如何表现。其回报不仅是降低支出,更是得到一个更易操作、更易调试、更值得信赖的系统。FINISHED