C/C++教程

使用Langchain入门LLM代理:RAG (检索增强生成)不够用时怎么办

本文主要是介绍使用Langchain入门LLM代理:RAG (检索增强生成)不够用时怎么办,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
适用于AI助手的大脑结构基本原则

大家好,这篇教程是我两周之前与Neurons Lab进行的书面形式。如果你想看视频教程,可以在YouTube上观看视频。

像往常一样,你可以在GitHub上找到代码库,并且这里还有一些独立的Colab笔记本。

  1. 规划与推理能力
  2. 不同种类的记忆
  3. 各种工具
  4. 构建完整的智能代理

关于代理的介绍

插图由作者提供。LLM 通常通过 RAG 架构来增强外部记忆。代理将这一概念扩展到记忆、推理、工具、答案和行动等。

我们开始讲座吧。虽然这个话题被广泛讨论,但很少有人积极使用这些代理;我们通常认为的代理其实只是大型语言模型。让我们考虑这样一个简单的任务:搜索足球比赛结果并将其保存为CSV文件。我们可以比较几个可用的工具:

  • GPT-4 带搜索和插件:如你在聊天历史记录中所见,由于代码错误,GPT-4 未能完成任务
  • AutoGPT 通过https://evo.ninja/ 至少可以生成某种 CSV 文件(虽然效果一般):

  • AgentGPT 通过 https://agentgpt.reworkd.ai/:将此任务当作合成数据生成器来处理,这并不是我们要求的,查看 聊天记录

由于现有的工具不太好用,让我们从头开始学习如何从基本原则构建代理。我参考了Lilian的博客文章作为结构基础,并在此基础上增加了更多自己的例子。

步骤 1:规划

从视觉上看,简单的“输入输出”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' 的提示信息。

还有其他一些值得注意的方法

  • 自我反省(Shinn & Labash 2023)是一个框架,旨在赋予代理动态记忆和自我反省能力,以增强推理能力。
  • 回溯链(CoH;Liu et al. 2023)通过向模型展示带有反馈的过去输出序列来鼓励模型自我改进。
第二步:记忆

我们可以将不同类型的记忆映射到LLM代理架构的各个部分。

  • 感觉记忆(瞬时记忆): 记忆的这一部分捕捉即时的感觉信息,比如我们看到、听到或感觉到的东西。在提示工程和AI模型的背景下,提示就像一个短暂的输入,类似于瞬间的触感。它是触发模型处理的初始刺激。
  • 短期记忆: 短期记忆暂时存储信息,通常与正在进行的任务或对话相关。在提示工程中,这相当于保留最近的聊天历史。这种记忆使代理能够在整个对话过程中保持上下文和连贯性,确保回答与当前对话相符。在代码中,你通常将其添加为对话历史
    从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",  
    )
  • 长期记忆: 长期记忆存储了事实性知识和程序性指令。在AI模型中,这体现在用于训练和微调的数据上。此外,长期记忆支持RAG框架的操作,使代理能够访问并整合学到的信息到响应中。这就像一个全面的知识库,代理从其中提取信息,生成有见地和相关性的输出。在代码实现中,通常将其添加为向量数据库
    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工具:Langchain自带了许多实用工具,涵盖互联网搜索、Arxiv工具包、Zapier和Yahoo Finance等多个功能。在这个简短的教程里,我们将尝试Tavily提供的网络搜索功能。
    从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公司的描述,尽管调用的是正确的长度计算自定义函数,但最终结果还是错了。让我们试试看能不能把它修好吧!

第4步:一起

我提供了一个干净的版本,将所有架构部分组合在一起 这个脚本。请看,注意我们可以轻松地将其分解并分别定义各个部分。

  • 各种类型的工具(搜索工具、自定义工具等)
  • 各种类型的记忆感官作为提示,短期作为可运行的消息记录,以及提示内的草稿板,长期记忆则通过向量数据库检索)
  • 任何类型的计划策略(作为提示的一部分,从LLMOps系统中获取)

代理的最终定义将会是如此简单:

这样简单

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项目中取得成功,有需要时别犹豫,可以随时联系我。

这篇关于使用Langchain入门LLM代理:RAG (检索增强生成)不够用时怎么办的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!