真实面经题目 · 原创解析
如何手写一个 Skill 系统,支持注册、发现和调用本地能力?
这题考手写 Skill 系统的最小实现,回答重点是注册、发现、参数校验、权限控制、调用分发、错误处理和可观测 trace。
真实面经题目 · 原创解析
这题考手写 Skill 系统的最小实现,回答重点是注册、发现、参数校验、权限控制、调用分发、错误处理和可观测 trace。
我会把 Skill 系统拆成三条主链路。注册阶段,Skill 提供 manifest,包括 name、description、inputSchema、outputSchema、permissions、version、handler 和 timeout,注册表校验名称唯一、schema 合法和权限声明完整。发现阶段,根据用户任务、关键词、标签、权限和运行环境返回候选 Skill,而不是把所有能力都暴露给模型;返回内容要足够模型判断何时使用,但不能泄露敏感实现。调用阶段,调度器根据 skillName 找到 handler,先校验参数 schema、用户权限、风险策略和超时预算,再在受控上下文中执行,最后返回结构化结果或标准错误。生产上还要支持版本管理、禁用、审计日志、调用耗时、失败率、幂等和取消。最小代码可以先实现内存注册表和统一 call 接口,但边界要按真实系统设计。
SkillRegistry 负责保存所有可用 Skill 的元数据和执行入口。注册时要校验 name 唯一、版本合法、schema 可解析、handler 存在、权限声明完整,避免运行时才发现能力不可用。
manifest 不只是给模型看的描述,还要给系统执行校验用。至少包含名称、用途、输入输出 schema、权限、超时、是否有副作用、版本和标签。高风险能力还要声明确认要求。
discover 不应返回全量 Skill,而应根据用户权限、任务类型、环境、风险等级和关键词筛选候选。这样可以降低模型选择压力,也能避免未授权能力被提示给模型。
call 入口要检查 Skill 是否存在、参数是否符合 schema、权限是否满足、是否超过预算、是否需要确认和是否被禁用。校验失败返回标准错误,不能直接进入 handler。
handler 返回应包含 ok、data、error、metadata 等字段,错误要区分参数错误、权限错误、执行错误、超时和取消。结构化结果方便模型恢复,也方便日志和指标统计。
真实系统还要考虑版本兼容、灰度、禁用开关、审计、调用频控、幂等键、取消、依赖健康检查和指标。手写题可以先实现最小骨架,但要说明这些扩展点。
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;
}
}
} 容易只注册 handler,漏掉 input schema、权限、版本、超时和错误语义,导致模型能看到能力但系统无法安全执行。
全量返回会增加模型选择噪声,也可能暴露用户无权使用的能力。发现结果应按任务、权限和环境过滤。
模型侧可以被提示生成正确参数,但系统侧必须强制校验。真实执行不能依赖模型自觉遵守 schema。
manifest 带 version,注册表支持多版本或灰度映射,调用日志记录实际版本,并保持输入输出兼容或提供迁移。