开发大型语言模型 (LLM) 代理应用程序时,你需要四个关键组件:代理核心、记忆模块、代理工具和规划模块。无论你是设计问答代理、多模态代理还是代理群体,你都可以考虑从开源到生产就绪的多种实施框架。
对于首次尝试开发LLM代理的人来说,本文提供以下内容:
你可能已经阅读过有关LangChain或LLaMa-Index代理的文章。目前有几个实施框架可用:
那么我应该推荐哪一个呢?答案是,“这取决于具体情况”。
单个代理框架
社区构建了几个框架,以推进LLM应用程序开发生态系统,为你提供开发代理的便捷途径。一些流行的框架包括LangChain、LlamaIndex和Haystack。这些框架提供了通用的代理类、连接器、记忆模块特性,第三方工具的访问以及数据检索和摄取机制。
选择哪个框架在很大程度上取决于你的管道细节和需求。在必须构建具有有向无环图(DAG)流程或具有独特属性的复杂代理的情况下,这些框架为提示和一般架构提供了一个很好的参考点,用于你自己的自定义实现。
多代理框架
你可能会问,“多代理框架有什么不同?”简短的答案是一个“世界”类。为了管理多个代理,你必须构建世界,或者说是他们相互交互的环境,以及他们与用户和环境中的工具互动。这里的挑战是,对于每个应用程序,世界都将不同。你需要的是一个专门用于构建仿真环境的工具箱,该工具箱可以管理世界状态,并拥有代理的通用类。你还需要一个通信协议来管理代理之间的流量。OSS框架的选择取决于你正在构建的应用程序类型以及所需的定制程度。
构建代理的推荐阅读清单
你可以使用大量资源和材料来激发你对代理可能性的思考,但以下资源是了解代理整体精神的绝佳起点:
如果你正在寻找更多阅读材料,我发现Awesome LLM-Powered Agent列表很有用。
在这个教程中,你将构建一个问答 (QA) 代理,以帮助你与你的数据对话。
为了展示一个相当简单的代理如何应对相当困难的挑战,你将构建一个能够从财报电话会议中挖掘信息的代理。你可以查看财报电话的文字记录。图1展示了财报电话的一般结构,以便你了解本教程使用的文件。
到本帖结束时,你构建的代理将能回答如下复杂和多层次的问题:
正如本系列的第一部分所描述的,有四个代理组件:
工具
为了构建一个LLM代理,你需要以下工具:
规划模块
有了这个LLM代理,你将能够回答诸如“2024年第一季度和第二季度之间的收入增长了多少?”这样的问题。这本质上将一个问题分成了三个:
答案是你必须构建一个问题分解模块:
decomp_template = """GENERAL INSTRUCTIONS You are a domain expert. Your task is to break down a complex question into simpler sub - parts. USER QUESTION {{user_question}} ANSWER FORMAT { "sub-questions" :[ "<FILL>" ]}"" |
如你所见,分解模块提示LLM将问题分解成较不复杂的部分。图3展示了答案的样子。
内存模块
接下来,你必须构建一个内存模块,以跟踪所有被问到的问题,或者只是保留所有子问题及其答案的列表。
class Ledger: def __init__( self ): self .question_trace = [] self .answer_trace = [] |
你用由两个列表组成的简单分类账来做到这一点:一个用来跟踪所有问题,一个用来跟踪所有答案。这有助于代理记住它已经回答了哪些问题,还有哪些问题尚未回答。
评估心理模型
在你构建代理核心之前,评估你现在拥有的内容:
此时,您可以将它们整合起来,看看它是否作为一个心理模型能工作(图4)。
template = """GENERAL INSTRUCTIONS Your task is to answer questions. If you cannot answer the question, request a helper or use a tool. Fill with Nil where no tool or helper is required. AVAILABLE TOOLS - Search Tool - Math Tool AVAILABLE HELPERS - Decomposition: Breaks Complex Questions down into simpler subparts CONTEXTUAL INFORMATION <No previous questions asked> QUESTION How much did the revenue grow between Q1 of 2024 and Q2 of 2024? ANSWER FORMAT {"Tool_Request": "<Fill>", "Helper_Request "<Fill>"}""" |
图4显示了LLM收到的答案。
你可以看到LLM请求使用搜索工具,这是一个合乎逻辑的步骤,因为答案可能就在语料库中。尽管如此,你知道没有任何文字记录包含答案。在下一步中(图5),你提供来自RAG pipeline的输入,答案是不可用的,所以代理随后决定将问题分解成更简单的子部分。
通过这个练习,你验证了逻辑核心机制是健全的。LLM根据需要选择工具和帮手。
现在,剩下的就是将这些整齐地封装在一个Python函数中,看起来可能是如下代码示例:
def agent_core(question): answer_dict = prompt_core_llm(question, memory) update_memory() if answer_dict[tools]: execute_tool() update_memory() if answer_dict[planner]: questions = execute_planner() update_memory() if no_new_questions and no tool request: return generate_final_answer(memory) |
代理核心
你刚才看到了一个代理核心的例子,那么剩下的是什么呢?嗯,代理核心不仅仅是把所有部件拼凑在一起。你必须定义代理应该执行其流程的机制。本质上有三个主要选择:
线性求解器
这是我之前讨论的那种执行类型。有一个单一线性解决方案链,代理可以使用工具和进行一级规划。虽然这是一个简单的设置,但真正复杂和细腻的问题通常需要分层思考。
单线程递归求解器
你还可以构建一个递归求解器,它构建一个问题和答案树,直到原始问题被回答。这棵树以深度优先遍历方式解决。以下代码示例显示了逻辑:
def Agent_Core(Question, Context): Action = LLM(Context + Question) if Action = = "Decomposition" : Sub Questions = LLM(Question) Agent_Core(Sub Question, Context) if Action = = "Search Tool" : Answer = RAG_Pipeline(Question) Context = Context + Answer Agent_Core(Question, Context) if Action = = "Gen Final Answer”: return LLM(Context) if Action = = "<Another Tool>" : <Execute Another Tool> |
多线程递归求解器
与其迭代地解决树,不如为树上的每个节点启动并行执行线程。这种方法增加了执行复杂性,但由于LLM调用可以并行处理,因此带来大量的延迟优势。
祝贺你!你现在已经具备了构建相当复杂的代理所需的知识!下一步是将前面讨论的原则适应到你的问题中。