Skip to content

Chapter 6 · Type-Safe Tools ​

Chapter goals: Master the four-element definition of defineTool() and useTool() mounting, understand the complete lifecycle and error semantics of tool calls, and write tools that models love to use correctly.

6.1 What a Tool Is (and Isn't) ​

A tool is a function you写 that exposes "what the model might need to do" to it: look up orders, create tickets, process refunds. The key division of labor:

  • The model decides when to call — it reads the tool's name, description, and参数模式 then自主决策;
  • Your code decides how to execute — the run function contains your 100%可控 application logic.

Difference from skills (Chapter 7): skills teach "process knowledge"; tools give "execution capability." Difference from sandboxes: sandboxes provide file and shell环境; tools连 your application systems.

6.2 defineTool Four Elements ​

typescript
// src/tools/lookup-order.ts
import { defineTool } from '@flue/runtime';
import * as v from 'valibot';
import { orders } from '../shared/orders.ts';

export const lookupOrder = defineTool({
  // ① name: the name the model uses when calling (globally unique, snake_case convention)
  name: 'lookup_order',
  // ② description: the model's only "manual" — clearly state what it does, when to use it, what it returns
  description: 'Query the current status of an order by order ID. Use when the user asks about order progress or delivery time.',
  // ③ input: Valibot top-level object schema; model params pass validation before entering run
  input: v.object({
    orderId: v.string(),
  }),
  // ④ run: your execution code. data is the validated强类型参数
  async run({ data }) {
    const order = await orders.get(data.orderId);
    // Result envelope: { output } not a bare value
    return { output: { status: order.status, eta: order.eta } };
  },
});
typescript
// src/agents/order-assistant.ts —— mount the tool
'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); // One line mount; can mount multiple
  return 'Help customers query order status and estimated delivery time.';
}

defineTool() validates the definition and freezes the return — naturally suited for the src/tools/ directory shared across agents; one-off tools can also inline the定义 object directly into useTool({...}).

6.3 Complete Journey of a Tool Call ​

text
Model sees name + description + input (JSON Schema)
        │ Model decides to call, produces parameters
        ▼

Flue validates params against input schema
        │ Validation fails → error fed back to model (run不执行), model修正重试
        ▼
run({ data, signal, log, toolCallId }) executes
        │ throw → becomes a model-visible错误结果; agent doesn't crash
        ▼
{ output } serialized and fed back to model; reasoning continues

Three semantices必须理解:

typescript
// Semantics 1: output envelope. output must be JSON-serializable;
// bare string is shorthand for { output: <string> }
const simple = defineTool({
  name: 'ping',
  description: 'Connectivity test.',
  async run() {
    return 'pong'; // Shorthand, equivalent to { output: 'pong' }
  },
});

// Semantics 2: terminate. End the current round (same as finish/give_up contract)
const submitReport = defineTool({
  name: 'submit_report',
  description: 'Submit the final audit report and end this round of work.',
  input: v.object({ markdown: v.string() }),
  async run({ data }) {
    await reports.save(data.markdown);
    return { output: 'Report submitted', terminate: true };
  },
});

// Semantics 3: throw doesn't crash. Errors are model-visible feedback
const risky = defineTool({
  name: 'deploy_service',
  description: 'Deploy the specified service to production.',
  input: v.object({ service: v.string() }),
  async run({ data }) {
    if (!allowedServices.has(data.service)) {
      // The model sees this message and tries an alternative or tells the user
      throw new Error(`Service ${data.service} is not on the whitelist; deployment prohibited`);
    }
    return { output: await doDeploy(data.service) };
  },
});

Don't swallow errors

The model can only反应 to failures it can see. Catching an exception and returning vague success misleads the model into thinking the操作 succeeded — this is更危险 than throwing直接.

6.4 Extra Context in run and Output Validation ​

Besides data, run can also receive:

typescript
const searchCode = defineTool({
  name: 'search_code',
  description: 'Search for code snippets in the repository.',
  input: v.object({ query: v.string() }),
  // Optional: declare output schema; return values also validated and强类型化
  output: v.object({ matches: v.number(), files: v.array(v.string()) }),
  async run({ data, signal, log, toolCallId }) {
    // signal: abort signal. Pass to your async ops; stop promptly on cancellation
    const res = await github.search(data.query, { signal });
    // log: progress log. Flows into session events; model doesn't see this
    log.info(`Search for "${data.query}" hit ${res.total_count} locations`);
    // toolCallId: unique ID for this call; correlate with external side effects
    metrics.track('code_search', { callId: toolCallId });
    return { output: { matches: res.total_count, files: res.items.map(i => i.path) } };
  },
});

Naming conflict rule: tool names must be unique within a single render, and cannot占用 framework-reserved names (task, activate_skill, read_skill_resource); otherwise the装配 throws on collision.

Agents with沙箱 auto-get built-in tools: read, write, edit, bash, grep, glob — without a sandbox, these tools don't exist and the model can't call them.

6.5 Tool Design Best Practices ​

typescript
// ❌ Anti-pattern: vague description, broad params, no error path
const badTool = defineTool({
  name: 'do_stuff',
  description: 'Process data.',
  input: v.object({ payload: v.any() }),
  async run() { /* ... */ return { output: 'ok' }; },
});

// ✅ Good tool: description含触发时机, params精确, error info actionable
const refundOrder = defineTool({
  name: 'refund_order',
  description:
    'Initiate a refund for an order. Use only when the user explicitly requests a refund AND the order status is delivered. '
    + 'Returns the refund transaction ID; returns the rejection reason if the order does not meet conditions.',
  input: v.object({
    orderId: v.describe(v.string(), 'Order ID, format: ORD-2024-xxxx'),
    reason: v.picklist(['damaged', 'not_received', 'changed_mind']),
  }),
  async run({ data }) {
    const order = await orders.get(data.orderId);
    if (order.status !== 'delivered') {
      throw new Error(`Order status is ${order.status}; only delivered orders can be refunded`);
    }
    const refund = await payments.refund(order, data.reason);
    return { output: { refundId: refund.id, amount: refund.amount } };
  },
});

Description含触发时机 ("use only when..."), params narrowed with picklist, error info tells the model what to do next — these three points directly决定 the model's tool-call accuracy.

6.6 Chapter Summary ​

  • Tool = model decision + your code execution; four elements: name / description / input / run;
  • Params validated by Valibot before entering run; output goes through { output, terminate? } envelope;
  • throw becomes a model-visible error反馈; agent doesn't crash; don't swallow errors;
  • run also receives signal / log / toolCallId; output schema is optional for return validation;
  • Good description = capability + trigger timing — this is the key to correct tool调用.

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. In defineTool, which part is the model NOT able to "see" and base decisions on?

2. What happens when run function参数validation fails?

3. What does { output, terminate: true } in a tool's return value do?

4. Which is the most important factor for the model to call a tool correctly?

🛠️ Hands-On Exercises ​

  1. Wrap a public API (e.g., weather, exchange rate) as a tool;写清触发时机 in the description; test the model's call accuracy.
  2. Add an output schema to a tool;故意 return the wrong type; observe the runtime validation behavior.
  3. Write a tool with whitelist validation; trigger with非法 params; confirm the model understands the error and取替代路径.

Tools mastered. Next, learn Skills — on-demand domain知识包.