构建
关于大模型必须知道的事

内容生成类 AI 产品的后台服务大有不同

2023 年 ChatGPT 的出现,带动了一大批的 AI 产品的诞生,所有的这些 AI 产品都集中在文字、图片和视频的生成领域。这些 AI 产品和传统的 2C SaaS 产品有一个巨大的区别(除了 AI):

  • 构建传统的 2C SaaS 产品从技术角度会着重考虑 QPS 性能容量,你永远也不想开发一款互联网 SaaS 产品最多只能提供 10 个并发任务。
  • AI 生成式产品在处理 AI 任务的时候,需要耗费大量的算力,同时也带来了巨大的延迟,一个图片、音视频任务都可能需要分钟级以上的时间去处理。AI 任务的排队基本是铁定的事实,区别只在于长短上。

所以,传统的 2C 互联网产品很流行使用 QPS 这个技术指标,而今天的 AI SaaS 产品,很难再使用 QPS 来度量 (或许,可以升级到 QPH 了)。

针对 AI 任务的大延迟,消费者端基本也具备一致的共识,所以这类 AI 应用的后台设计需要重点考虑任务调度,任务排队,任务优先级,任务限流,任务延迟后的用户体验等等。

理解 AI 模型的能力边界

在具体实现 AI 产品之前,我们应该深度使用每一款大模型,确保自己对模型有比较深刻、完整的理解。除了知道模型能够做什么以外,更重要的是知道模型能做到什么程度。

搞清楚模型的硬性指标有哪些,比如: chatgpt 3.5 有 16k 的 token 支持,这个 16k 是 input 和 output 的 token 总和。然而,gemini 1.0 有 32k 的 token 支持,但是 32k 是 input 占用了 30k,output 只有 2k。看出区别了吗?gemini 1.0 2k 的 output 限制,已经决定了你不能让它一次给你输出很长的一段文字,比如用它写小说等长输出场景。

相对于模型的硬性指标,我们更应该关注模型那些看不见的能力,比如稳定性,比如细节处理能力。这些能力都是需要在大量的深度使用上,才能真正体会得到的,我的建议是从一开始用比较保守的态度去对待 LLM ,不要试图把所有的任务都扔给 LLM 处理,在你的业务流程中,能用写代码解决的就老实写代码,把真正需要 LLM 的任务交给 LLM。在 Podwise 开发中,花了非常多的时间打磨 LLM 的输出稳定性。

自己写代码还是调用 LLM ?这一刀从哪里切下去,就是我们理解 AI 模型的边界。

模型选择

如果使用 langchain 这种框架,那基本可以选择调用任何一个模型,它已经帮你做好了封装和模型的对接。当然,如果像我们一样,使用了其他编程语言,或者不想用 langchain,那恭喜你,你可以非常自由的使用相关模型提供的 api or sdk。但要告诉你的是,永远不应该只锁定在一个模型上,比如 chatgpt。所以,从一开始就可以留出未来多模型的位置和空间。

Podwise 目前是同时支持 chatgpt 和 gemini 的,也可以非常方便的再扩展到一个新的模型上。具体使用哪个模型,哪个版本,完全根据模型的效果和成本进行综合评估。在一些特殊的地方,我们甚至使用了动态调用多模型来获取结果,比如 gemini 安全限制严格,导致一些播客内容无法得到期望的结果,那我们会根据 gemini 的结果动态决策是否调用 chatgpt ,等等。

另外,业务流程中不应该出现模型相关的信息和选项,除非你是在做模型代理之类的产品。用户关心的永远是结果和质量,而不是你的技术实现。但是技术实现有时候可以增强用户信心,这个可以在营销环节去呈现。

不建议一开始选择开源模型,虽然成本可能更低,但不可控性太高,运维管理的投入精力也很大。这不是一个独立开发者应该投入的方向。如果有时间,有人力,有技术投入在开源模型上,也建议是在 mvp 推出,验证市场需求之后,再考虑。

AI 工作流

Podwise 的整个 AI 后台服务主要由两条工作流驱动完成:

第一条工作流负责从任务队列获取播客 episode 进行转录成最终的 transcript 文本。这个工作流具体就会涉及到: 下载音频文件 -> 探测语言 -> 预采样音频内容 -> whisper 转录音频 -> 生成分段 -> 优化 transcript -> 写入 db。

第二条工作流负责从任务队列获取被第一条工作流处理完的 episode,然后执行 LLM 总结任务。具体流程: load transcript -> 分段(split)-> 总结章节,抽取 highlights和关键词 -> 基于章节生成全文总结 -> 解释关键词 -> 基于章节生成 mindmap -> 写入 db。

为什么是两条工作流,而不是合二为一的一条?开发成一条明显是很自然的选择,一开始我们也是跑在一条工作流上。我们在 mvp 阶段,整个工作流服务都跑在我们本地电脑上,从下载音频到whisper 转录,再到 chatgpt 总结。然后就遇到一个很大的问题,由于网络问题,本地电脑调用 chatgpt 很容易出错,所以需要把第二部分拆分到 aws 免费的 1核1g ec2 上(这也是为什么有了在 Go 的技术选型章节里强调的资源节省问题)。现在,我们已经没有本地电脑运行了,全部在云上。

历史故事分享完了,再认真分享一下两条工作流的真正好处是什么。最大、最重要的好处就是可以轻松、方便的重跑 transcript 或者是总结。Podwise 做到 "能够在任何时候对 AI 生成结果进行低成本的重跑,重新处理" 是一个非常重要的设计,任何功能的加入都不能破坏这个设计。

针对工作流内部再介绍几个关键点:

一:podwise 除了使用 whisper 将音频转成文字外,在这个过程中,还加入了识别 speaker,并且将 speaker 的声音 embeding 成向量,再写入向量数据库 qdrant。借此,我们才做到了在节目的 transcript 中直接显示 speaker 的名字。或许有人会认为我们是采用 llm 从对话中分析出来的 speaker 名字,实际并不是。

二:whisper 转录出来的 transcript 很长,需要分段后,才能被 chatgpt,gemini 这样的大模型处理。对长文进行分段处理,基本是总结类产品的必备操作。podwise 采用了传统 NLP 的社区算法对文本进行向量相似度计算,从而更好的实现按对话逻辑和内容进行分段。

三:LLM 总结流程中的并发设计,涉及到了这样的几种模式(在 langchain 中也有这样的概念):

  • 无序并发:就是可以将多个任务提交给 llm 后,返回的结果不需要任何顺序。
  • 有序并发:将多个任务提交给 llm 后,返回的结果必须根据业务需要排序的。
  • 完全串行:就是多个任务只能串行,一个一个的执行,往往前一个任务是后一个任务的输入。 Podwise 将第三种串行执行这种模式提升到了工作流上完成控制,所以工作流是先做章节总结,然后做全文总结,最后是 mindmap。在工作流一个节点内部,大多数对 llm 的调用都是由无序或者有序并发模式组成的。

Prompt

做 AI 产品,除了要设计好的 AI 任务流程,合理的拆分业务以外,最重要的就是写好 prompt,管理好 prompt,持续迭代 prompt。

prompt 一般有两种形式:结构化 prompt 和对话式 prompt。

结构化 prompt 的优点是通过规范的结构把任务介绍得很清楚,缺点就是往往很长,比较复杂。而对话式 prompt 更加简单,更符合日常的说话习惯,缺点是难以一句话描述清楚任务,最后得不到满意的结果,需要进行多轮对话才能获得最终结果。两种 prompt 都有自己的适合场景,结构化的 prompt 更合适用来内置到产品工作流中,由开发者编写、维护,podwise 采用的就是这种复杂的 prompt 形式。对话式 prompt 就合适用在 chat bot 场景,直接由用户发出。

  1. 结构化 prompt

podwise 的结构化 prompt 框架覆盖了如下一些内容:

  • 定义角色
  • 介绍背景和输入的数据格式
  • 提出任务 (可能会有多个任务)
  • 执行所有任务的步骤
  • 定义输出格式
  • 给定输出例子

这是一个结构化 prompt 的大概框架,这个框架可以采用 markdown 来描述。podwise 的 prompt 有两个很关键的地方。第一个是多任务,第二个就是输出格式的控制。

  • 关于多任务: 首先,一定要明白在一个 prompt 里面内置多个任务,绝对不是一个好的选择,除非你有强烈的这样做的理由。podwise 选择做多任务的理由就是 “降低成本”,这对我们来讲是非常重要的事情,我相信对大多数独立开发者来说,都是重要的事情。

    podwise 在一个 prompt 同时执行 “总结章节”,“抽取highlight”,“抽取关键字” 等任务,就是为了让这些事情只需要输入一遍 transcript 就可以同时获取这些结果。如果单独执行每一个任务,那就需要把相同的 transcript 数据输入 llm 多次,这将会多消耗数倍的成本。

    但一定要明白,多任务无疑增加了 llm 执行的复杂度,这并不符合 “尽量给 LLM 简单、明确、较小的任务的原则“。经过测试,多任务执行的结果质量赶单任务是有差距的,这就需要不断的打磨和权衡吧。

  • 关于输出格式控制: 由于 podwise 的 llm 总结结果是需要在产品页面上进行结构化展示,并不是 chat bot 那样直接输出给用户(人),所以像 podwise 这类产品对 llm 的输出格式就需要严格定义,并且希望 llm 能够稳定且正确的输出。对于程序员来说,一般会选择 json 作为输出。但 podwise 并没有选择 json,而是选择了 markdown。原因是因为 llm 并不是绝对稳定,再规模上来后,总是会有不稳定输出的情况,偶尔输出的 json 都是非法的,这种情况只能重试 llm,浪费 token,增加成本。所以,podwise 实践的做法有这样的几点来控制输出,减少重试 llm。

    • prompt 中提供输出示例
    • 输出格式使用简单的 markdown 语法,自己解析 markdown
    • 借助编程做好容错处理
  1. prompt 管理

我们采用模板技术来定义 prompt,然后通过模板变量去控制 prompt ,比如多语言等。使用模板来管理 prompt 后,就不需要为不同的情况都写一份 prompt,只需要抽象好 prompt 模板 + 模板变量即可。

  1. prompt 测试

在调试 prompt 的时候,温度(temperature)应该是最常用的一个选项。也就是设置不同的温度,可能会得到不同的效果。像总结文章这种需求,需要基于原文的事实,那最好是温度设置低一些,倾向 0 都可以。温度设置得很高,大于 1 ,LLM 就会更大概率做自由发挥了。还是看自己的业务场景,以及更多的测试。

  1. prompt 迭代

在开发 AI 产品的时候,不要纠结一步到位写好 prompt ,还是需要将重心放到完成整个业务流程和功能上。prompt 的编写也和代码一样,需要持续的迭代、优化。所以,需要好的 prompt 管理方式,方便持续的迭代、测试改进。

对 prompt 不断地打磨,调试,并不是一件 roi 很高的事情,但有时候你又不得不做。