Bootstrap

Langchain框架中的Agents全解析:类型、工具与自定义实践

在这里插入图片描述


前言

    在AI与ML的浪潮中,Agents作为Langchain框架的基石,成为智能交互的关键。本博客深入剖析Agents组件,从基础概念到多样类型,逐一揭秘。从ZERO_SHOT_REACT_DESCRIPTION到高级CHAT与CONVERSATIONAL_REACT_DESCRIPTION,各类Agents针对不同需求优化,展现智能潜力。同时,SerpAPI、Dall-E、Text2Speech等工具与Python、GraphQL等技术为Agents构建提供强大支持。最后,文章介绍了如何自定义Agents,扩展功能,优化性能。


一、什么是Agents?

Agents 是一个具有智能功能的智能体,它使用 LLM 和工具来执行任务。

Agents 核心思想是使用LLM来选择要采取的一系列动作。在链式结构中,一系列动作是硬编码的(在代码中)。 在 Agents 中,使用语言模型作为推理引擎来确定要采取的动作及其顺序。

Agents 包括几个关键组件:

  • Agent: 用于生成指令和执行动作的代理。
  • Tool: 用于执行动作的函数。
  • Memory: 用于存储历史对话和生成的指令。
  • LLM: 用于生成指令和执行动作的 LLM。

举个栗子🌰

  • 会做数学题
  • 不知道答案的时候可以搜索

搭建工具

pip install google-search-results
pip install numexpr

import os
os.environ["SERPAPI_API_KEY"] = '在serpai中获得的API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()
tools = load_tools(["serpapi","llm-math"], llm=llm)

agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,#这里有不同的类型
    verbose=True,#是否打印日志
)
print(llm.invoke("请问现任的美国总统是谁?他的年龄的乘以100再加上320是多少?"))
print(agent.run("请问现任的美国总统是谁?他的年龄的除以2是多少?"))

在这里插入图片描述

二、Agents 的类型

  • ZERO_SHOT_REACT_DESCRIPTION 零样本反应描述
  • CHAT_ZERO_SHOT_REACT_DESCRIPTION 聊天零样本反应描述
  • CONVERSATIONAL_REACT_DESCRIPTION 会话反应描述
  • CHAT_CONVERSATIONAL_REACT_DESCRIPTION 聊天会话反应描述
  • STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION 聊天结构化零样本反应描述
  • STRUCTURED_ZERO_SHOT_REACT_DESCRIPTION 结构化零样本反应描述

1.ZERO_SHOT_REACT_DESCRIPTION

即在没有示例的情况下可以自主的进行对话的类型。

# 即在没有示例的情况下可以自主的进行对话的类型。

import os
os.environ["SERPAPI_API_KEY"] = '这里输入Serapi API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()
# 定义tools
tools = load_tools(["serpapi","llm-math"],llm=llm)

# 定义agent--(tools、agent、llm、memory)
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)

print(agent)
print("------------------------")
print(agent.agent.llm_chain.prompt.template)

#agent.invoke("现在美国总统是谁?他的年龄除以2是多少?")

2.CHAT_ZERO_SHOT_REACT_DESCRIPTION

零样本增强式生成,即在没有示例的情况下可以自主的进行对话的类型。

# 零样本增强式生成,即在没有示例的情况下可以自主的进行对话的类型。
import os
os.environ["SERPAPI_API_KEY"] = '这里输入Serapi API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()
tools = load_tools(["serpapi","llm-math"],llm=llm)
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)
print(agent)
print("------------------------")
print(agent.agent.llm_chain.prompt.messages[0].prompt.template)
print("------------------------")
# agent.invoke("现在美国总统是谁?他的年龄除以2是多少?")

3.CONVERSATIONAL_REACT_DESCRIPTION

一个对话型的agent,这个agent要求与memory一起使用

# 一个对话型的agent,这个agent要求与memory一起使用
import os
os.environ["SERPAPI_API_KEY"] = '输入 Serpapi API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()

from langchain.memory import ConversationBufferMemory
#记忆组件
memory = ConversationBufferMemory(
    memory_key="chat_history",
)

# 定义tool
tools = load_tools(["serpapi","llm-math"],llm=llm)

# 定义agent
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,#记忆组件
    verbose=True,
)
print(agent)
print(agent.agent.llm_chain.prompt.template)

agent.run("我是张三,今年18岁,性别女,现在在深圳工作,工作年限1年,月薪5000元")
agent.run("我的名字是什么?")
agent.run("有什么好吃的泰国菜可以推荐给我吗?")
agent.run("这些我都没吃过!我名字的最后一个字母是什么?1998年的世界杯谁夺冠了?")
agent.run("中国陕西西安现在的气温多少?截止目前我们聊了什么?")

4.CHAT_CONVERSATIONAL_REACT_DESCRIPTION

# CHAT_CONVERSATIONAL_REACT_DESCRIPTION 使用了chatmodel
import os
os.environ["SERPAPI_API_KEY"] = '输入 serpapi API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()

from langchain.memory import ConversationBufferMemory
#记忆组件
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True,
)

tools = load_tools(["serpapi","llm-math"],llm=llm)


agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.CHAT_CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,#记忆组件
    verbose=True,
)
print(agent)
print("1 ------------------------")
print(len(agent.agent.llm_chain.prompt.messages))
print("2 ------------------------")
print(agent.agent.llm_chain.prompt.messages[0].prompt.template)
print("3 ------------------------")
print(agent.agent.llm_chain.prompt.messages[1])
print("4 ------------------------")
print(agent.agent.llm_chain.prompt.messages[2].prompt.template)
print("5 ------------------------")
print(agent.agent.llm_chain.prompt.messages[3])

agent.run("有什么好吃的泰国菜可以推荐给我吗?用中文回答")

5. STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION

对输出做了结构化处理

# 对输出做了结构化处理
import os
os.environ["SERPAPI_API_KEY"] = '输入 serpapi API_KEY'
from langchain_community.llms.tongyi import Tongyi
from langchain.agents import load_tools
from langchain.agents import initialize_agent
from langchain.agents import AgentType

llm = Tongyi()
from langchain.memory import ConversationBufferMemory

# 记忆组件
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True,
)

## 定义tool
tools = load_tools(["serpapi", "llm-math"], llm=llm)

# 定义agent
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,  # agent类型
    memory=memory,  # 记忆组件
    handle_parsing_errors=True,
    verbose=True,

)
print(agent)
print(agent.agent.llm_chain.prompt.messages[0].prompt.template)
print(agent.agent.llm_chain.prompt.messages[1].prompt.template)

agent.run("有什么好吃的泰国菜可以推荐给我吗?用中文回答")

三、Tools

1.介绍

langchain预制了大量的tools,基本这些工具能满足大部分需求。 https://python.langchain.com.cn/docs/modules/agents/tools/

  • 加载预制tool的方法
  • 几种tool的使用方式
#添加预制工具的方法很简单
from langchain.agents import load_tools
tool_names = [...]
tools = load_tools(tool_names) #使用load方法

#有些tool需要单独设置llm
from langchain.agents import load_tools
tool_names = [...]
llm = ...
tools = load_tools(tool_names, llm=llm) #在load的时候指定llm

2.SerpAPI

最常见的聚合搜索引擎 https://serper.dev/dashboard,支持google\bing

from langchain.utilities.serpapi import SerpAPIWrapper


# 支持自定义参数,比如将引擎切换到bing,设置搜索语言等
params = {
    "engine": "bing",
    "gl": "us",
    "hl": "en",
}
# 实例化
search = SerpAPIWrapper(params=params)
search.run("美国现在的总统是谁?")

# tools = load_tools(["serpapi"],llm=llm)

3.Dall-E

Dall-E是openai出品的文生图AI大模型

pip install opencv-python scikit-image

from langchain.agents import initialize_agent, load_tools

tools = load_tools(["dalle-image-generator"])

agent = initialize_agent(
    tools, 
    llm, 
    agent="zero-shot-react-description",
    verbose=True
)

agent.run("Create an image of a halloween night at a haunted museum")

4.Eleven Labs Text2Speech

ElevenLabs 是非常优秀的TTS合成API

pip install elevenlabs
pip install --upgrade pydantic

import os

os.environ["ELEVEN_API_KEY"] = "输入 API_KEY"
from langchain.tools.eleven_labs import ElevenLabsText2SpeechTool

text_to_speak = "Hello! 你好! Hola! नमस्ते! Bonjour! こんにちは! مرحبا! 안녕하세요! Ciao! Cześć! Привіт! வணக்கம்!"

tts = ElevenLabsText2SpeechTool(
    voice="Bella",
    text_to_speak=text_to_speak,
    verbose=True
)
print(tts.name)

speech_file = tts.run(text_to_speak)
tts.stream_speech(text_to_speak)

from langchain.agents import initialize_agent, load_tools

tools = load_tools(["eleven_labs_text2speech"])

agent = initialize_agent(
    tools, 
    llm, 
    agent="zero-shot-react-description",
    verbose=True
)

agent.run("Create an image of a halloween night at a haunted museum")

5.GraphQL

一种api查询语言,类似sql,我们用它来查询奈飞的数据库,查找一下和星球大战相关的电影,API地址:https://swapi-graphql.netlify.app/.netlify/functions/index

pip install httpx gql > /dev/null
pip install gql
pip install requests_toolbelt
### from langchain.chat_models import ChatOpenAI
from langchain.agents import load_tools, initialize_agent, AgentType
from langchain.utilities import GraphQLAPIWrapper


tools = load_tools(
    ["graphql"],
    graphql_endpoint="https://swapi-graphql.netlify.app/.netlify/functions/index",
)

agent = initialize_agent(
    tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION, verbose=True
)


四、Tookit

tookit是langchain已经封装好的一系列工具,一个工具包是一组工具来组合完成特定的任务

1. python

一个python代码机器人
pip install langchain_experimental==0.0.4

from langchain_experimental.agents.agent_toolkits import create_python_agent
from langchain_experimental.tools import PythonREPLTool
from langchain_experimental.utilities import PythonREPL
from langchain.agents.agent_types import AgentType

agent_executor = create_python_agent(
    llm=llm,
    tool=PythonREPLTool(),
    verbose=True,
    agent_type=AgentType.OPENAI_FUNCTIONS,
    agent_executor_kwargs={"handle_parsing_errors": True},
)

agent_executor.run("生成10个斐波那契数列?")

在这里插入图片描述


def generate_fibonacci_sequence(n):
    sequence = []
    a, b = 0, 1
    for _ in range(n):
        sequence.append(a)
        a, b = b, a + b
    return sequence

fibonacci_sequence = generate_fibonacci_sequence(10)
print(fibonacci_sequence)
# 输出:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

2. SQL Database

使用SQLDatabaseChain构建的agent,用来根据数据库回答一般问题

from langchain.agents import create_sql_agent
from langchain.agents.agent_toolkits import SQLDatabaseToolkit
from langchain.sql_database import SQLDatabase
from langchain.agents import AgentExecutor
from langchain.agents.agent_types import AgentType
from langchain_community.chat_models import ChatTongyi

db = SQLDatabase.from_uri("sqlite:///db/Chinook.db")

toolkit = SQLDatabaseToolkit(db=db, llm=llm)

agent_executor = create_sql_agent(
    llm=llm,
    toolkit=toolkit,
    verbose=True,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
)
agent_executor.run("创建用户表,包括用户名和密码两个列")

五、自定义Agent

  • 定义一个class
  • 工具:默认搜索
  • 提示词:定义agent要做什么任务
  • outparse:约束LLM的行为和输出
  • 不同的LLM不同的质量

1.自定义 Tool

from langchain.agents import Tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import StringPromptTemplate
from langchain import SerpAPIWrapper, LLMChain
from typing import List, Union
from langchain.schema import AgentAction, AgentFinish, OutputParserException
from langchain_community.llms.tongyi import Tongyi
import re
import os

class MyAgentTool:
    
    def __init__(self) -> None:    
        os.environ["SERPAPI_API_KEY"] = '输入 Serpapi API_KEY'    
        self.serpapi = SerpAPIWrapper()
        
    def tools(self):
        return [
            Tool(
                name="search",
                description="适用于当你需要回答关于当前事件的问题时",
                func=self.serpapi.run,
            )
        ]
s = MyAgentTool()
s.serpapi.run("python")
    

2.自定义 Agents

from typing import Any


class MyAgent:
    llm = None
    tools = None
    def __init__(self) -> None:
        #agent的提示词,用来描述agent的功能
        self.template =  """尽你最大可能回答下面问题,你将始终用中文回答. 你在必要时可以使用下面这些工具:
                    {tools}
                    Use the following format:
                    Question: the input question you must answer
                    Thought: you should always think about what to do
                    Action: the action to take, should be one of [{tool_names}]
                    Action Input: the input to the action
                    Observation: the result of the action
                    ... (this Thought/Action/Action Input/Observation can repeat N times)
                    Thought: I now know the final answer
                    Final Answer: the final answer to the original input question
                    Begin! 记住使用中文回答,如果你使用英文回答将回遭到惩罚.
                    Question: {input}
                    {agent_scratchpad}"""
        #定义一个openai的llm
        self.llm = Tongyi()
        #工具列表
        self.tools = self.MyAgentTool().tools()
        #agent的prompt
        self.prompt = self.MyTemplate(
            template=self.template,
            tools=self.tools,
            #输入变量和中间变量
            input_variables=["input", "intermediate_steps"],
        )

        #定义一个LLMChain
        self.llm_chain = LLMChain(
            llm=self.llm,
            prompt = self.prompt
        )
        #工具名称列表
        self.toolnames = [tool.name for tool in self.tools]
        #定义一个agent
        self.agent = LLMSingleActionAgent(
            llm_chain=self.llm_chain,
            allowed_tools=self.toolnames,
            output_parser=self.MyOutputParser(),
            
            stop=["\nObservation:"],
        )
    
    #运行agent
    def run(self, input: str) -> str:
        #创建一个agent执行器
        agent_executor = AgentExecutor.from_agent_and_tools(
            agent=self.agent, 
            tools=self.tools, 
            handle_parsing_errors=True,
            verbose=True
        )
        agent_executor.run(input=input)

    #自定义工具类
    class MyAgentTool:
        def __init__(self) -> None:
            os.environ["SERPAPI_API_KEY"] = 'db166b810c6b85674b6ceab3bd4e10d5048e1ba837db1c0d962ad91b34558805'    
            self.serpapi = SerpAPIWrapper()
            
        def tools(self):
            return [
                Tool(
                    name="search",
                    description="适用于当你需要回答关于当前事件的问题时",
                    func=self.serpapi.run,
                )
            ]
    

    #自定义模版渲染类
    class MyTemplate(StringPromptTemplate):
        #渲染模版
        template: str
        #需要用到的工具
        tools:List[Tool]

        #格式化函数
        def format(self, **kwargs: Any) -> str:
            #获取中间步骤
            intermediate_steps = kwargs.pop("intermediate_steps")
            thoughts = ""
            for action, observation in intermediate_steps:
                thoughts += action.log
                thoughts += f"\nObservation: {observation}\nThought: "
            #将agent_scratchpad设置为该值
            kwargs["agent_scratchpad"] = thoughts
            # 从提供的工具列表中创建一个名为tools的变量
            kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
            #创建一个提供的工具名称列表
            kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
            return self.template.format(**kwargs)


    #自定义输出解析类
    class MyOutputParser(AgentOutputParser):
        #解析函数
        def parse(self, output: str) -> Union[AgentAction, AgentFinish]:
            #检查agent是否应该完成
            if "Final Answer:" in output:
                return AgentFinish(
                # 返回值通常始终是一个具有单个 `output` 键的字典。
                # It is not recommended to try anything else at the moment :)
                return_values={"output": output.split("Final Answer:")[-1].strip()},
                log=output,
                )
            #用正则解析出动作和动作输入
            regex = r"Action\s*\d*\s*:(.*?)\nAction\s*\d*\s*Input\s*\d*\s*:[\s]*(.*)"
            match = re.search(regex, output, re.DOTALL)
            #如果没有匹配到则抛出异常
            if not match:
                raise OutputParserException(f"Could not parse LLM output: `{output}`")
            action = match.group(1).strip()
            action_input = match.group(2)
            # 返回操作和操作输入
            return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=output)
            
myagent = MyAgent()
myagent.run("请问现任的美国总统是谁?他的年龄的除以2是多少?")

3.案例

from langchain.agents import Tool, tool, AgentExecutor, LLMSingleActionAgent, AgentOutputParser
from langchain.prompts import StringPromptTemplate
from langchain import SerpAPIWrapper, LLMChain
from typing import List, Union
from langchain.schema import AgentAction, AgentFinish, OutputParserException
from langchain_community.llms.tongyi import Tongyi
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
# 引入向量化的类
from langchain_community.vectorstores import Chroma
import re
import os

from typing import Any

#自定义工具类
class MyAgentTool:
    def __init__(self) -> None:
        self.db = Chroma(embedding_function=DashScopeEmbeddings(),
                          persist_directory="./chroma")
        
    def tools(self):
        return [
            Tool(
                name="kg_search",
                description="当你需要回答2024年NBA冠军球队的问题时使用",
                func=self.search
            )
        ]
    
    def search(self, query: str) -> str:
        return self.db.similarity_search(query,k=2)
    
    def getdb(self):
        return self.db.__len__()
    
#可用工具
@tool
def kgg_search(query: str): 
    """当你需要回答2024年NBA冠军球队的问题时才会使用这个工具。"""
    db = Chroma(embedding_function=DashScopeEmbeddings(),persist_directory="./chroma")
    return db.similarity_search(query,k=2)

tool_list = [kgg_search]
    

class MyAgent:
    llm = None
    tools = None
    def __init__(self,llm,tools) -> None:
        #agent的提示词,用来描述agent的功能
        self.template =  """尽你最大可能回答下面问题,你将始终用中文回答. 你在必要时可以使用下面这些工具:
                    {tools}
                    使用下面的格式回答问题:
                    问题: 输入的问题
                    分析: 你对问题的分析,决定是否使用工具
                    动作: 使用工具,工具名称 [{tool_names}]
                    输入: {input}
                    观察: 动作名称
                    ... 
                    分析: 我现在知道答案了
                    答案: 这是最终的答案
                    记住使用中文回答,如果你使用英文回答将回遭到惩罚.
                    问题: {input}
                    {agent_scratchpad}"""
                    
        #定义一个openai的llm
        self.llm = llm
        #工具列表
        self.tools = tools
        #agent的prompt
        self.prompt = self.MyTemplate(
            template=self.template,
            tools=self.tools,
            #输入变量和中间变量
            input_variables=["input", "intermediate_steps"],
        )

        #定义一个LLMChain
        self.llm_chain = LLMChain(
            llm=self.llm,
            prompt = self.prompt
        )
        #工具名称列表
        self.toolnames = [tool.name for tool in self.tools]
        #定义一个agent
        self.agent = LLMSingleActionAgent(
            llm_chain=self.llm_chain,
            allowed_tools=self.toolnames,
            output_parser=self.MyOutputParser(),
            
            stop=["\n观察:"],
        )
    
    #运行agent
    def run(self, input: str) -> str:
        #创建一个agent执行器
        agent_executor = AgentExecutor.from_agent_and_tools(
            agent=self.agent, 
            tools=self.tools, 
            handle_parsing_errors=True,
            verbose=True
        )
        # agent_executor = AgentExecutor(agent=self.agent, tools=self.tools, verbose=True,handle_parsing_errors=True)
        agent_executor.run(input=input)

    

    #自定义模版渲染类
    class MyTemplate(StringPromptTemplate):
        #渲染模版
        template: str
        #需要用到的工具
        tools:List[Tool]

        #格式化函数
        def format(self, **kwargs: Any) -> str:
            #获取中间步骤
            intermediate_steps = kwargs.pop("intermediate_steps")
            thoughts = ""
            for action, observation in intermediate_steps:
                thoughts += action.log
                thoughts += f"\n观察: {observation}\n想法: "
            #将agent_scratchpad设置为该值
            kwargs["agent_scratchpad"] = thoughts
            # 从提供的工具列表中创建一个名为tools的变量
            kwargs["tools"] = "\n".join([f"{tool.name}: {tool.description}" for tool in self.tools])
            #创建一个提供的工具名称列表
            kwargs["tool_names"] = ", ".join([tool.name for tool in self.tools])
            prompt_ret = self.template.format(**kwargs)
            return prompt_ret


    #自定义输出解析类
    class MyOutputParser(AgentOutputParser):
        #解析函数
        def parse(self, output: str) -> Union[AgentAction, AgentFinish]:
            #检查agent是否应该完成
            if "答案:" in output:
                return AgentFinish(
                # 返回值通常始终是一个具有单个 `output` 键的字典。
                # It is not recommended to try anything else at the moment :)
                return_values={"output": output.split("答案:")[-1].strip()},
                log=output,
                )
            #用正则解析出动作和动作输入
            regex = r"动作\s*\d*\s*:(.*?)\n输入\s*\d*\s*:[\s]*(.*)"
            match = re.search(regex, output, re.DOTALL)
            #如果没有匹配到则抛出异常
            if not match:
                raise OutputParserException(f"Could not parse LLM output: `{output}`")
            action = match.group(1).strip()
            action_input = match.group(2)
            # 返回操作和操作输入
            return AgentAction(tool=action, tool_input=action_input.strip(" ").strip('"'), log=output)

myagent = MyAgent(Tongyi(),tool_list)
myagent.run("2024年NBA冠军球队是哪只?")

# tool = MyAgentTool()
# tool.search("2024年NBA冠军球队是哪只")

在这里插入图片描述

from langchain.agents import initialize_agent, AgentType

agent = initialize_agent(
    MyAgentTool().tools(),
    Tongyi(),
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
)
# agent.run("2024年NBA冠军球队是哪只?")
agent.invoke("2024年NBA冠军球队是哪只?")

在这里插入图片描述


在这里插入图片描述

;