Dawson Zhu头像
关注
《Agentic Design Patterns》第 5 章导读:工具使用(Tool Use)封面图

《Agentic Design Patterns》第 5 章导读:工具使用(Tool Use)

《Agentic Design Patterns》第 5 章导读:工具使用(Tool Use)

本文是对开源书籍《Agentic Design Patterns》第 5 章的解读与导读,内容忠实呈现原文,并附个人思考。
原书在线阅读:https://adp.xindoo.xyz/ | 翻译项目代码仓库:https://github.com/xindoo/agentic-design-patterns


前面四章(提示词链、路由、并行化、反思)讲的都是在模型之间编排信息流。但一个残酷的现实是:LLM 本质上和外部世界是断开的——它只吃训练数据,知识是静态的,不能查天气、不能算账、不能关灯、不能发邮件。

第 5 章的**工具使用(Tool Use)**模式,正是要给模型装上"手和眼睛":让它能真正调用外部 API、数据库、执行代码、控制其他系统。这也是把模型从"文本生成器"变成"智能体"的关键一步。


一、什么是工具使用:函数调用(Function Calling)

工具使用通常通过**函数调用(Function Calling)**机制实现。它的完整流程书里拆得很细,我整理成一张流程表:

阶段做什么
1. 工具定义向 LLM 描述外部函数的能力:用途、名称、参数及类型/描述
2. LLM 决策模型结合用户请求 + 工具定义,判断"要不要调、调哪个"
3. 函数调用生成若决定调用,模型输出结构化对象(通常是 JSON),注明工具名和参数
4. 工具执行框架/编排层拦截这个结构化输出,真正执行外部函数
5. 观察/结果工具的执行结果返回给智能体
6. LLM 处理模型把工具输出当上下文,据此给用户最终答复,或决定下一步(再调用、反思、收尾)

一句话概括它的价值:它是连接"LLM 推理能力"与"外部海量功能"之间的那座桥,打破了模型只能靠记忆输出的天花板。

书中还提了一个很有启发性的观点:与其叫"函数调用",不如用更宽的**“工具调用”**视角——因为"工具"可以是传统函数、复杂 API 端点、数据库查询,甚至是发给另一个专业智能体的指令。这样想,主智能体就能扮演"编排者",把分析任务委托给"分析师智能体"、通过 API 查外部库,构建出真正复杂的生态系统。

二、应用场景一览

场景典型工具智能体流程示例
外部信息检索天气 API问"伦敦天气",LLM 调气象工具 → 取数 → 格式化回复
与数据库/API 交互库存、订单、支付 API问"X 有货吗",LLM 调库存 API → 返回数量 → 告知用户
计算与数据分析计算器、股票 API、电子表格问"买 100 股 AAPL 的潜在利润",LLM 先后调股票 + 计算器工具
发送通信邮件发送 API“给 John 发封关于明天会议的邮件” → LLM 提取收件人/主题/正文并调用
执行代码代码解释器用户贴代码问"这段做什么",LLM 用解释器运行并分析输出
控制系统/设备智能家居 API“关掉客厅灯” → LLM 用命令+目标设备调用家居工具

在这里插入图片描述

图 1:智能体使用工具的几个示例——检索、计算、通信、执行代码、控制设备。

三、代码实战一:用 LangChain 搭建工具调用智能体

LangChain 的做法分两步:先定义工具(通常用 @tool 装饰器封装一个 Python 函数),再把工具和 LLM 绑定成"会调工具的智能体"。

pip install langchain langchain-openai langchain-google-genai
import os, getpass
import asyncio
import nest_asyncio
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool as langchain_tool
from langchain.agents import create_tool_calling_agent, AgentExecutor

# 需要支持函数调用/工具调用的模型
llm = ChatGoogleGenerativeAI(model="gemini-2.0-flash", temperature=0)

## --- 定义工具 ---
@langchain_tool
def search_information(query: str) -> str:
    """提供有关给定主题的事实信息。使用此工具查找诸如'法国首都'或'伦敦的天气?'等短语的答案。"""
    simulated_results = {
        "weather in london": "伦敦目前多云,温度为 15°C。",
        "capital of france": "法国的首都是巴黎。",
        "population of earth": "地球的估计人口约为 80 亿人。",
        "tallest mountain": "珠穆朗玛峰是海拔最高的山峰。",
        "default": f"'{query}' 的模拟搜索结果:未找到特定信息,但该主题似乎很有趣。"
    }
    return simulated_results.get(query.lower(), simulated_results["default"])

tools = [search_information]

## --- 创建工具调用智能体 ---
agent_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个有用的助手。"),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),   # 智能体内部步骤的占位符
])

agent = create_tool_calling_agent(llm, tools, agent_prompt)
agent_executor = AgentExecutor(agent=agent, verbose=True, tools=tools)

## --- 运行 ---
async def main():
    tasks = [
        agent_executor.ainvoke({"input": "法国的首都是什么?"}),
        agent_executor.ainvoke({"input": "伦敦的天气怎么样?"}),
        agent_executor.ainvoke({"input": "告诉我一些关于狗的事情。"}),  # 触发默认
    ]
    results = await asyncio.gather(*tasks)
    for r in results:
        print(r["output"])

nest_asyncio.apply()
asyncio.run(main())

要点:@langchain_tool 把普通 Python 函数变成可被模型感知的工具;create_tool_calling_agent 把"LLM + 工具 + 提示词"绑成智能体;AgentExecutor 是运行时——负责在"模型决定调用"和"真正执行工具"之间搭桥。

四、代码实战二:用 CrewAI——"会抛错"的工具更专业

CrewAI 的示例有一个很值得学的工程细节:工具返回干净数据 + 主动抛错,让智能体自己去处理异常。

## pip install crewai langchain-openai
import os, logging
from crewai import Agent, Task, Crew
from crewai.tools import tool

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

## --- 1. 工具:返回干净数据,找不到就抛 ValueError ---
@tool("Stock Price Lookup Tool")
def get_stock_price(ticker: str) -> float:
    """获取给定股票代码符号的最新模拟股票价格。以浮点数返回。如果未找到代码,则引发 ValueError。"""
    simulated_prices = {"AAPL": 178.15, "GOOGL": 1750.30, "MSFT": 425.50}
    price = simulated_prices.get(ticker.upper())
    if price is not None:
        return price
    raise ValueError(f"未找到代码 '{ticker.upper()}' 的模拟价格。")

## --- 2. 定义智能体 ---
financial_analyst_agent = Agent(
    role='高级财务分析师',
    goal='使用提供的工具分析股票数据并报告关键价格。',
    backstory="你是一位经验丰富的财务分析师,擅长使用数据源查找股票信息。",
    verbose=True,
    tools=[get_stock_price],
    allow_delegation=False,
)

## --- 3. 定义任务:明确成功与失败的处理方式 ---
analyze_aapl_task = Task(
    description=(
        "Apple(代码:AAPL)的当前模拟股票价格是多少?"
        "使用 'Stock Price Lookup Tool' 查找它。"
        "如果未找到代码,你必须报告无法检索价格。"
    ),
    expected_output="一个清晰的句子,说明 AAPL 的模拟股票价格,例如:'AAPL 的模拟股票价格是 $178.15。'",
    agent=financial_analyst_agent,
)

## --- 4. 组建团队并运行 ---
financial_crew = Crew(agents=[financial_analyst_agent], tasks=[analyze_aapl_task])
result = financial_crew.kickoff()
print(result)

设计亮点:让工具通过抛异常而不是返回"折中字符串"来报错,是因为智能体天生会处理异常——它能据此决定"下一步干什么"(比如报告失败),而不是傻乎乎拿一条错误字符串继续往下走。

五、代码实战三:Google ADK 的预构建工具

ADK 的杀手锏是大量开箱即用的预构建工具,无需自己写函数:

from google.adk.agents import Agent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools import google_search
from google.genai import types
import nest_asyncio, asyncio

APP_NAME, USER_ID, SESSION_ID = "Google Search_agent", "user1234", "1234"

## 使用预构建的 Google 搜索工具定义智能体
root_agent = Agent(
  name="basic_search_agent",
  model="gemini-2.0-flash-exp",
  description="使用 Google 搜索回答问题的智能体。",
  instruction="我可以通过搜索互联网回答您的问题。随便问我什么!",
  tools=[google_search]   # 直接拿来即用
)

async def call_agent(query):
    session_service = InMemorySessionService()
    session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID)
    runner = Runner(agent=root_agent, app_name=APP_NAME, session_service=session_service)
    content = types.Content(role='user', parts=[types.Part(text=query)])
    events = runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=content)
    for event in events:
        if event.is_final_response():
            print("智能体响应:", event.content.parts[0].text)

nest_asyncio.apply()
asyncio.run(call_agent("最新的 AI 新闻是什么?"))

除此之外,ADK 还提供:

  • 内建代码执行:BuiltInCodeExecutor 提供一个沙盒化 Python 解释器,让模型自己写代码去计算——弥补了概率性语言生成做不了确定性精确计算的短板(比如直接写 Python 去算 (5+7)*3);
  • 企业搜索:VSearchAgent 帮你查询企业私有数据存储(Vertex AI Search),还能拿到来源归因(grounding),回答有出处;
  • Vertex 扩展:结构化 API 包装器,企业级安全与隐私控制,关键是——Vertex AI 会自动执行扩展,而普通工具调用需要你自己手动执行(这是两者最核心的区别)。

六、速览

  • 问题背景:LLM 是强大的文本生成器,但基本与外部世界断开——知识静态、止于训练数据,缺乏执行操作或检索实时信息的能力,无法完成需要调用 API/数据库/服务交互的任务。
  • 解决方案:以模型能理解的方式描述可用的外部"工具"(函数/API)。智能体 LLM 根据请求决定是否需要工具,生成结构化数据对象(JSON)指定函数名与参数;编排层执行调用、取回结果、反馈给 LLM,从而让模型把最新外部信息并入最终响应。
  • 实践建议:当智能体需要突破内部知识、与外部世界交互时使用。典型任务:实时数据(天气、股价)、私有/专有信息(查公司库)、精确计算、执行代码、触发其他系统操作(发邮件、控设备)。

可视化总结

在这里插入图片描述

图 2:工具使用设计模式——"模型决定 → 框架执行 → 结果回流"的闭环。

关键要点

  • 工具使用(函数调用)让智能体能访问动态信息、连接外部系统;
  • 核心是定义描述清晰、参数明确(LLM 可理解)的工具;
  • 模型决定"何时用、用哪个",并生成结构化工具调用;框架负责执行与回传结果;
  • LangChain 用 @tool 简化定义,create_tool_calling_agent + AgentExecutor 搭建智能体;
  • Google ADK 提供 Google 搜索、代码执行、Vertex AI 搜索等现成工具。

结语与个人思考

工具使用是这本书里第一个真正"让模型接地气"的模式——前四章都是在模型之间搬信息,这一章开始让模型去触碰真实世界。往后几乎所有高级模式(RAG、Agent 间通信、编码 Agent……)都建立在这个地基之上:没有工具,模型只是会聊天的辞典;有了工具,模型才成为能办事的智能体。

几条工程心得,书里也都点到了:

  • 工具描述决定了调用质量:模型靠"工具定义"来决定调不调、调哪个。描述含糊,模型就不敢用或乱用。所以给工具的人话描述和参数说明,往往比工具本身更重要。
  • 异常要"抛"给模型:像 CrewAI 那样让工具抛异常,比返回错误字符串更能引导模型做正确的事——模型天生会"思考下一步怎么办"。
  • 确定性工作交给工具:LLM 做乘法都容易算错。但凡涉及精确计算、CRUD、确定性逻辑,就交给工具/代码执行器,模型只负责"调度和表达"。
  • 注意执行权边界:像"Vertex 扩展自动执行 vs 工具调用需手动执行",是安全设计上的关键分水岭。让模型自动触发有副作用的操作(发邮件、转账)尤其要谨慎,这直接关系到后面会讲的 Guardrails(安全护栏)。

下一步建议阅读第 6 章 规划(Planning)——如果说工具是"手",那规划就是让智能体"想清楚先后顺序再动手"的"大脑",识别多步任务并拆分执行计划,实现真正的自主。


本文基于开源书籍《Agentic Design Patterns》(https://github.com/xindoo/agentic-design-patterns ,在线阅读 https://adp.xindoo.xyz/ )整理,供学习交流,版权归原作者所有。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/bumblebee16/article/details/167084590

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--