编码母题 / P0 / Agent 运行时 / 01

工具注册表与工具执行器

先读清题意与边界,独立尝试,再展开思路并对照可运行的参考代码。

重要程度必练难度中等预计完成35–45 分钟

难度按 LeetCode 简单/中等/困难三档估计;工程题并非 LeetCode 官方题目。用时指独立编码并检查主要边界的参考时间,因经验而异。

题目是什么

把工具注册表与执行器分开。工具声明名称、校验器和执行函数;调用结果统一结构化,明确区分未知工具、参数错误、执行失败和取消。

输入输出示例

未知工具应返回 TOOL_NOT_FOUND,而不是直接抛出内部异常或暴露 stack。

练习起手式与验证

复制代码后补全空方法,再运行下方三组验证;复杂题所需的辅助类型与输入准备一并放在代码块中。

/*
 * 题目:工具注册表与工具执行器
 * 要求:把工具注册表与执行器分开。工具声明名称、校验器和执行函数;调用结果统一结构化,
 * 明确区分未知工具、参数错误、执行失败和取消。
 * 示例:未知工具应返回 TOOL_NOT_FOUND,而不是直接抛出内部异常或暴露 stack。
 */
class ToolRegistry {
  constructor() {}
  register(tool) {}
  get(name) {}
  list() {}
}
class ToolExecutor {
  constructor(registry) {}
  async execute(call, context = {}) {}
}

(async () => {
  const registry = new ToolRegistry().register({
    name: "add",
    validate: (x) => ({
      ok: Number.isFinite(x?.a) && Number.isFinite(x?.b),
      message: "a/b required",
    }),
    execute: ({ a, b }) => a + b,
  });
  const executor = new ToolExecutor(registry);
  console.log(await executor.execute({ id: "1", name: "add", arguments: { a: 2, b: 3 } })); // 预期:{ toolCallId: "1", toolName: "add", ok: true, output: 5 }
  console.log((await executor.execute({ id: "2", name: "missing" })).error.code); // 预期:"TOOL_NOT_FOUND"
  console.log((await executor.execute({ id: "3", name: "add", arguments: {} })).error
      .code); // 预期:"INVALID_ARGUMENTS"
})().catch(console.error);

解题思路

展开思路、不变量与常见边界

关键切入点:Registry 管静态能力,Executor 管校验、取消、异常隔离和结果边界。

核心模型:Registry 负责静态能力发现,Executor 负责运行时边界。一次调用必须 关联 toolCallId;执行前验证,执行后统一封装。工具异常被隔离为结果而非击穿 整个批次。生产环境可在此叠加超时、权限、Trace 和幂等。

参考代码与验证

“关键机制”注释用于解释实现,下载链接提供未经页面注释修改的原始 JS 文件。

// 关键机制:Registry 管静态能力,Executor 管校验、取消、异常隔离和结果边界。
class ToolRegistry {
  constructor() {
    this.tools = new Map();
  }
  register(tool) {
    if (
      !tool ||
      typeof tool.name !== "string" ||
      typeof tool.execute !== "function"
    )
      throw new TypeError("invalid tool");
    if (this.tools.has(tool.name))
      throw new Error(`duplicate tool: ${tool.name}`);
    this.tools.set(tool.name, tool);
    return this;
  }
  get(name) {
    return this.tools.get(name);
  }
  list() {
    return [...this.tools.values()].map(({ name, description, schema }) => ({
      name,
      description,
      schema,
    }));
  }
}
class ToolExecutor {
  constructor(registry) {
    this.registry = registry;
  }
  async execute(call, context = {}) {
    const base = { toolCallId: call?.id, toolName: call?.name };
    const tool = this.registry.get(call?.name);
    if (!tool)
      return {
        ...base,
        ok: false,
        error: { code: "TOOL_NOT_FOUND", message: "tool not found" },
      };
    try {
      if (context.signal?.aborted)
        throw context.signal.reason || new Error("aborted");
      const validation = tool.validate
        ? tool.validate(call.arguments)
        : { ok: true };
      if (validation === false || validation?.ok === false)
        return {
          ...base,
          ok: false,
          error: {
            code: "INVALID_ARGUMENTS",
            message: validation?.message || "invalid arguments",
          },
        };
      const output = await tool.execute(call.arguments, context);
      return { ...base, ok: true, output };
    } catch (error) {
      const aborted = context.signal?.aborted;
      return {
        ...base,
        ok: false,
        error: {
          code: aborted ? "ABORTED" : "TOOL_EXECUTION_FAILED",
          message: aborted ? "tool execution aborted" : "tool execution failed",
        },
      };
    }
  }
}

// 三组以上可直接运行的验证;异步题使用独立执行块。
(async () => {
  const registry = new ToolRegistry().register({
    name: "add",
    validate: (x) => ({
      ok: Number.isFinite(x?.a) && Number.isFinite(x?.b),
      message: "a/b required",
    }),
    execute: ({ a, b }) => a + b,
  });
  const executor = new ToolExecutor(registry);
  console.log(await executor.execute({ id: "1", name: "add", arguments: { a: 2, b: 3 } })); // 预期:{ toolCallId: "1", toolName: "add", ok: true, output: 5 }
  console.log((await executor.execute({ id: "2", name: "missing" })).error.code); // 预期:"TOOL_NOT_FOUND"
  console.log((await executor.execute({ id: "3", name: "add", arguments: {} })).error
      .code); // 预期:"INVALID_ARGUMENTS"


})().catch(console.error);

复杂度分析

时间复杂度:O(1) 执行开销。单次调用只查找、校验并派发一个工具;工具自身耗时另计。

空间复杂度:O(T)。注册表保存 T 个工具描述,单次执行临时对象为常数级。

下载原始 JS 文件