- 加载器/校验:目录式 configs/skills/<id>/skill.yaml(兼容旧单文件 <id>.yaml,先目录后文件); 新字段 version/when-to-use/invocation(三态)/triggers/paths/allowed-tools/disallowed-tools/ context(ine|fork)/agent/model/effort/metadata;**依赖环 ⇒ 加载期拒**(运行期绝不挂死); depends 指向不存在步骤 ⇒ 拒;同一工具同时在 allowed 与 disallowed ⇒ 拒;错误可分类(哨兵 + %w) - Runner.Run 真执行(替换 ErrRunPending):步骤拓扑(无依赖可并行、上限 2)+ guardrail 强制点 (enforced 违反 ⇒ 中止并如实报「哪条、哪一步」;否则 WARN 继续)+ 失败不静默跳过 + ctx 取消传播 - 🔴 能力收缩只收不放:该轮可用工具 = allowed-tools ∩ 安全闸 ∩ 注册表,再减 disallowed-tools - 另有渐进披露相关的 listing/surface/activation(清单投影与激活判定) ⚠️ 未接线:本片是库与执行器,尚未把技能清单接进模型面(见 report 的分阶段)。
223 lines
7.9 KiB
Go
223 lines
7.9 KiB
Go
package sdk
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
)
|
||
|
||
// ErrInvalidSkillField 表示技能定义里的某个字段取值非法:枚举越界,或声明自相
|
||
// 矛盾(例如同一个工具同时出现在 allowed-tools 与 disallowed-tools)。
|
||
// ParseInvocation / ParseContextMode 用它分类错误,调用方用 errors.Is 判定。
|
||
var ErrInvalidSkillField = errors.New("sdk: invalid skill field")
|
||
|
||
// Step 是技能工作流里的一步(design/09 §6.2)。
|
||
type Step struct {
|
||
// Name 是步骤标识,技能内唯一;depends 按它引用。
|
||
Name string
|
||
// Prompt 是这一步交给模型的指令。
|
||
Prompt string
|
||
// Depends 列出必须先完成的前序步骤名。
|
||
Depends []string
|
||
}
|
||
|
||
// Ref 是技能可以引用的外部素材(工具、文档、文件等)。
|
||
type Ref struct {
|
||
// Name 标识该引用。
|
||
Name string
|
||
// Type 是自由取值的种类,例如 "tool"、"doc"、"file"。
|
||
Type string
|
||
// Value 是引用载荷或指针。
|
||
Value string
|
||
// Optional 为真表示该引用缺失不算失败。
|
||
Optional bool
|
||
}
|
||
|
||
// Guardrail 是技能执行期间施加的约束(design/09 §6.4-4)。
|
||
type Guardrail struct {
|
||
// ID 命名该约束。
|
||
ID string
|
||
// Message 是约束内容(也是违规时如实回显的那一句)。
|
||
Message string
|
||
// Enforced 为真表示违反即中止技能;为假表示只 WARN 后继续。
|
||
Enforced bool
|
||
}
|
||
|
||
// Skill 是可复用的高阶工作流:一组带提示词的步骤,配以基础提示词、引用与约束。
|
||
// 它比 Tool 更结构化,通常由多个步骤组合而成。
|
||
//
|
||
// 实现必须并发安全。
|
||
type Skill interface {
|
||
// Name 返回技能的唯一标识。
|
||
Name() string
|
||
// Description 说明技能做什么;模型据此挑选技能。
|
||
Description() string
|
||
// Prompt 返回技能的基础系统提示词。
|
||
Prompt() string
|
||
// Steps 返回有序的工作流步骤。
|
||
Steps() []Step
|
||
// Refs 返回技能可引用的素材。
|
||
Refs() []Ref
|
||
// Guardrails 返回技能施加的约束。
|
||
Guardrails() []Guardrail
|
||
}
|
||
|
||
// Invocation 是技能的调用三态(design/09 §6.2 定稿,对齐 Agent Skills 的
|
||
// `disable-model-invocation` / `user-invocable` 两枚开关压成的一枚枚举)。
|
||
type Invocation string
|
||
|
||
const (
|
||
// InvocationBoth:清单含 description,人与模型都可调(缺省)。
|
||
InvocationBoth Invocation = "both"
|
||
// InvocationModelOnly:清单**不含** description(省上下文),只有模型可调。
|
||
InvocationModelOnly Invocation = "model-only"
|
||
// InvocationUserOnly:清单含 description,**模型不可调**(只认显式调用)。
|
||
InvocationUserOnly Invocation = "user-only"
|
||
)
|
||
|
||
// Valid 报告 i 是否是已知的调用态;空值不算合法(缺省由 OrDefault 补)。
|
||
func (i Invocation) Valid() bool {
|
||
switch i {
|
||
case InvocationBoth, InvocationModelOnly, InvocationUserOnly:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// OrDefault 把空值补成 InvocationBoth(design/09 §6.2 的缺省列)。
|
||
func (i Invocation) OrDefault() Invocation {
|
||
if i == "" {
|
||
return InvocationBoth
|
||
}
|
||
return i
|
||
}
|
||
|
||
// ModelInvocable 报告模型是否可以调用该技能(user-only 不可调)。
|
||
func (i Invocation) ModelInvocable() bool { return i.OrDefault() != InvocationUserOnly }
|
||
|
||
// UserInvocable 报告人是否可以显式调用该技能(model-only 不给人调)。
|
||
func (i Invocation) UserInvocable() bool { return i.OrDefault() != InvocationModelOnly }
|
||
|
||
// ListingHasDescription 报告该调用态下清单是否要带 description
|
||
// (model-only 不带:清单只给名字,省上下文)。
|
||
func (i Invocation) ListingHasDescription() bool { return i.OrDefault() != InvocationModelOnly }
|
||
|
||
// ParseInvocation 解析磁盘/YAML 上的调用态字符串。空串取缺省 both;其余非法值
|
||
// 一律报错(绝不静默当成 both —— 那是把配置写错伪装成成功)。
|
||
func ParseInvocation(s string) (Invocation, error) {
|
||
trimmed := Invocation(strings.TrimSpace(s))
|
||
if trimmed == "" {
|
||
return InvocationBoth, nil
|
||
}
|
||
if !trimmed.Valid() {
|
||
return "", fmt.Errorf("sdk: invocation %q: %w", s, ErrInvalidSkillField)
|
||
}
|
||
return trimmed, nil
|
||
}
|
||
|
||
// ContextMode 是技能的上下文模式(design/09 §6.2/§6.4-6)。
|
||
type ContextMode string
|
||
|
||
const (
|
||
// ContextInline:在当前会话上下文内跑(缺省)。
|
||
ContextInline ContextMode = "inline"
|
||
// ContextFork:在隔离子代理里跑,不看会话历史,结论回灌父会话。
|
||
ContextFork ContextMode = "fork"
|
||
)
|
||
|
||
// Valid 报告 c 是否是已知的上下文模式;空值不算合法。
|
||
func (c ContextMode) Valid() bool {
|
||
switch c {
|
||
case ContextInline, ContextFork:
|
||
return true
|
||
default:
|
||
return false
|
||
}
|
||
}
|
||
|
||
// OrDefault 把空值补成 ContextInline(缺省)。
|
||
func (c ContextMode) OrDefault() ContextMode {
|
||
if c == "" {
|
||
return ContextInline
|
||
}
|
||
return c
|
||
}
|
||
|
||
// ParseContextMode 解析磁盘/YAML 上的上下文模式。空串取缺省 inline;其余非法值
|
||
// 报错(理由同 ParseInvocation)。
|
||
func ParseContextMode(s string) (ContextMode, error) {
|
||
trimmed := ContextMode(strings.TrimSpace(s))
|
||
if trimmed == "" {
|
||
return ContextInline, nil
|
||
}
|
||
if !trimmed.Valid() {
|
||
return "", fmt.Errorf("sdk: context %q: %w", s, ErrInvalidSkillField)
|
||
}
|
||
return trimmed, nil
|
||
}
|
||
|
||
// SkillSpec 是技能定义的完整只读视图(design/09 §6.2 定稿字段)。
|
||
//
|
||
// 它是 Skill 之上的**可选**扩展:未实现它的技能一律按各字段的缺省取值看待,
|
||
// 因此既有的第三方 Skill 实现不需要改动即可继续注册与运行。
|
||
type SkillSpec interface {
|
||
Skill
|
||
// Version 是定义版本(形状迁移用),缺省 1。
|
||
Version() int
|
||
// WhenToUse 是「何时使用」补充说明,与 Description 一起进清单并计入预算。
|
||
WhenToUse() string
|
||
// Invocation 是调用三态。
|
||
Invocation() Invocation
|
||
// Triggers 是触发词,与 Paths 一起决定自动激活。
|
||
Triggers() []string
|
||
// Paths 是 glob;仅当工作文件命中时自动激活。
|
||
Paths() []string
|
||
// AllowedTools 是**能力上限**(不是免审批名单),见 design/09 §6.4-3。
|
||
AllowedTools() []string
|
||
// DisallowedTools 从上限里剔除。
|
||
DisallowedTools() []string
|
||
// ContextMode 是上下文模式(对应 YAML 键 `context`;方法名不叫 Context 以
|
||
// 避免与标准库 context 混淆)。
|
||
ContextMode() ContextMode
|
||
// Agent 是 context: fork 时使用的子代理类型。
|
||
Agent() string
|
||
// Model 是可选模型覆盖;per-role 路由优先于它(design/04 §3)。
|
||
Model() string
|
||
// Effort 是可选思考档位覆盖。
|
||
Effort() string
|
||
// Metadata 是自由 KV,宿主不解释。
|
||
Metadata() map[string]string
|
||
}
|
||
|
||
// SkillView 把任意 Skill 归一化成 SkillSpec:实现了 SkillSpec 的原样返回,
|
||
// 否则套一层全缺省的包装。宿主用它读技能字段,就不必到处写类型断言。
|
||
func SkillView(s Skill) SkillSpec {
|
||
if s == nil {
|
||
return nil
|
||
}
|
||
if spec, ok := s.(SkillSpec); ok {
|
||
return spec
|
||
}
|
||
return plainSkill{Skill: s}
|
||
}
|
||
|
||
// plainSkill 为只实现基础 Skill 的技能补齐缺省字段。
|
||
type plainSkill struct{ Skill }
|
||
|
||
func (p plainSkill) Version() int { return DefaultSkillVersion }
|
||
func (p plainSkill) WhenToUse() string { return "" }
|
||
func (p plainSkill) Invocation() Invocation { return InvocationBoth }
|
||
func (p plainSkill) Triggers() []string { return nil }
|
||
func (p plainSkill) Paths() []string { return nil }
|
||
func (p plainSkill) AllowedTools() []string { return nil }
|
||
func (p plainSkill) DisallowedTools() []string { return nil }
|
||
func (p plainSkill) ContextMode() ContextMode { return ContextInline }
|
||
func (p plainSkill) Agent() string { return "" }
|
||
func (p plainSkill) Model() string { return "" }
|
||
func (p plainSkill) Effort() string { return "" }
|
||
func (p plainSkill) Metadata() map[string]string { return nil }
|
||
|
||
// DefaultSkillVersion 是未声明 version 时的定义版本(design/09 §6.2 缺省列)。
|
||
const DefaultSkillVersion = 1
|