中文译本。原文见同目录 README.md
第 3 课:工具 - 给 Agent 双手
引言
在第 1 课中,我们确立了 Agent 是大脑(LLM)加双手(工具)加控制循环(编排)。第 2 课探索了大脑。现在我们给 Agent 双手。
没有工具,LLM 只能生成文本。它可以推理一个问题,但不能在数据库里查答案;可以规划一次部署,但不能真正跑部署脚本;可以起草邮件,但不能发出去。
工具弥合「思考」与「行动」之间的鸿沟。它们是 Agent 与外部世界交互的机制——读数据、调 API、执行代码、采取行动。
本课讲解工具是什么、不同类型的工具、函数调用底层如何工作、设计工具的最佳实践,以及即便有经验的工程师也会踩的常见坑。
为什么 Agent 需要工具
考虑这个场景。用户问你的 Agent:「有多少未关闭的 bug 分配给我?」
没有工具时: LLM 无法访问你的 bug 跟踪器。它可能说「我无法访问你的 bug 跟踪器」(运气好),或幻觉出一个数字(运气不好)。
有工具时:
Agent 想「我需要查 bug 跟踪器。」它调用 get_bugs(assignee="current_user", status="open") 工具。工具查询你的 Jira/Linear/GitHub Issues 实例,返回 [{id: 1234, title: "Login timeout"}, {id: 1235, title: "CSS overflow on mobile"}]。Agent 统计结果并回复:「你有 2 个未关闭 bug:Login timeout(#1234)和 CSS overflow on mobile(#1235)。」
工具把 Agent 从聪明的对话伙伴,变成真正能把事情做完的能干助手。
工具能带来什么
| 没有工具 | 有工具 |
|---|---|
| 根据训练数据回答(可能过时) | 根据实时数据回答 |
| 描述应该做什么 | 真正去做 |
| 推理代码 | 运行代码并看到输出 |
| 猜测当前状态 | 查询并观察真实状态 |
| 起草消息 | 发送消息 |
工具的类型
工具有多种形态。理解不同类型有助于为你的 Agent 选择正确做法。
1. 函数工具(自定义函数)
这些是你定义、Agent 可调用的函数。你控制实现、输入、输出与错误处理。
例子:
search_database(query, filters)- 查询内部数据库create_ticket(title, description, priority)- 创建支持工单send_notification(user_id, message)- 发送推送通知get_weather(city)- 从第三方 API 获取天气run_test_suite(test_path)- 执行测试并返回结果
函数工具是最常见的类型。它们是你把 Agent 能力扩展到具体用例的主要方式。
2. 内置工具(平台提供)
这些是你所用平台或框架提供的工具。预构建、开箱即用。
Google Cloud / Gemini 内置工具:
| 工具 | 做什么 |
|---|---|
| Google Search | 用实时网页搜索结果锚定模型回复。通过提供当前信息减少幻觉。 |
| Code Execution | 在沙箱环境中运行 Python 代码。适合数学、数据分析与生成图表。 |
| URL Context | 获取并处理给定 URL 的内容。 |
何时用内置工具:
- 你需要的功能已作为内置存在
- 希望平台管理执行、沙箱与扩缩容
- 希望避免自行维护常见能力的实现
何时改为构建自定义函数工具:
- 需要访问自己的系统与数据
- 需要自定义逻辑或业务规则
- 内置工具覆盖不了你的用例
3. Agent 工具(以 Agent 为工具)
这是一种强大模式:一个 Agent 把另一个 Agent 当作工具使用。调用方 Agent 把子任务委派给专业化 Agent,拿回结果,再继续自己的工作流。
例子:
Main Agent (Trip Planner)
|
+-- calls --> Flight Search Agent
| (specialized in finding flights)
|
+-- calls --> Hotel Agent
| (specialized in hotel bookings)
|
+-- calls --> Activity Agent
(specialized in local activities)
主 Agent 协调整体行程规划。每个子 Agent 是其领域专家,有自己的工具与指令。主 Agent 不必知道航班搜索如何工作——它只需问 Flight Search Agent 并拿回结果。
何时用 Agent 工具:
- 子任务足够复杂,值得有自己的专业化工具与指令
- 希望分离关注点、让每个 Agent 更简单
- 不同子任务受益于不同模型或配置
函数调用逐步讲解
函数调用是启用工具使用的核心机制。下面逐步说明它如何工作。
流程
Step 1: You define tools and send them to the model with the user's message
Step 2: The model decides whether to call a tool (and which one)
Step 3: The model returns a structured tool call (function name + arguments)
Step 4: YOUR CODE executes the function (the model never runs it)
Step 5: You send the function result back to the model
Step 6: The model uses the result to generate a response (or call another tool)
这一点至关重要:模型不执行工具。它提出工具调用。你的代码执行它们。模型从不直接碰你的数据库、API 或系统。你始终掌控一切。
逐步例子
我们用天气 Agent 走一遍具体例子。
步骤 1:定义工具并发送请求
tools = [
{
"name": "get_weather",
"description": "Get the current weather for a given city. Returns temperature, conditions, and humidity.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city name, e.g. 'London' or 'San Francisco'"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}
}
]
# Send to model with user message
response = model.generate(
messages=[{"role": "user", "content": "What is the weather in Tokyo?"}],
tools=tools
)
步骤 2-3:模型返回工具调用
模型不以文本回复。相反,它返回结构化的工具调用:
{
"tool_call": {
"name": "get_weather",
"arguments": {
"city": "Tokyo",
"units": "celsius"
}
}
}
模型弄清了:
- 需要天气工具(不是别的工具)
- 城市是 "Tokyo"(从用户消息抽取)
- 对东京用摄氏度可能合适(从上下文推断)
步骤 4:你的代码执行函数
# This is YOUR code - you control what happens here
def get_weather(city, units="celsius"):
# Call a real weather API
response = weather_api.get(city=city, units=units)
return {
"city": city,
"temperature": response.temp,
"conditions": response.conditions,
"humidity": response.humidity
}
# Execute the tool call
result = get_weather(**tool_call.arguments)
# result = {"city": "Tokyo", "temperature": 18, "conditions": "Partly cloudy", "humidity": "65%"}
步骤 5-6:把结果送回并得到回复
response = model.generate(
messages=[
{"role": "user", "content": "What is the weather in Tokyo?"},
{"role": "assistant", "tool_call": tool_call},
{"role": "tool", "content": json.dumps(result)}
],
tools=tools
)
# Model responds: "The weather in Tokyo is 18 degrees Celsius and partly cloudy
# with 65% humidity."
多次工具调用
有时模型需要调用多个工具才能回答问题。可能有两种方式:
顺序(一个接一个):
User: "Compare the weather in Tokyo and London"
Step 1: Model calls get_weather(city="Tokyo")
Step 2: You execute, return result
Step 3: Model calls get_weather(city="London")
Step 4: You execute, return result
Step 5: Model synthesizes both results into a comparison
并行(同时):
有些模型支持并行工具调用,模型在单次响应中提出多个调用。对同一天气对比,模型可一次返回 get_weather("Tokyo") 与 get_weather("London")。你执行两者,把两个结果送回,模型再综合。这更快,因为省了一次往返。
带工具的完整 Agent 循环
在真实 Agent 中,工具调用发生在循环里:把用户消息与工具发给模型,检查响应是文本答案(完成)还是工具调用(执行它,把结果追加到对话,再循环回去)。持续进行,直到模型判定已有足够信息可以回复。
工具调用流程:逐步演示
观看 Agent 如何处理「东京天气怎么样?」——用控件逐步前进。
工具设计最佳实践
如何设计工具,直接影响 Agent 表现。模型需要理解你的工具才能正确使用它们。以下是最重要的原则。
1. 使用清晰、描述性的名称
工具名称是模型首先看到的东西。它应立刻传达工具做什么。
| 差名称 | 好名称 | 原因 |
|---|---|---|
do_thing |
search_knowledge_base |
具体说明搜索什么 |
api_call |
create_support_ticket |
描述动作与目标 |
process |
validate_email_address |
清晰说明处理什么、如何处理 |
get_data |
get_order_by_id |
指定什么数据、如何查找 |
run |
execute_sql_query |
明确运行什么 |
2. 单一职责
每个工具应把一件事做好。就像代码中的函数,工具应有单一、清晰的目的。
差:一个包揽一切的工具
{
"name": "manage_orders",
"description": "Create, read, update, or delete orders",
"parameters": {
"action": {"type": "string", "enum": ["create", "read", "update", "delete"]},
"order_id": {"type": "string"},
"order_data": {"type": "object"}
}
}
模型现在既要弄清用哪个 action,又要弄清每个 action 需要什么参数。这会导致错误。
好:每个动作单独工具
[
{
"name": "get_order",
"description": "Look up an order by its ID. Returns order status, items, and shipping info.",
"parameters": {
"order_id": {"type": "string", "description": "The order ID, e.g. 'ORD-12345'"}
}
},
{
"name": "create_order",
"description": "Create a new order with the specified items.",
"parameters": {
"items": {"type": "array", "description": "List of item IDs and quantities"},
"shipping_address": {"type": "string"}
}
},
{
"name": "cancel_order",
"description": "Cancel an existing order. Only works for orders not yet shipped.",
"parameters": {
"order_id": {"type": "string"},
"reason": {"type": "string", "description": "Reason for cancellation"}
}
}
]
每个工具有清晰目的与具体参数。模型容易选对。
3. 写描述性参数
参数描述告诉模型传什么。对格式、约束与默认值要具体。
差:
{
"name": "search",
"parameters": {
"q": {"type": "string"},
"n": {"type": "integer"}
}
}
模型只能猜 q 和 n 是什么意思。
好:
{
"name": "search_products",
"parameters": {
"query": {
"type": "string",
"description": "Search query for product name or description, e.g. 'wireless headphones'"
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return. Range: 1-50. Default: 10"
}
}
}
4. 返回简洁、有用的输出
工具结果会回到模型的上下文窗口。返回太多数据浪费 token,并可能迷惑模型。
差:返回带一切信息的原始 API 响应
{
"status": 200,
"headers": {"content-type": "application/json", "x-request-id": "abc123", ...},
"data": {
"order": {
"id": "ORD-12345",
"internal_id": "a1b2c3d4-e5f6-7890",
"created_at": "2024-03-15T10:30:00Z",
"updated_at": "2024-03-15T14:22:00Z",
"customer_id": "CUST-789",
"customer_hash": "sha256:abcdef...",
"status": "shipped",
"status_code": 3,
"status_history": [...50 entries...],
"items": [...full product details with all fields...],
"shipping": {...full carrier details...},
"billing": {...full payment details...},
"metadata": {...internal tracking data...}
}
}
}
好:只返回 Agent 需要的内容
{
"order_id": "ORD-12345",
"status": "shipped",
"items": ["Wireless Headphones x1", "USB-C Cable x2"],
"tracking_number": "1Z999AA10123456784",
"estimated_delivery": "March 20, 2026"
}
5. 包含清晰的错误消息
工具调用失败时,错误消息应帮助模型理解哪里错了、下一步试什么。
差错误:
{"error": "Failed"}
好错误:
{
"error": "ORDER_NOT_FOUND",
"message": "No order found with ID 'ORD-99999'. Please verify the order ID and try again. Order IDs follow the format ORD-XXXXX."
}
好的错误消息给模型足够信息,要么纠正做法(也许 ID 格式错了),要么向用户说清楚。
6. 在描述中记录工具行为
工具描述是你告诉模型何时以及如何使用工具的机会。包含:
- 工具做什么(主要动作)
- 何时使用(条件与触发)
- 返回什么(输出格式)
- 局限(它不能做什么)
例子:
{
"name": "search_knowledge_base",
"description": "Search the company knowledge base for relevant articles and documentation. Use this tool when the user asks a question about company policies, product features, or internal processes. Returns the top 5 most relevant articles with titles, summaries, and links. Does not search external websites - use web_search for that."
}
N x M 集成问题
随着 Agent 系统增长,工具集成成为重大挑战。原因如下。
问题
想象你有 N 个不同的 AI 应用(编程助手、研究 Agent、支持机器人)和 M 个不同服务(Slack、Jira、GitHub、Salesforce)。没有标准化时,每个应用都需要自定义代码对接每个服务——那是 N x M 个集成要构建与维护。
为何标准化重要
像 Model Context Protocol(MCP) 这类标准,旨在通过在 AI 应用与工具提供方之间创建通用接口来解决这个问题。每个应用实现一次协议。每个工具提供方实现一次协议。新应用自动与所有现有工具兼容,反之亦然。
可以想成 USB。 USB 之前,每个设备有自己的专有线缆。USB 之后,任何设备都能连任何电脑。工具标准旨在成为「AI 工具的 USB」。
选择框架或构建工具时,考虑其他 Agent 能否复用你的工具(建成 MCP server 以便移植),以及你的 Agent 能否使用其他来源的工具(支持 MCP 以获得广泛兼容)。标准仍在演进,请按你的时间线评估。
工具设计的常见坑
1. 工具太多淹没上下文
每个工具定义都占用模型上下文窗口空间。更重要的是,模型必须从全集中推理该用哪个工具。工具太多会导致:
- 决策瘫痪:模型难以选对工具
- 选错工具:相似工具多时,模型选错
- 浪费上下文:工具定义挤占有用的对话历史
多少工具算太多?
没有硬性规则,但有这些指引:
| 工具数量 | 指引 |
|---|---|
| 1-5 | 对多数模型没问题。无需特殊处理。 |
| 5-15 | 名称与描述清晰、彼此区分时效果好。 |
| 15-30 | 考虑分组相关工具,或用工具选择步骤。 |
| 30+ | 可能太多。用两阶段:先选类别,再选具体工具。 |
大型工具集的两阶段做法:
Stage 1: Model picks a category
"User wants to manage their order -> Category: Order Management"
Stage 2: Model sees only the tools in that category
[get_order, create_order, cancel_order, update_shipping]
-> Model picks: get_order
这让每个单独决策保持可管理。
2. 模糊的工具描述
若模型分不清何时用某个工具,就会用错或根本不用。
差:
{
"name": "lookup",
"description": "Looks up information"
}
关于什么的信息?模型何时该调用这个而不是别的工具?响应是什么格式?
好:
{
"name": "lookup_employee",
"description": "Look up an employee by name or employee ID. Returns their department, role, email, and manager. Use this when the user asks about a specific person at the company."
}
3. 薄 API 包装(泄漏实现细节)
常见错误是把原始 API 端点包装成工具,没有任何抽象。通用的 api_request(method, url, headers, body) 工具迫使模型知道你的 URL 结构、认证头与请求格式——它经常会弄错。
相反,构建像 get_customer_orders(customer_email, status_filter) 这样的专用工具,把 HTTP 细节藏在干净接口后面。你的代码处理认证、URL 构造与响应解析。模型只需说它想要什么。
4. 缺少错误信息
工具调用失败且错误消息无用时,Agent 会卡在重试循环或彻底放弃。
常见失败模式:
| 错误类型 | 差响应 | 好响应 |
|---|---|---|
| 未找到 | {"error": true} |
{"error": "CUSTOMER_NOT_FOUND", "message": "No customer with email 'jn@example.com'. Did you mean 'jane@example.com'?"} |
| 无效输入 | 500 Internal Server Error |
{"error": "INVALID_DATE_FORMAT", "message": "Expected date in YYYY-MM-DD format, got '03/15/2024'"} |
| 限流 | {"error": "fail"} |
{"error": "RATE_LIMITED", "message": "Too many requests. Try again in 30 seconds."} |
| 认证失败 | null |
{"error": "UNAUTHORIZED", "message": "API key expired. This tool is temporarily unavailable."} |
5. 返回太多数据
大工具响应会吃掉上下文窗口,并可能用不相关细节迷惑模型。管理策略:
- 在源头过滤:只从数据库/API 查询你需要的
- 选择相关字段:不要返回每个字段——挑 Agent 需要的
- 分页:返回子集,并提供获取更多的选项
- 摘要:对大文本响应,返回前先摘要
- 截断:限制长字符串字段(例如
message[:200])
6. 未处理工具超时
外部 API 可能慢或无响应。始终为 HTTP 调用设置超时,失败时返回描述性错误消息,而不是让 Agent 无限挂起。
工具设计检查清单
为 Agent 设计工具时用这份检查清单:
[ ] Name clearly describes the action (verb + noun)
[ ] Description explains what, when, and what it returns
[ ] Each tool has a single responsibility
[ ] Parameters have descriptive names (not abbreviations)
[ ] Parameters include descriptions with format examples
[ ] Required vs optional parameters are clearly marked
[ ] Output is concise - only fields the agent needs
[ ] Error messages are descriptive and actionable
[ ] Timeouts are implemented for external calls
[ ] Authentication is handled in the tool code, not by the model
[ ] Output size is bounded (truncation, pagination, or summarization)
[ ] Tool is tested independently before connecting to the agent
ELI5:什么是工具?
工具就像手机上的 App
想想你的智能手机。手机本身很聪明——有强处理器、好屏幕和好操作系统。但没有 App,它做不了多少事。
- 想查天气?需要天气 App。
- 想发消息?需要消息 App。
- 想导航?需要地图 App。
- 想拍照?需要相机 App。
每个 App 给手机新能力。手机本身不知道如何预报天气——它只知道如何打开天气 App、要预报、并显示结果。
LLM 就像没有 App 的手机。 它很聪明,但不能查真实天气、发真实消息或查真实路线。它只能根据训练时学到的内容谈论这些事。
工具就像给手机装 App。 每个工具给 Agent 新能力:
get_weather是天气 Appsend_email是邮件 Appsearch_database像针对你数据的专用搜索 Apprun_code像编程 App
就像用 App 一样:
- 手机(模型)根据你问什么决定打开哪个 App
- App(工具)做实际工作
- 手机把结果展示给你
设计工具时,你本质上是在为 Agent 建应用商店。名称清晰、功能有用的好 App 让手机更能干。名称混乱、行为有 bug 的烂 App 令人沮丧。
Google Cloud 上的工具
Google Cloud 提供多种方式给 Agent 工具:
Vertex AI 函数调用
Vertex AI 支持用 Gemini 模型做函数调用。你把工具定义为函数声明,模型会在适当时生成结构化函数调用。
Agent Development Kit(ADK)工具
Agent Development Kit(ADK)提供结构化方式为 Agent 定义与管理工具。ADK 支持:
- 函数工具:把任意 Python 函数包装成 Agent 工具
- 内置工具:Google Search、Code Execution 等
- Agent 工具:把另一个 ADK Agent 当作工具
- 第三方工具:与 LangChain 工具、CrewAI 工具和 MCP server 集成
ADK 处理工具定义格式、执行与结果传递,让你专注工具逻辑而非管道。
了解更多: ADK Tools Documentation
内置锚定工具
Google Search grounding 可作为 Vertex AI 上 Gemini 模型的内置工具。启用后,模型可搜索网页,把回复锚定在当前信息上——无需自定义工具代码。
综合起来:一个实践例子
让我们为一个简单的 DevOps Agent 设计工具集,帮助工程师调查生产问题。
Agent 的目的
帮助 on-call 工程师诊断并响应生产告警。
工具集设计
| 工具 | 目的 | 何时使用 |
|---|---|---|
get_alert_details |
获取特定告警详情 | 工程师询问某个告警时 |
query_metrics |
获取服务的时间序列指标 | 调查性能问题时 |
search_logs |
按服务与时间范围搜索应用日志 | 查找错误或模式时 |
list_recent_deployments |
显示服务的近期部署 | 检查是否部署引起问题时 |
get_runbook |
获取特定告警类型的 runbook | Agent 需要建议修复步骤时 |
create_incident |
创建事件工单 | 问题已确认且需要跟踪时 |
注意每个工具如何遵循最佳实践:清晰的动词+名词名称、解释「做什么/何时/返回什么」的描述、单一职责,以及带例子的描述性参数。
交互例子
Engineer: "Alert ALT-9876 just fired. What is going on?"
Agent thinking: "I need to look up this alert first."
-> Calls: get_alert_details(alert_id="ALT-9876")
<- Returns: {service: "auth-service", type: "high_latency", severity: "P1",
description: "p99 latency >2s for 5 minutes"}
Agent thinking: "High latency on auth-service. Let me check metrics and recent deploys."
-> Calls: query_metrics(service="auth-service", metric="latency_p99", time_range="1h")
-> Calls: list_recent_deployments(service="auth-service", limit=5)
<- Metrics show latency spike starting at 14:05
<- Deploy #456 happened at 14:02
Agent thinking: "Latency spiked 3 minutes after a deploy. Let me check error logs."
-> Calls: search_logs(service="auth-service", query="timeout OR slow", level="error", time_range="1h")
<- Logs show: "Database connection pool exhausted" errors starting at 14:05
Agent thinking: "The deploy likely changed something that exhausts the DB connection pool.
Let me get the runbook for guidance."
-> Calls: get_runbook(alert_type="high_latency")
<- Runbook: Step 1: Check recent deploys. Step 2: Compare config changes...
Agent responds:
"Here is what I found for ALT-9876:
**Issue:** auth-service p99 latency spiked above 2 seconds starting at 14:05.
**Likely cause:** Deploy #456 (at 14:02) appears to have triggered database connection pool
exhaustion. Error logs show 'Database connection pool exhausted' errors starting 3 minutes
after the deploy.
**Recommended actions:**
1. Review the changes in deploy #456, particularly any database configuration changes
2. Consider rolling back deploy #456 if the latency is impacting users
3. Check the connection pool size setting - it may need to be increased
Would you like me to create an incident ticket for this?"
此例说明:设计良好、名称清晰、描述到位、输出简洁的工具,如何让 Agent 通过多步高效调查问题。
关键要点
工具弥合思考与行动之间的鸿沟。 没有工具,Agent 只是聊天机器人。有了工具,它可以与真实系统和数据交互。
模型从不直接执行工具。 它提出工具调用。你的代码执行它们。你始终掌控实际发生什么。
三类工具:函数工具(自定义)、内置工具(平台提供)、Agent 工具(把另一个 Agent 当作工具)。对你的具体用例从函数工具开始。
工具设计是面向模型的 UX 设计。 清晰名称、描述性参数、简洁输出与有用的错误消息,决定模型是用得好还是挣扎。
留意工具数量。 更多工具意味着更多上下文占用与更难的决策。让工具集聚焦 Agent 真正需要的。
标准化降低集成负担。 MCP 等协议旨在通过 AI 应用与工具之间的通用接口解决 N x M 问题。
接下来?
现在我们理解了大脑(LLM)与双手(工具),下一课用编排层把它们合在一起——管理 Agent 如何思考、行动、观察并重复直到完成工作的控制循环。
评论