工具使用 #

概述 #

工具(Tools)是 Agent 与外部世界交互的桥梁。通过工具,Agent 可以搜索网络、查询数据库、调用 API、执行代码等。LangGraph 提供了强大的工具集成能力。

text
┌─────────────────────────────────────────────────────────────┐
│                    Agent 工具交互                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────┐                                               │
│   │  用户   │                                               │
│   └────┬────┘                                               │
│        │                                                    │
│        ▼                                                    │
│   ┌─────────┐     决定使用工具     ┌─────────┐              │
│   │  Agent  │ ──────────────────> │  Tool   │              │
│   │   LLM   │                     │ 搜索/DB │              │
│   └────┬────┘                     └────┬────┘              │
│        │                               │                    │
│        │<──────────────────────────────┘                    │
│        │         获取结果                                   │
│        │                                                    │
│        ▼                                                    │
│   ┌─────────┐                                               │
│   │  回答   │                                               │
│   └─────────┘                                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

定义工具 #

使用 @tool 装饰器 #

python
from langchain_core.tools import tool

@tool
def add(a: int, b: int) -> int:
    """Add two numbers together."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two numbers together."""
    return a * b

tools = [add, multiply]

使用 Pydantic 定义参数 #

python
from langchain_core.tools import tool
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    query: str = Field(description="Search query")
    max_results: int = Field(default=5, description="Maximum results")

@tool(args_schema=SearchInput)
def search(query: str, max_results: int = 5) -> list[str]:
    """Search for information."""
    return [f"Result {i} for {query}" for i in range(max_results)]

从函数创建工具 #

python
from langchain_core.tools import StructuredTool

def get_weather(location: str) -> str:
    """Get weather for a location."""
    return f"Weather in {location}: Sunny, 25°C"

weather_tool = StructuredTool.from_function(get_weather)

异步工具 #

python
@tool
async def async_search(query: str) -> str:
    """Async search tool."""
    result = await search_api(query)
    return result

工具结构 #

text
┌─────────────────────────────────────────────────────────────┐
│                    工具结构                                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Tool 属性:                                                 │
│  ├── name: 工具名称                                         │
│  ├── description: 工具描述                                  │
│  ├── args_schema: 参数 Schema                               │
│  └── return_direct: 是否直接返回结果                        │
│                                                             │
│  示例:                                                     │
│  {                                                          │
│    "name": "search",                                        │
│    "description": "Search for information",                 │
│    "parameters": {                                          │
│      "type": "object",                                      │
│      "properties": {                                        │
│        "query": {"type": "string", "description": "..."}   │
│      },                                                     │
│      "required": ["query"]                                  │
│    }                                                        │
│  }                                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

ToolNode #

LangGraph 提供了预构建的 ToolNode 来执行工具。

基本使用 #

python
from langgraph.prebuilt import ToolNode

tools = [add, multiply, search]
tool_node = ToolNode(tools)

graph.add_node("tools", tool_node)

ToolNode 工作流程 #

text
┌─────────────────────────────────────────────────────────────┐
│                    ToolNode 工作流程                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  输入:                                                     │
│  state["messages"][-1].tool_calls                           │
│  [                                                          │
│    {"name": "search", "args": {"query": "weather"}},        │
│    {"name": "add", "args": {"a": 1, "b": 2}}                │
│  ]                                                          │
│                                                             │
│  处理:                                                     │
│  1. 解析 tool_calls                                         │
│  2. 查找对应的工具                                          │
│  3. 执行工具调用                                            │
│  4. 生成 ToolMessage                                        │
│                                                             │
│  输出:                                                     │
│  {"messages": [ToolMessage(content="...", tool_call_id=..)]}│
│                                                             │
└─────────────────────────────────────────────────────────────┘

自定义 ToolNode #

python
from langgraph.prebuilt import ToolNode

class CustomToolNode(ToolNode):
    def _invoke(self, tool_call, tool):
        try:
            result = super()._invoke(tool_call, tool)
            return result
        except Exception as e:
            return ToolMessage(
                content=f"Error: {str(e)}",
                tool_call_id=tool_call["id"]
            )

将工具绑定到 LLM #

使用 bind_tools #

python
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)

response = llm_with_tools.invoke("What is 2 + 3?")
print(response.tool_calls)

工具调用格式 #

python
response = llm_with_tools.invoke("Search for Python tutorials")

print(response.tool_calls)
# [
#   {
#     "name": "search",
#     "args": {"query": "Python tutorials"},
#     "id": "call_123"
#   }
# ]

构建 Tool Agent #

完整示例 #

python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

class State(TypedDict):
    messages: Annotated[list, add_messages]

@tool
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b

@tool
def multiply(a: int, b: int) -> int:
    """Multiply two numbers."""
    return a * b

tools = [add, multiply]
llm = ChatOpenAI(model="gpt-4o-mini")
llm_with_tools = llm.bind_tools(tools)

def agent_node(state: State):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

def should_continue(state: State):
    if state["messages"][-1].tool_calls:
        return "tools"
    return END

tool_node = ToolNode(tools)

graph = StateGraph(State)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)

graph.add_edge(START, "agent")
graph.add_conditional_edges("agent", should_continue)
graph.add_edge("tools", "agent")

app = graph.compile()

使用预构建 Agent #

python
from langgraph.prebuilt import create_react_agent

agent = create_react_agent(llm, tools)

result = agent.invoke({"messages": [("user", "What is 2 + 3 * 4?")]})

工具类型 #

1. 计算工具 #

python
@tool
def calculate(expression: str) -> float:
    """Evaluate a mathematical expression."""
    return eval(expression)

@tool
def convert_units(value: float, from_unit: str, to_unit: str) -> float:
    """Convert between units."""
    conversions = {
        ("km", "miles"): 0.621371,
        ("miles", "km"): 1.60934,
    }
    factor = conversions.get((from_unit, to_unit), 1)
    return value * factor

2. 搜索工具 #

python
@tool
def web_search(query: str) -> str:
    """Search the web for information."""
    import requests
    response = requests.get(f"https://api.search.com?q={query}")
    return response.text

@tool
def wikipedia_search(query: str) -> str:
    """Search Wikipedia."""
    import wikipedia
    return wikipedia.summary(query)

3. 数据库工具 #

python
@tool
def query_database(sql: str) -> list:
    """Execute SQL query."""
    import sqlite3
    conn = sqlite3.connect("database.db")
    cursor = conn.execute(sql)
    return cursor.fetchall()

4. API 工具 #

python
@tool
def call_api(endpoint: str, method: str = "GET", data: dict = None) -> dict:
    """Call an external API."""
    import requests
    if method == "GET":
        return requests.get(endpoint).json()
    elif method == "POST":
        return requests.post(endpoint, json=data).json()

5. 文件工具 #

python
@tool
def read_file(path: str) -> str:
    """Read file content."""
    with open(path, "r") as f:
        return f.read()

@tool
def write_file(path: str, content: str) -> str:
    """Write content to file."""
    with open(path, "w") as f:
        f.write(content)
    return f"Written to {path}"

高级工具模式 #

工具返回 Command #

python
from langgraph.types import Command

@tool
def transfer_to_agent(agent_name: str) -> Command:
    """Transfer to another agent."""
    return Command(goto=agent_name)

工具访问状态 #

python
from langchain_core.tools import InjectedToolArg
from typing import Annotated

@tool
def save_to_memory(
    content: str,
    state: Annotated[dict, InjectedToolArg]
) -> str:
    """Save content to memory."""
    state["memory"].append(content)
    return "Saved"

工具错误处理 #

python
@tool
def risky_operation(data: str) -> str:
    """Perform a risky operation."""
    try:
        result = process(data)
        return result
    except ValueError as e:
        return f"Error: {e}"
    except Exception as e:
        return f"Unexpected error: {e}"

工具重试 #

python
from tenacity import retry, stop_after_attempt

@tool
@retry(stop=stop_after_attempt(3))
def fetch_with_retry(url: str) -> str:
    """Fetch URL with retry."""
    import requests
    return requests.get(url).text

工具选择控制 #

强制使用工具 #

python
llm_with_tools = llm.bind_tools(
    tools,
    tool_choice="search"  # 强制使用 search 工具
)

自动选择 #

python
llm_with_tools = llm.bind_tools(
    tools,
    tool_choice="auto"  # 让 LLM 决定
)

不使用工具 #

python
llm_with_tools = llm.bind_tools(
    tools,
    tool_choice="none"  # 不使用工具
)

工具调用流程 #

text
┌─────────────────────────────────────────────────────────────┐
│                    工具调用流程                              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 用户输入                                                 │
│     "What is the weather in Beijing?"                       │
│                                                             │
│  2. Agent 分析                                               │
│     LLM 决定需要调用 weather 工具                            │
│                                                             │
│  3. 生成 tool_calls                                          │
│     [{"name": "weather", "args": {"city": "Beijing"}}]      │
│                                                             │
│  4. ToolNode 执行                                            │
│     调用 weather("Beijing")                                 │
│                                                             │
│  5. 返回结果                                                 │
│     ToolMessage(content="Sunny, 25°C")                      │
│                                                             │
│  6. Agent 处理结果                                           │
│     LLM 生成最终回答                                        │
│                                                             │
│  7. 输出                                                     │
│     "The weather in Beijing is sunny, 25°C."                │
│                                                             │
└─────────────────────────────────────────────────────────────┘

多工具并行调用 #

python
response = llm_with_tools.invoke(
    "What is 2+3 and what is 4*5?"
)

print(response.tool_calls)
# [
#   {"name": "add", "args": {"a": 2, "b": 3}, "id": "call_1"},
#   {"name": "multiply", "args": {"a": 4, "b": 5}, "id": "call_2"}
# ]

ToolNode 会自动并行执行多个工具调用。

工具最佳实践 #

1. 清晰的描述 #

python
@tool
def search(query: str) -> str:
    """
    Search for information on the web.
    
    Args:
        query: The search query string.
        
    Returns:
        A string containing search results.
    """
    return search_api(query)

2. 参数验证 #

python
from pydantic import BaseModel, Field, validator

class EmailInput(BaseModel):
    to: str = Field(description="Recipient email")
    subject: str = Field(description="Email subject")
    body: str = Field(description="Email body")
    
    @validator("to")
    def validate_email(cls, v):
        if "@" not in v:
            raise ValueError("Invalid email")
        return v

@tool(args_schema=EmailInput)
def send_email(to: str, subject: str, body: str) -> str:
    """Send an email."""
    return f"Email sent to {to}"

3. 安全考虑 #

python
import os

ALLOWED_PATHS = ["/data", "/tmp"]

@tool
def safe_read_file(path: str) -> str:
    """Read file safely."""
    abs_path = os.path.abspath(path)
    if not any(abs_path.startswith(p) for p in ALLOWED_PATHS):
        return "Error: Access denied"
    with open(abs_path, "r") as f:
        return f.read()

4. 限制工具权限 #

python
@tool
def limited_query(sql: str) -> list:
    """Execute read-only SQL query."""
    forbidden = ["DROP", "DELETE", "UPDATE", "INSERT"]
    if any(word in sql.upper() for word in forbidden):
        return "Error: Only SELECT queries allowed"
    return execute_query(sql)

调试工具调用 #

查看工具调用 #

python
for step in app.stream({"messages": [...]}):
    if "tools" in step:
        for msg in step["tools"]["messages"]:
            print(f"Tool: {msg.name}")
            print(f"Args: {msg.tool_call_kwargs}")
            print(f"Result: {msg.content}")

使用 LangSmith #

python
import os
os.environ["LANGSMITH_TRACING"] = "true"

# 所有工具调用会自动追踪

下一步 #

现在你已经掌握了工具使用,接下来学习 记忆与持久化,了解如何让 Agent 记住历史!

最后更新:2026-03-30