LangChain中不同Agent类型使用实操三

作者: adm 分类: AI,python 发布时间: 2025-02-16

尝试让ZeroShotAgent具有记忆力

*注意,程序中增加了memory和chat_history等使其有记忆力的代码,可以让ZeroShotAgent具有记忆力
注意,从输出结果看它确实记住对话历史,在第三轮对话中它知道“这两个单词“是指第一轮的’hello’和第二轮的’world’,同时,在第四轮对话中,显示它是记住了前面两轮的答案的。

代码如下,注意和前面代码的差别。

#给出三轮问题,第三个问题"这两个单词的长度之和是多少?"需要调用前两个问题的结果,实验证明它记住了。
from langchain.agents import Tool, ZeroShotAgent, AgentExecutor
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
import os

# 1. 定义工具函数
def simple_add(expression: str) -> str:
    """简单的加法计算器"""
    try:
        # 移除所有空格和引号
        expression = expression.strip().strip('"\'')
        # 分割数字
        numbers = expression.split('+')
        # 转换为整数并求和
        result = sum(int(num.strip()) for num in numbers)
        return str(result)
    except Exception as e:
        return f"计算失败:{str(e)}"

def get_word_length(word: str) -> str:
    """计算字符串长度"""
    try:
        # 移除所有空格和引号
        word = word.strip().strip('"\'')
        return str(len(word))
    except Exception as e:
        return f"计算失败:{str(e)}"

# 2. 创建工具列表
tools = [
    Tool(
        name="SimpleAdd",
        func=simple_add,
        description="简单的加法计算器,输入格式为'数字+数字',例如:1+2"
    ),
    Tool(
        name="WordLength",
        func=get_word_length,
        description="计算字符串长度,输入一个字符串,返回它的字符数,例如:'hello'"
    )
]

# 3. 创建ZeroShotAgent的提示模板
prefix = """你是一个帮助用户解决问题的助手。你可以使用以下工具:

{tools}

请使用以下格式:

Question: 输入的问题
Thought: 你需要思考如何解决这个问题
Action: 要使用的工具名称
Action Input: 工具的输入
Observation: 工具的输出
... (这个思考/行动/观察可以重复多次)
Thought: 我现在知道最终答案
Final Answer: 对原始输入问题的最终答案

重要提示:
1. 如果问题涉及多个步骤,你需要一步步思考
2. 可以使用一个工具的输出作为另一个工具的输入
3. 在得到所有需要的结果后,给出最终答案
4. 记住之前的对话历史,可以在需要时使用之前的结果

对话历史:
{chat_history}

现在开始:

Question: {input}
"""

suffix = """
{agent_scratchpad}
"""

# 创建llm
BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model= MODEL,  # 默认的大模型为GPT-3.5-turbo,比较便宜
    openai_api_base= BASE_URL,
    openai_api_key= API_KEY
)

# 创建memory
memory = ConversationBufferMemory(
    memory_key="chat_history",
    input_key="input",
    output_key="output",
    return_messages=True
)

# 创建agent
agent = ZeroShotAgent(
    llm_chain=LLMChain(llm=llm, prompt=PromptTemplate.from_template(prefix + suffix)),
    tools=tools,
    verbose=True
)

# 执行
agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=10,
    memory=memory
)

# 准备工具描述
tools_description = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])

# 运行多轮对话
print("第一轮:计算第一个单词的长度")
result1 = agent_executor.invoke({
    "input": "单词'hello'的长度是多少?",
    "tools": tools_description
})
print("输出:", result1["output"])

print("\n第二轮:计算第二个单词的长度")
result2 = agent_executor.invoke({
    "input": "单词'world'的长度是多少?",
    "tools": tools_description
})
print("输出:", result2["output"])

print("\n第三轮:计算两个单词长度之和")
result3 = agent_executor.invoke({
    "input": "这两个单词的长度之和是多少?",
    "tools": tools_description
})
print("输出:", result3["output"]) 

print("\n第四轮:计算两个单词长度之和")
result4 = agent_executor.invoke({
    "input": "既然已经知道了这两个单词的长度,请直接计算这两个单词的长度之和是多少?",
    "tools": tools_description
})
print("输出:", result4["output"]) 

输出结果如下

第一轮:计算第一个单词的长度


> Entering new AgentExecutor chain...
Thought: 这个问题是要求我计算单词'hello'的长度。我可以使用WordLength工具来完成这个任务。
Action: WordLength
Action Input: 'hello'
Observation: 5
Thought:我现在知道最终答案了。
Final Answer: 单词'hello'的长度是5。

> Finished chain.
输出: 单词'hello'的长度是5。

第二轮:计算第二个单词的长度


> Entering new AgentExecutor chain...
Thought: 我需要计算单词'world'的长度。
Action: WordLength
Action Input: 'world'
Observation: 5
Thought:单词'world'的长度是5。
Final Answer: 单词'world'的长度是5。

> Finished chain.
输出: 单词'world'的长度是5。

第三轮:计算两个单词长度之和


> Entering new AgentExecutor chain...
Thought: 我需要分别计算两个单词的长度,然后将它们相加。
Action: WordLength
Action Input: 'hello'
Observation: 5
Thought:单词'hello'的长度是5。接下来我需要计算单词'world'的长度。
Action: WordLength
Action Input: 'world'
Observation: 5
Thought:单词'world'的长度也是5。现在我需要将这两个单词的长度相加。
Action: SimpleAdd
Action Input: '5+5'
Observation: 10
Thought:单词'hello'和'world'的长度之和是10。

Final Answer: 这两个单词的长度之和是10。

> Finished chain.
输出: 这两个单词的长度之和是10。

第四轮:计算两个单词长度之和


> Entering new AgentExecutor chain...
Thought: 根据之前的对话历史,我已经知道'hello'的长度是5,'world'的长度也是5。现在我需要计算这两个长度之和。
Final Answer: 这两个单词的长度之和是10。

> Finished chain.
输出: 这两个单词的长度之和是10。

继续尝试让ZeroShotAgent既具有记忆力,又能同时调用自定义函数和内置函数(serpAPI llm-math)

from langchain.agents import Tool, ZeroShotAgent, AgentExecutor
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain_community.utilities import SerpAPIWrapper
from langchain.chains import LLMMathChain
import os

os.environ["SERPAPI_API_KEY"] = 'Your_Own_key' 
# 1. 定义自定义工具函数
def get_word_length(word: str) -> str:
    """计算字符串长度"""
    try:
        # 移除所有空格和引号
        word = word.strip().strip('"\'')
        return str(len(word))
    except Exception as e:
        return f"计算失败:{str(e)}"

def get_temperature_difference(cities: str) -> str:
    """计算两个城市的温度差"""
    try:
        # 分割输入字符串获取两个城市名
        city1, city2 = [city.strip() for city in cities.split(',')]
        
        # 使用serpapi查询两个城市的温度
        search = SerpAPIWrapper()
        temp1 = search.run(f"{city1} 今天温度")
        temp2 = search.run(f"{city2} 今天温度")
        
        # 提取温度数值(假设温度格式为"XX°C")
        temp1 = int(temp1.split('°')[0])
        temp2 = int(temp2.split('°')[0])
        
        # 计算温度差
        diff = abs(temp1 - temp2)
        return f"{city1}和{city2}的温度差是{diff}°C"
    except Exception as e:
        return f"计算温度差失败:{str(e)}"

# 2. 创建工具列表
# 创建serpapi工具
search = SerpAPIWrapper()
serpapi_tool = Tool(
    name="Search",
    func=search.run,
    description="用于搜索信息的工具,可以查询历史人物、天气等信息"
)

# 创建llm-math工具
BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model= MODEL,  # 默认的大模型为GPT-3.5-turbo,比较便宜
    openai_api_base= BASE_URL,
    openai_api_key= API_KEY
)
math_chain = LLMMathChain.from_llm(llm=llm)
math_tool = Tool(
    name="Calculator",
    func=math_chain.run,
    description="用于进行数学计算的工具,可以处理各种数学运算"
)

# 创建自定义工具
word_length_tool = Tool(
    name="WordLength",
    func=get_word_length,
    description="计算字符串长度,输入一个字符串,返回它的字符数"
)

temp_diff_tool = Tool(
    name="TemperatureDiff",
    func=get_temperature_difference,
    description="计算两个城市的温度差,输入格式为'城市1,城市2',例如:'北京,上海'"
)

tools = [serpapi_tool, math_tool, word_length_tool, temp_diff_tool]

# 3. 创建ZeroShotAgent的提示模板
prefix = """你是一个帮助用户解决问题的助手。你可以使用以下工具:

{tools}

请使用以下格式:

Question: 输入的问题
Thought: 你需要思考如何解决这个问题
Action: 要使用的工具名称
Action Input: 工具的输入
Observation: 工具的输出
... (这个思考/行动/观察可以重复多次)
Thought: 我现在知道最终答案
Final Answer: 对原始输入问题的最终答案

重要提示:
1. 如果问题涉及多个步骤,你需要一步步思考
2. 可以使用一个工具的输出作为另一个工具的输入
3. 在得到所有需要的结果后,给出最终答案
4. 记住之前的对话历史,可以在需要时使用之前的结果
5. 使用TemperatureDiff工具时,输入格式必须是'城市1,城市2',例如:'北京,上海'

对话历史:
{chat_history}

现在开始:

Question: {input}
"""

suffix = """
{agent_scratchpad}
"""

# 创建memory
memory = ConversationBufferMemory(
    memory_key="chat_history",
    input_key="input",
    output_key="output",
    return_messages=True
)

# 创建agent
agent = ZeroShotAgent(
    llm_chain=LLMChain(llm=llm, prompt=PromptTemplate.from_template(prefix + suffix)),
    tools=tools,
    verbose=True
)

# 执行
agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=10,
    memory=memory
)

# 准备工具描述
tools_description = "\n".join([f"{tool.name}: {tool.description}" for tool in tools])


# 5. 运行交互式对话
def print_welcome_message():
    print("\n欢迎使用智能助手!我可以帮您:")
    print("1. 查询信息(如历史人物、天气等)")
    print("2. 进行数学计算")
    print("3. 计算字符串长度")
    print("4. 计算两个城市之间的温度差")
    print("\n您可以随时输入 'quit' 或 'exit' 退出对话。")
    print("=" * 50)

def main():
    print_welcome_message()
    
    while True:
        try:
            user_input = input("\n请输入您的问题: ").strip()
            
            if user_input.lower() in ['quit', 'exit']:
                print("\n感谢使用,再见!")
                break
                
            if not user_input:
                print("请输入有效的问题!")
                continue
                
            result = agent_executor.invoke({
                "input": user_input,
                "tools": tools_description
            })
            print("\n助手回答:", result["output"])
            
        except KeyboardInterrupt:
            print("\n\n程序被中断,正在退出...")
            break
        except Exception as e:
            print(f"\n发生错误:{str(e)}")
            print("请重新输入您的问题。")


if __name__ == "__main__":
    main() 

输出结果如下:

欢迎使用智能助手!我可以帮您:
1. 查询信息(如历史人物、天气等)
2. 进行数学计算
3. 计算字符串长度
4. 计算两个城市之间的温度差

您可以随时输入 'quit' 或 'exit' 退出对话。
==================================================

请输入您的问题: 请问特朗普的年纪


> Entering new AgentExecutor chain...
Thought: 我需要查询特朗普的年龄。
Action: Search
Action Input: 特朗普 年龄
Observation: 78 years
Thought:我现在知道最终答案。
Final Answer: 特朗普的年龄是78岁。

> Finished chain.

助手回答: 特朗普的年龄是78岁。

请输入您的问题: 请问奥巴马年纪


> Entering new AgentExecutor chain...
Thought: 用户想了解奥巴马的年龄,我需要使用Search工具来获取相关信息。
Action: Search
Action Input: 奥巴马的年纪
Observation: 63 years
Thought:我现在知道最终答案。
Final Answer: 奥巴马的年龄是63岁。

> Finished chain.

助手回答: 奥巴马的年龄是63岁。

请输入您的问题: 请问这两个人谁年轻


> Entering new AgentExecutor chain...
Thought: 我需要知道这两个人的年龄,然后比较他们的年龄。
Final Answer: 根据之前的对话历史,特朗普的年龄是78岁,奥巴马的年龄是63岁。所以奥巴马比特朗普年轻。

> Finished chain.

助手回答: 根据之前的对话历史,特朗普的年龄是78岁,奥巴马的年龄是63岁。所以奥巴马比特朗普年轻。

请输入您的问题: 请问毛泽东的出生地和周恩来的出生地现在哪个温度高?


> Entering new AgentExecutor chain...
Thought: 我需要查询毛泽东和周恩来出生地的当前温度,并比较它们的高低。
Action: Search
Action Input: 毛泽东出生地
Observation: Shaoshan, Xiangtan, China
Thought:我找到了毛泽东的出生地是韶山,位于中国湘潭。接下来,我需要查询韶山的当前温度。
Action: Search
Action Input: 韶山天气
Observation: {'type': 'weather_result', 'temperature': '72', 'unit': 'Fahrenheit', 'precipitation': '15%', 'humidity': '87%', 'wind': '2 mph', 'location': '湘潭市韶山市', 'date': 'Tuesday 11:00 PM', 'weather': 'Cloudy'}
Thought:韶山的当前温度是72华氏度。接下来,我需要查询周恩来出生地的当前温度。
Action: Search
Action Input: 周恩来出生地
Observation: Huaian, Huaian, China
Thought:我找到了周恩来的出生地是淮安。接下来,我需要查询淮安的当前温度。
Action: Search
Action Input: 淮安天气
Observation: {'type': 'weather_result', 'temperature': '61', 'unit': 'Fahrenheit', 'precipitation': '0%', 'humidity': '71%', 'wind': '7 mph', 'location': '中国江苏省淮安市', 'date': 'Tuesday 11:00 PM', 'weather': 'Cloudy'}
Thought:淮安的当前温度是61华氏度。韶山的温度是72华氏度,因此韶山的温度更高。
Final Answer: 毛泽东出生地韶山的温度比周恩来出生地淮安的温度高。

> Finished chain.

2. 在agent的调用中用AgentType.ZERO_SHOT_REACT_DESCRIPTION

注意,定义方式如下,此时不再需要prompt。

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    memory=memory,
    handle_parsing_errors=True,
    max_iterations=10,
    early_stopping_method="generate",
 )

此时发现,memory不再起作用。我先查了特朗普的年纪,又查了奥巴马的年纪,然后问这两个的年纪差。结果,它就不知道”这两个人”是谁。
示例代码如下:

from langchain.agents import Tool, initialize_agent, AgentType
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain_community.utilities import SerpAPIWrapper
from langchain.chains import LLMMathChain
import os

os.environ["SERPAPI_API_KEY"] = 'YourOwnKey' 

# 1. 定义自定义工具函数
def get_word_length(word: str) -> str:
    """计算字符串长度"""
    try:
        # 移除所有空格和引号
        word = word.strip().strip('"\'')
        return str(len(word))
    except Exception as e:
        return f"计算失败:{str(e)}"

def get_temperature_difference(cities: str) -> str:
    """计算两个城市的温度差"""
    try:
        # 分割输入字符串获取两个城市名
        city1, city2 = [city.strip() for city in cities.split(',')]
        
        # 使用serpapi查询两个城市的温度
        search = SerpAPIWrapper()
        temp1 = search.run(f"{city1} 今天温度")
        temp2 = search.run(f"{city2} 今天温度")
        
        # 提取温度数值(假设温度格式为"XX°C")
        temp1 = int(temp1.split('°')[0])
        temp2 = int(temp2.split('°')[0])
        
        # 计算温度差
        diff = abs(temp1 - temp2)
        return f"{city1}和{city2}的温度差是{diff}°C"
    except Exception as e:
        return f"计算温度差失败:{str(e)}"

# 2. 创建工具列表
# 创建serpapi工具
search = SerpAPIWrapper()
serpapi_tool = Tool(
    name="Search",
    func=search.run,
    description="用于搜索信息的工具,可以查询历史人物、天气等信息"
)

# 创建llm-math工具
BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model= MODEL,  # 默认的大模型为GPT-3.5-turbo,比较便宜
    openai_api_base= BASE_URL,
    openai_api_key= API_KEY
)
math_chain = LLMMathChain.from_llm(llm=llm)
math_tool = Tool(
    name="Calculator",
    func=math_chain.run,
    description="用于进行数学计算的工具,可以处理各种数学运算"
)

# 创建自定义工具
word_length_tool = Tool(
    name="WordLength",
    func=get_word_length,
    description="计算字符串长度,输入一个字符串,返回它的字符数"
)

temp_diff_tool = Tool(
    name="TemperatureDiff",
    func=get_temperature_difference,
    description="计算两个城市的温度差,输入格式为'城市1,城市2',例如:'北京,上海'"
)

tools = [serpapi_tool, math_tool, word_length_tool, temp_diff_tool]

# 3. 创建memory
memory = ConversationBufferMemory(
    memory_key="chat_history",
    input_key="input",
    output_key="output",
    return_messages=True
)

#4,创建agent
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    memory=memory,
    handle_parsing_errors=True,
    max_iterations=10,
    early_stopping_method="generate",
 )

# 5. 运行交互式对话
def print_welcome_message():
    print("\n欢迎使用智能助手!我可以帮您:")
    print("1. 查询信息(如历史人物、天气等)")
    print("2. 进行数学计算")
    print("3. 计算字符串长度")
    print("4. 计算两个城市之间的温度差")
    print("\n您可以随时输入 'quit' 或 'exit' 退出对话。")
    print("=" * 50)

def main():
    print_welcome_message()
    
    while True:
        try:
            user_input = input("\n请输入您的问题: ").strip()
            
            if user_input.lower() in ['quit', 'exit']:
                print("\n感谢使用,再见!")
                break
                
            if not user_input:
                print("请输入有效的问题!")
                continue
                
            result = agent.invoke({
                "input": user_input
            })
            print("\n助手回答:", result["output"])
            
        except KeyboardInterrupt:
            print("\n\n程序被中断,正在退出...")
            break
        except Exception as e:
            print(f"\n发生错误:{str(e)}")
            print("请重新输入您的问题。")

if __name__ == "__main__":
    main() 

输出结果如下

请输入您的问题: 请查一下特朗普的年纪


> Entering new AgentExecutor chain...
我需要查找特朗普的出生日期,以便计算他的年龄。  
Action: Search  
Action Input: "特朗普出生日期"  

Observation: June 14, 1946 (age 78 years), Jamaica Hospital Medical Center, New York, NY
Thought:特朗普出生于1946年6月14日。现在是2023年,所以我可以计算他的年龄。  
Action: Calculator  
Action Input: 2023 - 1946  


Observation: Answer: 77
Thought:特朗普在2023年满77岁,但由于他的生日在6月14日,所以在此之前他是76岁。  
Final Answer: 特朗普现在77岁。

> Finished chain.

助手回答: 特朗普现在77岁。

请输入您的问题: 请查一下奥巴马的年纪


> Entering new AgentExecutor chain...
我需要查找奥巴马的出生年份,以便计算他的年龄。  
Action: Search  
Action Input: "奥巴马出生年份"  

Observation: August 4, 1961 (age 63 years), Kapiʻolani Medical Center for Women & Children, Honolulu, HI
Thought:奥巴马出生于1961年8月4日,现在是2023年,所以我可以计算他的年龄。  
Action: Calculator  
Action Input: 2023 - 1961  


Observation: Answer: 62
Thought:奥巴马在2023年时62岁,但由于他的生日在8月4日,所以在2023年8月4日之后,他将满63岁。  
Final Answer: 奥巴马现在62岁,2023年8月4日将满63岁。

> Finished chain.

助手回答: 奥巴马现在62岁,2023年8月4日将满63岁。

请输入您的问题: 请计算一下这两个人的年龄差


> Entering new AgentExecutor chain...
我需要知道这两个人的出生年份或年龄,以便计算他们的年龄差。请提供这两个人的相关信息。
Observation: Invalid Format: Missing 'Action:' after 'Thought:
Thought:Question: 请计算一下这两个人的年龄差
Thought: 我需要知道这两个人的出生年份或年龄,以便计算他们的年龄差。请提供这两个人的相关信息。

Observation: Invalid Format: Missing 'Action:' after 'Thought:
Thought:Question: 请计算一下这两个人的年龄差
Thought: 我需要知道这两个人的出生年份或年龄,以便计算他们的年龄差。请提供这两个人的相关信息。
Action: None
......

3. STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION

3. STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION

特点:支持结构化输入

结构化输入处理:能够理解并处理结构化数据(如字典、列表)
多工具协同:可以协调多个工具完成复杂任务
零样本学习:不需要示例就能理解任务
对话记忆:通过memory参数保留对话上下文
适用场景:需要处理结构化数据的任务
已跑通的示例代码如下

from langchain_openai import ChatOpenAI
from langchain.tools import Tool
from langchain.memory import ConversationBufferMemory
import math,os
from typing import Dict, List, Union
import json

# 1. 定义结构化工具集
def get_employee_info(employee_id: str) -> Dict[str, str]:
    """获取员工基本信息"""
    employee_db = {
        "1001": {"name": "张三", "department": "研发部", "position": "高级工程师"},
        "1002": {"name": "李四", "department": "市场部", "position": "市场经理"},
        "1003": {"name": "王五", "department": "财务部", "position": "会计"}
    }
    return employee_db.get(employee_id, {"error": "员工不存在"})

def calculate_salary(base: float, bonus: float, tax_rate: float) -> Dict[str, float]:
    """计算薪资"""
    gross = base + bonus
    tax = gross * tax_rate
    net = gross - tax
    return {
        "gross_salary": round(gross, 2),
        "tax": round(tax, 2),
        "net_salary": round(net, 2)
    }

def get_department_employees(department: str) -> List[Dict[str, str]]:
    """获取部门员工列表"""
    department_db = {
        "研发部": [
            {"id": "1001", "name": "张三", "position": "高级工程师"},
            {"id": "1004", "name": "赵六", "position": "初级工程师"}
        ],
        "市场部": [
            {"id": "1002", "name": "李四", "position": "市场经理"},
            {"id": "1005", "name": "钱七", "position": "市场专员"}
        ],
        "财务部": [
            {"id": "1003", "name": "王五", "position": "会计"}
        ]
    }
    return department_db.get(department, [])

# 2. 创建工具实例
def parse_department_input(input_str: str) -> str:
    """解析部门输入参数"""
    try:
        # 尝试解析JSON输入
        params = json.loads(input_str)
        if isinstance(params, dict) and "department" in params:
            return params["department"]
        elif isinstance(params, dict) and "department_name" in params:
            return params["department_name"]
        return input_str
    except:
        # 如果不是JSON,直接返回输入字符串
        return input_str

tools = [
    Tool(
        name="GetEmployeeInfo",
        func=lambda x: str(get_employee_info(x)),
        description="根据员工ID获取员工详细信息,输入应为员工ID字符串"
    ),
    Tool(
        name="CalculateSalary",
        func=lambda x: str(calculate_salary(**eval(x))),
        description="计算薪资,输入应为字典格式的JSON字符串,包含base(基本工资), bonus(奖金), tax_rate(税率)字段"
    ),
    Tool(
        name="GetDepartmentEmployees",
        func=lambda x: str(get_department_employees(parse_department_input(x))),
        description="获取部门所有员工列表,输入可以是部门名称字符串或JSON格式(包含department或department_name字段)"
    )
]

# 3. 初始化记忆和LLM
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True,
    input_key="input",
    output_key="output"
)

BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model=MODEL,
    openai_api_base=BASE_URL,
    openai_api_key=API_KEY,
    temperature=0  # 降低随机性
)

# 4. 创建结构化聊天代理
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,
    memory=memory,
    verbose=True,
    handle_parsing_errors=True,
    max_iterations=3
)

# 5. 执行多轮结构化对话
print("===== 第一轮:查询员工信息 =====")
response1 = agent.run({"input": "请查询员工ID 1001的信息"})
print(response1)

print("\n===== 第二轮:薪资计算 =====")
response2 = agent.run({"input": "假设这位员工的基本工资是15000,奖金3000,税率0.2,请计算他的税后薪资"})
print(response2)

print("\n===== 第三轮:部门查询 =====")
response3 = agent.run({"input": "请列出研发部的所有员工"})
print(response3)

print("\n===== 第四轮:综合问题 =====")
response4 = agent.run({"input": "根据之前查询的研发部员工信息,计算研发部所有员工的基本工资总和,假设每人基本工资都是15000"})
print(response4)

结果输出如下:

> Entering new AgentExecutor chain...
Thought: 我需要使用GetEmployeeInfo工具来获取员工信息
Action:

{
“action”: “GetEmployeeInfo”,
“action_input”: “1001”
}

Observation: {'name': '张三', 'department': '研发部', 'position': '高级工程师'}
Thought:我找到了员工1001的信息
Action:

{
“action”: “Final Answer”,
“action_input”: “员工1001的信息如下:姓名:张三,部门:研发部,职位:高级工程师。”
}


> Finished chain.
员工1001的信息如下:姓名:张三,部门:研发部,职位:高级工程师。

===== 第二轮:薪资计算 =====


> Entering new AgentExecutor chain...
Thought: 需要使用CalculateSalary工具来计算税后薪资。
Action:

{
“action”: “CalculateSalary”,
“action_input”: “{“base”: 15000, “bonus”: 3000, “tax_rate”: 0.2}”
}


Observation: {'gross_salary': 18000, 'tax': 3600.0, 'net_salary': 14400.0}
Thought:I now know the answer to the user's question. 
Action:

{
“action”: “Final Answer”,
“action_input”: “该员工的税后薪资为14400.0元。”
}


> Finished chain.
该员工的税后薪资为14400.0元。

===== 第三轮:部门查询 =====


> Entering new AgentExecutor chain...
Thought: 我需要使用GetDepartmentEmployees工具来获取研发部的所有员工列表。

Action:

{
“action”: “GetDepartmentEmployees”,
“action_input”: “{“department”: “研发部”}”
}

Observation: [{'id': '1001', 'name': '张三', 'position': '高级工程师'}, {'id': '1004', 'name': '赵六', 'position': '初级工程师'}]
Thought:我得到了研发部的员工列表。
Action:

{
“action”: “Final Answer”,
“action_input”: “研发部的员工包括:张三(高级工程师),赵六(初级工程师)。”
}


> Finished chain.
研发部的员工包括:张三(高级工程师),赵六(初级工程师)。

===== 第四轮:综合问题 =====


> Entering new AgentExecutor chain...
Thought: 首先需要获取研发部的所有员工信息,然后计算基本工资总和。
Action:

{
“action”: “GetDepartmentEmployees”,
“action_input”: “{“department”: “研发部”}”
}


Observation: [{'id': '1001', 'name': '张三', 'position': '高级工程师'}, {'id': '1004', 'name': '赵六', 'position': '初级工程师'}]
Thought:现在我已经获得了研发部的员工信息,接下来我将计算他们的基本工资总和。
Action:

{
“action”: “CalculateSalary”,
“action_input”: “{“base”: 15000, “bonus”: 0, “tax_rate”: 0}”
}



Observation: {'gross_salary': 15000, 'tax': 0, 'net_salary': 15000}
Thought:计算结果显示每位员工的基本工资为15000,现在我将计算研发部所有员工的基本工资总和。
Action:

{
“action”: “Final Answer”,
“action_input”: “研发部所有员工的基本工资总和为30000。”
}


> Finished chain.
研发部所有员工的基本工资总和为30000。

4. CONVERSATIONAL_REACT_DESCRIPTION

特点:保留对话历史,支持多轮交互
适用场景:聊天机器人等需要记忆上下文的应用
*** 这是使agent有记忆力的最正确的选择,简单易用***
注意agent定义方式如下,此法也不要定义prompt

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    verbose=True,
    memory=memory,
    handle_parsing_errors=True
)

可执行的示例代码如下

from langchain.agents import Tool, AgentExecutor, initialize_agent
from langchain.agents.agent_types import AgentType
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from langchain.memory import ConversationBufferMemory
from langchain_community.utilities import SerpAPIWrapper
from langchain.chains import LLMMathChain
from langchain.agents import load_tools
import os

os.environ["SERPAPI_API_KEY"] = 'your-own-key' 

# 1. 定义自定义工具函数
def get_word_length(word: str) -> str:
    """计算字符串长度"""
    try:
        # 移除所有空格和引号
        word = word.strip().strip('"\'')
        return str(len(word))
    except Exception as e:
        return f"计算失败:{str(e)}"

def get_temperature_difference(cities: str) -> str:
    """计算两个城市的温度差"""
    try:
        # 分割输入字符串获取两个城市名
        city1, city2 = [city.strip() for city in cities.split(',')]
        
        # 使用serpapi查询两个城市的温度
        search = SerpAPIWrapper()
        temp1 = search.run(f"{city1} 今天温度")
        temp2 = search.run(f"{city2} 今天温度")
        
        # 提取温度数值(假设温度格式为"XX°C")
        temp1 = int(temp1.split('°')[0])
        temp2 = int(temp2.split('°')[0])
        
        # 计算温度差
        diff = abs(temp1 - temp2)
        return f"{city1}和{city2}的温度差是{diff}°C"
    except Exception as e:
        return f"计算温度差失败:{str(e)}"

# 2. 创建LLM
BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model= MODEL,  # 默认的大模型为GPT-3.5-turbo,比较便宜
    openai_api_base= BASE_URL,
    openai_api_key= API_KEY
)

# 3. 加载内置工具
builtin_tools = load_tools(
    ["serpapi", "llm-math"],
    llm=llm
)

# 4. 创建自定义工具
custom_tools = [
    Tool(
        name="WordLength",
        func=get_word_length,
        description="计算字符串长度,输入一个字符串,返回它的字符数"
    ),
    Tool(
        name="TemperatureDiff",
        func=get_temperature_difference,
        description="计算两个城市的温度差,输入格式为'城市1,城市2',例如:'北京,上海'"
    )
]

# 5. 合并所有工具
tools = builtin_tools + custom_tools

# 6. 创建memory
memory = ConversationBufferMemory(
    memory_key="chat_history",
    return_messages=True
)

# 7. 创建agent
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    verbose=True,
    memory=memory,
    handle_parsing_errors=True
)

# 8. 打印欢迎信息和使用说明
print("欢迎使用智能助手!我可以帮你:")
print("1. 查询历史人物信息(使用serpapi)")
print("2. 进行数学计算(使用llm-math)")
print("3. 计算字符串长度(使用WordLength工具)")
print("4. 计算两个城市的温度差(使用TemperatureDiff工具)")
print("5. 回答其他问题(如果工具可用)")
print("\n示例问题:")
print("- 历史上今天出生的两个最重要的人是谁?")
print("- 计算 123 + 456 等于多少?")
print("- 单词'hello'的长度是多少?")
print("- 北京和上海今天的温度差是多少?")
print("\n输入 'quit' 或 'exit' 退出对话")
print("-" * 50)

# 9. 开始对话循环
while True:
    try:
        # 获取用户输入
        user_input = input("\n请输入你的问题: ").strip()
        
        # 检查是否退出
        if user_input.lower() in ['quit', 'exit']:
            print("感谢使用,再见!")
            break
        
        # 如果输入为空,继续下一轮
        if not user_input:
            continue
        
        # 执行agent
        result = agent.invoke({"input": user_input})
        print("\n助手:", result["output"])
        
    except KeyboardInterrupt:
        print("\n\n程序被中断,正在退出...")
        break
    except Exception as e:
        print(f"\n发生错误:{str(e)}")
        print("请尝试用其他方式提问,或输入 'quit' 退出程序") 

输出结果如下

欢迎使用智能助手!我可以帮你:
1. 查询历史人物信息(使用serpapi)
2. 进行数学计算(使用llm-math)
3. 计算字符串长度(使用WordLength工具)
4. 计算两个城市的温度差(使用TemperatureDiff工具)
5. 回答其他问题(如果工具可用)

示例问题:
- 历史上今天出生的两个最重要的人是谁?
- 计算 123 + 456 等于多少?
- 单词'hello'的长度是多少?
- 北京和上海今天的温度差是多少?

输入 'quit' 或 'exit' 退出对话
--------------------------------------------------

请输入你的问题: 请问特朗普多大了


> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? Yes
Action: Search
Action Input: 特朗普年龄
Observation: 78 years
Thought:Do I need to use a tool? No
AI: 特朗普现在78岁。

> Finished chain.

助手: 特朗普现在78岁。

请输入你的问题: 请问奥巴马多大了?


> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? No
AI: 奥巴马现在的年龄是63岁。

> Finished chain.

助手: 奥巴马现在的年龄是63岁。

请输入你的问题: 请问他们谁老


> Entering new AgentExecutor chain...
Thought: Do I need to use a tool? No
AI: 特朗普现在78岁,而奥巴马现在63岁,所以特朗普比奥巴马年长。

> Finished chain.

助手: 特朗普现在78岁,而奥巴马现在63岁,所以特朗普比奥巴马年长。

请输入你的问题: 请问年长几岁?


> Entering new AgentExecutor chain...
Thought: 需要计算两者的年龄差。
Action: Calculator
Action Input: 78 - 63
Observation: Answer: 15
Thought:Do I need to use a tool? No  
AI: 特朗普比奥巴马年长15岁。

> Finished chain.

助手: 特朗普比奥巴马年长15岁。

请输入你的问题:

5. SELF_ASK_WITH_SEARCH

特点:自问自答模式,适合需要中间推理步骤的任务
适用场景:需要分解问题并逐步回答的场景
前面已经使用过了SerpAPI工具,下面可执行示例单独使用该工具,以实现SELF_ASK_WITH_SEARCH

from langchain.agents import initialize_agent, AgentType
from langchain_community.utilities import SerpAPIWrapper
from langchain_openai import ChatOpenAI
from langchain.tools import Tool
from langchain.prompts import PromptTemplate
import os

# 需要设置SerpAPI的API key
os.environ["SERPAPI_API_KEY"] = "your_api_key"  # 替换为实际key
# 1. 创建llm
BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1"
MODEL = "qwen-turbo"
API_KEY = os.getenv('DASHSCOPE_API_KEY')
llm = ChatOpenAI(
    model=MODEL,
    openai_api_base=BASE_URL,
    openai_api_key=API_KEY,
    temperature=0  # 降低随机性
)

# 2. 配置搜索工具(需 SerpAPI Key)
search = SerpAPIWrapper() 
tools = [
    Tool(
        name="Intermediate Answer",
        func=search.run,
        description="用于搜索事实性问题的答案。输入应该是一个具体的搜索查询。",
    )
]

# 3. 定义自定义Prompt模板
template = """问题: {input}

让我们一步一步地思考这个问题。

首先,我们需要确定需要搜索的具体问题。
然后,使用搜索工具来获取信息。
最后,基于搜索结果给出完整的答案。

搜索工具: {agent_scratchpad}

请按照以下格式回答:

思考: [你的思考过程]
搜索: [具体的搜索查询]
中间答案: [搜索结果]
最终答案: [完整的答案]

注意:
- 确保搜索查询具体且明确
- 如果信息不完整,继续提出新的搜索查询
- 最终答案必须包含所有重要信息

最终答案:"""

prompt = PromptTemplate(
    input_variables=["input", "agent_scratchpad"],
    template=template
)

# 4. 创建Agent
agent = initialize_agent(
    tools,
    llm,
    agent=AgentType.SELF_ASK_WITH_SEARCH,
    verbose=True,
    handle_parsing_errors=True,  # 添加错误处理
    max_iterations=5,  # 增加迭代次数以支持更多轮次的问答
    early_stopping_method="generate",  # 添加提前停止方法
    prompt=prompt  # 使用自定义prompt
)

# 5. 执行示例
print("=== 搜索任务 ===")
try:
    result = agent.invoke({
        "input": "特斯拉的CEO是谁?他目前还领导哪些公司?"
    })
    print("\n最终答案:", result["output"])
except Exception as e:
    print(f"\n执行过程中出现错误: {str(e)}")

结果如下

=== 搜索任务 ===


> Entering new AgentExecutor chain...
Yes.
Follow up: 谁是特斯拉的CEO?
Intermediate answer: Elon Musk
Follow up: Elon Musk 还领导哪些公司?
Intermediate answer: [' entity_type: related_questions.', '他是SpaceX的创始人、董事长、首席执行官、首席工程师,特斯拉投资人、首席执行官、产品设计师、 前董事长,无聊公司创始人,Neuralink、OpenAI联合创始人,同时也是X公司的首席 ...', '马斯克的公司 · 1、SpaceX(太空探索技术公司) · 2、Tesla(特斯拉) · 3、SolarCity(太阳城公司) · 4、Starlink(卫星互连公司) · 5、The Boring Company(解决交通 ...', '埃隆马斯克之所以被称为“硅谷钢铁侠”,并非浪得虚名。  从网上公开的资料来看,他参与创立,投资或管理着多家公司,就大众熟知的就有Paypal,SpaceX,特斯 ...', '马斯克(Musk)是OpenAI的联合创始人,但于2019年离开了公司,并成立了自己的AI公司XAI。在过去的几年中,他在公司的方向上与奥特曼(Altman)争吵不休。他去年就 ...', '除了可能放松的政府监管外,马斯克与特朗普 的联盟可能还有助于他获得联邦合同。据报道,在过去十年中,SpaceX和特斯拉已经获得了至少154亿美元的政府合同。', '他是SpaceX的創始人、董事長、執行長、首席 工程師,特斯拉投資人、執行長、產品設計師、前董事長,無聊公司創始人,Neuralink、OpenAI聯合創始人,同時也是X公司的技術長、董事 ...', '据了解,马斯克的收购行动获得了多家投资者的支持,包括Valor Equity Partners、Baron Capital、Atreides Management、Vy Capital以及由Palantir联合创始人乔·朗斯代尔领导的 ...', 'Elon Musk 参与创办并领导着Tesla、SpaceX、Neuralink 和The Boring Company。 作为Tesla 的联合创始人兼首席执行官,Elon 负责引领公司电动汽车、电池产品和 太阳能产品的 ...', 'Elon Musk是Technoking of Tesla,自2008年10月起担任Tesla Motors, Inc.首席执行官,自2004年4月起担任Tesla Motors, Inc.成员。Musk先生 自2002年5月起担任先进火箭和航天器 ...', '2002年:Musk投资1亿美元,创办美国太空探索技术公司(SPACE X),出任CEO兼首席技术官。 2004年:Musk向特斯拉汽车 公司投资630万美元,出任该公司董事长。 2006年:Musk投资1000 ...']
So the final answer is: Elon Musk is the CEO of Tesla and he currently also leads SpaceX, Neuralink, The Boring Company, and is involved with other companies like OpenAI and X (formerly Twitter).

> Finished chain.

最终答案: Elon Musk is the CEO of Tesla and he currently also leads SpaceX, Neuralink, The Boring Company, and is involved with other companies like OpenAI and X (formerly Twitter).

五、选择指南
简单查询/单工具调用:

优先选择LLMSingleActionAgent
示例:单词长度计算、简单问答
需要上下文记忆:

选择CONVERSATIONAL_REACT_DESCRIPTION
示例:聊天机器人
也可选择agent = ZeroShotAgent()的方式,但定义比较复杂,还需要prompt进行配合。
复杂多步任务:

选择ZERO_SHOT_REACT_DESCRIPTION或agent = ZeroShotAgent()或SELF_ASK_WITH_SEARCH
示例:数学计算+搜索组合任务
结构化数据处理:

选择STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION
示例:表格数据处理
六、性能优化建议
对于高频简单查询,使用SingleAction Agent减少延迟
复杂任务合理设置max_iterations防止无限循环
对话场景使用ConversationBufferWindowMemory控制内存增长
生产环境考虑添加异常处理和超时机制

# 带安全限制的Agent执行示例
from langchain.agents import AgentExecutor

safe_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=tools,
    max_iterations=5,  # 限制最大迭代次数
    early_stopping_method="generate",  # 提前停止策略
    handle_parsing_errors=True  # 处理解析错误
)

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!