ChatGPT 的出现让大模型再一次成为业界的关注热点,然而,并不是每个组织都要去训练及生成大模型的,而且各个组织的技术积累和计算资源也不太允许这样去做。更多的时候, 我们还是基于大模型开发业务应用。所谓智能原生(AI Native),往往是指那些没有大模型就无法成立的应用,那是一些新的业务机会和挑战。很多时候, 我们还只是Applied AI, 即通过AI 尤其是大模型为应用赋能。
不论是AI 原生还是AI 赋能的应用,都会面临如何构建基于大模型APP 的问题,基于大模型的App 在系统架构和开发方式上有什么不同呢?
1. 试图理解LLM 的能力边界
AI 并不神秘, 也不存在迷信。应用任何一种技术都应该了解它的能力边界,不但知道它能干什么,还要知道它不能干什么,或者至少要知道这种技术当前的局限是什么。
1.1 LLM 的基本能力
到目前为止,大模型的主要能力如下:
使用LLM进行语言理解和处理,而不是作为知识源
LLM是在互联网上的大量文本数据上进行预训练的,这些训练数据为大模型提供了知识。这也允许大模型在广泛的任务中进行泛化,然后下游进行微调。
在构建基于大模型的App时,很容易将这些LLM简单地用作知识/事实源(即搜索引擎)。实际上,我们应该利用LLM强大的语言理解和处理能力。这将允许它“理解”用户请求并提供“响应”。它应该提供与目标应用程序相关的知识和数据,并且只应从我们提供的数据返回事实/信息。
LLM也可以用于基本的推理
除了语言理解之外,在逐步提示下工作时,一些LLM在基本推理方面的表现也不错。这可以让我们利用LLM将用户请求/响应分解为较小的任务。
使用LLM进行审查,评估和反馈
LLM在审查文本并报告其中的问题方面比从头开始生成文本要有效得多。因此,我们尽可能多地使用这种技术,将LLM的输出发送回LLM并要求它反复检查输出。
使用LLM进行文本转换,扩展,摘要
这是NLP自身的能力,将非结构化文本转换为JSON格式,反之亦然,扩展短文本或者摘要长文本。
1.2 让LLM 回答它无法知道的问题
在老码农看来, LLM 体现出的涌现能力仍然是基于现有数据推理的潜在关系,真正的无中生有往往是幻觉的来源。一般来说,我们有两种不同的方法来让大语言模型回答LLM无法知道的问题:模型微调和上下文注入。
将预训练模型调优的过程称为Fine-tuning,指的是使用额外的数据对现有的语言模型进行训练,以优化其针对特定任务的表现。与从头开始训练语言模型不同,我们使用已经预训练过的模型,如LLama,并通过添加特定于用例的训练数据来调整模型以适应特定任务的需求。微调是调整模型以适应特定任务的一种方式,但它并不能真正向模型中注入新的领域知识。这是因为该模型已经在大量的通用语言数据上进行了训练,而特定领域数据通常不足以取代模型已学习到的内容。换句话说,微调帮助模型适应了其语言交流的方式,但不一定是它所传达的内容。
使用上下文注入时,我们不修改语言模型,而是专注于修改提示本身并将相关上下文插入到提示中,其工作原理可能是这样的:
因此,需要思考如何为提示语提供正确的信息, 需要一个能够识别最相关数据的过程。通过使用嵌入技术,我们可以将文本转换为向量,从而在多维嵌入空间中表示文本。在空间中距离较近的点通常在相似的上下文中使用。为了避免相似性搜索耗时过长,一般会将向量存储在向量数据库中并进行索引。
2. 基于大模型 API 的简单应用构建所面临的问题
构建大模型App 最直接的方式是在LLM API上创建一个简单的应用程序层,可以将LLM与应用程序的用例、数据和用户会话联系起来,可用于维护与用户的先前交互的记忆和状态,或将目标分解为较小的任务和子任务。
但是,在LLM之上构建简单的应用程序层存在着一些不足:
- 对用户的响应将是不可预测的,并且会包含幻觉。
- 响应将不与目标应用程序的数据和用例相关。
- 无法为自己的产品建立护城河,任何人都可以轻松地实现相同的结果。
- LLM API 的成本较高,而且可能相当高。
- LLM是无状态的,没有代理功能。
相反,我们需要使用自己的专有数据和知识构建护城河,需要减少不必要的调用,并在可能的情况下使用更便宜的模型,还需要迭代地编排和自动化底层LLM,以改进任务规划和推理能力。
那么,面对基于大模型的App, 是否存在通用性或具有指导性的参考架构呢?
3. 大模型App 的系统架构思考
基于LLM 的应用开发框架(例如LangChain)提供了围绕大模型构建应用程序的结构化方法。但是,这里从抽象层尝试给出大模型App 的系统架构。
3.1 应用编排器
编排器简单地位于应用程序栈的下方,并将其他模块连接在一起。其中,构建多租户组件非常重要。这将确保:
- 为每个用户进行个性化设置
- 隐私保护,确保只为正确的用户检索记忆,上下文等。
而且,以下的每个模块都需要考虑多个多租户实例的设计。
3.2 任务计划器
一个不错的方法是获得用户的请求/目标,并使用模型将其分解为子任务。每个子任务可以根据应用程序进一步分解为较小的任务/目标。这是一个持续的过程,随着用户完成目标,LLM可以用于扩展当前任务和子任务,或者修剪不再必要的任务。许多框架和开源项目都提供此类功能,一个典型的示例可能是AutoGPT。
一般地,可以按以下方式进行处理:
- 获取用户目标并将其发送到具有良好推理功能的LLM
- 提示LLM将其分解为子任务并返回为JSON列表
- 将子任务保存到数据库中
- 应用程序可以根据子任务更新用户界面
- 根据需要迭代为较小的子任务
3.3 上下文数据向量存储
一般地,我们可能不应该在目标应用程序中使用预训练的LLM知识,而是为每个提示提供任何必要的上下文信息,并指定LLM仅基于提示中包含的信息进行响应。这将确保响应与目标应用程序和用例相关。通过使用向量嵌入和向量数据库,可以根据语义检索每个提示的子集的上下文数据,从而实现更高的效率,提升性能并降低成本。
该方法如下所示:
- 每当有新的上下文信息时,将其分成若干部分,并使用LLM生成向量嵌入。然后将嵌入存储在向量数据库中,还将在每个嵌入中存储附加信息(例如URL、图像、源文本等)。
- 在向LLM发送请求之前,始终将请求作为查询发送到向量存储中。获取前N个相关结果并将它们添加到请求提示中,指定LLM应仅使用提示中的信息,然后提交提示词。
- 收到响应后,将其与发送的上下文数据进行比较,确保没有幻觉并且它与目标应用程序的数据相关。
- 进行迭代,其中响应用于生成对向量数据库的新查询,然后使用结果作为下一个LLM的提示词。
- 还可以要求LLM生成一个查询到向量存储,以获取所需的附加信息。
需要要注意的,从向量数据库接收到的记录除了文本之外还包含其他数据,可能是图像、URL、视频URL等,目标应用程序可以使用此信息增强用户界面的响应。
3.4 记忆型数据的向量存储
记忆型数据的向量存储类似于上下文数据的向量存储,但是,它由先前使用应用程序生成的LLM提示和响应的键值对进行填充。目标是允许LLM参考以前的交互,以个性化用户需求并引导走向正确的方向。
记忆数据也可以使用时间戳、位置等进行标记,以允许过滤或对相关记忆数据的修剪。
一般用例:
- 根据用户在用户界面中的操作,发出请求。请求转换为向量嵌入,并发送到内存向量存储中以检索任何相关的记忆数据。
- 记忆可能包括特定的交互,例如,用户发表过评论
- 然后将记忆与用户请求以及从上下文存储中提取的任何上下文一起添加到提示中。在提示中,记忆可能以“这里是以前的交互列表,请在响应时考虑这些,以确保您遵守以前的请求和偏好”的文本为前缀。
- 然后,将提示发送到LLM。
- 生成的提示和响应在当前会话期间转换为向量嵌入,并存储在内存向量存储中。只要它们在未来的LLM交互中具有语义相关性,就会检索它们。
3.5 提示管理器
很多时候,尤其是相对复杂的场景中,提示词往往冗长而复杂。构建一个提示管理器,它可以接受许多属性并以正确的结构构建提示。
另外,为了能够在目标应用程序中使用响应,必须能够预测将收到的格式。最好的方法是在提示词中提供预期的JSON格式。这种JSON格式可以包括要修改的UI元素、要采取的操作等属性。
3.6 响应管理器
响应管理器类似于提示管理器,但它用于验证响应,可以处理以下内容:
- 检查响应格式以确保符合提示中发送的要求。(例如,验证JSON格式)
- 验证响应是否符合加载的上下文和内存数据,以确保其不是幻觉。
- 将响应发送回LLM,以及原始提示,并要求LLM决定我们是否有良好的质量响应。
- 检查LLM的响应是否存在不良内容、负面情绪等。
如果响应管理器认为当前的LLM响应存在问题,那么它可以生成一个带有拒绝原因的新提示,并将其提交给LLM以获取新的响应。这可以迭代地进行,直到响应满足所有标准和安全检查。
3.7 效果评估器
LLM可以很好地评估用户的提示词并根据预定义的标准对其进行评分。一种常见的方式是在完成任务后,提示用户提供反馈,然后通过这些提示,LLM根据以下标准评估反馈:
- 用户是否报告了任何不满?(-1=未知,0=没有不满,10=严重不满)
- 用户是否喜欢这个体验?(-1=未知,0=完全不喜欢,10=非常喜欢)
- 用户是否感觉完成了目标?(-1=未知,0=没有,10=完全达到)
- 等等。
最后,LLM将以JSON格式返回反馈,评估结果可以存储在数据库中,还可以使用这些结果构建新的功能。
3.8 大模型管理器
每一种大模型模型都有着自己的优缺点,我们可能需要利用多个LLM来进行应用程序的开发,以充分利用它们的优势。选择要使用的模型时,一般的考虑因素如下:
- LLM推理成本和API成本
- 针对不同的用例场景筛选大模型的类型。例如,使用编码器模型进行情感分析,使用解码器模型进行文本生成或聊天,对于基本的文本操作,选择较小、更快、更便宜的模型。
- 文本嵌入模型用于语义搜索和生成向量嵌入
- 微调模型以在特定任务上获得更好的性能
- 指令微调模型可以充当的助手,例如RLHF的应用
LLM提供商一般会允许我们对每个请求选择要使用的模型,一个请求的输出也可以链接到第二个模型进行文本操作或审查。例如,当需要重要推理任务时,可以使用GPT-4,然后使用GPT-3进行基本文本操作或完成。这将有助于控制API成本,并确保为每个请求使用最合适的模型。我们还可以为某些任务使用开源更便宜的模型。
通过大模型管理器,可以将API和模型使用之间的差异从应用程序中抽象出来,还可以使用LLM的插件方法,轻松引入新模型。
4. 构建大模型App 的简单示例
构建一个基于大模型的app,大概可以采用以下步骤:
- 在待创建或已有的App中引入用户显式用自然语言进行交互的入口(也可以采用隐式方式);
- 明确所需解决的问题领域空间,加载目标领域的文档内容,并对文本进行分割;
- 采用嵌入模型,将文本数据生成向量;
- 构建面向向量存储的向量数据库并构建索引;
- 选择目标模型,将API 引入系统;
- 创建 prompt 模版,并支持配置和优化;
4.1 引入自然语言交互
每个App 都有相应的用户交互设计(UI/UX),为了通过大模型为应用赋能,面对非结构化数据,例如工作描述、简历、电子邮件、文本文档、PowerPoint 幻灯片、语音录音、视频和社交媒体, 自然语言交互有着广泛的应用场景。
自然语言的引入一般会以助手的方式呈现,可以直接采用chat的方式,简单而言,是在产品中引入了可以查看历史记录的输入框。
4.2 文档加载与文件分割
有很多现成的文档加载器,可以用于HTML页面、S3、PDF、Office文档等的加载器。一般地,可以利用企业中现有的文集存储或者知识库,采用批处理的方式完成目标数据集的加载, 然后采用事件触发的方式实现实时加载。
然后,需要将文本分成较小的文本块。每个文本块在嵌入空间中表示一个数据点,使计算机能够确定这些块之间的相似性。常见的方式是使用较大的文本块,但也可以进行一些实验,找到最适合用例的最佳大小。请记住,每个LLM都有token限制(GPT 3.5的令牌限制为4000个),需要确保整个提示的token数不超过单次LLM API 调用的token限制。
4.3 文本数据的向量生成
我们需要将文本转换为可理解和可比较算法的形式,必须找到一种将人类语言转换为比特和字节的数字形式的方法。嵌入模型通过分析单词通常出现的上下文来尝试学习这个目标。嵌入式模型为我们提供了嵌入空间中每个单词的向量。最后,通过用向量表示它们,进而能够进行数学计算,例如计算单词之间的相似度作为数据点之间的距离。
将文本转换为嵌入,常见的方法有Word2Vec,GloVe,fastText或ELMo。以Word2Vec为例,为了在嵌入空间中捕捉单词之间的相似性,Word2Vec使用了一个简单的神经网络。实际上,现有的模型要大得多,因此以更高维度的空间表示单词,例如OpenAI的Ada嵌入模型使用了1536个维度,训练过程结束后,各个权重就描述了在嵌入空间中的位置。这些预训练向量使我们能够以如此精确的方式捕捉单词及其意义之间的关系,以至于我们可以对它们进行计算。我们还可以选择不同的嵌入模型,如Ada、Davinci、Curie和Babbage。其中,Ada-002目前是最快且成本最低的模型,而Davinci通常能够提供较高的准确性和性能。
我们使用嵌入模型的目标是将文本块转换为向量。在Ada-002中,这些向量具有1536个输出维度,这意味着它们表示一个在1536维空间中具有特定位置或方向的点。将文本块和用户的问题表示为向量时为了确定两个数据点之间的相似度,需要计算它们在多维空间中的接近程度,这可以通过距离度量来实现。
4.4 构建向量数据库并创建索引
将文本数据转换为向量只是第一步,为了能够高效地搜索我们的嵌入向量,我们需要将它们存入向量数据库并进行索引。
向量数据库是一种专为存储和检索可以表示为向量的大量数据而优化的数据存储类型。这些类型的数据库允许根据各种标准(如相似度度量或其他数学运算)高效地查询和检索数据子集。索引是向量数据库的重要组成部分,提供了一种将查询映射到向量存储中最相关文档或项的方法,而不必计算每个查询与每个文档之间的相似度。
常见的一些向量数据库如下所示:
4.5 模型选择与API 应用
当选择模型的时候,可以遵循上文提到的那些原则。以Open AI 平台为例,Text-davinci-003模型可能目前是最大且最强大的模型。另一方面,像Text-ada-001这样的模型更小、更快速,而且更具成本效益。与最强大的模型Davinci相比,Ada更便宜。因此,如果Ada的性能满足我们的需求,不仅可以省钱,还可以实现更短的响应时间。
我们也可以先使用Davinci进行尝试,然后评估是否也可以使用Ada获得足够好的结果。在模型选择后,首先要设置API key获得访问权限,然后再尝试设置一些首选项,在不同的参数中进行一些调试。
4.6 Prompt 模版的配置优化与使用
Prompt提示指定了模型回答问题所需的模式即所需的行为风格,希望LLM在其中生成答案。将Prompt提示应用在LLM上,这里有一些简单的示例:
- 摘要生成:“将以下文本总结为3段供高管阅读:[文本]”
- 知识提取:“基于这篇文章:[文本],在购买房屋之前人们应该考虑哪些因素?”
- 写作内容(例如邮件、信息、代码):“给[人名]发送一封邮件,询问项目文档的进展情况。使用非正式、友好的语气。”
- 语法和风格改进:“将这段文本改正为标准英语,并改变语气为更友好的:[文本]”
- 分类:“将每条消息分类为订单的类型:[文本]”
除了对Prompt及其模版的管理和使用,我们还可以对大模型的限制,只允许LLM 利用指定数据库中存储的信息。这个限制使我们能够提供LLM生成回答所依赖的来源,这对于可追溯性和建立信任至关重要。此外,它还帮助解决了生成不可靠信息的问题,并能够提供可以在企业环境中用于决策的答案。
5 小结
当然,我们可以利用一些现有的或者发展中的应用框架或者平台,例如开源框架LangChain。如果觉得LangChain 用起来不够简洁, 可以尝试一下LLMFarm,LLMFarm可以简单地类比为可视化的LangChain。如果仍然觉得复杂, 还可以尝试使用蓝莺IM 基于大模型的企业知识库。
因此,构建一个基于大模型的应用并没有想象中的那么困难, 但充分利用大模型的能力来为业务赋能却不是那么容易,仍然需要探索并寻找最佳实践。