大家好,这篇教程是我两周之前与Neurons Lab进行的书面形式。如果你想看视频教程,可以在YouTube上观看视频。
像往常一样,你可以在GitHub上找到代码库,并且这里还有一些独立的Colab笔记本。
关于代理的介绍
插图由作者提供。LLM 通常通过 RAG 架构来增强外部记忆。代理将这一概念扩展到记忆、推理、工具、答案和行动等。
我们开始讲座吧。虽然这个话题被广泛讨论,但很少有人积极使用这些代理;我们通常认为的代理其实只是大型语言模型。让我们考虑这样一个简单的任务:搜索足球比赛结果并将其保存为CSV文件。我们可以比较几个可用的工具:
由于现有的工具不太好用,让我们从头开始学习如何从基本原则构建代理。我参考了Lilian的博客文章作为结构基础,并在此基础上增加了更多自己的例子。
从视觉上看,简单的“输入输出”LLM应用与诸如思维链(chain of thought)、带有自我一致性的思维链(self-consistent chain of thought)和思维树(tree of thought)等技术之间的不同之处。
你可能已经见过旨在提升大型语言模型性能的各种技术,例如提供技巧甚至开玩笑地威胁它们。这种技术要求模型逐步思考,以便进行自我修正。这种做法已经发展出了更高级的版本,例如“思考链与自我一致性”还有“思想之树”,它会产生多个想法,再进行重新评估和整合,最后提供输出。
在本教程中,我大量使用了Langsmith,这是一个用于生产化LLM应用程序的平台。例如,在构建思维树提示语时,我将我的子提示语保存在提示仓库中并加载它们。
from langchain import hub from langchain.chains import SequentialChain # 顺序链 (SequentialChain) cot_step1 = hub.pull("rachnogstyle/nlw_jan24_cot_step1") cot_step2 = hub.pull("rachnogstyle/nlw_jan24_cot_step2") cot_step3 = hub.pull("rachnogstyle/nlw_jan24_cot_step3") cot_step4 = hub.pull("rachnogstyle/nlw_jan24_cot_step4") model = "gpt-3.5-turbo" chain1 = LLMChain( # 链式模型 (LLMChain) llm=ChatOpenAI(temperature=0, model=model), prompt=cot_step1, output_key="solutions" # 输出键 (output_key) ) chain2 = LLMChain( # 链式模型 (LLMChain) llm=ChatOpenAI(temperature=0, model=model), prompt=cot_step2, output_key="review" # 输出键 (output_key) ) chain3 = LLMChain( # 链式模型 (LLMChain) llm=ChatOpenAI(temperature=0, model=model), prompt=cot_step3, output_key="deepen_thought_process" # 深化思维过程 (deepen_thought_process) ) chain4 = LLMChain( # 链式模型 (LLMChain) llm=ChatOpenAI(temperature=0, model=model), prompt=cot_step4, output_key="ranked_solutions" # 输出键 (output_key) ) overall_chain = SequentialChain( # 顺序链 (SequentialChain) chains=[chain1, chain2, chain3, chain4], input_variables=["input", "perfect_factors"], # input (输入) 和 perfect_factors (理想因素) output_variables=["ranked_solutions"], verbose=True # verbose=True (详细模式) )
你可以查看这个笔记本的结果:this notebook,我想在这里强调的是,在像Langsmith这样的LLMOps系统中正确定义和版本化推理步骤的过程。你还可以看到其他流行的推理技术,例如ReAct或带有搜索的自我询问,比如在公开仓库中。
第一步: `prompt = hub.pull("hwchase17/react")`,从hub中拉取名为 'hwchase17/react' 的提示信息。 第二步: `prompt = hub.pull("hwchase17/self-ask-with-search")`,从hub中拉取名为 'hwchase17/self-ask-with-search' 的提示信息。
还有其他一些值得注意的方法
我们可以将不同类型的记忆映射到LLM代理架构的各个部分。
从langchain_community.chat_message_histories 导入 ChatMessageHistory 从langchain_core.runnables.history 导入 RunnableWithMessageHistory 从langchain.agents 导入 AgentExecutor 从langchain.agents 导入 create_openai_functions_agent llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) tools = [retriever_tool] agent = create_openai_functions_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) message_history = ChatMessageHistory() agent_with_chat_history = RunnableWithMessageHistory( agent_executor, lambda session_id: message_history, input_messages_key="input", history_messages_key="chat_history", )
from langchain.text_splitter import RecursiveCharacterTextSplitter() from langchain_community.document_loaders import WebBaseLoader() from langchain_community.vectorstores import FAISS() from langchain_openai import OpenAIEmbeddings() # WebBaseLoader 用于加载网站内容 loader = WebBaseLoader("https://neurons-lab.com/") docs = loader.load() # RecursiveCharacterTextSplitter 用于将文档分割成指定大小的块 documents = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200 ).split_documents(docs) # FAISS 用于创建向量存储 vector = FAISS.from_documents(documents, OpenAIEmbeddings()) # retriever 用于从向量存储中检索数据 retriever = vector.as_retriever()
实践中,你希望用另一条独立的推理线路(可以是另一个大型语言模型,即特定领域的LLM,或用于图像分类的其他机器学习模型)或使用更基于规则的方法,或通过API来增强你的代理程序。
ChatGPT 插件 和 OpenAI API 功能调用(function calling)1 是大型语言模型(LLM)具备工具使用能力的实际应用中的良好例子。
从langchain.utilities.tavily_search导入TavilySearchAPIWrapper模块 从langchain.tools.tavily_search导入TavilySearchResults类 search = TavilySearchAPIWrapper() tavily_tool = TavilySearchResults(api_wrapper=search) llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0) agent_chain = initialize_agent ([retriever_tool, tavily_tool], llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, )
@tool
装饰器来让 Langchain 识别它。别忘了指定输入和输出的类型。但最重要的是在 """ """
之间的函数说明——这样你的代理就可以了解这个工具的作用,并将其功能描述与其他工具进行比较。from langchain.pydantic_v1 import BaseModel, Field from langchain.tools import BaseTool, StructuredTool, tool @tool def calculate_length_tool(a: str) -> int: """此函数用于计算输入字符串的长度.""" return len(a) llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0) agent_chain = initialize_agent( [retriever_tool, tavily_tool, calculate_length_tool], llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True, )
你可以在这个脚本中找到它的用法示例在此脚本中,但你也可以看到一个错误——它没有正确获取Neurons Lab公司的描述,尽管调用的是正确的长度计算自定义函数,但最终结果还是错了。让我们试试看能不能把它修好吧!
我提供了一个干净的版本,将所有架构部分组合在一起 这个脚本。请看,注意我们可以轻松地将其分解并分别定义各个部分。
代理的最终定义将会是如此简单:
这样简单
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) agent = create_openai_functions_agent(llm, tools, prompt) agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True) agent_with_chat_history = RunnableWithMessageHistory( agent_executor, lambda 会话ID: 消息历史, input_messages_key="输入", history_messages_key="聊天历史", )
如您可以在脚本输出中看到(或您可以运行该脚本),它解决了之前部分中与工具相关的问题。发生了什么变化?我们定义了一个完整的架构,在这个架构中,短期记忆起着关键作用。我们的代理获得了消息历史和草稿板作为推理结构的一部分,这使它能够提取正确的网站描述并计算其长度,从而解决问题。
我希望这份关于LLM代理架构核心元素的指南文章能帮助你设计可以自动化认知任务的功能型机器人。再次强调,确保代理的所有元素齐全是非常重要的。正如你可能已经注意到的,缺少短期记忆或工具描述不完整,都可能导致代理的推理过程出现问题,甚至可能导致在简单的任务(如生成摘要及其长度计算)上给出错误的答案。祝你在AI项目中取得成功,有需要时别犹豫,可以随时联系我。