安装包

%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%。返回带有上下文的最终结果。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐