ツールはエージェントが外部世界に作用する手段であり、エージェント開発で最も多く実装するコンポーネントである。
defineTool関数でツールを定義する。
function defineTool<TInput, TOutput>(
config: ToolConfig<TInput, TOutput>
): ToolDefinition<TInput, TOutput> {
return Object.freeze({
name: config.name ?? "",
description: config.description,
inputSchema: config.inputSchema,
needsApproval: config.needsApproval ?? "never",
clientSide: config.clientSide ?? false,
execute: config.execute,
});
}
ツール定義は3つの要素から構成される。
descriptionはLLMがツールの用途を判断するための説明文で、ツール選択の精度に直結する。
inputSchemaはZodスキーマで引数の型と制約を定義し、LLMへの仕様伝達と実行時のバリデーションの二重の役割を持つ。
executeは実際の処理を行う非同期関数である。
具体例として天気情報を取得するツールを見る。
import { defineTool } from "nimbus/tools";
import { z } from "zod/v4";
export default defineTool({
name: "get-weather",
description: "Get the current weather for a given city",
inputSchema: z.object({
city: z.string().describe("City name, e.g. 'Tokyo'"),
}),
async execute({ city }) {
const conditions = ["sunny", "cloudy", "rainy"];
const condition = conditions[city.length % conditions.length];
const tempC = 15 + (city.length % 20);
return { city, condition, temperature: { celsius: tempC } };
},
});
nameはツールの識別子で、LLMがツールを呼び出す際に使用する。
省略した場合、ファイルシステム規約によりagent/tools/get-weather.tsに配置すればget-weatherが自動付与される。
.describe()でフィールドの説明を付与すると、LLMがどのような値を渡すべきか判断しやすくなる。
承認ワークフロー
承認ワークフロー(Human-in-the-Loop)は、ツールの実行前に人間の判断を挟む仕組みである。
needsApprovalオプションで制御する。
const approval = {
always: (): ApprovalConfig => "always",
never: (): ApprovalConfig => "never",
once: (): ApprovalConfig => "once",
};
function requiresApproval(tool: ToolDefinition, args: unknown): boolean {
const config = tool.needsApproval;
if (config === true || config === "always") return true;
if (config === false || config === "never") return false;
if (config === "once") return true;
if (typeof config === "function") return config(args);
return false;
}
"always"はすべての呼び出しで承認を要求する。
"once"は初回のみ要求し、以降は自動承認する。
関数を渡せば、引数の内容に基づいた条件付き承認を実装できる。
たとえば、削除操作だけ承認を要求し、参照操作は自動承認するといった制御が可能になる。
ツール呼び出しの状態遷移
ツール呼び出しは7つの状態を遷移する。
この状態遷移はコンパニオンオブジェクトパターンで実装されている。
TypeScriptでは型と値が同じ名前を共有できるため、ToolCallを型としても値としても使える。
const ToolCall = {
create: (toolCallId, toolName, args): PendingToolCall => ({
kind: "Pending", toolCallId, toolName, args,
}),
requestApproval: (call: PendingToolCall): AwaitingApprovalToolCall => ({
kind: "AwaitingApproval",
toolCallId: call.toolCallId,
toolName: call.toolName,
args: call.args,
requestedAt: Date.now(),
}),
approve: (call: AwaitingApprovalToolCall, approvedBy): ApprovedToolCall => ({
kind: "Approved",
// ... 元の呼び出しのフィールドを引き継ぐ
approvedAt: Date.now(),
approvedBy,
}),
// ... reject, startExecution, complete, fail
} as const;
遷移関数は入力の型を制約している。
approveはAwaitingApprovalToolCall型しか受け取らない。
Pending状態のツール呼び出しを直接承認しようとすると、コンパイルエラーになる。
不正な遷移を型システムが静的に排除する。