基于langchain构建简单的数学agent
经过测试add_numbers(加法函数)、subtract_numbers(减法函数)、multiply_numbers(乘法函数)和 divide_numbers(除法函数)应进行修改,以使用浮点转换来处理小数,更严格地验证输入,并为边缘情况提供清晰的错误消息。但是,如果工具返回复杂的输出(例如像 sum_numbers_with_complex_output 中的字典),你就需要切换到像 G
安装包
%pip install langchain==0.3.23 | tail -n 1
%pip install langchain-ibm==0.3.10 | tail -n 1
%pip install langchain-community==0.3.16 | tail -n 1
%pip install wikipedia==1.4.0 | tail -n 1
%pip install openai==1.77.0 | tail -n 1
%pip install langchain-openai==0.3.16 | tail -n 1
加载模型
可以用lm radio,ollama本地部署小模型,比如gpt-oss亲测笔记本8g显存即可跑起来,为了促进大家自己动手,这里简单的写下基本调用方式
from langchain_openai import ChatOpenAI
openai_llm = ChatOpenAI(
model="gpt-4.1-nano",
api_key = "your openai api key here",
)
构造函数
注意定义的函数里要加上docstring,也就是注释,以便llm更好的理解这个function。
def add_numbers(inputs:str) -> dict:
"""
input:
一个包含数字的字符串
Returns:
包含结果的字典格式
Example Input (Dictionary):
{"numbers": [10, 20, 30]}
Example Input (String):
"Add the numbers 10, 20, and 30."
Example Output:
{"result": 60}
"""
numbers = [int(x) for x in inputs.replace(",", "").split() if x.isdigit()]
result = sum(numbers)
return {"result": result}
构造tools
方法1:
LangChain中的Tool类充当结构化包装器,将常规Python函数转换为与代理兼容的工具。每个工具都需要三个关键组件:1.名称,2.执行的操作功能也就是定义的函数,3.对该tools的工作进行描述
from langchain.agents import Tool
add_tool=Tool(
name="AddTool",
func=add_numbers,
description="Adds a list of numbers and returns the result.")
print("tool object",add_tool)
通过Tool我们能生成一个tool,tool具有name,invoke(tool包装的函数),description三个属性:
print("Tool Name:")
print(add_tool.name)
# Tool description
print("Tool Description:")
print(add_tool.description)
# Tool function
print("Tool Function:")
print(add_tool.invoke)
方法2:
from langchain_core.tools import tool
import re
@tool
def add_numbers(inputs:str) -> dict:
"""
Adds a list of numbers provided in the input string.
Parameters:
- inputs (str):
string, it should contain numbers that can be extracted and summed.
Returns:
- dict: A dictionary with a single key "result" containing the sum of the numbers.
Example Input:
"Add the numbers 10, 20, and 30."
Example Output:
{"result": 60}
"""
# Use regular expressions to extract all numbers from the input
numbers = [int(num) for num in re.findall(r'\d+', inputs)]
# numbers = [int(x) for x in inputs.replace(",", "").split() if x.isdigit()]
result = sum(numbers)
return {"result": result}
绝对值和
from typing import List
@tool
def add_numbers_with_options(numbers: List[float], absolute: bool = False) -> float:
"""
Adds a list of numbers provided as input.
Parameters:
- numbers (List[float]): A list of numbers to be summed.
- absolute (bool): If True, use the absolute values of the numbers before summing.
Returns:
- float: The total sum of the numbers.
"""
if absolute:
numbers = [abs(n) for n in numbers]
return sum(numbers)
``
测试
```python
print(add_numbers_with_options.invoke({"numbers":[-1.1,-2.1,-3.0],"absolute":False}))
print(add_numbers_with_options.invoke({"numbers":[-1.1,-2.1,-3.0],"absolute":True}))
@tool 装饰器会创建一个结构化工具(StructuredTool),其模式信息从函数签名和文档字符串中提取,如下所示。这有助于大语言模型(LLMs)更好地理解工具期望的输入以及如何正确使用该工具。虽然两种方法都有效,但在现代 LangChain 应用中,@tool 通常是更优选择,尤其是在结合 LangGraph 和函数调用模型时。
使用typing统一格式
在创建工具时,你必须准确指定它们的返回值。这有助于智能体理解和处理不同的可能输出。下面的改进型函数里当数字成功求和时,它会返回一个包含浮点值的字典;如果未找到数字或者在处理过程中出现问题,则会返回一条作为字符串的描述性错误信息。
from typing import Dict, Union
@tool
def sum_numbers_with_complex_output(inputs: str) -> Dict[str, Union[float, str]]:
"""
Extracts and sums all integers and decimal numbers from the input string.
Parameters:
- inputs (str): A string that may contain numeric values.
Returns:
- dict: A dictionary with the key "result". If numbers are found, the value is their sum (float).
If no numbers are found or an error occurs, the value is a corresponding message (str).
Example Input:
"Add 10, 20.5, and -3."
Example Output:
{"result": 27.5}
"""
matches = re.findall(r'-?\d+(?:\.\d+)?', inputs)
if not matches:
return {"result": "No numbers found in input."}
try:
numbers = [float(num) for num in matches]
total = sum(numbers)
return {"result": total}
except Exception as e:
return {"result": f"Error during summation: {str(e)}"}
函数sum_numbers_from_text返回一种简单直接的输出格式。它从输入字符串中提取所有整数值,将它们相加,并以浮点数形式返回总和。该函数假设输入中至少存在一个有效的数字,且不处理未找到任何数字或可能出现错误的情况。
@tool
def sum_numbers_from_text(inputs: str) -> float:
"""
Adds a list of numbers provided in the input string.
Args:
text: A string containing numbers that should be extracted and summed.
Returns:
The sum of all numbers found in the input.
"""
# Use regular expressions to extract all numbers from the input
numbers = [int(num) for num in re.findall(r'\d+', inputs)]
result = sum(numbers)
return result
initialize_agent
1.zero-shot-react-description :
零样本推理:智能体能够通过逐步思考问题来解决未曾见过的任务。适合手动定义tools
React 框架:一个逻辑循环包括如下:
推理→思考任务。
行动→使用工具执行操作。
观察→检查工具的输出。
计划→决定下一步做什么。
示例:
{"input": "Add 10, 20, two and 30"}
让智能体把 10、20、“two” 和 30 这几个数加起来。智能体首先注意到其中一个输入是单词 “two” 而不是数字,于是将 “two” 转换成了它的数字形式,也就是 2。在整理出数字列表(10、20、2 和 30)后,智能体决定使用 AddTool 来进行加法运算。它把这些数字传给该工具,工具计算出总和并返回结果 62。最后,智能体给出的答案是:62。
from langchain.agents import initialize_agent
agent = initialize_agent([add_tool], llm, agent="zero-shot-react-description", verbose=True, handle_parsing_errors=True)
[]里填写前面手动的定义的tools
2. Structured chat zero shot react-description
在 LangChain 中选择代理时,有两个因素很重要:代理类型和工具格式,尤其是工具的返回类型。像 zero-shot-react-description 这类代理,期望工具接收并返回纯字符 串,这与手动定义的 Tool (…) 包装器配合得很好。相比之下,结构化代理如 structured-chat-zero-shot-react-description 或 openai-functions)旨在通过 @tool 装饰器处理结构化的输入和输出。如果工具返回一个字典,而代理却期望得到一个字符串,就可能会导致键错误或解析失败。
agent_2 = initialize_agent([sum_numbers_from_text], llm, agent="structured-chat-zero-shot-react-description", verbose=True, handle_parsing_errors=True)
response = agent_2.invoke({"input": "Add 10, 20 and 30"})
print(response)
[]前面填写@tool装饰器定义的工具
3.多个输入参数工具
现在,让我们来看看具有多个输入的工具。下面的智能体使用 Granite 作为大语言模型,并使用 add_numbers_with_options 作为工具,该工具可接受多个输入参数。但是,如果工具返回复杂的输出(例如像 sum_numbers_with_complex_output 中的字典),你就需要切换到像 GPT 这样的模型,并使用同时支持多输入工具和结构化输出的智能体类型。
@tool
def sum_numbers_from_text(inputs: str) -> float:
"""
Adds a list of numbers provided in the input string.
Args:
text: A string containing numbers that should be extracted and summed.
Returns:
The sum of all numbers found in the input.
"""
# Use regular expressions to extract all numbers from the input
numbers = [int(num) for num in re.findall(r'\d+', inputs)]
result = sum(numbers)
return result
测试:
agent_openai = initialize_agent(
[add_numbers_with_options],
llm_ai,
agent="openai-functions",
verbose=True
)
response = agent_openai.invoke({
"input": "Add -10, -20, and -30 using absolute values."
})
print(response)
4.create_react_agent
由于 LangChain 的 AgentExecutor 即将被弃用,LangGraph 中的
create_react_agent 为构建 AI 代理提供了一种更灵活且更强大的替代方案。
此函数可创建一个基于图的代理,该代理能与聊天模型协同工作,
并支持工具调用功能。
关键参数:
1. model
指的是驱动智能体推理过程的语言模型。
要让智能体具备完整功能,该模型必须支持工具调用。
2. tools
这是智能体可用于执行各类操作的工具列表。
工具可以是:
LangChain 工具。
用 @tool 装饰器装饰的 Python 函数。
ToolNode 类的实例。
每个工具都应具备名称、描述以及自身的实现逻辑。
3. prompt(可选)
用于自定义提供给大语言模型(LLM)的指令。
它可以有多种形式:
字符串(会被转换为 SystemMessage)。
直接使用 SystemMessage 对象。
用于转换状态的函数。
用于处理状态的 Runnable。
与传统的 AgentExecutor(它使用固定的循环结构)不同,create_react_agent 创建了一个包含以下关键节点的图:
1. agent node:使用消息历史调用大语言模型(LLM)
2. tools node:执行大语言模型(LLM)响应中的任何工具调用
3.continue/end node:根据是否存在工具调用来管理工作流
运行流程:
1. 用户消息进入图谱
2. 大语言模型生成响应,可能包含工具调用
3. 如果存在工具调用,就会执行这些调用,并将其结果添加到消息历史中
4. 更新后的消息被发送回大语言模型
5. 这个循环会持续进行,直到大语言模型在响应时不再包含工具调用
6. 返回包含所有消息的最终状态
示例用法:
%pip install langgraph==0.6.1 | tail -n 1
from langgraph.prebuilt import create_react_agent
agent_exec = create_react_agent(model=llm, tools=[sum_numbers_from_text])
msgs = agent_exec.invoke({"messages": [("human", "Add the numbers -10, -20, -30")]})
print(msgs["messages"][-1].content)
多工具使用
广泛的任务。多种工具的协同运作确保智能体能够无缝管理复杂的工作流程,这使其成为构建强大且多功能的人工智能系统的重要框架。为了演示这一概念,现在创建更多工具,例如一个数学工具包。除了加法工具外,你现在要创建减法、乘法和除法工具。这些工具将被集成到一个能够处理各种数学查询的智能体中,以此展示多种工具如何在单个人工智能系统中协同工作。
构建减法工具:
注意写docstring
@tool
def subtract_numbers(inputs: str) -> dict:
"""
从字符串提取数字,第一个是被减数,后面是减去的数,比如“100减去20再减去10等于多少?”
这个函数被设计用来处理字符串格式的输入,其中数字是分开的
通过空格、逗号或其他分隔符。它解析字符串,提取有效的数值,
并执行一步一步的减法操作。
Parameters:
- inputs (str):
A string containing numbers to subtract. The string may include spaces, commas, or
other delimiters between the numbers.
Returns:
- dict:
A dictionary containing the key "result" with the calculated difference as its value.
If no valid numbers are found in the input string, the result defaults to 0.
Example Input:
"100, 20, 10"
Example Output:
{"result": 70}
Notes:
- Non-numeric characters in the input are ignored.
- If the input string contains only one valid number, the result will be that number negated.
- Handles a variety of delimiters (e.g., spaces, commas) but does not validate input formats
beyond extracting numeric values.
"""
# Extract numbers from the string
numbers = [int(num) for num in inputs.replace(",", "").split() if num.isdigit()]
# If no numbers are found, return 0
if not numbers:
return {"result": 0}
# Start with the first number
result = numbers[0]
# Subtract all subsequent numbers
for num in numbers[1:]:
result -= num
return {"result": result}
乘法工具
@tool
def multiply_numbers(inputs: str) -> dict:
"""
从字符串中提取数字并计算它们的乘积。
Parameters:
- inputs (str): A string containing numbers separated by spaces, commas, or other delimiters.
Returns:
- dict: A dictionary with the key "result" containing the product of the numbers.
Example Input:
"2, 3, 4"
Example Output:
{"result": 24}
Notes:
- If no numbers are found, the result defaults to 1 (neutral element for multiplication).
"""
# Extract numbers from the string
numbers = [int(num) for num in inputs.replace(",", "").split() if num.isdigit()]
print(numbers)
# If no numbers are found, return 1
if not numbers:
return {"result": 1}
# Calculate the product of the numbers
result = 1
for num in numbers:
result *= num
print(num)
return {"result": result}
```
除法工具
```python
@tool
def divide_numbers(inputs: str) -> dict:
"""
Extracts numbers from a string and calculates the result of dividing the first number
by the subsequent numbers in sequence.
Parameters:
- inputs (str): A string containing numbers separated by spaces, commas, or other delimiters.
Returns:
- dict: A dictionary with the key "result" containing the quotient.
Example Input:
"100, 5, 2"
Example Output:
{"result": 10.0}
Notes:
- If no numbers are found, the result defaults to 0.
- Division by zero will raise an error.
"""
# Extract numbers from the string
numbers = [int(num) for num in inputs.replace(",", "").split() if num.isdigit()]
# If no numbers are found, return 0
if not numbers:
return {"result": 0}
# Calculate the result of dividing the first number by subsequent numbers
result = numbers[0]
for num in numbers[1:]:
result /= num
return {"result": result}
测试工具
# Testing multiply_tool
multiply_test_input = "2, 3, and four "
multiply_result = multiply_numbers.invoke(multiply_test_input)
print("--- Testing MultiplyTool ---")
print(f"Input: {multiply_test_input}")
print(f"Output: {multiply_result}")
# Testing divide_tool
divide_test_input = "100, 5, two"
divide_result = divide_numbers.invoke(divide_test_input)
print("--- Testing DivideTool ---")
print(f"Input: {divide_test_input}")
print(f"Output: {divide_result}")
构建agent
tools = [add_numbers,subtract_numbers, multiply_numbers, divide_numbers]
from langgraph.prebuilt import create_react_agent
# Create the agent with all tools
math_agent = create_react_agent(
model=llm,
tools=tools,
# Optional: Add a system message to guide the agent's behavior
prompt="You are a helpful mathematical assistant that can perform various operations. Use the tools precisely and explain your reasoning clearly."
)
构建测试用例
test_cases = [
{
"query": "Subtract 100, 20, and 10.",
"expected": {"result": 70},
"description": "Testing subtraction tool with sequential subtraction."
},
{
"query": "Multiply 2, 3, and 4.",
"expected": {"result": 24},
"description": "Testing multiplication tool for a list of numbers."
},
{
"query": "Divide 100 by 5 and then by 2.",
"expected": {"result": 10.0},
"description": "Testing division tool with sequential division."
},
{
"query": "Subtract 50 from 20.",
"expected": {"result": -30},
"description": "Testing subtraction tool with negative results."
}
]
与直接调用工具返回简单字典不同,LangGraph 智能体返回的是一个复杂结构,其中包含作为消息列表的整个对话历史。要找到计算结果,必须在该列表中定位特定的 ToolMessage(通过其名称与某个数学工具匹配来识别),然后解析其内容,该内容包含作为 JSON 字符串的实际结果。结果无法直接从响应对象中获取,而是嵌套在消息历史中,需要遍历消息以找到并提取相关数据,以便与预期值进行比较。
correct_tasks = []
# Corrected test execution
for index, test in enumerate(test_cases, start=1):
query = test["query"]
expected_result = test["expected"]["result"] # Extract just the value
print(f"\n--- Test Case {index}: {test['description']} ---")
print(f"Query: {query}")
# Properly format the input
response = math_agent_new.invoke({"messages": [("human", query)]})
# Find the tool message in the response
tool_message = None
for msg in response["messages"]:
if hasattr(msg, 'name') and msg.name in ['add_numbers', 'new_subtract_numbers', 'multiply_numbers', 'divide_numbers']:
tool_message = msg
break
if tool_message:
# Parse the tool result from its content
import json
tool_result = json.loads(tool_message.content)["result"]
print(f"Tool Result: {tool_result}")
print(f"Expected Result: {expected_result}")
if tool_result == expected_result:
print(f"✅ Test Passed: {test['description']}")
correct_tasks.append(test["description"])
else:
print(f"❌ Test Failed: {test['description']}")
else:
print("❌ No tool was called by the agent")
print("\nCorrectly passed tests:", correct_tasks)
经过测试add_numbers(加法函数)、subtract_numbers(减法函数)、multiply_numbers(乘法函数)和 divide_numbers(除法函数)应进行修改,以使用浮点转换来处理小数,更严格地验证输入,并为边缘情况提供清晰的错误消息。例如,divide_numbers 函数应明确检查除以零的情况,并且所有函数都应妥善处理非数字输入,如 “two(二)” 或 “hundred(百)”。测试用例应在基本运算之外进行扩展,纳入除以零、空输入以及数字与文本混合输入(例如 “divide one hundred by 5(用 5 除 100)”)等边缘情况。还应考虑添加针对小数的测试(例如 “multiply 3.5 by 2(3.5 乘以 2)”)和连续运算的测试(例如 “multiply 10 by 2 then add 5(10 乘以 2 然后加 5)”)。
增加built-in工具
如果我想让他帮我在网上查找数据,并使用数学工具完成计算,那么可以加入wiki内置工具
from langchain_community.utilities import WikipediaAPIWrapper
# Create a Wikipedia tool using the @tool decorator
@tool
def search_wikipedia(query: str) -> str:
"""Search Wikipedia for factual information about a topic.
Parameters:
- query (str): The topic or question to search for on Wikipedia
Returns:
- str: A summary of relevant information from Wikipedia
"""
wiki = WikipediaAPIWrapper()
return wiki.run(query)
加入agent里
tools_updated = [add_numbers, new_subtract_numbers, multiply_numbers, divide_numbers, search_wikipedia]
# Create the agent with all tools including Wikipedia
math_agent_updated = create_react_agent(
model=llm,
tools=tools_updated,
prompt="You are a helpful assistant that can perform various mathematical operations and look up information. Use the tools precisely and explain your reasoning clearly."
)
测试
query = "What is the population of Canada? Multiply it by 0.75"
response = math_agent_updated.invoke({"messages": [("human", query)]})
print("\nMessage sequence:")
for i, msg in enumerate(response["messages"]):
print(f"\n--- Message {i+1} ---")
print(f"Type: {type(msg).__name__}")
if hasattr(msg, 'content'):
print(f"Content: {msg.content}")
if hasattr(msg, 'name'):
print(f"Name: {msg.name}")
if hasattr(msg, 'tool_calls') and msg.tool_calls:
print(f"Tool calls: {msg.tool_calls}")
流程:
代理首先使用search_wikipedia查找加拿大的人口。从Wikipedia的响应中提取数值。使用multiply_numbers计算总数的75%。返回带有上下文的最终结果。
更多推荐
所有评论(0)