什么是 LangChain Agent
在 LangChain 中,Agent 是一个代理,接收用户的输入,采取相应的行动然后返回行动的结果。Agent 可以看作是一个自带路由消费 Chains 的代理,基于 MRKL 和 ReAct 的基本原理,Agent 可以使用工具和自然语言处理问题。官方也提供了对应的 Agent,包括 OpenAI Functions Agent、Plan-and-execute Agent、Self Ask With Search 类 AutoGPT 的 Agent 等。Agent 的作用是代表用户或其他系统完成任务,例如数据收集、数据处理、决策支持等。Agent 可以是自主的,具备一定程度的智能和自适应性,以便在不同的情境中执行任务。我们今天主要了解基于 ReAct 原理来实现的 Agent。
ReAct
ReAct 是一个结合了推理和行动的语言模型。虽然 LLM 在语言理解和交互决策制定方面展现出了令人印象深刻的能力,但它们的推理(例如链式思考提示)和行动(例如行动计划生成)的能力主要被视为两个独立的主题。ReAct 的目标是探索如何使用 LLM 以交错的方式生成推理痕迹和特定任务的行动,从而在两者之间实现更大的协同作用。
想象一下,你有一个智能助手机器人,名叫小明。你给小明一个任务:去厨房为你做一杯咖啡。小明不仅要完成这个任务,还要告诉你他是如何一步步完成的。
没有 ReAct 的小明:
-
小明直接跑到厨房。
-
你听到了一些声音,但不知道小明在做什么。
-
过了一会儿,小明回来给你一杯咖啡。
这样的问题是,你不知道小明是怎么做咖啡的,他是否加了糖或奶,或者他是否在过程中遇到了任何问题。
有 ReAct 的小明:
-
小明告诉你:"我现在去厨房。"
-
小明再说:"我找到了咖啡粉和咖啡机。"
-
"我现在开始煮咖啡。"
-
"咖啡煮好了,我要加点糖和奶。"
-
"好了,咖啡做好了,我现在给你拿过去。"
这次,你完全知道小明是怎么做咖啡的,知道他的每一个步骤和决策。
ReAct 就是这样的原理。它不仅执行任务(行动),还会告诉你它是如何思考和决策的(推理)。这样,你不仅知道任务完成了,还知道为什么这样做,如果有问题,也更容易找出原因。
更多关于 ReAct 的内容可以查看这篇文章[1]。
提示词模板
要实现 Agent,我们需要先定义一套基于 ReAct 的提示词模板,示例中的 Agent 就是基于 ReAct 原理来实现的,为了方便理解,我们将官方的英文提示词模板换成中文和去掉一些没必要的内容,修改后的提示词模板内容如下:
bash
template = """尽你所能回答以下问题,你可以使用以下工具:
{tools}
请按照以下格式:
问题:你必须回答的输入问题
思考:你应该始终考虑该怎么做
行动:要采取的行动,应该是[{tool_names}]中的一个
行动输入:行动的输入
观察:行动的结果
... (这个思考/行动/行动输入/观察可以重复N次)
思考:我现在知道最终答案了
最终答案:对原始输入问题的最终答案
开始吧!
问题:{input}
{agent_scratchpad}"""
简单介绍一下这个提示词模板,首先提示词模板中会引用到一些工具,可以看到模板中有 2 个变量:tools
和tool_names
,tools 变量是一个列表,包含了所有的工具,列表中的每个元素包含了工具的名称和描述,而 tool_names 变量是工具名称的列表,传入具体的工具后,会生成对应的工具列表,比如我们有如下 2 个工具,解析后如下所示:
bash
尽你所能回答以下问题,你可以使用以下工具:
search: 实时联网搜索的工具
math: 数学计算的工具
请按照以下格式:
......
行动:要采取的行动,应该是[search, math]中的一个
......
这样 LLM 在执行任务时就知道要使用哪些工具,以及在提取信息时可以提取到正确的工具名。
模板中下面的思考/行动/行动输入/观察
就是标准的 ReAct 流程,思考是指思考如何解决问题,行动是具体的工具,行动输入是工具用到的参数,观察是工具执行完成后得到的结果,这个流程可以重复多次,直到最终得到最终答案。
模板最后还有 2 个变量:input
和agent_scratchpad
,input 是用户输入的问题,agent_scratchpad 是之前的思考过程(下面解析代码时会讲),包括了思考、行动、行动输入和观察等,这个变量在 Agent 执行过程中会被更新,代入具体的值后,模板会生成如下的提示词:
bash
......
问题:北京的天气怎么样
思考: 我们需要通过 search 工具查找北京天气。
行动: search
行动输入: "北京天气"
观察: 6日(今天). 多云转晴. 32/22℃. <3级
思考:
从问题下面一句到最后结束就是agent_scratchpad
的值。
Langchain 组件
Langchain Agent 相关有下面几个关键组件:
代理
这是负责决定下一步采取什么动作的类。 这是由语言模型和提示驱动的。 该提示可以包括以下内容:
-
代理的个性(对于以某种方式响应很有用)
-
代理的背景上下文(对于给予其更多关于所要求完成的任务类型的上下文很有用)
-
调用更好推理的提示策略(最著名/广泛使用的是ReAct)
工具
工具是代理调用的函数。 这里有两个重要的考虑因素:
-
给代理访问正确工具的权限
-
以对代理最有帮助的方式描述工具
如果没有这两者,您想要构建的代理将无法工作。 如果您不给代理访问正确工具的权限,它将永远无法完成目标。 如果您不正确描述工具,代理将不知道如何正确使用它们。
代理执行器
代理执行器是代理的运行时。 这是实际调用代理并执行其选择的动作的部分。 以下是此运行时的伪代码:
bash
next_action = agent.get_action(...)
while next_action != AgentFinish:
observation = run(next_action)
next_action = agent.get_action(..., next_action, observation)
return next_action
虽然这看起来很简单,但此运行时为您处理了几个复杂性,包括:
-
处理代理选择不存在的工具的情况
-
处理工具发生错误的情况
-
处理代理生成无法解析为工具调用的输出的情况
-
在所有级别上记录和可观察性(代理决策,工具调用)-可以输出到stdout或LangSmith
代理运行时
AgentExecutor
类是LangChain支持的主要代理运行时.
记忆体
大多数(如果不是全部)内存模块的核心实用类之一是 ChatMessageHistory
类。这是一个超轻量级的包装器,它公开了方便的方法来保存人类消息、AI 消息,然后获取它们全部。
如果您在链外管理内存,可能需要直接使用此类。
bash
from langchain.memory import ChatMessageHistory history = ChatMessageHistory() history.add_user_message("hi!") history.add_ai_message("whats up?")
history.messages
[HumanMessage(content='hi!', additional_kwargs={}), AIMessage(content='whats up?', additional_kwargs={})]
langchain通过在历史信息基础支持上进行封装,提供了 带窗口大小的以及缓冲 ConversationBufferWindowMemory,
对回话记录进行摘要使用的 ConversationSummaryMemory。
代码案例
python
from pydantic import BaseModel, Field
import datetime
from langserve import add_routes
from typing import Any, List, Union
from fastapi import status as fastapistatus
from fastapi.encoders import jsonable_encoder
from fastapi.responses import JSONResponse
from fastapi import FastAPI, HTTPException
from langchain_ollama import ChatOllama
from langchain.agents import tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.agents.format_scratchpad.openai_tools import (
format_to_openai_tool_messages,
)
from langchain.agents import AgentExecutor
from langchain.agents.output_parsers.openai_tools import (
OpenAIToolsAgentOutputParser
)
import logging
from langchain.memory import (
ConversationSummaryBufferMemory,
ConversationBufferWindowMemory
)
logger = logging.getLogger(__name__)
# 注册计算字符串长度的工具,当询问计算输入长度时即可通过agent_scratchpad 后的关键字
# 关联上该工具计算输出,从而在该类问题中使用工具计算结果回答
@tool
def get_word_length(word: str) -> int:
"""Returns the length of a word."""
return len(word)
tools = [get_word_length]
class Agent:
def __init__(self, name, agent_id, role_describe, model="qwen2:7b",
temperature=0.7, token_limit=75):
self.name = name
self.agent_id = agent_id
# 使用 ollama 中的模型 "qwen2:7b" 作为llm model
llm = ChatOllama(
model=model,
temperature=temperature,
verbose=True
)
# 提示词构建
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
role_describe,
),
("user", "{input}"),
# agent_scratchpad 记录了每步思考过程
MessagesPlaceholder(variable_name="agent_scratchpad"),
# chat_history 注入对话历史,使用对对话历史的管理可实现记忆
MessagesPlaceholder(variable_name="chat_history"),
]
)
self.prompt = prompt
# 窗口历史,即记忆只记录最近 token_limit 句对话
memory = ConversationBufferWindowMemory(k=token_limit,
memory_key="chat_history",
return_messages=True)
self.memory = memory
# 绑定工具
llm_with_tools = llm.bind_tools(tools)
self.tools = llm_with_tools
agent = (
{
"input": lambda x: x["input"],
"agent_scratchpad": lambda x: format_to_openai_tool_messages(
x["intermediate_steps"]
),
"chat_history": lambda x:
memory.load_memory_variables({})["chat_history"],
}
| prompt
| llm_with_tools
| OpenAIToolsAgentOutputParser()
)
# 运行agent runner
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
self.agent_executor = agent_executor
def chat(self, input_text):
# 输入input, 进行对话
response = self.agent_executor.invoke({"input": input_text})
self.memory.save_context(inputs={'input': response['input']},
outputs={'output': response['output']})
if response:
return response['output']
else:
return ('There are no result..')
def command_chat(agent):
while True:
user_input = input("input:")
user_output = daxiong_agent.chat(user_input)
print(user_output)
if __name__ == "__main__":
daxiong_agent = AgentRole("daxiong", "1234567", "你是一个中二的智能助手")
command_chat(daxiong_agent)