AI产品的成本已经低到令人发指了!一顿午饭钱,就能顶住十万人提问。

3,199 阅读10分钟

先看图,这是一个平均每天十万+PV、产生过亿tokens消耗的产品,而这个产品的平均响应时间不到三秒,上亿的tokens费用每天只要二三十块,本文讲一下我们是如何做到的。

30y.jpg

贵&慢

如果有朋友已经上线了AI产品的话,相信应该你们也碰到了这个两个问题:

  1. 流程设计完成之后,发现自己的AI产品流程执行太慢,领导不满意。
  2. tokens如流水一般,钱花的太快。

解决方案:

  1. 使用上下文缓存。
  2. 优化流程
  3. 选择适当的模型。

今天,我们先重点聊聊上下文缓存,优化流程和选择合适的模型是一个复杂且需要深入研究的话题,我们会在后续的文章中详细探讨。

上下文缓存的解决方案又分两部分:

  1. 利用云端厂商提供的上下文缓存能力。
  2. 业务方(也就是我们),通过自己的方案进行缓存控制。

云端上下文缓存(Context API)

先看效果:

指标提升比例案例(前 → 后)
速度+27%4秒 → 2.9秒
费用-70%10万/年 → 3万/年

实测下来,基本上可以做到提升 27% 的响应速度和 减免70% 的费用消耗。

原本我们需要四秒响应,现在只需要不到三秒就可以,70%的费用减免可以从原本的十万每年一下降到三万每年。

这就是上文缓存带来的能力,接下来介绍一下我使用的缓存方案,来自火山方舟的上下文缓存(Context API)

上下文缓存(Context API)是火山方舟提供的一个高效的缓存机制,旨在优化生成式AI在不同交互场景下的性能和成本。它通过缓存部分上下文数据,减少重复加载或处理,提高响应速度和一致性。

在方舟中目前存在session 缓存前缀缓存两种缓存方式:

  • session 缓存:保留初始信息,同时自动管理上下文,在动态对话尤其是超长轮次对话场景的可用性更强,搭配模型使用,可以获得高性能和低成本。
  • 前缀缓存:保留初始信息,适合静态Prompt模板的反复使用场景。

应用场景如下:

功能场景举例适用缓存类型
标准化对话开场白用于统一对话开场白,如"您好,请问有什么可以帮您?"前缀缓存
特定任务的指令如翻译任务中的固定指令"请将以下内容翻译成英文:"前缀缓存
规则化模板FAQ系统的固定回答前缀,如"关于这个问题,我们的官方回答是:"前缀缓存
长期多轮对话陪伴类AI需要记住用户长期偏好和过往对话内容Session 缓存
连续问题回答客服机器人需要基于之前的问题上下文回答后续相关问题Session 缓存
长篇文本生成生成小说或长文时需要回顾之前已生成的内容以保持连贯性Session 缓存
控制抖动时机对延时敏感的场景,希望将网络抖动控制在非关键对话阶段Session 缓存
控制历史采纳量对模型效果敏感的场景,需要精确控制模型采纳的历史对话轮数Session 缓存

我们需要按照如下步骤使用缓存:

  1. 在火山方舟开通对应模型的缓存能力
  2. 创建缓存
  3. 使用缓存

session缓存和前缀缓存支持的模型不一样,按照我们常用的模型来说:session缓存支持doubao-1.5,前缀缓存支持doubao-1.5deepseek v3deepseek r1

选择缓存的时候,也需要先看下自己的业务使用了哪个模型。

示例代码

官方都没给node的示例代码,还得是贴心的我,给大家一份node版本的示例:

无论想要使用哪个缓存,区别就是创建缓存的时候传入的mode参数:common_prefix代表前缀缓存,session代表session 缓存。

创建缓存

// ! 创建缓存 - 
async function createCache() {
  const response = await fetch('https://ark.cn-beijing.volces.com/api/v3/context/create', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer 自己的key',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      model: model,  // 放自己在火山上创建的推理点EP,建议接入doubao-1.5-pro-32k
      messages: [
        {
          "role": "system", "content": `你的名字是小王` }
      ],
      ttl: 3600, // 缓存存在时间
      mode: "common_prefix",  // 换成 session 就是使用session缓存
    })
  });

  const data = await response.json();
  console.log(data);
}
createCache()

使用缓存

上一步创建缓存最后输出的data中,有一个context_id字段,是ctx开头的。这个字段可以再我们调用大模型时传入,大模型就可以知道我们要使用某个制定的缓存内容了。 比如上面代码我们缓存的内容是:你得名字是小王, 那么我们调用大模型时,问你叫什么,大模型便会回复我们小王

import OpenAI from 'openai';

const model = '' // 放自己在火山上创建的推理点EP,建议接入doubao-1.5-pro-32k
const openai = new OpenAI({
  apiKey: '', // 换成自己的key
  baseURL: 'https://ark.cn-beijing.volces.com/api/v3/context', 
});

async function main() {
  const res = await openai.chat.completions.create({
    messages: [
      { role: 'user', content: `你叫什么?` },
    ],
    temperature: 1,
    context_id: "ctx-20250501105839-8qzbt",
    top_p: 1,
    model: model,
    stream: true
  });
  for await (const part of res) {
    process.stdout.write(part.choices[0]?.delta?.content || '');
  }
  process.stdout.write('\n');
}

main();

这里需要注意的是,如果我们使用缓存那么baseurl就不再是原来的https://ark.cn-beijing.volces.com/api/v3 而是https://ark.cn-beijing.volces.com/api/v3/context

此处做的是示例,在真实的线上环境中,利用缓存能力缓存了绝大部分的输入tokens,例如我们某个意图的判别提示词有3000+输入tokens的消耗,这还只是产品响应流程中的一个节点,这样的节点我们有十几个,响应流程每次执行会用到2-3个节点,使用了缓存之后,我们每次都缓存了大几千的tokens,这给我们省了很多费用。

计费

  • 输入(元/千 token):新的请求中您无需重新发送历史对话,输入 token 仅代表添加到正在进行的对话中的新文本。在 rolling_tokens 模式下触发重新计算时,会将保存的历史对话重新计算和缓存,与新输入内容一样计费。

  • 缓存命中(元/千 token):缓存中使用的内容。方舟自动处理历史记录,并输入给模型,这部分内容即命中缓存内容,他们也会产生费用,但是优化了计算和读入缓存的开销,计费费率会显著低于新输入内容。

  • 存储(元/千 token/小时):历史对话存储在 Session 缓存中,会产生存储费用。计算方式根据每个自然小时使用缓存的最大量乘以单价进行累加。 举例说明(单价为虚拟单价,仅做举例使用):单价为 0.000017 元/千 token/小时,第 1 小时 Session 缓存最大的缓存用量 10k token,第 2 小时 Session 缓存最大的缓存用量 15k token,那么存储费用为:0.000017*10+0.000017*15 = 0.000425 元

限制

  • session 缓存不支持并发调用。
  • 前缀缓存支持并发调用。
  • 前缀缓存过期时长可配置范围在1小时到7天,即ttl字段设置范围(单位:秒)为 [3600, 604800]。
  • 都不支持 Partial 模式(又称 Prefill Response)。

Prefill Response 一个新概念,简单解释一下就是调用上下文缓存对话 API时,messages数组的最后一条消息的role字段取值可以是usersystem,而不能为assistant

这是个很好的技巧,不会的同学要记好:如果我们的messages中最后一条的roleassistant,对于大模型来说assistant的内容就是大模型自己前面输出,所以大模型会顺着这个内容开始输出,这就让我们很好的控制了大模型的输出!

例如, 我们传入了这样一个messagesuser1+1,assistant回答=,大模型接下来的输出就会被控制的很好,始终是2,而不是其他什么思考逻辑之类的废话:

 const res = await openai.chat.completions.create({
    messages: [
      { role: 'user', content: `1+1` },
      { role: 'assistant', content: `=` },
    ],
  });

业务方的缓存方案

我们业务上的缓存,主要是缓存用户的query与对应的大模型回复。当不同的用户使用了相似的问题进行提问时,我们使用缓存的大模型回复内容进行回复。

在性能、费用要求都相对充裕的情况下,并不建议大家在业务侧自己做缓存,自己做缓存比较吃场景,一旦做不好带来的问题比带来的收益要大很多。

目前比较适合场景有:

  • 用户的问题相对简单、单一的场景
  • 对回复时效性要求高,且对于回复的精准性要求不高的场景
  • 预算不够,又得上智能化产品的场景。

比较落地的方案有两部分,缓存知识库缓存tools:

  1. 利用知识库

放弃知识库筛选多条内容让AI总结回复的用法,转而使用匹配答案的思路。

提前离线生产一批问题,投入知识库,然后将知识库的匹配数量调整为1,同时把匹配分数限制调高。

这样尽可能保证用户query和知识库的命中率,基本上做到每个从知识库匹配到的答案,都是正确答案。

但是知识库难免会出现匹配失误的情况,而我们场景又要求比较严格时,我们还可以再加入相关性验证的逻辑,即:使用AI对我们知识库中的信息和用户的query进行相关性验证。

例如:知识库中存有解释字段 解决XX问题,当AI拿用户的query和知识库中匹配到的信息进行相关性验证时,他就可以知道是否是解决用户问题的答案了。

  1. tools缓存

适用于Agent流程使用了大量function call 工具匹配 + 参数提取的产品。

本文不对function call做赘述,后面会出一期function call的能力和应用详解。不了解function call的能力的同学,可以先行百度一下。

当我们大量使用 function call的能力时,我们是需要调用API,然后交给大模型进行回复。

这里我们可以缓存对应的tools名称+参数和对应的大模型回复,下次直接进行回复。

当下一次出现对用的tools名称+参数时,直接拿出缓存的回复内容进行回复。

结语

当下各家厂商推出了不同优惠政策,我们只要善于利用就可以给自己给公司节约不少钱:

  • deepseek的闲时半价。
  • 火山的上下文缓存。
  • 火山的批量推理半价优惠。

这些优惠对我们无论是线上还是线下、离线还是在线的应用,都具有非常大的优惠,例如本文讲的上下文缓存。

还有我们可以把任务做离线的批量推理,享受半价优惠。

说起来,有没有人好奇火山的批量推理 + 上下文缓存一起用,能便宜到什么程度??🤣🤣🤣

哈哈哈哈.....,想多了各位,火山的上下文缓存只支持在线推理,不支持和批量推理一起使用。

☺️你好,我是华洛,如果你对程序员转型AI产品负责人感兴趣,请给我点个赞。

你可以在这里联系我👉www.yuque.com/hualuo-fztn…

已入驻公众号【华洛AI转型纪实】,欢迎大家围观,后续会分享大量最近三年来的经验和踩过的坑。

专栏文章

# 从0到1打造企业级AI售前机器人——实战指南一:根据产品需求和定位进行agent流程设计🧐

# 聊一下MCP,希望能让各位清醒一点吧🧐

# 实战派!百万PV的AI产品如何搭建RAG系统?

# 团队落地AI产品的全流程

# 30行代码,随时进行云端大模型能力评估!

# 5000字长文,AI时代下程序员的巨大优势!