本文定位:AI 应用性能 / 成本治理 / 高可用设计
示例环境:Java 21、Spring Boot 3.3、Redis、PostgreSQL、OpenTelemetry。模型价格会变化,文章只讨论测量与治理方法,不绑定某个供应商价格。
摘要
AI Demo 上线后,最容易失控的两个指标是成本和延迟。Prompt 变长、检索片段增多、工具重复调用、失败重试和长历史对话都会让一次请求消耗更多 Token;模型响应慢、Rerank 排队、数据库连接池耗尽又会把用户体验拖垮。
本文先建立成本与延迟的测量模型,再讨论上下文裁剪、缓存、批处理、流式输出、模型路由、限流、超时、熔断和降级。重点是说明优化顺序:先测出钱花在哪里,再决定哪些内容值得缓存或压缩,避免为了追求一个理论上的低延迟而牺牲正确性和安全性。
一、成本模型
一次 RAG 请求成本可以拆成:
总成本 = Embedding成本 + 关键词/向量检索成本 + Rerank成本 + 模型输入Token成本 + 模型输出Token成本 + 重试成本 + 工具和基础设施成本很多团队只看模型输出 Token,忽略了输入上下文。RAG 把 10 个大 Chunk 塞进 Prompt 后,输入成本和延迟可能远高于回答本身。每次请求至少记录输入 Token、输出 Token、检索条数、Rerank 次数、重试次数和最终模型。
二、延迟分解
用户感知的流式体验主要受首 Token 延迟影响,但系统总耗时还包括完整输出和后处理。监控 P50、P95、P99,以及首 Token、每 Token 间隔和完成时间,不能只看平均响应时间。
三、上下文裁剪比换模型更先做
上下文应经过“过滤—排序—去重—压缩—限长”流程:
- 过滤无权限、过期和低相关文档。
- 按 Rerank 分数、版本和来源排序。
- 对重复或高度相似 Chunk 去重。
- 对长文本只保留与问题相关的句子,但保留引用定位。
- 设置输入 Token 硬上限。
publicList<Evidence>trimContext(List<Evidence>evidence,intmaxChars){Set<String>seen=newHashSet<>();List<Evidence>result=newArrayList<>();intused=0;for(Evidenceitem:evidence){Stringfingerprint=normalize(item.content());if(!seen.add(fingerprint))continue;if(used+item.content().length()>maxChars)break;result.add(item);used+=item.content().length();}returnList.copyOf(result);}裁剪不能只按字符串长度截断。切掉条款的例外条件会导致答案错误,因此最好按句子、列表项或语义单元裁剪,并保留文档标题、版本和页码。
四、缓存设计
缓存适合稳定、重复和可安全复用的数据:Embedding 结果、公开文档的检索结果、相同问题的最终答案。用户权限和知识库版本必须进入缓存键。
embedding:{model}:{textHash} retrieve:{tenant}:{permissionHash}:{kbVersion}:{queryHash} answer:{tenant}:{permissionHash}:{promptVersion}:{model}:{queryHash}最终答案缓存需要注意两个问题:一是用户权限可能发生变化,二是文档更新后旧答案可能失效。缓存 TTL 不能代替版本失效,发布新知识库版本时要主动失效相关键。对高风险问题,可以只缓存检索结果,不缓存最终答案。
五、流式输出和取消
流式输出能降低用户感知等待,但不能解决模型总耗时。客户端断开后,服务端需要取消模型请求和工具任务,否则会出现用户看不到结果但后台仍持续消耗成本。
publicFlux<String>stream(Publisher<AiEvent>events,CancellationTokentoken){returnFlux.from(events).takeUntil(event->token.isCancelled()).map(AiEvent::safeText).doOnCancel(()->{token.cancel();audit.record("client_cancelled");});}对于有副作用的工具,取消请求不一定等于取消业务动作。必须区分“停止生成文本”和“停止已经提交的业务执行”,后者需要状态机和补偿策略。
六、模型路由与备用模型
可以按任务复杂度选择模型:简单分类和查询使用低成本模型,复杂总结或多步推理使用更强模型。路由规则必须可解释,并记录实际命中模型。
publicModelRouteroute(RequestProfileprofile){if(profile.requiresTool()&&profile.risk()==RiskLevel.HIGH){returnModelRoute.of("quality-model",Duration.ofSeconds(30));}if(profile.inputTokens()<600&&profile.intent()==Intent.CLASSIFY){returnModelRoute.of("fast-model",Duration.ofSeconds(8));}returnModelRoute.of("balanced-model",Duration.ofSeconds(15));}备用模型切换需要注意上下文和工具兼容性。若备用模型不支持某种工具调用格式,不能在超时后无条件切换。高风险场景宁可降级为“展示证据并转人工”,也不要为了成功率让不兼容模型执行动作。
七、限流、超时和熔断
限流至少分为用户、租户、接口和模型供应商四层。对 Token 而不是只对请求数限流,因为一个超长请求可能消耗几十个普通请求的资源。
publicvoidcheckQuota(AuthContextauth,UsageEstimateestimate){Quotaquota=quotaService.current(auth.tenantId());if(quota.remainingTokens()<estimate.inputTokens()){thrownewQuotaExceededException("超过租户Token额度");}rateLimiter.acquire(auth.tenantId(),estimate.weight());}超时要分阶段设置:Embedding、检索、Rerank、模型首 Token、模型完成和工具调用分别配置。一个总超时不能替代阶段超时,否则某个下游卡住后很难定位。
熔断发生后,降级逻辑不能让模型凭空回答。可以选择关键词检索、返回已确认的缓存结果、提交异步任务或转人工。
八、重试会放大成本
只对明确的临时错误重试,并设置最大次数和指数退避。重试请求要带相同幂等键,特别是涉及工具调用时。每次重试增加的 Token 和供应商费用都要进入成本统计。
| 错误 | 是否通常重试 | 说明 |
|---|---|---|
| 认证失败 | 否 | 修复配置或权限 |
| 参数校验失败 | 否 | 修改输入 |
| 429 限流 | 有条件 | 按 Retry-After 退避 |
| 网络超时 | 有条件 | 限次、幂等 |
| 供应商 5xx | 有条件 | 备用模型或降级 |
| 引用校验失败 | 最多一次 | 第二次失败转人工 |
无限重试既可能制造账单,也可能造成工具重复执行。
九、成本报表
按天、租户、应用、模型、工作流和用户群统计:请求数、输入输出 Token、平均成本、P95 延迟、失败率、重试率和人工转接率。成本报表要能回答“钱花在什么场景、哪个版本、哪个租户、哪类问题”。
高成本请求要保存原因标签,例如上下文过长、工具轮次过多、重复提问、超时重试或模型路由错误。没有原因标签,成本优化只能靠猜。
十、上线检查
- 是否记录输入输出 Token 和每阶段耗时。
- 缓存键是否包含租户、权限和知识库版本。
- 客户端断开后是否取消可取消的后台请求。
- 工具调用是否使用幂等键。
- 是否按阶段设置超时和限流。
- 备用模型是否支持相同的输出和工具协议。
- 熔断后是否有安全降级,而非无依据回答。
- 是否有租户级成本预算和告警。
十一、一次可复现的性能实验
建议建立三个固定场景:短问题、长上下文问题和需要工具调用的问题。每个场景分别用 1、5、20、50 并发运行,预热后重复多轮,记录首 Token、完整耗时、输入输出 Token、错误率、重试次数和峰值显存或连接数。不要只跑一次,因为网络抖动和供应商排队会影响结果。
实验报告中至少包含:代码提交号、模型与 Prompt 版本、知识库版本、缓存状态、测试机器、并发、样本数量和统计方式。P95 应说明是对所有请求统计,还是只统计成功请求;失败请求不能被简单剔除,否则结果会过于乐观。
还可以做一个“上下文裁剪前后”的对照:保持模型和问题集不变,只改变证据条数与去重策略,再比较引用准确率、输入 Token 和延迟。如果质量没有下降而成本明显降低,才说明裁剪真正有效。
十二、预算与配额
成本治理最好有租户级预算、应用级预算和单请求硬上限。预算接近阈值时,可以降低非关键场景的模型级别或暂停批量任务;高风险业务则应转人工,而不是悄悄降低质量。所有预算拒绝和降级都要对用户给出明确提示,并在审计中记录。
十三、总结
性能和成本优化的第一步是测量,不是换模型。先把 Token、检索候选、Rerank、模型首 Token、完成时间、重试和工具轮次记录下来,再针对最昂贵、最慢或最容易失败的阶段优化。
一个成熟的 AI 系统应该在“质量、成本、延迟、可靠性”之间做明确取舍:普通问题追求快和便宜,高风险问题追求可验证和可追责,所有降级都必须保留安全边界。
读者讨论
如果你的 AI 应用费用突然上升,建议先按“输入 Token、重试次数、上下文条数、工具轮次”四项拆账,通常很快能定位主因。