8种典型GenAI应用架构

查看任何 LLM 教程,建议的用法包括调用 API、向其发送提示并使用响应。

假设你希望 LLM 生成感谢信,可以执行以下操作:

import openai
recipient_name = "John Doe"
reason_for_thanks = "helping me with the project"
tone = "professional"
prompt = f"Write a thank you message to {recipient_name} for {reason_for_thanks}. Use a {tone} tone."
response = openai.Completion.create("text-davinci-003", prompt=prompt, n=1)
email_body = response.choices[0].text

虽然这对于 PoC 来说很好,但使用将 LLM 视为另一个文本到文本(或文本到图像/音频/视频)API 的架构进行生产会导致应用程序在风险、成本和延迟方面设计不足。

解决方案不是走向另一个极端,每次都通过微调 LLM 和添加护栏等方式过度设计你 的应用程序。与任何工程项目一样,目标是针对每个用例的具体情况找到复杂性、适用性、风险、成本和延迟之间的适当平衡。在本文中,我将描述一个可帮助您实现这种平衡的框架。

0、LLM 应用程序架构的框架

这是我建议你用来决定 GenAI 应用程序或代理的架构的框架。我将在后面的部分中介绍下图中显示的八种替代方案。

为你的 GenAI 应用程序选择正确的应用程序架构

此处的轴(即决策标准)是风险和创造力。对于你要使用 LLM 的每个用例,首先确定您需要从 LLM 获得的创造力以及用例带来的风险量。这可以帮助你缩小适合您的选择范围。

请注意,是否使用 Agentic Systems 是一个完全正交的决定 - 当任务太复杂而无法通过单个 LLM 调用完成或任务需要非 LLM 功能时,使用 Agentic Systems。在这种情况下,你可以将复杂任务分解为更简单的任务并在代理框架中协调它们。本文向您展示了如何构建 GenAI 应用程序(或代理)来执行其中一项简单任务。

0.1为什么第一个决策标准是创造力

为什么创造力和风险作为一个轴? LLM 是一种非确定性技术,如果你不需要所创建内容具有那么多独特性,那么 LLM 带来的麻烦会比其价值更大。

例如,如果你要生成一堆产品目录页面,它们到底需要有多大的不同?你的客户希望获得有关产品的准确信息,并且可能并不真正关心所有 SLR 相机页面是否都以相同的方式解释 SLR 技术的优势——事实上,一定程度的标准化可能更有利于便于比较。在这种情况下,你对 LLM 的创造力要求很低。

事实证明,减少非确定性的架构也会减少对 LLM 的总调用次数,因此也有降低使用 LLM 的总体成本的副作用。由于 LLM 调用比典型的 Web 服务慢,因此这也有减少延迟的良好副作用。这就是为什么 y 轴是创造力,以及为什么我们在该轴上也有成本和延迟。

说明性:按创造力排序的用例

你可以查看上图中列出的说明性用例,并讨论它们是否需要低创造力或高创造力。这实际上取决于你的业务问题。如果你是杂志或广告代理,即使是你的信息内容网页(与产品目录页面不同)也可能需要有创意。

0.2 为什么第二个决策标准是风险

LLM 倾向于幻觉细节并在其训练数据中反映偏见和毒性。鉴于此,直接将 LLM 生成的内容发送给最终用户存在风险。解决这个问题会增加很多工程复杂性——你可能必须引入人工来审查内容,或者在你的应用程序中添加护栏以验证生成的内容是否违反政策。

如果你的用例允许最终用户向模型发送提示,并且应用程序在后端采取行动(许多 SaaS 产品中的常见情况)以生成面向用户的响应,则与错误、幻觉和毒性相关的风险相当高。

同一用例(艺术生成)可能根据上下文带来不同级别和种类的风险,如下图所示。

例如,如果你正在为电影生成背景乐器音乐,则相关风险可能涉及错误地复制受版权保护的音符,而如果你正在生成向数百万用户广播的广告图像或视频,则可能会担心毒性。这些不同类型的风险与不同级别的风险相关。

再举一个例子,如果你正在构建一个企业搜索应用程序,该应用程序从你的公司文档存储或技术文档中返回文档片段,则与 LLM 相关的风险可能非常低。如果你的文档存储包含医学教科书,则与搜索应用程序返回的脱离上下文的内容相关的风险可能很高。

与按创造力排序的用例列表一样,你可以对按风险排序的用例进行争论。但是,一旦你确定了与用例相关的风险及其所需的创造力,建议的架构就值得考虑作为起点。然后,如果你了解每个架构模式背后的“原因”,则可以选择一种平衡你需求的架构。

在本文的其余部分,我将从图中的 #1 开始描述架构。

1、每次生成(针对高创造力、低风险任务)

这是作为默认的架构模式 — 每次你想要生成内容时,调用已部署的 LLM 的 API。这是最简单的,但它也涉及每次进行 LLM 调用。

通常,你将使用 PromptTemplate 并根据运行时参数模板化发送给 LLM 的提示。使用允许你交换 LLM 的框架是个好主意。

对于根据提示发送电子邮件的示例,我们可以使用 langchain:

prompt_template = PromptTemplate.from_template(
    """
    You are an AI executive assistant to {sender_name} who writes letters on behalf of the executive.
    Write a 3-5 sentence thank you message to {recipient_name} for {reason_for_thanks}.
    Extract the first name from {sender_name} and sign the message with just the first name.
    """
)
...
response = chain.invoke({
    "recipient_name": "John Doe",
    "reason_for_thanks": "speaking at our Data Conference",
    "sender_name": "Jane Brown",
})

因为每次都会调用 LLM,所以它只适用于需要极高创造力的任务(例如,你希望每次都发送不同的感谢信)并且你不担心风险(例如,如果最终用户在点击“发送”之前阅读和编辑便笺)。

采用此模式的常见情况是用于内部用户的交互式应用程序(因此它需要响应各种提示)(因此风险较低)。

2、响应/提示缓存(中等创造力、低风险任务)

你可能不想再次向同一个人发送相同的感谢信。你希望每次都不一样。

但是,如果你要根据过去的票据构建搜索引擎,例如协助内部客户支持团队,该怎么办?在这种情况下,你确实希望重复问题每次都生成相同的答案。

大幅降低成本和延迟的一种方法是缓存过去的提示和响应。你可以使用 langchain 在客户端进行此类缓存:

from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache

set_llm_cache(InMemoryCache())

prompt_template = PromptTemplate.from_template(
    """
    What are the steps to put a freeze on my credit card account?
    """
)
chain = prompt_template | model | parser

当我尝试时,缓存的响应只花费了 1/1000 的时间,完全避免了 LLM 调用。

缓存除了客户端缓存精确的文本输入和相应的响应之外,还有用(见下图)。

响应缓存减少了 LLM 调用的次数;上下文缓存减少了每次调用中处理的令牌数量。它们共同减少了令牌的总数,从而减少了成本和延迟。作者绘制的图表

Anthropic 支持“提示缓存”,你可以要求模型在服务器端缓存提示的一部分(通常是系统提示和重复上下文),同时在每个后续查询中继续向其发送新指令。使用提示缓存可以降低每个查询的成本和延迟,同时不影响创造力。当示例变大时,它在 RAG、文档提取和小样本提示中特别有用。

Gemini 将此功能分为上下文缓存(可降低成本和延迟)和系统指令(不会减少令牌数量,但会减少延迟)。OpenAI 最近宣布支持提示缓存,其实现会自动缓存之前发送到 API 的提示的最长前缀,只要提示长度超过 1024 个令牌即可。像这样的服务器端缓存不会降低模型的功能,只会降低延迟和/或成本,因为您仍可能会对同一文本提示获得不同的结果。

内置缓存方法需要精确的文本匹配。但是,可以以利用您的案例细微差别的方式实现缓存。例如,您可以将提示重写为规范形式以增加缓存命中的机会。另一个常见的技巧是存储 100 个最常见的问题,对于任何足够接近的问题,您可以重写提示以改为询问存储的问题。在多轮聊天机器人中,您可以获得用户对这种语义相似性的确认。像这样的语义缓存技术会在一定程度上降低模型的能力,因为即使是相似的提示,你也会得到相同的响应。

3、预生成模板(中等创造性、低-中等风险任务)

有时,你并不介意在相同情况下向每个人生成相同的感谢信。也许你正在给购买产品的客户写感谢信,你不介意向购买该产品的任何客户生成相同的感谢信。

同时,这种用例的风险更高,因为这些通信是发送给最终用户的,并且没有内部工作人员能够在发送之前编辑每封生成的信件。

在这种情况下,预生成模板响应会很有帮助。例如,假设你是一家旅游公司,提供 5 种不同的套餐。你只需要为每个套餐发送一条感谢信息。也许你想为单独旅行者、家庭旅行者和团体发送不同的信息。你仍然只需要 3 倍于套餐数量的信息。

prompt_template = PromptTemplate.from_template(
    """
    Write a letter to a customer who has purchased a tour package.
    The customer is traveling {group_type} and the tour is to {tour_destination}.
    Sound excited to see them and explain some of the highlights of what they will see there
    and some of the things they can do while there.
    In the letter, use [CUSTOMER_NAME] to indicate the place to be replaced by their name
    and [TOUR_GUIDE] to indicate the place to be replaced by the name of the tour guide.
    """
)
chain = prompt_template | model | parser
print(chain.invoke({
    "group_type": "family",
    "tour_destination": "Toledo, Spain",
}))

对于给定的团体类型和旅游目的地,结果的消息如下:

Dear [CUSTOMER_NAME],

We are thrilled to welcome you to Toledo on your upcoming tour! We can't wait to show you the beauty and history of this enchanting city.

Toledo, known as the "City of Three Cultures," boasts a fascinating blend of Christian, Muslim, and Jewish heritage.  You'll be mesmerized by the stunning architecture, from the imposing Alcázar fortress to the majestic Toledo Cathedral. 

During your tour, you'll have the opportunity to:

* **Explore the historic Jewish Quarter:** Wander through the narrow streets lined with ancient synagogues and traditional houses. 
* **Visit the Monastery of San Juan de los Reyes:** Admire the exquisite Gothic architecture and stunning cloisters.
* **Experience the panoramic views:** Take a scenic walk along the banks of the Tagus River and soak in the breathtaking views of the city.
* **Delve into the art of Toledo:** Discover the works of El Greco, the renowned painter who captured the essence of this city in his art.

Our expert tour guide, [TOUR_GUIDE], will provide insightful commentary and share fascinating stories about Toledo's rich past. 

We know you'll have a wonderful time exploring the city's treasures. Feel free to reach out if you have any questions before your arrival.

We look forward to welcoming you to Toledo!

Sincerely,

The [Tour Company Name] Team 

你可以生成这些消息,让人工审核它们,然后将它们存储在数据库中。

如你所见,我们要求 LLM 在消息中插入占位符,我们可以动态替换这些占位符。每当需要发送响应时,请从数据库中检索消息并用实际数据替换占位符。

使用预生成的模板将原本需要每天审核数百条消息的问题转变为仅在添加新行程时才需要审核几条消息的问题。

4、小型语言模型(低风险,低创造力)

最近的研究表明,消除 LLM 中的幻觉是不可能的,因为它源于学习我们想要的所有可计算函数之间的矛盾。对于更有针对性的任务,较小的 LLM 比对于所需任务而言太大的 LLM 产生幻觉的风险更小。你可能正在使用前沿 LLM 来完成不需要它带来的能力和世界知识的任务。

在用例中,如果你有一个非常简单的任务,不需要太多创造力和非常低的风险承受能力,你可以选择使用小型语言模型 (SLM)。这确实会牺牲准确性——在 2024 年 6 月的一项研究中,一位微软研究人员发现,从与发票相对应的非结构化文本中提取结构化数据时,他们的小型基于文本的模型 (Phi-3 Mini 128K) 可以获得 93% 的准确率,而 GPT-4o 可以实现 99% 的准确率。

LLMWare 团队评估了各种 SLM。在撰写本文时(2024 年),他们发现 Phi-3 是最好的,但随着时间的推移,越来越小的模型正在实现这种性能。

以图形方式表示这两项研究,SLM 越来越以越来越小的尺寸实现其准确性(因此幻觉越来越少),而 LLM 一直专注于提高任务能力(因此幻觉越来越多)。对于文档提取等任务,这些方法之间的准确度差异已经稳定下来(见图)。

趋势是,SLM 使用越来越小的模型获得相同的准确度,而 LLM 使用越来越大的模型专注于更多功能。简单任务的准确度差异已经稳定下来

如果这种趋势持续下去,预计越来越多的企业任务将使用 SLM 和非前沿 LLM,这些任务只需要较低的创造力,并且对风险的容忍度较低。从文档创建嵌入(例如用于知识检索和主题建模)是倾向于符合此特征的用例。使用小型语言模型执行这些任务。

5、组装重新格式化(中等风险,低创造力)

组装重新格式化背后的基本思想是使用预生成来降低动态内容的风险,并仅将 LLM 用于提取和汇总,这些任务即使“实时”完成也只会带来低风险。

假设你是机械零件制造商,需要为产品目录中的每个项目创建一个网页。你显然关心准确性。你不想声称某些物品是耐热的,但实际上并非如此。你不希望 LLM 产生幻觉,认为需要安装该部件。

你可能有一个描述每个部件属性的数据库。一种简单的方法是使用 LLM 为每个属性生成内容。与预生成的模板(上面的模式 #3)一样,在将内容存储在内容管理系统中之前,请确保让人工对其进行审核。

prompt_template = PromptTemplate.from_template(
    """
    You are a content writer for a manufacturer of paper machines.
    Write a one-paragraph description of a {part_name}, which is one of the parts of a paper machine.
    Explain what the part is used for, and reasons that might need to replace the part.
    """
)
chain = prompt_template | model | parser
print(chain.invoke({
    "part_name": "wet end",
}))

但是,简单地附加所有生成的文本会导致阅读起来不太愉快。相反,你可以将所有这些内容组合到提示的上下文中,并要求 LLM 将内容重新格式化为所需的网站布局:

class CatalogContent(BaseModel):
    part_name: str = Field("Common name of part")
    part_id: str = Field("unique part id in catalog")
    part_description: str = Field("short description of part")
    price: str = Field("price of part")

catalog_parser = JsonOutputParser(pydantic_object=CatalogContent)

prompt_template = PromptTemplate(
    template="""
    Extract the information needed and provide the output as JSON.
    {database_info}
    Part description follows:
    {generated_description}
    """,
    input_variables=["generated_description", "database_info"],
    partial_variables={"format_instructions": catalog_parser.get_format_instructions()},
)

chain = prompt_template | model | catalog_parser

如果你需要总结评论或交易有关商品的文章,可以在批处理管道中完成此操作,并将摘要也输入上下文中。

6、ML 模板选择(中等创造力,中等风险)

组装式重新格式化方法适用于内容相当静态的网页(如产品目录页面)。但是,如果你是电子商务零售商,并且想要创建个性化推荐,则内容会更加动态。你需要从 LLM 中获得更高的创造力。你在准确性方面的风险承受能力仍然大致相同。

在这种情况下,你可以做的是继续为每种产品使用预生成的模板,然后使用机器学习来选择要使用的模板。

例如,对于个性化推荐,你可以使用传统的推荐引擎来选择要向用户显示哪些产品,并为该产品提取适当的预生成内容(图像 + 文本)。

如果你要针对不同的客户旅程定制网站,也可以使用这种结合预生成 + ML 的方法。你将预先生成登录页面并使用倾向模型来选择下一步最佳操作。

7、微调(高创造力,中等风险)

如果你的创造力需求很高,那么就无法避免使用 LLM 来生成你需要的内容。但是,每次生成内容意味着你无法扩展人工审核。

有两种方法可以解决这个难题。从工程复杂性的角度来看,更简单的方法是教 LLM 生成你想要的内容,而不是生成你不想要的内容。这可以通过微调来实现。

有三种方法可以微调基础模型:适配器调整、蒸馏和人工反馈。每种微调方法都解决了不同的风险:

  • 适配器调整保留了基础模型的全部功能,但允许你选择特定风格(例如适合你公司声音的内容)。这里解决的风险是品牌风险。
  • 蒸馏近似于基础模型的能力,但只适用于有限的任务,并且使用可在现场或防火墙后部署的较小模型。这里解决的风险是保密性。
  • 通过 RLHF 或 DPO 进行人工反馈,模型可以以合理的准确度开始,但通过人工反馈会变得更好。这里解决的风险是适合用途。

微调的常见用例包括能够创建品牌内容、机密信息摘要和个性化内容。

8、护栏(高创造力,高风险)

如果你想要全方位的功能,并且需要缓解多种风险,该怎么办?也许你担心品牌风险、机密信息泄露和/或希望通过反馈不断改进?

此时,除了全力以赴构建护栏之外别无选择。护栏可能涉及预处理输入模型的信息、后处理模型的输出或根据错误条件迭代提示。

预构建的护栏(例如 Nvidia 的 NeMo)可用于通常需要的功能,例如检查越狱、屏蔽输入中的敏感数据和自我检查事实。

你可能需要构建的护栏

但是,你可能必须自己实施一些护栏(见上图)。需要与可编程护栏一起部署的应用程序是你可以选择实施 GenAI 应用程序的最复杂方式。在采取这条路线之前,请确保这种复杂性是合理的。

9、结束语

我建议你使用一个平衡创造力和风险的框架来决定 GenAI 应用程序或代理的架构。创造力是指生成内容所需的独特性水平。风险与 LLM 生成不准确、有偏见或有害内容的影响有关。解决高风险场景需要工程复杂性,例如人工审核或护栏。

该框架由八种架构模式组成,可解决创造力和风险的不同组合:

  • 每次生成:为每个内容生成请求调用 LLM API,提供最大的创造力,但成本和延迟较高。适用于没有太多风险的交互式应用程序,例如内部工具。
  • 响应/提示缓存:适用于中等创造力、低风险任务。缓存过去的提示和响应以降低成本和延迟。当需要一致的答案时很有用,例如内部客户支持搜索引擎。提示缓存、语义缓存和上下文缓存等技术可在不牺牲创造力的情况下提高效率。
  • 预生成模板:使用预生成、经过审查的模板执行重复性任务,减少不断人工审核的需要。适用于需要标准化但个性化内容的中等创造力、低中风险情况,例如旅游公司的客户沟通。
  • 小型语言模型 (SLM):与较大的 LLM 相比,使用较小的模型来减少幻觉和成本。非常适合低创造力、低风险的任务,例如嵌入创建以进行知识检索或主题建模。
  • 组装重新格式化:使用 LLM 进行重新格式化和总结,并使用预生成的内容来确保准确性。适用于产品目录等内容,其中某些部分的内容准确性至关重要,而其他部分则需要创造性写作。
  • ML 模板选择:利用机器学习根据用户上下文选择合适的预生成模板,平衡个性化与风险管理。适用于个性化推荐或动态网站内容。
  • 微调:涉及微调 LLM 以生成所需内容,同时尽量减少不良输出,解决与品牌声音、机密性或准确性相关的风险。适配器调整侧重于风格调整、特定任务的提炼以及持续改进的人工反馈。
  • 护栏:高创造力、高风险的任务需要护栏通过预处理、后处理和迭代提示来减轻多种风险,包括品牌风险和机密性。现成的护栏解决了越狱和敏感数据屏蔽等常见问题,而定制的护栏可能是行业/应用程序特定要求所必需的。

通过使用上述框架来构建 GenAI 应用程序,你将能够平衡每个用例的复杂性、适用性、风险、成本和延迟。


原文链接:How to Choose the Architecture for Your GenAI Application

BimAnt翻译整理,转载请标明出处