智能体中的工具调用

Tool use in agents

基于大语言模型的智能体可以通过工具扩展能力。工具是它在推理循环中调用的外部函数或 API。本课讨论为什么需要工具、如何定义和注册计算器、搜索及自定义 API,智能体如何决定调用时机,以及如何通过 LangGraph 实现工具调用循环。学习后,你应当理解:

  • 工具对智能体自主性与能力的重要作用。
  • 如何提供合适的 schema 和描述,组织并注册工具。
  • 智能体如何决定何时、以什么方式调用工具,以及如何组织相关提示词。
  • 如何在 LangGraph 循环中实现工具调用节点。
  • 如何处理工具输出,返回决策过程,最终形成答案。

工具如何增强 LLM 智能体

即使先进的 LLM 也有固有限制:

  • 知识存在截止时间。
  • 无法始终可靠完成精确计算。
  • 受到训练数据范围限制。

外部工具提供超出模型基础知识的信息与操作,使智能体能够:

  • 取得动态的新信息:搜索网页或查询数据库,获得训练集中没有的最新数据。实战示例会将向量数据库封装成可调用工具。
  • 执行专业或精确计算:LLM 擅长推理,但提示不合适时也可能在普通数学题上出错。计算器、代码执行或科学 API 可以提供准确结果。
  • 与外部服务交互:例如查询天气、预订航班或访问专有知识库。
  • 实时适应请求:通过工具取得当前信息,例如查询实时股价,再针对用户问题回答。

工具让智能体能够决定查询资料、计算或调用 API,提高实际场景中的能力和可靠性。例如,用户问“库存里有哪些耳机?”时,普通 LLM 不知道当前库存,也可能产生幻觉;具有内部库存查询工具的智能体,可以先取得最新商品信息再回答。

整体流程仍是思考 → 行动 → 观察。LLM 处理用户输入,决定调用工具还是直接回答;需要工具时,输出名称与参数;系统执行后把结果传回,模型据此继续处理并生成答案。复杂任务可能需要多轮或多个工具。

基本工具使用流程如下:

图 1 · Agent工具调用的六个阶段
下载 SVG下载 SVG
Agent
函数定义与工具注册表属于 Agent;模型交互和工具调用由 Agent 执行。
调用阶段
查询、函数定义、模型交互、工具调用、再次模型交互与回答按图中编号展开。
Agent工具调用的六个阶段
Agent把函数定义提供给模型,根据模型选择调用工具,再把结果交给模型生成面向用户的回答。
  1. 用户查询传给智能体,通常是一个 Python 应用。
  2. 可用函数和工具在智能体代码中定义,属于程序性记忆。
  3. 工具列表与用户查询一同通过提示词提供给 LLM,由模型决定调用哪些函数及参数。
  4. 智能体应用执行函数。
  5. 用户查询与函数返回数据一起再次提供给 LLM。
  6. 模型组织答案,通过智能体返回用户。

工具使智能体超越内置知识和文本生成能力。下面介绍如何定义和注册工具。

组织与注册工具

智能体框架中的工具通常是执行特定操作的函数或 API,例如查询数据库的 Python 函数、REST API 调用、计算函数或网页搜索。工具用途很广,通常都可以封装为 Python 函数,包括需要时的代码执行。每个工具一般具有:

  • 名称:简短标识符,例如 calculator、web_search 或 retrieve_context。
  • 描述:说明用途和预期输入。工具定义会进入模型上下文,模型需要据此决定何时调用,因此描述非常重要,也是上下文与提示词工程的一部分。
  • 输入 schema 或函数签名:明确参数,例如计算器需要两个数字,搜索需要查询字符串。
  • 函数实现:调用时执行的实际代码,例如算术运算或 API 请求。

工具需要向 LLM 或智能体框架注册,让模型知道它们存在以及如何调用。在 LangGraph 与 LangChain 工具接口中,通常先通过装饰器或函数包装工具,再绑定到模型。LangChain 的 @tool 可以根据 Python 函数提取输入类型和 docstring,生成工具描述:

from langchain_core.tools import tool

@tool
def calculate_sum(a: float, b: float) -> float:
    """将两个数字相加并返回结果。

    Args:
        a: 第一个操作数。
        b: 第二个操作数。

    Returns:
        两个数字的和。
    """
    return a + b

@tool
def retrieve_context(query: str, top_k: int = 5) -> str:
    """查询向量数据库,取得与查询相关的上下文。

    Args:
        query: 相似度搜索使用的查询。
        top_k: 需要检索的文本块数量。

    Returns:
        从向量数据库取得的上下文。
    """
    return query_db(query, top_k)

示例定义了计算工具 calculate_sum 和向量搜索工具 retrieve_context。@tool 将函数转换为工具对象,从函数名取得名称,从 docstring 取得描述。例如,calculate_sum 的描述是“将两个数字相加并返回结果”。其中 query_db 由应用提供真实的向量数据库查询实现。

Docstring 清楚说明用途与输入。LLM 很大程度上根据用户需求与工具描述的匹配程度选择工具,因此好的描述可以提高选对工具的机会。

将工具注册到模型:定义后,通过绑定让模型知晓工具。例如:

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model_name="gpt-4.1-mini", temperature=0)

# 将工具绑定到 LLM
llm_with_tools = llm.bind_tools([calculate_sum, retrieve_context])

bind_tools([...]) 将工具及 schema 提供给聊天模型。框架在底层把描述和用法加入上下文,或使用模型原生 function calling API。llm_with_tools 的调用方式与普通模型相同,同时能够提出工具调用请求。

框架使用方便,也隐藏了一些实现细节。采用工具框架前,应理解底层过程,并尽可能能够调整实现,尤其在生产环境中需要进一步提高系统质量时。后续还会讨论如何自行实现工具选择。

工具设计建议:

  • 名称清楚具体,体现功能,例如 retrieve_context。
  • 描述简洁且信息充分,便于模型与用户请求比较。
  • 功能集中,使用多个范围明确的小工具。
  • 工具数量以必要为限。大量工具可能令模型困惑;需要较多工具时,可以通过路由或功能分组组织。
  • 认真定义输入 schema,明确多参数或结构化输入的格式,利用 int、str 或 JSON Schema 等标准类型。

接下来讨论提示词格式,以及模型在推理中如何决定调用工具。

工具调用的提示词格式

工具代码准备好后,还需要让模型知道调用方法。常见方式有两种:

  • 模型原生 function calling:GPT-4.1 等模型允许通过 API 提供函数列表和 schema,模型需要工具时直接输出结构化名称与参数。LangChain bind_tools 会利用相应能力,框架在底层发送定义。
  • 通过特殊格式提示,采用 ReAct 风格:没有专用工具 API 的模型,可以在 system 或 few-shot 提示词中描述工具,并要求采用指定格式。例如:“你可使用计算器 calculate_sum(a, b) 和库存上下文检索 retrieve_context(query, top_k)。需要工具时,按 <tool_name>(<args>) 输出行动;否则直接回答。”模型可能产生 calculate_sum(2, 3),框架解析后执行。此方法需要认真设计格式与示例。

通过绑定工具,或手动加入工具定义并按照 LangGraph 所需格式组织模型消息,可以让框架识别工具请求。模型通常输出:

  • 无需工具时的直接答案。
  • 需要工具时,框架能够识别的结构化调用。

例如,发送普通问候:

result = llm_with_tools.invoke("Hello world!")

通常得到“你好,有什么可以帮助你?”一类普通回应,无需工具。如果询问商品库存:

result = llm_with_tools.invoke("What earphones can I get?")

输出可能包含工具调用。LangChain/LangGraph 返回的 AIMessage 具有 tool_calls 属性;也可以使用 Instructor 等框架,再转换为兼容的 AIMessage,实战采用了这种方式。例如:

[{'name': 'retrieve_context', 'args': {'query': 'earphones', 'top_k': 5}, 'id': 'xyz', 'type': 'tool_call'}]

这里模型选择 retrieve_context,参数为 query="earphones"、top_k=5。模型已经表达需求,接下来由框架或应用代码执行工具并返回结果。提示词和结构化格式让系统能够准确理解这一请求。

工具选择取决于上下文、输入与描述的匹配。清楚的工具说明和提示词策略很重要;格式不佳或描述含糊时,模型可能误用工具,或需要时没有调用。采用 ReAct 风格的框架还常加入 few-shot 调用示例。

通过 LangGraph 实现工具调用

LangGraph 将智能体定义为节点函数与转换边组成的图。接入工具通常需要:

  1. 定义工具函数,例如前面的 calculate_sum 与 retrieve_context。
  2. 将工具绑定到 LLM;可以使用 LangChain,也可以手动管理以获得更具体的控制。
  3. 创建 LLM 节点,根据当前对话状态调用模型,取得下一条响应。
  4. 创建工具执行节点,检查响应中的调用,执行对应工具并将结果加入状态。也可以使用 ToolNode;本课手动实现,以说明工作机制。
  5. 配置图边与循环:LLM 请求工具时进入工具节点,否则结束;工具完成后返回 LLM 节点,使模型利用结果继续处理。

步骤 1、2:工具与模型。假设已完成前述定义和绑定,llm_with_tools 可以使用,例如 GPT-4.1-mini 或支持工具调用的本地模型。为 LangGraph 定义状态:

from langgraph.graph import StateGraph, START, END
from operator import add
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, AnyMessage
from typing import List, Annotated
from pydantic import BaseModel

# LangGraph 状态保存对话中的消息列表
class MessagesState(BaseModel):
    messages: Annotated[List[AnyMessage], add]

MessagesState 保存对话消息。add 指定更新时追加新消息,保留已有历史。

步骤 3:LLM 节点。接收包含当前全部消息的状态,调用模型取得下一条响应:

def run_llm(state: MessagesState) -> dict:
    # 使用当前对话调用已绑定工具的 LLM
    output_message = llm_with_tools.invoke(state.messages)
    # 返回需要追加的消息更新
    return {"messages": [output_message]}

这是简化示例。invoke(...) 返回 AIMessage;如果未用 LangChain 绑定工具,需要把结果转换成相同消息格式,以便利用 LangGraph 的组件。节点将消息包装为列表返回,由 add 合并到现有消息列表。

步骤 4:工具执行节点。只有模型请求工具时才运行。它检查最新的 AIMessage,执行调用并追加结果。消息格式十分重要,ToolNode 同样依赖正确格式:

def execute_tools(state: MessagesState) -> dict:
    # 最新 AIMessage 可能包含工具调用
    ai_message = state.messages[-1]
    results = []
    if hasattr(ai_message, "tool_calls") and ai_message.tool_calls:
        for tool_call in ai_message.tool_calls:
            tool_name = tool_call['name']
            tool_args = tool_call.get('args', {})
            # 按名称找到工具,也可以使用工具字典
            if tool_name == "calculate_sum":
                result_content = calculate_sum.invoke(tool_args)
            elif tool_name == "retrieve_context":
                result_content = retrieve_context.invoke(tool_args)
            else:
                result_content = f"[Unknown tool {tool_name}]"
            # 使用 tool_call_id 将结果关联到调用
            tool_msg = ToolMessage(
                content=str(result_content),
                tool_call_id=tool_call['id'],
                name=tool_name,
            )
            results.append(tool_msg)
    return {"messages": results}

节点读取最新消息的 tool_calls,将每次调用分配给相应工具。实际应用中,可以用 tool_name 到工具对象的字典,使查找更清楚。工具结果封装为 ToolMessage,这是 LangChain 表示工具输出的消息类型。返回 {"messages": results} 后,LangGraph 根据 add 追加消息,对话历史便包含带工具来源标记的结果。

步骤 5:图边与循环逻辑。定义 llm_node 和 tool_node 两个节点。流程从 START 进入 LLM,需要工具时执行工具再返回 LLM,无需工具时进入 END:

# 构建状态图
workflow = StateGraph(MessagesState)
workflow.add_node("llm_node", run_llm)
workflow.add_node("tool_node", execute_tools)

def tool_needed(state: MessagesState):
    # 检查最新 AIMessage 是否请求工具
    ai_msg = state.messages[-1]
    if hasattr(ai_msg, "tool_calls") and ai_msg.tool_calls:
        return "tool_node"
    return END

workflow.add_conditional_edges(
    "llm_node", tool_needed, {"tool_node": "tool_node", END: END}
)
# 工具完成后,让 LLM 继续处理结果
workflow.add_edge("tool_node", "llm_node")
workflow.add_edge(START, "llm_node")
graph = workflow.compile()

tool_needed 检查最新模型输出:包含工具请求时返回 tool_node,否则返回 END。它作为 llm_node 的条件边,实现每轮继续行动或结束的选择。工具节点到 LLM 节点的普通边,则保证工具完成并追加 ToolMessage 后,控制返回模型。模型在下一次输入中看到工具结果,可继续生成解答,通常也可直接给出最终答案。图从 LLM 节点开始,当模型不再请求新工具时结束。

LangGraph 面向有状态的循环工作流设计,需要连续使用多个工具时,上述图也能重复运行:第一次工具完成后,模型可以请求第二次工具,再进入同一循环。

实际使用时,也可以采用现成的 ToolNode。理解手动实现,有助于高级定制。Pydantic 状态、节点更新和工具调用接口可参见LangGraph Graph API与LangChain Tools。

处理工具响应与智能体循环

循环建立后,还需要正确接收和使用工具响应,最终形成答案:

  1. 记录工具输出:execute_tools 将结果包装为 ToolMessage 并加入状态,让工具响应进入 LLM 上下文,对应行动 → 观察。
  2. 返回 LLM:模型得到原始问题与工具答案,继续处理。多数情况下,可以结合新信息形成完整回答。
  3. 决定停止或继续:复杂问题可能一次调用仍不足够。模型可以再次输出工具请求,循环重复。只有输出不含 tool_calls 时,tool_needed 才转向 END。
  4. 生成最终答案:信息充足时,模型返回普通答案,图结束并将结果交给用户。最终文本应清楚呈现答案,移除内部工具调用语法和无关内容,提示词应明确这一要求。

实战中,LangGraph 帮助以 ToolMessage 注入结果;开发者还需检查答案是否恰当利用了工具信息。例如,工具提供重要上下文时,模型应将其用于回答,可以通过“使用工具后,将结果纳入答案”等指令改善。循环完成后返回答案,还需考虑后续对话如何继续。LangGraph 可以维护消息状态;跨独立调用或会话持久保存,需要相应的状态传递或 checkpoint 配置。

工具使用与自主执行

工具让智能体获取信息、进行计算,并执行内置知识之外的操作。清楚的 schema 与描述帮助模型理解用途和参数,决策循环则让系统动态选择工具调用或直接回答。正确组织模型消息、执行结果与状态,是可靠工具智能体的基础。

搜索课程

正在加载课程目录…