从2022年12月以来,chatGPT 的横空出世掀起了新一波的 AI 浪潮,热度一直居高不下直到现在。半年时间里,从底层模型 API 到上层应用的生态逐渐建立,经过一轮轮迭代不断完善创新。本文将结合开源框架和应用程序,从工程师的角度,与大家讨论如何对大语言模型进行封装和应用,并从工程层面解决现有问题,搭建完整可商用的 AI 应用程序。
LLM,Large Language Model,即大语言模型。这个“大”是说参数量大(通常数十亿个权重或更多),是一种语言模型的概念。为了更深入理解,我们选用OpenAI 公司的 GPT 模型进行讨论。本文实验都在 GPT3.5 的模型上进行(GPT4 太贵了)。
目录
- GPT 技能点分析
- 如何克服 GPT 现有短板
- 构建完整 AI 应用
SOHU
一、GPT技能点分析
开始之前,先科普一下:GPT 是 OpenAI 训练的模型,chatGPT 是在 GPT 基础上构建的聊天应用程序,有很大的区别。
01
事实
要对 GPT 进行应用,首先要了解清楚 GPT 提供给我们什么。GPT 对外暴露的内容非常简单清晰:一个聊天接口(https://platform.openai.com/docs/api-reference/chat/create)。当然实际不止一个接口,还包含了一些指定场景,如文本补全、文本编辑。但是我们常用的就是最为通用的聊天接口。
如下图所示,接口入参必填参数包括应用的模型名称、消息列表。消息列表是一个 jsonArray,可以包括历史消息。模型区分了消息文本的角色,分为系统、用户和 AI 三个角色。以便于区分历史上下文和设定统一背景。还有一些其他可选字段:比如 temprature 可以控制返回的随机性,0代表最不随机,如果完全相同的输入,大概率给出完全相同的输出。1代表最随机;stream 表示是否需要以流的形式返回,就像 chatGPT 逐个文本进行返回;max_token 规定了单次请求的 token 数量限制,毕竟 token 就代表了成本。
返回的结果也很简单:一句 AI 的回复 content ,和本次对话的 token 消耗值。API 如下图所示:
02
优势
首先来看 GPT 的优势。作为开发工程师,从应用的视角看,总结出如下几点:
- 无与伦比的自然语言掌握能力,在某些角度是超越了我们普通人的。相信大家或多或少领略过,可以很强大地做文本生成、文本理解和文本翻译等。
- 某些领域有推理和抽象思维的能力,尤其写代码方面。也通过了各种人类的考试,如SAT、美国医学执照考试、律师资格考试等。注意是某些领域,不完全。
- 他还有强大的泛化能力,在新的样本上表现很好,支持了跨各个垂直领域的对话。
- GPT 有对人类的“理解能力”。心智理论(Theory of Mind, ToM)是指通过将心理状态归因于他人来理解他人的能力,或者更简单的说,是推测其他人心里在想什么的能力。GPT 在心智理论测试中表现出色。
- 多样性和可控性,GPT 模型提供了一些参数,增强它的多样性,比如Temprature。
实际上,在人工智能领域是有很多分支的。比如推荐、模式识别、自然语言、计算机视觉等方向。但是 GPT 都能解决吗?并不是。那么一个主要在自然语言领域的模型,为什么让大家都有惊艳的感觉呢?我认为归功于以下两点:
- 语言是人类与外部世界交互的主要方式,尤其是在信息时代。尽管 GPT 只是一个语言模型,但这个它在各种领域和任务上展现的能力,让你觉得他真的“懂”。
- 原有用到人工智能的领域,大多需要专有的模型。每一种专有的模型之间都有比较大的鸿沟,成本很高,包括学习成本和训练成本。GPT 如此通用的模型加上如此方便的交互方式,给一些需要 AI 但是没有能力搞AI的公司或个人,实现了技术的普惠。在很快的产品周期里,就可以将人工智能的进步转化为真正投入使用的实际产品。对于我们纯开发工程师来说,是“弯道超车”的重要机会。
多提一句,各种优势的来源,就是大语言模型的“涌现”能力。涌现可以用一句经典的话来概括:量变引起质变。很多方面的测试任务表明,模型量级在大于10的22次方之后,能力的增幅得到了大幅度的提升。(论文来源:Emergent Abilities of Large Language Models, https://arxiv.org/abs/2206.07682)
03短板
了解了 GPT 的优势,但是作为工程师在应用落地过程中更要关心它不足的地方,并给出解决方案。我总结了几个主要的点,下面和大家一一分析。
对prompt要求较高
GPT 对 prompt 的要求体现在两个方面:其一是对于 prompt 表达的敏感性,其二是对 token 的数量限制。
- 对于 prompt 表达的敏感性
prompt 就是提示,也可以简单理解为向 GPT 输入的问题。GPT 对于提示的措辞很敏感。这意味着在与 GPT 进行对话时,用户需要非常准确地表达自己的意图,并且对 GPT 的思考范围和返回方式作出明确的规定。否则 GPT 会天马行空,无法满足落地应用确定性的要求。 - token 的数量限制
对于 GPT 模型来说,有很强的 token 数量限制,输入和输出都会算 token 。GPT3.5 的 token 上限是4096,GPT4 是8000或32000,且根据 token 数计费。具体 token 的计算涉及到 NLP 领域的分词相关算法,大家可以根据 OpenAI 提供的工具进行查询:https://platform.openai.com/tokenizer。这个规定大大限制了我们和 GPT 交流的方式,比如上下文的篇幅限制。下图是实验的时候 token 超出限制的报错。
缺乏规划性和工作记忆
首先解释规划性。我们应该都知道贪心法和动态规划的区别,GPT 模型依赖于生成下一个词的局部和贪心过程,没有对任务或输出进行全局或深入的理解。因此,该模型擅长产生流畅和连贯的文本,但在解决无法按照顺序方式处理的复杂或创造性问题方面存在局限性。
比如生成回文诗,尝试了很多遍都无法正确生成:
再比如经典的汉诺塔问题,如果问 GPT 通用解决方案是什么,甚至写代码他都可以。但是如果具体提问有三根柱子,五个盘子,如何挪动,很大概率会出错。
再来看工作记忆,其实就类似人类打草稿,只不过人类的心算就是在心里打草稿。工作记忆关系到模型对于一个整体任务的拆解,和对于中间步骤的处理,所以影响到了复杂任务的处理结果。
这两个短板其实和人很像,我们人脑很多情况下也是无法用动态规划的思路来解决问题的。我们心算个位数的加减乘除还可以,但是三位数及以上的乘除法对一般人的心算难度同样很大,出错率会变高。只不过我们的上限比现阶段的 GPT 高一些。
没有记忆能力 : 短期记忆 + 长期记忆
注意这里的记忆和刚才所说的工作记忆不是一回事儿。工作记忆有点类似于内存或者草稿,是模型内部处理问题。而这里的记忆我们分为了短期记忆和长期记忆。
- 短期记忆:因为 GPT 模型是无状态的,每次我们调用 OpenAI 的接口它都不知道前后的上下文。所以短期记忆是指单个用户同一个会话的历史上下文。下图是直接调用 OpenAI 接口,并不能产生短期记忆。
- 长期记忆:是指一些特定领域的常识和专业的知识等,需要长期存储在模型中,影响所有用户的每一次会话请求。由于 GPT 是一个通用的大语言模型,所以并没有深入地学习各个垂直领域的专业知识,在应对专业问题的时候力不从心。可以从感性上理解为,这是模型的知识盲区。
无法与外界交互
GPT 是一个语言模型,训练的数据知识截止到2021年。同时现阶段他无法使用外部工具,不能建立与外部新知识的连接。如果问 GPT 最近发生的新闻,他肯定不知道。如果强制要求一定要返回,就算说了回答也不一定正确,这很大程度上造成了对于事实性问题的谬误。
安全性合规性问题
人是会被接收到的信息影响的,所以作为涉及到自然语言领域的应用,一定要考虑安全性和合规性的问题。对于 AI 应用来说分为两个方面:一方面是对输入的判定,非法或不合规的输入不应该进行响应。另一个方面是对输出的判定,虽然 OpenAI 已经对 GPT 模型本身做了很多安全性的管控,但是毕竟是机器自动生成的,包括国内外规定不尽相同,所以需要对输出进行合法合规的校验。
非多模态、不可解释等问题
GPT 起码 GPT3.5 目前还是一个专注于自然领域的模型,对多模态的信息并没有很好的支持。同时,GPT 模型对于问题解决和对话输出的内在逻辑并不可解释,还处于黑盒状态,这对应用者控制 GPT 有一定阻碍。
04结论
总体来说,GPT 模型在许多任务中的能力,是可以与人类相媲美的。尤其是自然语言方面,可以说是 unparalleled。但是也存在上述各种实际问题。但是在现在的水平上,已经可以作为部分 AI 应用的底层依赖。我们的使命就是通过工程的手段尽可能解决上述各种问题。
二、如何克服GPT现有短板
针对第一章节所说的问题,我们从工程的角度尽可能地提出解决方案,并结合 langChain 框架现有的功能,展示作为黑盒模型的封装,应该从哪些方向着手。
01
对prompt要求较高的问题 —— 系统性prompt工程
首先给出一个错误的 prompt 示例:
这样一句简单的提示,并没有对 GPT 的思考和返回做任何约束,具有极大的不确定性,对于落地的应用程序是不可接受的。这句话可以作为 chatGPT 的用户输入,但是 chatGPT 也已经是成熟的产品,用户的输入一定不是最终与 OpenAI 交互的 prompt 。
笔者认为,系统性的 prompt 工程要注意以下三点:
- 必须有清晰明确的指令。
接下来给出在实验中用到的真实 prompt :
如上图所示,标出了对应的注意点,主要有以下几点:
- 要区分消息角色,system /assistant / user。在 system 中给出整体背景和限定,其他角色可以加入上下文;
- 对输入输出格式进行结构化,严格限定,便于 GPT 区分,也便于前置输入和后续提取模型返回值并向用户展示;
- 对任务的具体内容进行清晰详细的表述;
- 如果必要,给出特殊情况的处理方案;
- 如果必要,限定模型思考的步骤和范围;
- 给出适当示例;
- 尽可能用英文,因为 GPT 对英文的理解能力还是强于中文的。
- 要注意 token 的限制,包括最大数量限制和成本控制。
token 数量限制是作为工程必须考虑的问题,甚至是需要优先考虑的问题,因为涉及到产品的成本。 - 不断调整 prompt ,并持久化为固定资产。
实际开发工程中,不可能一次就确定完美的 prompt ,或者说没有完美的 prompt 。只有在不断试错的过程中不断调整,并通过各种测试反馈,形成“极优解”而不是“最优解”。确定 prompt 后,即成为这个应用的固定资产,和应用的代码一样,是核心价值与竞争力所在。
下面挑几个封装来看,在系统性 prompt 工程方面, langChain 做了哪些努力。更详细的请见:https://python.langchain.com/docs/modules/model_io/prompts/
- langChain 封装了 prompt 的生成方式,封装为 PromptTemplate。
- 实现了参数的解析,可以手动指定参数,也可以自动识别 {} 提取参数
- 区分了不同的角色,分为 SystemMessagePromptTemplate、 AIMessagePromptTemplate、 HumanMessagePromptTemplate,都属于 MessagePromptTemplate
- 多个 MessagePromptTemplate 可以封装成为 ChatMessagePromptTemplate ,其中包含了多轮 AI 和 Human 的对话历史,也是我们最常用的 PromptTemplate
- langChain 封装了 prompt 中示例的输入,封装为 ExampleSelector。
可以自定义 ExampleSelector ,并添加多个示例
可以根据 token 长度的限制,自适应示例个数和选择示例
如下图,创建了 LengthBasedExampleSelector ,并规定最大长度是25。如果输入很简短的“big”,则自动附加了很多个示例。如果输入一个长字符串,则只选择了一个示例加入 prompt 。
可以根据本轮输入内容的相似性匹配,在 prompt 中添加示例
如下图所示,在同样的示例集合中,输入“worried”一词 SemanticSimilarityExampleSelector 给出的是 happy 和 sad 作为示例,因为都属于一种情绪。
具体的实现方式,是在 SemanticSimilarityExampleSelector 中传入了用于生成测量语义相似性的文本嵌入,和用于存储文本嵌入并进行相似性搜索的向量数据库。
向量数据库的原理,简单来讲。文本添加的时候,通过分词,再通过特定的神经网络,将文本转化为一个维数很高的向量。搜索的时候经过同样处理,通过比较目标词转换成的向量与已有向量之间的余弦距离等计算方式,得出最为相近的文本。这是省略了很多细节的版本,由于不是本文主题,就不过多介绍。但是向量数据库对 LLM 的助力是极大的。
02
缺乏规划性和工作记忆的问题 —— 思维链模式
思维链:Chain of Thought,简称CoT。是一种推理模式,就是主动要求 GPT 去思考,把一个复杂的问题用推理的方法拆分成多个。实践上就是我们就是要告诉 GPT,把任务分解。如果模型不能主动分解为正确的步骤,就需要人帮他把步骤显示地加到 prompt 里去。
比如下图的示例,提问有多少个质数,他会先把所有质数列出来,再进行计数。而且整个过程是显式地打印出来的,这会比直接给出答案正确率高很多。
再比如下图,提问哪种更快,他会先计算出总数再进行比较。
思维链模式是大语言模型应用的一大利器,大大增加了返回的可靠性。
03
没有短期记忆和长期记忆的问题 —— 外挂记忆
对于没有记忆能力这个问题来说,我们可以给它外挂记忆。
- 短期记忆
我们可以用内存或者数据库,将一段会话中的历史内容放到 prompt 里。这样回答最新的问题的时候就知道上下文了。有很多种方式,比如多轮记忆都放进去;比如使用 FIFO 避免 token 限制;再比如进行摘要提取,提取的方法有很多,让 GPT 自己提取就不错;同样也可以做基于向量的相似度优先记忆。图示如下: - 长期记忆
最好的方式是对模型进行微调训练,让他真正学习到我们想让他了解的数据或知识。是有几个问题,首先是数据量和数据质量的要求都很高,其次是价格太贵,训练和调用都要根据 token 算钱,还比正常的调用要贵。而且 OpenAI 现在只开放了下图这四种模型:
其他还有办法吗?向量数据库又登场了。可以将专业的数据存储在向量数据库中,在生成最终的 prompt 之前,先从向量数据库找出与原本问题最相近的数据,拼接成 prompt 一起投喂给 GPT ,模型会根据 prompt 中包含的信息,给出与私有数据或专业知识更贴近的回答。这是工程角度可以解决的最简便的方法,类似一个“乞丐版”的小模型。
在基于 LLM 的应用中,向量过于重要,所以最近做向量数据库的项目或公司都有很多资金在投。
04
无法与外界交互的问题 —— 代理扩展工具
与外界交互的问题是最容易想到解决方案的。和计算机相关的事情都可以通过代码和网络来完成,只要 GPT 生成命令,我们就可以使用工具帮助它控制。可以进行谷歌搜索,可以与 MySQL 等各种数据库进行操作,可以读写文件和操作命令行等。
可以和外界交互之后,GPT 的能力得到了一个非常大的提升。以前很多解决不了的问题,现在都可以解决了,所以我们又有了新的处理问题的范式:ReAct 。注意,这个和前端的 react 没有任何关系,意思是 Reason + Action。每一次动作要先思考,形成 Thought ,再行动进行 Action,最后观察形成 Observation。上一轮的观察结果再去支持下一轮的思考,直至结束。
ReAct 的优势有两点:
- ReAct 提示语言模型以交错的方式产生与任务相关的语言推理轨迹和动作,这使得模型能够动作态推理,以创建、维护和调整动作的高级计划(推理到动作),同时也与外部环境互动,将额外信息纳入推理(动作到推理)。总结成一句话就像:“学而不思则罔,思而不学则殆”。
- 除了普遍适用性和性能提升外,推理和动作的结合也有助于提高模型的可解释性、可信度和所有领域的可诊断性,因为人类可以很容易地区分来自模型内部知识和外部环境的信息,以及检查推理轨迹以了解模型动作的决策基础。在过程中也可以对模型的想法进行纠偏。
有研究人员对 ReAct 和 CoT 两种范式进行了对比。总体而言,最好的方法是 ReAct 和 CoT 的结合,允许在推理过程中使用内部知识和外部获得的信息。通过实验得出了各种模式下的有效性分析:
ReAct 和 CoT 的区别是什么呢?基于以上研究,主要表现为以下几点:
- 幻觉是 CoT 的一个严重问题,即错误步骤和结论。相比之下, ReAct 的问题解决轨迹更接地气,以事实为导向,并且值得信赖,这要归功于外部知识库的访问。
- 虽然交错推理、动作和观察步骤提高了 ReAct 的基础性和可信度,但这样的结构约束也降低了其制定推理步骤的灵活性,导致推理错误率高于 CoT 。有一种 ReAct 特有的频繁错误模式,即模型重复生成之前的想法和动作,我们将其归为 "推理错误 "的一部分,因为模型未能推理到合适的下一步动作并跳出循环。
- 对于 ReAct 来说,通过搜索获得有效信息是至关重要的。非信息性搜索,占错误案例的23%,这使得模型推理脱轨,使其很难恢复和重新表述思想。这也许是事实性和灵活性之间的预期权衡,促使我们使用结合两种方法的策略。
05
安全性合规性问题 —— 各阶段校验
对于合规性问题,我们可以从三个步骤考虑。
- OpenAI 本身提供了用于校验的 moderation 接口,而且此接口是免费的,不消耗 token 。如下图,输入“I want to kill them”,返回数据中 flagged 为 true ,表示这个输入触发了安全规则。 category_score 中给出了各个方面的详细打分,violence 最高,表示这个输入有很强的暴力倾向。
- 由于国内外规定不尽相同,对于国内的合规性要求,我们要经过严格的国内权威平台校验。
- 同时,我们也可以使用 GPT 本身的文本理解能力进行其他方面的判别,如情感倾向等。
06
其他通用工程问题 —— 工程实践解决
实际应用落地过程中,还会有很多其他的工程实践问题。比如并发、不同模块之间连接、各个环节的 mock 、各种测试验证等,像 langChain 这样的框架都给出了解决方案。
这里提一个比较有意思的:缓存问题。因为 GPT 响应时间比较长,而且如果相同问题每次都请求, token 消耗也比较贵,所以比如智能客服等场景,建立缓存很有必要。但都是自然语言,比如用户说“这个房子怎么样”和“这个楼盘怎么样”,实际是很相近的问题,但是通过 equals 等判断是不能命中的。怎么建立自然语言的映射呢?向量!
07
GPT发展方向及应用工程调整
GPT 等模型是在不断发展的,面对他的发展方向,我们需要不断调整。比如, token 数量一定是会放宽的,而且速度会非常快;对于多模态的支持非常关键,科学家们正在不断努力;外部工具使用的支持,模型本身也在做,而且在 GPT4 上已经有部分可以应用了。还有很多方面,在模型本身做了这些之后,上述的一些策略就会失去作用,也需要针对新的特性去进行加强。当然不能说因为会失效所以现在就不做了,工程师就是要在现有工具的基础上争分夺秒地为应用到业务落地而服务。
三、构建完整AI应用
01
我们应该用GPT来做什么
基于刚才的分析和工程优化,笔者得出一个思考:GPT 是“人”,不是机器。这涉及到一个很关键的问题——我们应该用 GPT 来做什么?
- 首先,它擅长做自然语言相关的 AI 应用;
- 其次,它可以做某些人类做的推理的事情;
- 但是,它难以做人类做的过于复杂和创造性的事情;
- 最后,不应该让 GPT 做机器做的事情。什么叫机器做的事情?机器的执行是严密的,是不能出错的。如果出错,大概率是写代码的人的错误。但是人是可能出错的, AI 也是可能出错的。举个例子:一个公司想做数据分析,但是没有 BI 工程师。如果他想向 GPT 寻求帮助,是要直接把所有数据塞给 GPT 然后问他具体的问题吗。当然不是,这样做 token 数量会爆炸性增长,且放不下所有数据。何况 GPT 的计算能力本来就很差。正确的做法是将数据库的表结构输入给 GPT ,用一些清晰明了的示例表达清楚诉求,然后让 GPT 写好 SQL ,在自己的数据库服务器中运行。这里的数据库就是机器,但写 SQL 的是 GPT (也就是应该人做的工作)。
02
构建一个AI应用的全流程
最后整理一下,构建一个 AI 应用的全流程。其实整个过程都围绕着提高确定性。
- UI 层:用户界面层,接收输入返回输出。
- 会话处理层:包括对用户输入的格式化解析、对话管理等功能。如果要用向量数据库做缓存,就应该在这一层。
- 数据审计层:负责对用户数据进行审计和保护,同时对出入的自然语言的安全性和合规性进行评估。注意,不仅要评估用户的输入,也要评估模型的输出。
- 操作编排层:这个层级可以管理和协调多个语言模型、工具。比如 langChain 对外部工具的使用等。
- LLM 增强层:这个层级可以对语言模型进行额外的优化和增强,比如长期记忆、短期记忆相关的各种操作。
- LLM 层:最底层是语言模型本身,其实就是对OpenAI 的接口请求。
最后总结下来,在一个完整应用中,GPT 这个黑盒模型就只是右下角的底层依赖,其他全是工程相关的设计和实现。
03
现阶段的生态
如上图,虽然 GPT3.5 横空出世并没有多久,但是已经形成了较为完善的生态体系。最底层模型层,是黑盒模型的 API 调用。上层框架层,各个主流语言都有类似 langChain 的封装。再上层应用层,搭建了各个实际需求场景下的应用程序。我们做 AI 应用时,可以直接依赖 OpenAI 的模型接口,也可以在框架的基础上方便地操作,甚至在类似 autoGPT 这种通用应用基础上搭建。笔者推荐基于 langChain 等框架,既屏蔽了底层通用的工程细节,相对也有更高的灵活度。
04
对待GPT的正确态度
最后呢,想明确我们对待 GPT 等大语言模型的态度:
- 不夸大 GPT 作为一种 AI 模型的效果
- 更不轻视 GPT 帮助 AI 工程应用的能力
祝作为工程师的大家都能产出优秀的 AI 应用。
参考资料
- Emergent Abilities of Large Language Models(https://arxiv.org/abs/2206.07682)
- Sparks of Artificial General Intelligence: Early experiments with GPT-4(https://arxiv.org/abs/2303.12712)
- LLM 应用参考架构:ArchGuard Co-mate 实践示例(https://www.phodal.com/blog/llm-reference-architecture/)
- ChatGPT 实用指南(ChatGPT 实用指南(二))