Flue Tools 完全指南:为 Agent 定义、挂载与保护工具调用
发布时间:2026/9/16 11:01:15 锦皓数字建站

Flue Tools 完全指南为 Agent 定义、挂载与保护工具调用【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue这篇技术指南以 Fluesandbox agent framework的 Tools 机制为核心讲解如何用defineTool定义自定义工具、用useTool把它挂载进 Agent、理解一次工具调用的完整生命周期以及沙箱内置工具、harness 工具、durable 持久化工具、条件工具、访问保护与 MCP 服务器的接入。读完你将从零写出可复用、可恢复、可安全授权给模型调用的生产级工具。什么是 Tool在 Flue 中tool工具是你编写的一个函数把它用文字描述给模型模型在运行过程中可以按需调用它——查一笔订单、提交一个工单、发起一笔退款。职责边界非常清晰模型决定“何时调用”when to call你的代码决定“发生什么”what happens。与此相对skill 提供可复用的指令文本sandbox 提供文件与命令访问能力而 tool 执行的是你应用自身的代码。本篇指南将覆盖用useTool定义并挂载自定义工具、沙箱自带的文件与 Shell 工具、harness 工具、durable 持久化工具、条件工具以及如何保护工具所能访问的资源。第一个工具四要素与defineTool一个工具定义由四个部分组成模型调用它所使用的name、教会模型何时使用的description、可选的参数inputschema以及包含你业务代码的run函数。用defineTool(...)定义import { defineTool } from flue/runtime; import * as v from valibot; import { orders } from ../shared/orders.ts; export const lookupOrder defineTool({ name: lookup_order, description: Look up one order by id and return its current status., input: v.object({ orderId: v.string() }), async run({ data }) { const order await orders.get(data.orderId); return { output: { status: order.status, eta: order.eta } }; }, });然后在 Agent 中用useToolhook 挂载它use agent; import { useModel, useTool } from flue/runtime; import { lookupOrder } from ../tools/lookup-order.ts; export function OrderAssistant() { useModel(anthropic/claude-haiku-4-5); useTool(lookupOrder); return Help customers check the status of their orders.; }模型读取工具的 name、description 和 input schema当它判断该工具适用时就会携带参数发起调用Flue 用 schema 校验参数、执行你的run函数、再把结果返回给模型。关于defineTool的校验与冻结从源码看tool.tsdefineTool会先调用assertToolDefinition做严格校验——name/description必须是非空字符串、input必须是top-level 的 Valibot 对象 schema、harness/durable必须是布尔值、run必须是函数并且拒绝任何未声明字段比如把harnes: true拼错会在定义期直接抛错而不是等到模型调用期才崩溃。校验通过后返回Object.freeze(...)冻结对象这正是适合从src/tools/目录导出、跨 Agent 共享的自然形态。对于一次性使用的工具useTool也接受内联的相同定义对象见下文 条件工具。无论哪种方式每个激活的工具必须有唯一名字重名、或与框架保留名如task、activate_skill冲突会在工具集合装配时抛出异常。一次工具调用的完整生命周期模型看到什么每个挂载的工具以name、description和inputschema 呈现给模型schema 会转换为 JSON Schema没有inputschema 的工具呈现空对象。description 是模型唯一的“文档”要写清楚这个工具做什么、何时使用、返回什么。模糊的描述是工具被错误调用或根本不被调用的最常见原因。输入Valibot schema 解析input是 Valibot 的实现得到印证解析失败会抛出ToolInputValidationError。输出结果信封{ output?, terminate? }run返回的是结果信封{ output?, terminate? }而不是裸值output是 JSON 兼容数据对象、数组、字符串、数字——任何可 JSON 序列化的内容会被 JSON 字符串化后交给模型裸字符串返回是{ output: string }的简写只有未声明outputschema 时才允许不返回任何值其他任何裸返回都会抛错terminate: true会在当前工具批次落定后结束 Agent 的这一轮与内置finish/give_up使用同一契约当返回形状也需要类型化与校验时可额外声明outputschema。从 resolveToolRun / coerceToolRunEnvelope 源码看信封契约是严格的返回普通对象时任何未知键都会抛错必须显式return { output: value }terminate必须是布尔值返回 number、boolean、array、null 或类实例等裸值都会被明确拒绝并给出迁移提示。这个解析点是所有执行方实时自定义工具、durable 恢复重放、独立运行的唯一入口保证了语法糖与拒绝规则永不分叉。带outputschema 的完整示例const checkInventory defineTool({ name: check_inventory, description: Check the stock level for one SKU., input: v.object({ sku: v.string() }), output: v.object({ inStock: v.number(), warehouse: v.string() }), async run({ data }) { return { output: await inventory.lookup(data.sku) }; }, });错误处理throw 不会搞崩 Agentrun内部的 throw 不会导致 Agent 崩溃而是变成模型可见的错误结果让它重试、换一种方法或告知用户。请务必 throw或返回描述性的失败值而不要吞掉错误——模型只能对它能看见的失败做出响应。上下文中的其他成员除了data每次run还会收到signal—— 本次调用的AbortSignal。把它传给自己的异步工作取消的工具调用才能及时停止。即使run忽略信号也不会卡死 Agent信号触发时运行时放弃 await调用以AbortError失败提示工作可能仍在运行孤儿 Promise 的最终结果会被丢弃log—— 面向长时间运行工具的进度日志log.info(...)、log.warn(...)、log.error(...)。日志行以事件形式流入会话供应用观察它们不属于结果的一部分模型永远看不到。从 tool-types.ts 源码可见其契约toolCallId—— 本次特定调用的 id与该调用的会话事件携带的 id 相同。可用它把副作用与引发副作用的调用关联起来。定义上的可选标志会进一步扩展上下文harness: true增加harnessdurable: true增加step二者在下文详述。完整契约见defineTool参考。沙箱内置工具拥有 sandbox 的 Agent 会获得一组操作它的标准内置工具没有沙箱时这些工具不在集合里——模型无法调用不存在的东西工具作用read读取文件截断到 2000 行或 50KB支持 offset/limitwrite写入文件按需创建文件与父目录edit按精确文本替换编辑文件bash执行 Shell 命令并返回 stdout/stderrgrep按正则表达式搜索文件内容glob按文件名模式查找文件每个工具的参数、截断限制与错误行为详见 Agent Behavior — Built-in tools。在这组工具之上框架会在能力具备时追加自己的工具task用于 subagent 委派始终存在、Agent 拥有 skills 时出现activate_skill、skill 打包了资源文件时出现read_skill_resource。这些名字是保留的——自定义工具不能占用它们。沙箱适配器可以替换这套工具集合详见 Sandbox-provided tools 与 Sandbox Adapter API 中的SandboxToolFactory。从 sandbox.ts 源码可看到这类保障的实现细节例如write工具的“自动创建父目录”由统一的writeFileCreatingParents辅助函数保证——先尝试写入快乐路径只花一次调用失败时再逐级创建父目录并重试本地沙箱、bash factory 与 SandboxDriver 包装器共享这一条契约。Harness 工具触达 Agent 运行时普通工具是其输入的纯函数数据进、结果出。当工具需要回触 Agent 自身的运行时——它的沙箱或模型本身——声明harness: true。此时run函数会收到harness这个同时面向两者的接口harness.sandbox—— Agent 的实时环境readFile、writeFile、exec以及其它 sandbox verbs直接触碰且不产生会话记录。Agent 未声明 sandbox 时抛错。从 harness.ts 源码看sandbox是一个实时 getter 而非快照条件式useSandbox()可能在轮次边界切换环境此接口会跟随因此做条件切换的代码不应跨边界缓存返回的引用harness.prompt(text, options?)—— 在 harness 自己的 scratch 会话中运行一次模型操作。重复调用会延续该会话因此后续 prompt 能看到前面调用建立的内容。传options.result一个 Valibot schema可要求校验过的结构化数据传options.tools可仅为该次操作提供额外工具。一个 harness 工具可以在一次工具调用背后完成“暂存输入 → 运行聚焦的模型工作 → 校验结果”import { defineTool } from flue/runtime; import * as v from valibot; const Report v.object({ riskLevel: v.picklist([low, medium, high]), summary: v.string() }); export const reviewContract defineTool({ name: review_contract, description: Review one supplied contract and return a structured risk report., input: v.object({ contract: v.string() }), harness: true, async run({ harness, data }) { await harness.sandbox.writeFile(contract.md, data.contract); const { data: report } await harness.prompt( Review contract.md for non-standard terms and assess the risk., { result: Report }, ); return { output: report }; }, });Harness 调用的作用域是本次工具调用harness 在调用运行时物化、落定时关闭。它们计入委派深度上限且其开启的任何子会话都会保留在父会话上供检视——与委派 subagent 的记账方式相同。因为 harness 只存在于 Agent 会话内harness: true的工具永远不会独立运行不带该标志的工具则完全无法触达运行时从 validateAndRunTool 源码可见独立运行时声明harness: true会直接抛错。完整表面见 Harness 参考。Durable 工具崩溃后恢复不重放副作用当进程在一轮中间崩溃时Flue 会从其 durable 记录中恢复会话——但一个正在飞行中的普通工具调用不会被重新执行。运行时无法知道哪些副作用已经发生所以被打断的调用以“结果未知”错误落定模型从那里继续。对于必须完成的工作——一笔支付、一次多步同步、一个资源开通任务——把工具声明为durable: true。这会把它切换到另一套契约run收到step每个副作用都通过step.do(name, fn)进行import { defineTool } from flue/runtime; import * as v from valibot; import { billing, projects, DEFAULT_PROJECTS } from ../shared/provisioning.ts; export const provisionWorkspace defineTool({ name: provision_workspace, description: Provision a customer workspace: create the tenant, then seed each default project., input: v.object({ customerId: v.string() }), durable: true, async run({ data, step }) { const tenant await step.do(create-tenant, () billing.createTenant(data.customerId)); for (const project of DEFAULT_PROJECTS) { await step.do(seed:${project.name}, () projects.seed(tenant.id, project)); } return { output: { tenantId: tenant.id, projects: DEFAULT_PROJECTS.length } }; }, });step.do(name, fn)对本次工具调用按名字对fn只执行一次并在 resolve 之前持久化记录其返回值。当运行中途被打断时恢复会重新执行整个调用已完成的步骤直接返回记录值而不再运行执行从第一个未完成的步骤继续。如果上面的例子崩溃在create-tenant与第三个seed:步骤之间重放会从记录中恢复租户和前两个 seed然后从第三个继续。Durable 工具的四条铁律一切有副作用的操作都放进 step。step 之间的代码在恢复时会重新执行所以要让它廉价且无副作用——只做派生值、分支、循环名字标识工作。名字必须确定性派生seed:${project.name}绝不能来自随机数或时间在一次调用内复用名字会抛错。这一点由 claimStepName 在运行时强制空名字、以及同一调用内重复使用同一名字都会直接抛错防止恢复时两个步骤静默混用同一个 memo值是 JSON 且应保持小巧。大型工件存入沙箱记录一个指针即可。step.do的值还会通过 cloneStepValue 强制 JSON 可序列化契约步骤是“记录恰好一次、执行至少一次”。在步骤完成与记录落定之间的狭窄窗口内崩溃会重跑该步骤因此涉及外部效应的步骤应各自具备幂等性。步骤记录是运行层面的记账模型只看到工具的最终结果步骤进度以调用日志事件的形式实时浮现。抛错不是中断——和普通工具一样durable 工具抛错会以模型可见的工具错误落定不会自动重试。步骤的作用域是一次调用模型再次调用该工具时它们重新开始。两个标志可以组合——durable: true, harness: true的工具同时收到step和harness把harness.prompt(...)包进 step恢复时就不会重新 prompt。参见 Durability 了解它在更大恢复模型中的位置。条件工具用渲染逻辑控制工具“存在性”Agent 函数在每次模型调用前都会重新渲染每次渲染都从零声明它的工具集合。这让工具的存在与否成为程序逻辑的一部分把useTool包进条件里工具就只存在于条件成立的渲染中。配合 persistent state 使用Agent 就能解锁自己的能力use agent; import { useModel, usePersistentState, useTool } from flue/runtime; import * as v from valibot; import { approvals } from ../shared/approvals.ts; import { publishRelease } from ../tools/publish-release.ts; export function ReleaseManager() { useModel(anthropic/claude-sonnet-4-6); const [approved, setApproved] usePersistentState(approved, false); useTool({ name: record_approval, description: Record an operator approval code for this release., input: v.object({ code: v.string() }), async run({ data }) { if (!(await approvals.verify(data.code))) return Invalid approval code.; setApproved(true); return Approval recorded. The publish tool is now available.; }, }); if (approved) useTool(publishRelease); return Prepare the release. Publishing unlocks once an operator approves.; }在操作员批准之前publish_release不存在——未挂载的工具不可能被调用这比“指示模型不要用它”强得多。当集合在渲染之间变化时运行时会下一个轮次边界把差异以resources信号告知模型“New tool available: …”保持对话记录的连贯。具体叙述方式见 Dynamic resources。注意变更工具集合会重写 provider 的 tools 数组从而失效其 prompt 缓存所以应把工具门控在很少变化的状态上。例外是“由一次已完成的工具调用解锁”的情况正如这里record_approval解锁publish_release当前 Anthropic 模型除 Haiku 外会在它出现的对话位置加载其定义缓存得以保留。这种工具构建方式与 custom hooks 天然互补一个把门控、工具与配套指令打包在一起的useEscalation()hook可以分享给所有需要相同行为的 Agent。保护访问参数不是授权边界工具的参数是模型选择的输入不是授权边界。你的应用应该决定工具能使用哪个客户、账户、仓库或凭据然后只让模型在该边界内选择值。对于接收按客户分派事件的 Agent——一个支持系统 webhook、一条聊天平台消息——把应用已验证的授权标识放在投递信号delivered signal的attributes里用useDelivery()读取它而不是信任模型提供的值use agent; import { useDelivery, useModel, useTool } from flue/runtime; import * as v from valibot; import { orders } from ../shared/orders.ts; export function CustomerOrders() { useModel(anthropic/claude-haiku-4-5); const delivery useDelivery(); const customerId delivery.kind signal ? delivery.attributes?.customerId : undefined; useTool({ name: lookup_customer_order, description: Look up one order belonging to this customer., input: v.object({ orderId: v.string() }), async run({ data }) { const status customerId ? await orders.getStatus(customerId, data.orderId) : undefined; return status ?? No accessible order was found.; }, }); return Help this customer check the status of their orders.; }模型可以选择要查询的订单 ID但它不能选择查询所用的客户——customerId来自投递信号的attributes由调用dispatch(...)的可信代码设置。你的路由或分派代码仍然必须在附加该标识前验证调用者参见 Agents 与 Routing。同一原则适用于工具触碰任何模型不应选择之处的场景在 harness 工具 内部以及包装 provider SDK 的工具中——可信代码通过闭包或配置绑定 token、仓库或目的地工具只暴露那个窄动作。参见 Channels 指南中的 Use provider SDKs除非应用有明确的授权设计否则避免暴露任意目的地或 API 方法的通用 provider 工具。连接 MCP 服务器远程 MCP。从 use-mcp-connection.ts 源码可以看到几个关键语义声明在每次提交初始化时读取一次、所有声明的服务器并行连接连接在实例内存生命周期内复用auth是例外——每次请求时重新解析这是接入按用户或轮换凭据的接缝把持久键如用户 id 放在 resolver 的闭包里取当前 tokentoken 永不触碰持久状态tools字段按服务器自身的工具名做挂载白名单服务器未暴露的名字会报错。若要在子代理渲染中使用需注意useMcpConnection在 subagent 渲染中不可用——应在根 Agent 上声明连接并通过父级的工具把需要的东西交给委派。下一步Agent Hooks ——useTool所属的 hook 模型包括持久状态与自定义 hooks。Agent API —— 完整的defineTool、useTool、ToolContext与 harness 契约。Sandboxes —— 带来内置文件与 Shell 工具的环境。Subagents —— 通过内置task工具委派聚焦的工作。Durability —— 会话、状态与 durable 工具步骤如何被恢复。【免费下载链接】flueThe sandbox agent framework.项目地址: https://gitcode.com/GitHub_Trending/flue1/flue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。