看看任何LLM的教程,通常建议的使用方式是调用API接口,发送提示,然后使用回复。比如,如果你想让LLM帮你写一封感谢信,你可以这样做,比如说:
导入 openai 包
收件人姓名 = "约翰·多伊"
感谢原因 = "帮助我处理这个项目"
语气 = "专业"
提示语 = f"写一封感谢信给 {收件人姓名},感谢对方 {感谢原因}。使用 {语气} 语气。"
响应结果 = openai.Completion.create("text-davinci-003", prompt=提示语, n=1)
邮件内容 = 响应结果.choices[0].text
这样的做法对于PoC(即Proof of Concept,概念验证)来说是可以接受的,但是用这种将LLM当作普通的文本到图像/音频/视频等API的架构直接应用于实际生产环境,会导致应用程序在风险、成本和延迟方面存在不足。
解决方案不是走向另一个极端,不要过度复杂化你的应用程序,通过微调LLM或添加防护措施等,每次都不必这么做。目标与任何工程项目一样,是在每个具体用例的需求中找到复杂性、适用性、风险、成本和延迟之间的正确平衡。在这篇文章中,我将描述一个可以帮助你找到这种平衡的框架。
LLM应用架构框架这里有一个框架,帮你确定生成AI应用程序或代理的架构设计。我将在下面的部分中逐一介绍如图所示的八个选项。
选择适合您GenAI应用程序的正确架构。作者绘制的图。
这里的维度(即决策标准)是风险和创造力。对于你打算使用的每个大规模语言模型用例,首先确定你需要从语言模型中获取的创造力以及该用例的风险。这有助于你找到最适合你的平衡点。
需要注意的是,是否使用代理系统与此完全无关——当任务太复杂,无法通过单次大型语言模型请求完成,或者任务需要其他非语言模型的功能时,使用代理系统。在这种情况下,您需要将复杂任务拆分成更简单的任务,并在代理系统中协调它们。这篇文章会教您如何构建一个生成式AI应用程序(或一个代理)来执行其中一个简单任务。
为什么第一个选择标准是创造力?
为什么创造力和风险是关键因素?大规模语言模型是一种不可预测的技术,如果你并不真的需要太多独特的内容,那么它们可能会带来更多麻烦,而不是带来多少好处。
例如,如果您正在生成许多产品目录页面,它们真的需要差别很大吗?您的客户希望产品信息准确无误,并且可能并不在意所有单反相机页面都以相同方式解释单反技术的优势——事实上,一定程度的标准化可能对于易于比较来说是非常可取的。在这种情况下,您对大型语言模型的创意要求不高。
原来,减少非确定性的架构设计也会减少调用LLM的总次数,因此也有降低使用LLM的成本的效果。因为调用LLM比调用普通网络服务要慢,这也会减少延迟。这也是为什么y轴表示创意,同时成本和延迟也体现在这个轴上的原因。
作者绘制的图,展示了按创新程度排序的使用案例。
你可以看看上面图表中提到的示例用例,并讨论它们需要多少创意。这主要取决于你具体的问题。即使是你网站上的信息性页面(如不像产品目录页),也可能需要创意。
为什么选择风险作为第二个决策标准呢?
大规模语言模型倾向于虚构细节,并且会反映出训练数据中的偏见和毒性。鉴于此,直接将大规模语言模型生成的内容发送给最终用户,存在一定的风险。解决这个问题会增加很多工程复杂性——你可能需要引入人工审核内容,或者在应用程序中添加防护措施,以确保生成的内容不违反相关规定。
如果你的应用场景允许用户向模型发送请求,并且应用程序在后端(许多SaaS产品中都很常见的情况)处理以生成用户界面的反馈,那么与错误或毒性相关联的风险很高。
同样的用例(艺术生成)在不同的上下文中可能涉及不同层次和类型的風險,如图所示。例如,如果你为一部电影生成背景音乐,相关的风险可能涉及不小心复制了受版权保护的音符;而如果你生成的是要向数百万用户广播的广告图片或视频,你可能会担心内容的毒性问题。这些不同类型的风险与不同的风险级别相关。再比如,如果你正在构建一个企业搜索应用程序,该应用程序从你的企业文档库或技术文档中返回文档片段,与LLM相关的风险可能相对较低。如果你的文档库包含医学教科书,返回的与上下文无关的内容可能带来很高的风险。
举例说明:按风险排列的例子。作者绘制。
就像根据创意对用例排序一样,你也可以对用例按风险排序有所争议。但一旦你确定了每个用例相关的风险及其所需的创新,所建议的架构作为起点值得考虑。然后,如果你理解每种架构模式背后的“原因”,你就可以选择一种能够平衡你需求的架构模式。
接下来,我会从#1开始,依次介绍架构。
1. 每次生成(适用于高创造力、低风险的任务类型)这是默认的做法——每次需要生成内容时,就调用部署的LLM的API。这也是最简单的,每次都需要调用一次LLM。
一般情况下,你会使用一个PromptTemplate,并根据运行时参数来定制发送给LLM的提示。使用一个可以让你轻松替换LLM的框架是个不错的选择。
如果我们需要按照提示发送电子邮件,可以考虑试试langchain,这样更符合中文口语表达。
prompt_template = PromptTemplate.from_template(
"""
你是 {sender_name} 的AI执行助理,负责代笔写信。
请为 {recipient_name} 写一封3-5句的感谢信,感谢 {reason_for_thanks}。
从 {sender_name} 提取名字并在信末仅署名。
"""
)
...
response = chain.invoke({
"recipient_name": "John Doe",
"reason_for_thanks": "在我们的数据大会上发言",
"sender_name": "Jane Brown",
})
由于你每次调用LLM,这仅适用于需要极高创造力的任务(例如,每次你都想写不同的感谢信),且不必担心风险(如果最终用户可以在点击“发送”之前阅读并编辑这封信,那么就无需担心)。
一个常见的使用这种模式的情况是针对内部用户的交互式应用程序,因为这样的应用程序需要应对各种提示,而且风险较低。
2. 响应和提示缓存(用于中等创意,低风险任务类型)你肯定不想每次都用同一封感谢信给同一个人吧,每次都要有点不一样。
但是如果你正在构建一个基于过去票证的搜索工具,比如为了帮助内部的客户支持团队,在这种情况下,你希望重复的问题每次都得到相同的答案。
通过缓存过去的提示和响应,可以大幅降低成本和延迟。你可以在客户端(client)使用langchain来缓存这些提示和响应。
from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache
set_llm_cache(InMemoryCache())
prompt_template = PromptTemplate.from_template(
"""
冻结我的信用卡账户的步骤是什么?」
"""
)
chain = prompt_template | model | parser
当我试用时,缓存响应只用了千分之一的时间,并且完全避免了调用大模型。
缓存不仅限于客户端缓存精确文本输入及其相应响应(见下图)。Anthropic提供了一种‘提示缓存’功能,允许你让模型在服务器端缓存一部分提示(通常是系统提示和重复的上下文),同时在后续查询中发送新的指令。使用提示缓存可以减少每次查询的成本和延迟,从而不影响创造力。这种方法特别有用,尤其是在这些场景中:检索增强生成(RAG)、文档提取和少量样本提示场景中,当示例变大时。
响应缓存机制减少了LLM调用次数;上下文缓存减少了每个单独调用中处理的令牌数。两者共同减少了总的令牌数量,从而降低了成本并减少了延迟。图表由作者绘制
双子座(Gemini)将此功能分离为上下文缓存(context caching)(这可以降低成本并减少延迟)和系统指令(system instructions)(这不会减少标记数量,但可以减少延迟)。OpenAI 最近宣布支持提示缓存,其实现会自动缓存之前发送到API的提示中最长的前缀,只要提示的长度超过1024个token。这样的服务器端缓存不会减少模型的功能,只会减少延迟和/或成本,因为您可能会对相同的文本提示得到不同的结果。
内置的缓存方法要求完全匹配文本。然而,你可以根据你的具体情况来实现缓存。例如,你可以将提示标准化,以增加缓存命中的机会。另一个常见的技巧是存储最常见的100个问题,对于任何足够接近的问题,都可以将其重写为已存储的问题的提问形式。在多轮对话聊天机器人中,你可以让用户提供确认,以验证这些语义相似性。这样的语义缓存技术会略微削弱模型的能力,因为你将得到即使是相似提示的相同回复。
3. 预先生成的模板(用于中等创意水平,较低风险的工作)有时,你并不会介意同样的感谢信被发送给所有人,因为他们情况相同。例如,你可能正在给那些购买了某产品的客户写感谢信,你可能觉得向购买了该产品的任何客户发送同样的感谢信也没问题。
在这种场景下的通信存在更高的风险,因为这些通信是直接发送给最终用户,并且没有内部人员能够在发送前对每封生成的信件进行编辑。
在这种情况下,预先准备一些模板回复可能会有所帮助。假如你是一家旅行社,提供五种不同的套餐。你只需要为每个套餐准备一个感谢信息。或许你希望为单独旅行者、家庭和团体提供不同的信息。你仍然只需要为每个套餐准备三倍数量的信息。
prompt_template = PromptTemplate.from_template(
"""
为购买了旅游套餐的客户写一封信。
该客户是与 {group_type} 一起旅游,旅游目的地是 {tour_destination}。
表达见到他们的兴奋之情,并介绍他们在那里可以看到的一些亮点以及在那里可以参与的一些活动。
在信中,使用 [CUSTOMER_NAME] 表示将被客户姓名替换的位置,如 [CUSTOMER_NAME],
使用 [TOUR_GUIDE] 表示将被导游姓名替换的位置。
"""
)
chain = prompt_template | model | parser
print(chain.invoke({
"group_type": "家庭",
"tour_destination": "西班牙的托莱多",
}))
结果会是这样的消息,对于特定的组类型和旅游目的地,如下所示:
亲爱的[CUSTOMER_NAME],
我们非常欢迎您即将来到托莱多!我们迫不及待地想向您展示这座城市的美丽与历史。
托莱多,作为“三种文化之城”而著称,融合了基督教、伊斯兰教和犹太教的遗产,令人着迷。您会被这些壮丽的建筑迷住,从高耸的阿兰哈兹城堡到雄伟的托莱多大教堂。
在您的旅行中,您将有机会:
* **探索犹太区的历史:** 漫步在狭窄的街道上,两旁是古老的犹太教堂和传统房屋。
* **参观圣约翰修道院:** 欣赏精致的哥特式建筑和令人惊叹的回廊。
* **享受全景视野:** 沿着塔古斯河散步,欣赏这座城市的壮丽景色。
* **探索托莱多的艺术:** 发现埃尔·格列柯的杰作,这位著名画家捕捉到了这座城市的精神。
我们的专业导游[TOUR_GUIDE]将为您提供详细的讲解,并分享托莱多丰富历史的有趣故事。
我们知道您将享受探索这座城市的宝藏。如果您有任何疑问,请随时与我们联系。
我们期待着欢迎您来到托莱多!
此致,敬礼
[旅游公司名称]团队
你可以生成这些消息,让一个人对其进行审核,然后将它们存储到你的数据库中。
正如你所见,我们让LLM在消息里插入占位符,这些占位符可以动态替换。每当需要发送响应时,从数据库中获取消息,并用实际数据替换这些占位符。
使用预生成的模板可以将需要每天审核数百条消息的问题变成只需在添加新旅程时审核几条消息的情况。
4. 小型语言模型(风险较小,创意较低)最近的研究显示,在大规模语言模型(LLM)中消除错觉是不可能的,因为这种错觉源于学习所有期望的可计算函数之间的矛盾。对于特定任务来说,规模较小的LLM出现错觉的风险小于那些规模过大,不适合特定任务的模型。你可能在用前沿的LLM来处理不需要其强大功能和广博知识的任务。
在某些情况下,如果你的任务简单,不需要太多创造力且风险容忍度非常低,你可以选择使用小型语言模型(SLM)。这样会牺牲一些准确性——2024年6月的一项研究,一位微软研究员发现,对于从非结构化文本中提取与发票相对应的结构化数据,他们的小型文本模型(Phi-3 Mini 128K)准确率为93%,而GPT-4o的准确率为99%。
LLMWare 团队评估了各种 SLM(小型语言模型)。截至撰写之时(2024年),他们发现Phi-3表现最佳,但随着时间推移,越来越小的模型也达到了类似表现。
用图形表示这两个研究,SLM在越来越小的规模中(因此越来越少的生成错误)逐渐达到其准确度,而LLM则专注于提升任务性能(因此越来越多的生成错误)。例如,对于像文档提取这样的任务,这两种方法之间的准确度差异已经趋于稳定(请参见图)。
趋势是 SLMs 采用越来越小的模型达到相同的准确度,而 LLMs 则专注于使用越来越大模型来拥有更多的功能。在简单任务上的准确性差异已经变得稳定。图表由作者绘制。
如果这种趋势继续,预计将会越来越多地使用SLM(小型语言模型)和非前沿的大型语言模型(LLM)来完成那些只需要低创造性且风险承受能力低的企业任务。比如,通过文档创建的嵌入,可用于知识检索和主题建模等场景,这些应用场景通常适合这种情况。对于这些任务,使用小型语言模型会更适合。
5. 组装重组(中等风险级别,低创新性)组装重组的基本理念是通过预生成处理来减少动态内容的风险,并仅使用LLM(大型语言模型)进行提取和总结,这些任务即使在实时处理时,引入的风险也很低。
假设你是一家生产机器零件的制造商,需要为产品目录中的每一种零件创建一个网页。你肯定非常注重准确性。不希望错误地宣称某些零件具有耐热性,也不希望LLM胡编乱造安装所需的工具。
你可能有一个数据库来描述每个零件的属性。利用大型语言模型为每个属性生成内容。就像之前提到的预生成模板(模式#3)一样,确保在将内容存入你的内容管理系统之前由人工审核。
prompt_template = PromptTemplate.from_template(
"""
你是一名纸机制造商的文案撰稿人。
写一段话描述纸机的一个部件,部件名为{part_name}。
解释该部件的作用以及可能需要更换它的原因。
"""
)
chain = prompt_template | model | parser
print(chain.invoke({
"part_name": "湿部",
}))
然而,简单地将所有生成的文本拼接在一起会让人觉得阅读起来不太舒服。你可以把这些内容放进提示里,然后让LLM把这些内容重新排版成你需要的网站样子。
class CatalogContent(BaseModel):
part_name: str = Field("零件的通用名称")
part_id: str = Field("目录中的唯一零件ID")
part_description: str = Field("零件描述信息")
price: str = Field("零件价")
catalog_parser = JsonOutputParser(pydantic_object=CatalogContent)
prompt_template = PromptTemplate(
template="""
提取所需信息,并提供JSON格式的输出。
{database_info}
零件描述如下:
{generated_description}
""",
input_variables=["generated_description", "database_info"],
partial_variables={"format_instructions": catalog_parser.get_format_instructions()},
)
chain = prompt_template | model | catalog_parser
如果你需要总结评论和交易相关的文章,你可以将这些操作放在批处理管道中,并将总结的内容加入到上下文中。
6. 选择合适的机器学习模板(中等程度的创意,中等风险程度)组装的重构方法适用于内容基本不变的网页(例如产品目录页面)。然而,如果你是一名电子商务零售商,并希望创建个性化推荐,内容就会更加动态化。你需要大型语言模型提供更多的创意。你对准确性的容忍度依然差不多。
在这种情况下,你可以继续使用预先为每个产品准备好的模板,然后让机器学习来帮你挑选合适的模板。
你可以使用传统的推荐引擎来选择向用户展示的商品。然后,获取相应的预生成内容(包括图片和文字)。
将预生成和机器学习结合的方法也可以在为不同的客户旅程定制网站时使用。你将预生成落地页,并利用倾向性模型来选择接下来的最佳行动。
7.精调(高创意性,中等程度的风险)
如果你的创作需求很高,无法避免使用LLM(大型语言模型)来生成你所需的内容。但是,每次生成内容都会意味着你无法扩大人工审查的范围。
解决这个难题有两种办法。从技术复杂度来看,简单一点的做法是通过微调让大模型生成你想要的内容,避免生成你不想要的内容。
有三种方法可以微调基础模型:适配微调、蒸馏和人工反馈:每种微调方法都应对不同的风险。
- 适配保留了基础模型的全部功能,但允许您选择特定的风格(例如符合您公司声音的内容)。这里解决的风险是品牌声誉风险。
- 蒸馏近似于基础模型的能力,但在一个有限的任务集上,并使用一个可以部署在本地或防火墙后面的较小模型。这里解决的风险是机密性风险。
- 通过RLHF或DPO的人类反馈,模型在一开始就具备了合理的准确性,并且会随着反馈不断改进。这里解决的风险是适用性问题。
常见的微调训练用例包括比如创建品牌内容、机密信息的摘要和个人化的内容。
8. 防护栏(高创意,高风险)如果你想要全方位的能力,并且担心不止一种风险——比如品牌风险、机密信息外泄,或者希望通过反馈来持续改善?
到那时,除了全力以赴地搭建防护机制之外别无选择。这种机制可能涉及对进入模型的信息进行预处理,对模型输出进行后处理,或者根据错误条件调整提示。
预构建的防护栏(例如 Nvidia 的 NeMo)用于实现常见的功能,如检测越狱、在输入中隐藏敏感数据以及自我验证事实。
你可能还得自己搭个护栏。图由作者提供。
然而,你可能需要自己动手做一些防护措施(如上图所示)。需要与可编程防护措施一同部署的应用程序是实施GenAI应用程序最复杂的方式之一。在决定采取这种方式之前,请确保这种复杂性确实有必要。
摘要我建议你用一个既能激发创意又能管理风险的框架来决定你的GenAI应用或代理架构。创意指的是生成内容的独特程度。风险是指LLM生成不准确、有偏见或有毒内容可能带来的后果。处理高风险场景通常需要复杂的工程措施,比如人工审核或设置防护措施。
这个框架由八个架构模式组成,这些模式处理了创新和风险的不同组合。
- 每次都需要生成: 每次内容生成请求都调用LLM API,提供最大的创造力,但成本和延迟较高。适合风险不大的互动应用程序,例如内部工具等应用程序。
- 响应/提示缓存: 用于中等创造力,低风险的任务。缓存过去的提示和响应,以降低成本和延迟。当需要一致的答案时很有用,例如内部客服搜索引擎。提示缓存、语义缓存和上下文缓存提高了效率,同时不牺牲创造力。
- 预生成模板: 使用预生成并经过审查的模板处理重复任务,减少持续的人工审查需求。适合中等创造力,低到中等风险的情况,需要标准化但个性化的内容,例如旅游公司的客户沟通。
- 小型语言模型(SLMs): 使用较小的模型来减少与大型LLM相比的幻觉和成本。适合低创造力,低风险的任务,如知识检索或主题建模的创建嵌入。
- 重组格式: 使用LLM进行格式化和总结,并使用预生成内容以确保准确性。适合像产品目录这样的内容,其中部分内容的准确性至关重要,而其他部分则需要创意写作。
- ML选择模板: 利用机器学习根据用户上下文选择适当的预生成模板,平衡个性化需求和风险控制。适合个性化推荐或动态网站内容。
- 微调: 涉及对LLM进行微调,以生成所需的内客同时最小化不需要的输出,解决与品牌声音、机密性或准确性相关的风险。适配器微调侧重风格上的调整,蒸馏特定任务,并通过人工反馈进行持续改进。
- 防护措施: 高创造力,高风险的任务需要防护措施来缓解多种风险,包括品牌风险和机密性,通过预处理,后处理和迭代提示。预设的防护措施可以解决常见的破解问题和敏感数据屏蔽问题,而针对行业/应用特定需求可能需要定制的防护措施。
通过使用上述框架来架构GenAI应用程序,您将能够平衡各个方面的复杂度、目的性适配性、风险、成本效益和延迟时间,从而优化每个使用场景的表现。
(定期提醒:这些文章仅代表我个人的看法,不代表我现任或前任雇主的看法。)
共同學習,寫下你的評論
評論加載中...
作者其他優質文章