05.1 模型只会说话,工具让它动手
我第一次给 Agent 接工具时,激动得不行。因为那一刻它从"只会聊"变成"真能干活"。
模型本身只能"想"和"说"。查数据库、发短信、改文件,它一样都干不了。
工具就是它的手。这是 Flue 里最关键的一步——把你的业务能力交给 Agent。
05.2 一个工具的四部分
我用 defineTool 定义工具。它有四个零件:
// src/shared/order-tools.ts
import { Type, defineTool } from '@flue/runtime';
export const lookupOrderStatus = defineTool({
name: 'lookup_order_status', // 1. 模型调用的名字
description: 'Look up order fulfillment status.', // 2. 帮模型判断何时用
parameters: Type.Object({ // 3. 输入参数(用 Type,非 valibot)
orderId: Type.String({ description: 'Order ID like order_1234' }),
}),
execute: async ({ orderId }) => { // 4. 真正干活的代码
// 这行是关键:返回字符串给模型
return `Order ${orderId} is packed.`;
},
});
四部分拆开看:名字、说明、参数、执行体。
注意:
execute必须返回字符串。返回对象会出错。要返回结构化数据,用
JSON.stringify(...)包一层。
05.3 工具不是鉴权边界
这点特别重要。模型的输入不可信,但凭证必须由你的代码给。
// 正确:客户 id 由代码定,模型只能选订单号
export default createAgent(({ id: customerId }) => ({
model: 'anthropic/claude-haiku-4-5',
tools: [
defineTool({
name: 'lookup_customer_order',
parameters: Type.Object({ orderId: Type.String() }),
execute: async ({ orderId }) => {
// 模型选订单号,但查哪个客户由代码定
return await orders.getStatus(customerId, String(orderId));
},
}),
],
}));
模型能选订单号,但选不了客户。凭证和越权防护,永远握在你代码手里。
标叔的经验:工具里别放密钥。
我见过有人把 API Key 写进工具参数描述,想让模型自己带。危险。密钥在
env里,代码注入,模型只传业务参数。
05.4 接 MCP 生态
你不用自己写所有工具。MCP 服务器能提供远程工具:
const inventory = await connectMcpServer('inventory', {
url: env.INVENTORY_MCP_URL,
headers: { Authorization: `Bearer ${env.INVENTORY_MCP_TOKEN}` },
});
// 连上后,工具名会变成 mcp__inventory__lookup_item
const harness = await init(agent, { tools: inventory.tools });
| 方式 | 适合 | 标叔的结论 |
|---|---|---|
| 自己写 defineTool | 业务逻辑、内部 API | 最灵活,首选 |
| 接 MCP 服务器 | 现成生态工具 | 省事,但多一跳网络 |
工具讲透了。下一章讲"技能"和"子智能体",让 Agent 既专业又会分工。