一架梯子,一头程序猿,仰望星空!
LangChain教程(Python版本) > 内容正文

LangChain Agent入门教程


LangChain Agent代理的核心思想是使用LLM作为大脑自动思考,自动决策选择执行不同的动作,最终完成我们的目标任务。

提示:从开发角度理解,就是我们提前开发好各种各样功能的API,然后给Agent一个任务,让LLM自己分析要调用哪个API可以完成任务。

举个例子方便理解LangChain Agent要解决的问题。

例如:

我们要调研下“docker是否可以作为生产环境部署方案”,我们首先会去百度搜索”docker介绍”,浏览下搜索结果,然后进一步搜索”docker部署的优缺点”,浏览下结果等等,最后得出结论。

LangChain Agent就是要模拟这个过程,我可以提前封装一序列工具(例如:百度搜索、提取URL内容等工具),然后给通过Agent下发一个目标任务“docker是否可以作为生产环境部署方案”,Agent就会构造提示词去调用大语言模型LLM,要实现这个目标任务,下一步执行什么动作(就是要调用那个工具),AI就会返回要调用的工具,代码就去执行这个工具,然后把工具执行的结果,回传给AI,再问下一步执行什么工具,反复执行这个过程就可以完成前面提到的任务。

提示:这个是自从GPT模型发布以来,相当炸裂的能力,让LLM作为大脑,主动思考,然后调用我们开发好的各种API,这样LLM的能力就相当强大,目前这个特性还在实验阶段,会反复调用LLM所以相当费token,执行一个任务下来分分钟几万个token,想省钱,建议先让LLM执行简单的逻辑判断任务。

核心概念

下面介绍相关的组件&概念

Agent(代理)

Agent可以理解成我们的助手,代理我们去做一些决策,在LangChain agent底层实现就是通过LLM决定下一步执行什么动作(或者说调用什么API),这里有个知名的ReAct模式,就是描述AI决策过程,感兴趣的同学可以去了解下。

LangChain针对不同的场景提供了几种不同类型的代理类型。

Tools(工具)

Tools我觉得理解成API更合适,就是我们提前封装好的各种功能的API,目的是扩展LLM的能力,由LLM根据问题决定调用那个具体的API来完成任务。

Toolkits(工具集合)

工具集合,通常提供给LLM的工具不会是一个、两个,会提供一组可用的工具给LLM,让LLM完成任务的时候有更多的能力选择。

AgentExecutor

代理执行器是负责执行LLM选择的工具(API)。以下是此运行时的伪代码:

# 通过LLM拿到需要执行的工具
next_action = agent.get_action(...)
# 如果没有完成任务,循环执行
while next_action != AgentFinish:
      # 执行动作
    observation = run(next_action)
    # 执行下一步要执行的工具
    next_action = agent.get_action(..., next_action, observation)
return next_action

执行过程虽然不复杂,执行器处理了很多细节问题主要包括:

  1. 处理agent选择不存在的工具的情况
  2. 处理工具出错的情况
  3. 处理agent产生无法解析为工具调用的输出的情况
  4. 调试问题。

快速开始

本节介绍LangChain的Agent的基础用法

1. 加载LLM

首先,让我们加载我们将用来控制代理程序(agent)的语言模型(llm)。

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

2. 定义工具

接下来,我们定义一些工具,让Agent调用。我们将编写一个非常简单的 Python 函数来计算传入单词的长度。

from langchain.agents import tool

@tool
def get_word_length(word: str) -> int:
    """返回单词的长度。"""
    return len(word)

get_word_length.invoke("abc")

注意:函数注释非常重要,它告诉LLM调用它能解决什么问题,get_word_length函数就是告诉LLM调用他可以计算单词长度。

3

定义工具集合

tools = [get_word_length]

3. 创建提示(prompt)

现在让我们创建提示(prompt)。因为OpenAI Function Calling已经针对工具使用进行了优化,所以我们几乎不需要关于推理或输出格式的任何说明。我们只有两个输入变量:inputagent_scratchpadinput代表用户输入的问题,agent_scratchpadagent的调用指令占位符,运行的时候会往提示词模板(prompt template)插入工具调用指令。

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是一个非常强大的助手,但不了解当前事件。",
        ),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

4. 将工具绑定到LLM

代理程序如何知道可以使用哪些工具?

这里依赖的是OpenAI的工具调用功能(有很多模型都支持类似的功能),我们只要把定义好的工具调用格式告诉模型就行。

# 将工具绑定到模型中
llm_with_tools = llm.bind_tools(tools)

5. 创建代理(agent)

将之前的内容整合在一起,我们现在可以创建代理程序了。我们将导入最后两个实用工具函数:一个用于将中间步骤(代理动作、工具输出)格式化为可发送给模型的输入消息的组件,另一个用于将输出消息转换为代理动作/代理结束。

from langchain.agents.format_scratchpad.openai_tools import format_to_openai_tool_messages
from langchain.agents.output_parsers.openai_tools import OpenAIToolsAgentOutputParser

# 定义一个chain,
# step1: 为prompt模板准备参数,从agent调用输入中提取input和intermediate_steps两个参数,input由用户输入,intermediate_steps由agent生成
# step2: 根据step1的参数,格式化prompt template
# step3: 调用模型
# step4: 处理模型输出
agent = (
    {
        "input": lambda x: x["input"],
        "agent_scratchpad": lambda x: format_to_openai_tool_messages(x["intermediate_steps"]),
    }
    | prompt
    | llm_with_tools
    | OpenAIToolsAgentOutputParser()
)

定义agent执行器

from langchain.agents import AgentExecutor

# verbose = True代表在控制台打印详细的日志
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

让我们通过示例来展示代理程序的运行:

# 调用agent
list(agent_executor.stream({"input": "eudca这个单词有几个字母"}))

agent输出日志示例


> 进入新的代理程序执行链...

调用: 使用 `{'word': 'educa'}` 参数来运行 `get_word_length` 


单词"educa"中有5个字母。

> 完成程序执行链。

通过这个例子,我们展示了代理程序的完整流程。

为Agent增加记忆功能

如果我们希望Agent记住之前说的内容,其实也很简单,就是把AI返回的内容插入到提示词(prompt)中一起提交给AI就行。

修改提示词模板

下面我们修改提示词模板(prompt template),增加对话历史模板变量

from langchain.prompts import MessagesPlaceholder

# 在消息模板中插入一个对话历史的占位符(chat_history),用来插入历史对话记录
MEMORY_KEY = "chat_history"
prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            "你是个很厉害的助手,但不擅长计算单词的长度。",
        ),
        MessagesPlaceholder(variable_name=MEMORY_KEY),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

修改agent流程定义

修改agent流程定义,为提示词模板(prompt template)提供对话历史数据,如下代码,新增chat_history参数处理

agent = (
    {
        "input": lambda x: x["input"],
        "agent_scratchpad": lambda x: format_to_openai_tool_messages(
            x["intermediate_steps"]
        ),
        "chat_history": lambda x: x["chat_history"],
    }
    | prompt
    | llm_with_tools
    | OpenAIToolsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

调用agent的时候提供对话历史数据

# 对话历史记录
chat_history = []

input1 = "educa这个单词有几个字母?"
# 调用agent,同时传入对话历史数据
result = agent_executor.invoke({"input": input1, "chat_history": chat_history})
# 调用agent结束后,把对话结果保存下来
chat_history.extend(
    [
        HumanMessage(content=input1),
        AIMessage(content=result["output"]),
    ]
)
# 再次调用agent,同时传入对话历史数据
agent_executor.invoke({"input": "这个词真的存在吗?", "chat_history": chat_history})

实际业务场景,你可以把对话历史保存到数据库中,根据业务需要把数据插入到提示词(prompt)中即可。

提示:大模型(LLM)的记忆功能,目前基本上都是通过把历史对话(chat history)内容插入到提示词(prompt)中提交给LLM实现,LangChain只是提供了一些封装,你可以选择不使用,自己把对话历史拼接到提示词模板(prompt template)即可。



关联主题