一文梳理大语言模型编程框架 原创
大语言模型(LLMs),以及一般的语言模型(LMs),催生了一种新的编程方式,其中“指令”不再是明确的应用程序编程接口(APIs),而是像英语这样的自然语言语句。该领域(一个被称为提示工程的新领域)的专家通过组合特定的关键词、提示格式,甚至认知模型来对他们的语言模型进行编程——或者从语言模型中引出特定行为。
过去两年表明,语言模型可以产生广泛的变革性影响,但在无缝集成到更大的程序环境方面存在固有局限。它们对事实和先前交互的记忆不完善,不能可靠地遵循逻辑结构,无法与外部环境交互或执行计算,并且对提示方式很敏感。为解决这些局限,已经出现了十多个流行的框架,它们在对语言模型之间以及与语言模型交互进行抽象时倾向于不同的理念。
本文对这些框架进行了迄今为止最全面的综述,采用了比现有讨论更系统的分类法。除了提供用于对与大型语言模型之间以及与大型语言模型交互进行抽象的框架情况的梳理之外,本文还提供了两种分类体系,用于对抽象的各种方法和理念进行推理:
语言模型系统接口模型(LMSI),一种新的七层抽象,受计算机系统和网络中的开放系统互连(OSI)模型启发,用于对近几个月出现的编程和交互框架进行分层。这些层在语言模型抽象的整个层次结构中从最高层抽象到最低层抽象依次呈现。 我们确定了五类语言模型抽象,它们在我们的综述中执行类似功能,对这些类别进行了分类。这些类别将大致处于相同层的库分组,并且大致按照从较高层抽象到较低层抽象的顺序呈现。 这两种分类体系——语言模型系统接口模型(LMSI)和语言模型抽象家族分类——通过它们对抽象层级的处理方式相互关联。语言模型抽象家族的排列形成一个谱系,大致与LMSI模型的抽象层相对应——特别是,它试图描述向用户或开发者暴露的细节程度。例如,在谱系的一端,较低层抽象涉及通过Python函数(例如,在LMSI的神经网络层)与大型语言模型的参数直接交互。在另一端,较高层抽象提供用于提示生成和优化的工具,能够对大型语言模型的响应进行递归式优化,而无需深入研究基础大型语言模型或流程的底层技术复杂性(例如,在LMSI的优化层或应用层)。
语言模型系统接口模型(LMSI)
在我们对语言模型框架的研究过程中,我们观察到一种趋势;我们可以大致将特定框架确定为高层抽象、低层抽象或混合层抽象。然而,如果不进一步分类,在这种情况下“高”或“低”到底意味着什么并不明确。
受计算机系统和网络中的OSI模型启发,我们提出了这些框架可能关注的不同功能层的概念,并且重要的是,它们可以很容易地在语言模型库和框架的层次结构中从高层到低层进行组织。我们总共确定了语言模型框架通常可操作/向用户暴露的七层抽象。这些层表示抽象程度不断提高,从直接与语言模型的架构、权重和参数交互——这是最低层抽象,即神经网络层,到最高层抽象,即把启用语言模型的应用程序当作一个黑箱来处理用户提供的一般高层任务,即用户层。
我们认为,除了实现对现有大型语言模型抽象框架进行组织/分类的目标之外,这些层有助于确定大型语言模型框架的不同重点和特性可以在何处相互分离。就像OSI模型的情况一样,我们希望分层能够让框架开发者清楚地了解在合适的层级设计他们的抽象,以便可能利用其他框架中已经实现的现有基础设施——有效地分离关注点,并允许框架在抽象边界/接口处将专业工作转移给其他框架。然而,在当前阶段,虽然许多框架处于较窄的层带内,但有些框架跨越的范围更广,这通常反映了一种扩展性设计,其中高层抽象可以与低层特性以一种向开发者暴露的方式进行交互。
下面列出了每层的描述和定义,而表1通过星级表示近期框架所侧重的层。
- 神经网络层——直接访问语言模型的架构和权重/解码组件。
- 提示层——通过应用程序编程接口(API)、手动聊天或其他界面向语言模型输入文本,可能对输入或输出文本序列没有限制。
- 提示约束层——对多组或多种类型的提示施加规则或结构(例如通过模板),或者对输出进行约束和验证。
- 控制层——框架中支持控制流,如条件、循环、分发。
- 优化层——基于某个指标对语言模型或语言模型系统的某些方面进行优化。
- 应用层——基于较低层构建的库、实用程序和应用程序代码,提供具有一定可配置性的通用现成解决方案。
- 用户层——人机交互层,应用程序直接根据人类指令执行由语言模型驱动的任务。
表1
五类语言模型框架
我们没有按照抽象顺序孤立地描述每个框架,而是确定了在我们的综述中出现的几类执行类似功能的库。这些类别将大致处于相同LMSI层的库分组,并大致按照抽象程度递增的顺序呈现。
第一组框架是那些专注于受控生成(如Guidance和LMQL)的框架。这些框架能够对语言模型的输出定义格式要求和其他约束(例如,通过正则表达式),这是围绕语言模型构建可靠系统的关键要素。在此基础上,用于模式驱动生成的库(如OpenAI的函数调用模式和Marvin)允许用户表达类型级别的输出结构(例如,作为Pydantic模式)。以DSPy为首的框架利用强大的模式和自然语言签名抽象,专注于大型语言模型程序的编译,围绕将程序(其中大型语言模型调用是首要构造)编译为自动生成的高质量提示链展开。编译驱动策略与带有预封装模块的提示工程工具(如LangChain和LlamaIndex)不同,在后者中,一组用于生成和解析提示的预封装实用程序,以及用于将生成的响应与其他语言模型调用或环境调用相连接的基本原语,可用于以更有意义的方式与语言模型交互。然而,这些工具的核心仍然依赖于手动提示工程技术。在最高层,我们讨论最具雄心的开放式代理框架(如AutoGPT和MetaGPT)和多代理聊天范式(如在CAMEL和AutoGEN中)。
表2(五类语言模型框架。注意:我们将最底层的基础提示层设为灰色,并且不将其计入,因为它只是简单地封装了语言模型基本的文本输入/输出行为,没有进一步的抽象。点击展开。)
0)基础提示
我们在语言模型框架家族中不太愿意纳入的最低层是在语言模型抽象框架出现之前的最先进水平;基础提示。
基础提示只是描述了用户在没有任何额外框架的情况下一直能够做到的事情,给定一个语言模型或语言模型的应用程序编程接口(API),基础提示可以被认为是围绕不同语言模型的简单包装器,它们以统一的方式表达其最原始的接口(文本输入,文本输出)。这是OpenAI(和其他提供商)的API的核心,也是LangChain - core包中的一些主要抽象。对于某些应用程序,这种原始形式为构建手工制作的提示或构建更高层抽象提供了足够的灵活性。
1)受控生成
语言模型以统计上可能但不确定的方式完成文本。这意味着语言模型的响应可能不可预测,使其难以集成到其他工作流程中。一类库通过应用基于模板或约束的受控生成技术来缓解此类担忧,例如Outlines、Guidance和LMQL。这些库使用模板引擎,其中模板表达带有“空位”的提示,以供生成内容来填充。这些语言模型生成的内容可以绑定到模板变量,并代入后续生成过程,引导输出。这些库还支持将输出约束为诸如子集成员、模式和长度等限制。LMQL利用大型语言模型的解码组件来实现这种过滤,而Outlines修改语言模型修改令牌概率的能力以支持输出约束。这些框架往往跨越神经网络层到提示约束层。
@lmql.query LMQL程序是用嵌入在Python中的领域特定语言(DSL)表示的,其中顶级字符串(可能包含用方括号表示且带有可选类型注释的“空位”)是用于补全的提示,而附加约束由where语句表示。LMQL引入了急切和部分求值语义的概念,它保守地近似判断对于特定生成,约束是否成立。利用这种语义,LMQL可以生成特定于模型的令牌掩码,以在解码过程中修剪搜索空间。
Outline具有类似的能力,能够通过正则表达式有效地引导生成,它也能支持LMQL所支持的许多约束。在内部,Outline将神经文本生成表述为有限状态机状态之间的转换。它在模型词汇表上建立索引,并通过操纵模型对令牌的概率来有效地保证生成文本的结构。该库还具有许多基于上下文无关语法(CFG)引导生成的内置特性,以实现更实用的用法,例如使用Jinjia模板语言进行提示生成,或使用json和Pydantic进行对象类型约束和模式定义。
然而,就LMQL而言,对于像GPT - 3和GPT - 4这样的托管端点,可能无法轻易访问语言模型的解码组件。此外,这些库使用的一些“填空”策略更适合用于生成任务的语言模型,而对于聊天模型(例如GPT - 4)则不太适用。
2)模式驱动生成
受控生成的一种更精细的抽象是模式驱动生成。在这里,语言模型与其宿主编程接口之间的边界受到一些预定义模式或一些对象规范语言(通常是JSON)的约束。这与Guidance和LMQL不同,在后者中,如果用户希望得到JSON输出,就必须指定JSON骨架及其内部内容限制。与受控生成相比,基于模式的方法可以更容易地集成到其他现有的编程逻辑中。然而,对生成内容类型的控制粒度可能更有限。这些库位于提示约束层,因为符合模式是限制语言模型输出的一种方式。
在Outlines、其他库(如Marvin和Instructor)以及最近的LangChain和Llamaindex中,这一步骤被进一步抽象。用户可以使用数据验证库Pydantic指定数据结构作为某些语言模型的输出模式(模型级抽象)。在内部,这些库通过预构建提示将模式编码为自然语言指令,要求语言模型以正确、可解析的格式输出。然而,对于Marvin和Instructor,有一些支持来利用基础语言模型供应商提供的更高级功能(例如,OpenAI的函数调用以确保可解析性)。预计这些库也将采用OpenAI的新JSON模式,以降低输出不可解析的可能性。
例如,Instructor为OpenAI API提供了一个替代客户端,它接受使用Pydantic指定的响应模型。在其实现中,它支持三种查询语言模型的模式,即json、函数调用和工具使用,每种模式都对应于OpenAI支持的等效API。为了在解析过程中引导语言模型,Instructor有一些内部提示来告知语言模型模式和输出格式。
在LangChain中,当偶尔出现验证错误时,支持更复杂的重试解析器,但输出不是通过OpenAI的函数调用API进行提示,而更多是通过自然语言指令。
除了输出数据模式,一些库在大型语言模型交互的背景下增加了对功能模式(函数级抽象)的支持,允许程序员用自然语言模式或签名对函数进行声明式注释,以表示其高层意图。这个想法最早出现在2023年初的DSP项目中,它是DSPy框架的前身。在DSP中,用户可以指定函数输入和输出的类型(在最简单的情况下:“问题,思考 -> 答案”),其中包括对参数含义和相关前缀的人类层面描述。输入和输出类型的集合形成了一个DSP模板,从中可以诱导出一个可调用的Python函数。Marvin最近通过其“AI函数”简化了这种模式,其中程序员对输入和输出类型以及函数文档字符串的注释用于根据一些运行时输入生成提示,以便从语言模型中提取输出。
在DSPy中,开发者同样可以指定自然语言签名,以声明式地定义大型语言模型需要解决的(子)任务,这些任务是从整体任务的简短描述以及输入和输出字段的描述中推导出来的。
3)编译大型语言模型程序
函数的自然语言规范的具体化在很大的搜索空间中可能会有所不同,这取决于所提供的示例(例如,少样本对比零样本)、提示策略(例如,思维链)、外部数据源(检索增强生成,RAG),甚至是多跳方法(例如,思维程序)。在Marvin中,具体化策略是固定的,开发者通常局限于使用所提供的现成解决方案。然而对于DSPy来说,自然语言签名与具体的查询策略是分离的,这意味着要使用一个签名,用户必须声明一个带有该签名的模块。这些模块还可以选择性地带有示例(或演示)列表。DSPy还包括许多复杂的模块,如思维链(ChainOfThought)、思维程序(ProgramOfThought)、多链比较(MultiChainComparison)和反应(ReAct),它们可以将这些提示或交互策略应用于任意的DSPy签名,而无需手动进行提示工程。
DSPy模块可以像PyTorch一样使用运行时定义(define - by - run)接口进行组合,允许构建任意的流程。DSPy中的一个强大概念是能够编译一个流程,通过选择超参数(如指令/提示或演示)甚至微调来对其进行优化。这个优化过程是通过提词器(teleprompter)进行的,它表示一种特定的优化策略。例如,自举少样本(BootstrapFewShot)提词器从少量输入(注意并不严格需要输出)中引导示例到模块流程。这个提词器在输入上运行流程,并选择满足某些可定制启发式的演示。
与其他技术相比,基于编译的系统有几个明显的优势,例如能够在不重新定义基础签名的情况下改变不同的提示策略,以及通过优化提高性能。然而,要获得编译的好处,需要涉及不少框架搭建工作,并且需要对底层的DSPy系统有一定的了解。对于需要大量与语言模型 - 环境交互的项目,或者不需要太多优化的简单项目,在现阶段采用这个系统可能具有挑战性。
4)提示工程实用程序
另一类库(包括LangChain和LlamaIndex)采取了一种非常不同的方法,通过大量预封装模块,这些库为使用语言模型的程序形成了“开箱即用”的解决方案。与DSPy等相比,例如,这些库往往有许多可用功能,更依赖提示工程,并专注于与大型语言模型本身的一些特定交互模型。例如,LangChain包括管理提示模板、输出解析、内存和检索模型集成的实用程序。
LangChain还提供了预构建的抽象,利用这些实用程序来提供与语言模型和外部环境进行交互的更复杂模式。由LangChain的领域特定语言——LangChain表达式语言(LCEL)指定的代理(Agent)和链(Chain)抽象,增强了实用程序的功能和可复用性,以解决更复杂的任务。一般来说,LCEL允许用户指定一组语言模型可用于与某些外部环境交互的工具或动作,以及一个预设提示和输出解析器来执行编排步骤,确定要调用哪些动作/工具以及如何将代理结果传递到代理循环的后续迭代。LangChain提供了大量预设提示和输出解析器(例如用于反应(ReAct)、思维链、自我询问等),可以帮助开发者快速构建适用于许多用例的复杂代理系统。然而,LCEL主要作为简单代理循环上的轻量级语法糖,虽然组件是可定制的,但在撰写本文时,它们相对于原生实现并没有实现更高级的功能,而且代理循环本身的交互模式在某种程度上是固定的。
在提示工程领域,LangChain和LlamaIndex都为执行文本提示工程提供了增强功能。除了基于替换的方法外,LangChain还支持一些提示优化技术。这类优化可能涉及选择有效示例或对各种“超参数”进行微调。LangChain还配备了示例选择器(Example Selectors),它们利用相似性(可能来自向量数据库)或更简单的度量(如n - gram重叠)等指标来提炼可能提高整体性能结果的示例子集。然而,这个过程是独立于语言模型和提示策略本身进行的。此外,示例选择器需要大量完全标注的示例来选择一个有益的子集。
作为一个以提示为中心的框架,LangChain还通过其LangSmith平台开发了一个用于托管提示的生态系统。提示可以托管在LangSmith中以便与他人共享。当与LCEL以及LangChain的代理和链抽象结合使用时,提示可以通过提示重用为有趣的语言模型交互模式提供支持。
5)开放式代理/流程
我们在最后一部分将开放式代理和多代理聊天归为一类,因为它们作为在代理系统层面运行的框架具有共同性质。
- i)开放式代理/流程 除了预封装实用程序外,LangChain还配备了现成的代理流程或“链”,可用于运行带有多种提示策略的复杂语言模型查询,这些策略是开箱即用的。然而,与AutoGPT等其他框架相比,LangChain缺少可以调用其他外部工具的预构建流程。相比之下,像AutoGPT这样的框架专注于构建在解决给定任务时需要最少人工干预的代理系统。在内部,该工具可以访问一组预先存在的工具(可能包括计算器、互联网访问、本地文件访问等)、一组驱动语言模型执行指定任务的固定提示,以及短期和长期记忆的固定模型。虽然你可以为特定目的定制这些工具,但它们在可扩展性方面并非设计得很方便,即你不能轻易地替换提示模式或方便地与现有程序集成。然而,这种不足被开箱即用的解决方案所平衡,该解决方案直接扩展了基础语言模型,集成了许多工具来高效地解决任务。
在这个类别中另一个值得注意的工具是MetaGPT,尽管这个框架在本文的其他部分也有涉及。MetaGPT以能够将“一行需求”作为输入并输出完整程序、研究文档等而自豪。在内部,它为不同的语言模型交互上下文分配角色,如软件工程师、项目经理、架构师和质量保证人员。与AutoGPT类似,这些角色具有内置在工具中的固定提示和能力,开箱即可使用。MetaGPT在内部编码了一个标准操作程序(SOP),它呈现了一些想象中的致力于解决你任务的公司,并将这个SOP作为一种“算法”来编排不同角色之间的协作。MetaGPT允许通过添加新角色并定义提示、能力及其与SOP的集成来进行扩展。这类似于AutoGPT中的插件概念。
BabyAGI也旨在利用语言模型自动执行一组任务。本质上,语言模型使用可用的执行代理来编排如何将任务分解为子任务以及如何完成这些子任务。
- ii)多代理聊天 AutoGen和MetaGPT是两个主要的对具有不同角色和特性的多个代理之间的聊天和通信进行抽象的框架。在AutoGen中,基础抽象由助手代理(Assistant Agents)和用户代理(UserProxyAgents)组成,助手代理是围绕语言模型的包装器,用户代理可以执行代码或与人类交互,允许代理系统与外部环境交互。为了便于多个代理交互的可能性,还可以引入群聊管理器(GroupChatManager)来管理不同聊天代理之间的交互方式。因此,鉴于能够组合不同代理及其交互,该库可以支持多种对话模式。MetaGPT采取了略有不同的方法。从表面上看,它只是一个用于执行由人工智能驱动任务的自动化工具,但在其核心,它展示了有趣的多代理抽象。从概念上讲,MetaGPT由一个标准操作程序(SOP)和一组角色(类似于AutoGen中的代理)组成。SOP包含关于管理不同角色/代理如何相互交互的指令,并封装了将分解的子任务分配给适当角色的分工。该框架提供了一个具有共享知识和上下文的全局环境,其中每个角色/代理可以被指定为“订阅”被认为相关的知识子集。
结论
近几个月来,大语言模型编程抽象领域有了显著发展,涌现出大量流行框架。我们提出了一个7层抽象模型来对所评估的库进行分类,并描绘了我们所观察到的关注点分离情况。然后,我们从内在和外在特征方面研究了五类框架。特别是,它们的内在特征包括是否易于扩展、它们支持哪些类型的特征以确保可靠或稳定的输出,以及它们的结构如何以便能够与其他库和框架交互。外在特征包括社区和生态系统方面,例如,可复用的提示策略,或基于该框架构建的可用于更丰富和更特定领域任务的库。为了通过这些特征和质量的视角对这些近期框架进行概览,我们在表3中列出了它们各自的实用程序/库/生态系统、可靠性、性能、可移植性和可扩展性。
译自(有删改):https://www.twosigma.com/articles/a-guide-to-large-language-model-abstractions
本文转载自公众号AIGC最前线 作者:实习小毕