60 秒回答模板

我会把 Skill 系统拆成三条主链路。注册阶段,Skill 提供 manifest,包括 name、description、inputSchema、outputSchema、permissions、version、handler 和 timeout,注册表校验名称唯一、schema 合法和权限声明完整。发现阶段,根据用户任务、关键词、标签、权限和运行环境返回候选 Skill,而不是把所有能力都暴露给模型;返回内容要足够模型判断何时使用,但不能泄露敏感实现。调用阶段,调度器根据 skillName 找到 handler,先校验参数 schema、用户权限、风险策略和超时预算,再在受控上下文中执行,最后返回结构化结果或标准错误。生产上还要支持版本管理、禁用、审计日志、调用耗时、失败率、幂等和取消。最小代码可以先实现内存注册表和统一 call 接口,但边界要按真实系统设计。

考点 注册
难度 真实面经题
回答目标 讲清工程边界与实现取舍

深入解析

01

注册表是核心数据结构

SkillRegistry 负责保存所有可用 Skill 的元数据和执行入口。注册时要校验 name 唯一、版本合法、schema 可解析、handler 存在、权限声明完整,避免运行时才发现能力不可用。

02

Manifest 要描述能力边界

manifest 不只是给模型看的描述,还要给系统执行校验用。至少包含名称、用途、输入输出 schema、权限、超时、是否有副作用、版本和标签。高风险能力还要声明确认要求。

03

发现要按上下文过滤

discover 不应返回全量 Skill,而应根据用户权限、任务类型、环境、风险等级和关键词筛选候选。这样可以降低模型选择压力,也能避免未授权能力被提示给模型。

04

调用前必须校验

call 入口要检查 Skill 是否存在、参数是否符合 schema、权限是否满足、是否超过预算、是否需要确认和是否被禁用。校验失败返回标准错误,不能直接进入 handler。

05

执行结果要结构化

handler 返回应包含 ok、data、error、metadata 等字段,错误要区分参数错误、权限错误、执行错误、超时和取消。结构化结果方便模型恢复,也方便日志和指标统计。

06

生产能力需要治理

真实系统还要考虑版本兼容、灰度、禁用开关、审计、调用频控、幂等键、取消、依赖健康检查和指标。手写题可以先实现最小骨架,但要说明这些扩展点。

ts

最小 TypeScript Skill 注册与调用骨架

type SkillContext = { userId: string; permissions: Set<string>; traceId: string };
type SkillResult = { ok: true; data: unknown } | { ok: false; error: string };

type Skill = {
  name: string;
  description: string;
  permissions: string[];
  validate(input: unknown): void;
  run(input: unknown, ctx: SkillContext): Promise<SkillResult>;
};

class SkillRegistry {
  private skills = new Map<string, Skill>();

  register(skill: Skill) {
    if (this.skills.has(skill.name)) throw new Error("duplicate skill");
    this.skills.set(skill.name, skill);
  }

  discover(ctx: SkillContext) {
    return [...this.skills.values()]
      .filter((s) => s.permissions.every((p) => ctx.permissions.has(p)))
      .map(({ name, description, permissions }) => ({ name, description, permissions }));
  }

  async call(name: string, input: unknown, ctx: SkillContext) {
    const skill = this.skills.get(name);
    if (!skill) return { ok: false, error: "skill_not_found" } satisfies SkillResult;
    if (!skill.permissions.every((p) => ctx.permissions.has(p))) {
      return { ok: false, error: "permission_denied" } satisfies SkillResult;
    }
    try {
      skill.validate(input);
    } catch {
      return { ok: false, error: "invalid_input" } satisfies SkillResult;
    }

    try {
      return await skill.run(input, ctx);
    } catch {
      return { ok: false, error: "execution_error" } satisfies SkillResult;
    }
  }
}
  • 示例只展示注册、发现和调用骨架;生产实现还需要 schema 库、超时、取消、审计、版本和风险确认。
  • discover 按权限过滤,call 入口再次校验,避免只靠发现阶段保护能力。

易错点

  • 只写一个函数 map,没有 manifest、schema 和权限信息。
  • discover 返回全量能力,忽略用户权限和运行环境。
  • 只在 Prompt 里要求参数正确,系统调用前不做 schema 校验。
  • handler 直接抛异常,缺少标准错误和恢复信息。
  • 没有审计 trace、耗时和失败指标,无法排查线上调用问题。
  • 忽略版本、禁用、超时、取消和幂等,骨架难以扩展到生产。

面试官追问

注册阶段最容易漏什么?

容易只注册 handler,漏掉 input schema、权限、版本、超时和错误语义,导致模型能看到能力但系统无法安全执行。

为什么 discover 不能返回所有 Skill?

全量返回会增加模型选择噪声,也可能暴露用户无权使用的能力。发现结果应按任务、权限和环境过滤。

参数校验应该放在模型侧还是系统侧?

模型侧可以被提示生成正确参数,但系统侧必须强制校验。真实执行不能依赖模型自觉遵守 schema。

如何支持 Skill 版本升级?

manifest 带 version,注册表支持多版本或灰度映射,调用日志记录实际版本,并保持输入输出兼容或提供迁移。