Files
AskaEth f75e7a9c9b feat(skills): 技能加载/校验/真执行第一片(design/09 §6 定稿落地)
- 加载器/校验:目录式 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 的分阶段)。
2026-10-06 09:03:35 +08:00

223 lines
7.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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