为什么“契约”是 Skill 的起点
Skill 的目标不是“回答得像人”,而是“交付得像组件”:可被调用、可被组合、可被测试。
真正让 Skill 变稳的,不是更长的提示词,而是:
- 输入契约:你允许什么、默认什么、拒绝什么
- 输出契约:你保证什么结构、哪些字段必有、排序与单位是什么
- 失败契约:失败时返回什么、是否重试、是否需要人类确认
先定义边界:这个 Skill 不做什么
工程化的第一条规则是:先写清楚“不做什么”。
例如「PR 摘要 Skill」可以明确:
- 只总结改动与风险,不做代码风格争论
- 不做部署决策(可以给建议,但不下结论)
- 不访问任何外部私密系统,除非显式授权并走工具调用
写“不做什么”,比写“我很强”更能提升稳定性。
输入契约(Input Contract)模板
你至少要列出:
- 字段名
- 类型(string/number/enum/list/object)
- 必填/可选
- 默认值
- 合法范围
- 例子
下面是一个“最小可用”的输入 schema(用 JSON 表达):
{
"skill": "pr_summary",
"version": "1.0.0",
"input": {
"repo": "string",
"pull_number": "number",
"audience": "enum(dev|pm|qa)",
"risk_level": "enum(low|medium|high)",
"max_words": "number"
},
"defaults": {
"audience": "dev",
"risk_level": "medium",
"max_words": 300
}
}
输出契约(Output Contract)模板
输出尽量结构化,尤其当它会被后续步骤消费(写评论、生成变更日志、触发发布)。
{
"output": {
"summary": "string",
"changes": [
{
"area": "string",
"description": "string"
}
],
"risks": [
{
"severity": "enum(low|medium|high)",
"reason": "string",
"mitigation": "string"
}
],
"followups": ["string"]
}
}
如果你必须输出 Markdown,也建议把结构固定成小节,并保证顺序一致(可测试、可 diff)。
失败契约:把“失败类型”写成枚举
不要用“失败了”这种描述。你需要可观测、可统计、可回归的失败类别:
INVALID_INPUT:参数缺失/非法TOOL_AUTH_REQUIRED:需要授权或权限不足TOOL_TIMEOUT:外部系统超时TOOL_RATE_LIMIT:被限流MODEL_REFUSAL:模型拒绝执行(安全策略)LOW_CONFIDENCE:置信度不足(触发人类确认)
输出时建议把失败信息也结构化:
{
"ok": false,
"error": {
"type": "TOOL_TIMEOUT",
"message": "GitHub API timeout",
"retryable": true,
"suggested_action": "Retry with backoff or switch to cached diff"
}
}
动手做:把契约落到“校验 + 提示 + 工具层”
契约不是写在文档里就结束,至少要落到三层:
- 校验层:进入 Skill 前校验输入(必填、范围、类型)
- 提示层:提示词里明确要求输出结构(字段名、顺序、允许为空)
- 工具层:工具调用返回也要映射到错误枚举(而不是裸字符串)
工程化清单(你做完就能上线 50%)
- 输入:必填/默认/范围/拒绝项齐全
- 输出:固定结构或固定小节顺序
- 失败:枚举化,可统计
- 降级:至少一种 fallback(缓存、摘要、人工确认)
常见坑
- 把 schema 当作文档:不做校验,等于没写
- 输出契约太大:字段越多越容易漂移,先做最小可用
- 没有失败枚举:后续做不了监控和回归
下一篇:Skill 的工程化目录与文档规范。