Eino Skill 机制架构分析

Eino Skill 机制架构分析

概述

本文档分析 eino 框架中 Skill 机制的设计与实现,包括渐进式加载、执行模式、扩展点以及与其他组件的集成方式。


1. 渐进式加载机制

设计理念

Skill 采用 Progressive Disclosure(渐进式展示) 模式,减少 token 消耗的同时保持 agent 对可用技能的认知。

加载策略

复制代码
第一阶段:List() → 只返回 FrontMatter(元数据)
              └─ name, description, context, agent, model

第二阶段:Get()  → 按需获取完整 Skill
              └─ Content(完整指令)+ BaseDirectory

实现位置

go 复制代码
// adk/middlewares/skill/skill.go
func (s *skillTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    skills, err := s.b.List(ctx)  // 仅加载元数据
    // ...
}

func (s *skillTool) InvokableRun(ctx context.Context, argumentsInJSON string, ...) (string, error) {
    skill, err := s.b.Get(ctx, args.Skill)  // 按需加载完整内容
    // ...
}

2. 文件访问机制

references/scripts 的处理方式

框架不会自动加载 references 或 scripts 文件,采用隐式按需访问策略:

组件 处理方式
SKILL.md 框架读取并解析
FrontMatter 框架解析(name/description/context/agent/model)
Content 框架读取并返回
references 不加载,由 Agent 按需访问
scripts 不加载,由 Agent 按需访问

设计思路

  1. BaseDirectory 提供路径标识
  2. Skill Content 说明访问方式
  3. 路径转换由 Agent 负责
go 复制代码
// prompt.go:144-147
userContent = `Base directory for this skill: %s

%s`

3. 云端存储支持

Backend 抽象接口

go 复制代码
// adk/filesystem/backend.go
type Backend interface {
    LsInfo(ctx context.Context, req *LsInfoRequest) ([]FileInfo, error)
    Read(ctx context.Context, req *ReadRequest) (*FileContent, error)
    GrepRaw(ctx context.Context, req *GrepRequest) ([]GrepMatch, error)
    GlobInfo(ctx context.Context, req *GlobInfoRequest) ([]FileInfo, error)
    Write(ctx context.Context, req *WriteRequest) error
    Edit(ctx context.Context, req *EditRequest) error
}

不同 Backend 的 BaseDirectory 含义

Backend 类型 BaseDirectory 含义 示例
本地文件系统 本地绝对路径 /home/user/skills/my-skill
内存存储 虚拟路径 /skills/my-skill
云端存储 云端路径标识符 s3://bucket/skills/my-skill

云端 Skill 的实现方案

  1. 实现云端 Backend:读取 SKILL.md 内容
  2. Skill Content 说明访问方式:告知 Agent 如何获取 references/scripts
  3. 提供对应的访问工具:让 Agent 能通过 BaseDirectory 访问云端资源

4. 与 ReAct 模式的集成

集成方式

Skill 已经天然支持 ReAct 模式,因为 ChatModelAgent 底层就是 ReAct 实现:

go 复制代码
// 创建 skill middleware
skillHandler, _ := skill.NewMiddleware(ctx, &skill.Config{
    Backend: backend,
})

// 加到 ChatModelAgent 的 Handlers 中
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    Name:        "my-agent",
    Model:       model,
    Handlers: []adk.ChatModelAgentMiddleware{
        skillHandler,  // 自动注入到 ReAct 流程
    },
})

工作原理

  1. BeforeAgent 注入系统提示:添加 Skills System 指导
  2. BeforeAgent 注入 skill 工具:添加到工具列表
  3. ReAct 循环中可用:Model 可以思考-调用 skill-执行任务
go 复制代码
// skill.go:246-250
func (h *skillHandler) BeforeAgent(ctx context.Context, runCtx *adk.ChatModelAgentContext) (...) {
    runCtx.Instruction = runCtx.Instruction + "\n" + h.instruction
    runCtx.Tools = append(runCtx.Tools, h.tool)
    return ctx, runCtx, nil
}

5. 工具集成

Skill 是自带的工具

无需单独实现,框架已提供完整的 skill 加载工具。

使用方式

go 复制代码
// 1. 创建 Backend(指定 skill 从哪里加载)
backend, _ := skill.NewBackendFromFilesystem(ctx, &skill.BackendFromFilesystemConfig{
    Backend: filesystem.NewLocalFSBackend(),
    BaseDir: "/path/to/skills",
})

// 2. 创建 skill middleware(框架自动配置)
skillHandler, _ := skill.NewMiddleware(ctx, &skill.Config{
    Backend: backend,
})

// 3. 配置到 Agent
agent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
    Model:    model,
    Handlers: []adk.ChatModelAgentMiddleware{skillHandler},
})

Function Call 流程

复制代码
用户请求 "处理这个PDF"
    ↓
Model 思考并查看可用技能列表
    ↓
Model 发起 function call: {"name": "skill", "arguments": "{"skill": "pdf"}"}
    ↓
skill.InvokableRun() 执行
    ↓
返回 pdf skill 的完整指令
    ↓
Model 按照指令执行任务

6. 核心数据结构

skillTool

go 复制代码
type skillTool struct {
    b        Backend        // skill 数据源
    toolName string         // 工具名称,默认 "skill"
    useChinese bool         // 是否使用中文提示

    // 可选组件
    agentHub   AgentHub     // 用于 fork 模式创建子 agent
    modelHub   ModelHub     // 用于 skill 指定模型时解析

    // 扩展点
    customToolDesc    ToolDescriptionFunc
    customToolParams  func(ctx context.Context, defaults) (map[string]*schema.ParameterInfo, error)
    buildContent      func(ctx context.Context, skill Skill, rawArgs string) (string, error)
    buildForkMessages func(ctx context.Context, in SubAgentInput) ([]adk.Message, error)
    formatForkResult  func(ctx context.Context, in SubAgentOutput) (string, error)
}

7. 三种执行模式

Inline 模式(默认)

  • 上下文:当前 agent
  • 历史消息:✅ 保留
  • 执行方式:返回 skill 内容作为 tool result
  • 适用场景:简单指令注入
go 复制代码
// 输出示例
Launching skill: pdf
Base directory for this skill: /skills/pdf

# PDF Processing Skill
...

Fork 模式

  • 上下文:新的子 agent
  • 历史消息:❌ 不保留
  • 执行方式:子 agent 从头开始,只有 skill 内容
  • 适用场景:需要干净上下文的重任务
go 复制代码
messages = []adk.Message{schema.UserMessage(skillContent)}

Fork With Context 模式

  • 上下文:新的子 agent
  • 历史消息:✅ 保留
  • 执行方式:子 agent 获得完整历史 + skill 内容
  • 适用场景:需要历史上下文的重任务
go 复制代码
messages = append(history, schema.ToolMessage(skillContent, toolCallID))

8. 扩展点详解

8.1 CustomToolParams - 自定义工具参数

go 复制代码
Config{
    Backend: backend,
    CustomToolParams: func(ctx context.Context, defaults map[string]*schema.ParameterInfo) (map[string]*schema.ParameterInfo, error) {
        defaults["task"] = &schema.ParameterInfo{
            Type:     schema.String,
            Desc:     "Specific task to perform",
            Required: false,
        }
        return defaults, nil
    },
}

8.2 BuildContent - 自定义 skill 内容生成

go 复制代码
BuildContent: func(ctx context.Context, skill Skill, rawArgs string) (string, error) {
    var args struct {
        Skill string `json:"skill"`
        Task  string `json:"task"`
    }
    json.Unmarshal([]byte(rawArgs), &args)

    return fmt.Sprintf("# %s\n\nTask: %s\n\n%s", skill.Name, args.Task, skill.Content), nil
}

8.3 BuildForkMessages - 自定义子 agent 消息

go 复制代码
BuildForkMessages: func(ctx context.Context, in SubAgentInput) ([]adk.Message, error) {
    return []adk.Message{
        schema.SystemMessage("You are a specialist for this task."),
        schema.UserMessage(in.SkillContent),
    }, nil
}

8.4 FormatForkResult - 自定义结果格式

go 复制代码
FormatForkResult: func(ctx context.Context, in SubAgentOutput) (string, error) {
    summary := summarizeResults(in.Results)
    return fmt.Sprintf("✅ Task completed\n\nSummary:\n%s", summary), nil
}

9. 与其他组件的交互

Backend 接口

go 复制代码
type Backend interface {
    List(ctx context.Context) ([]FrontMatter, error)
    Get(ctx context.Context, name string) (Skill, error)
}

ModelHub - 模型切换

go 复制代码
// skill 指定模型时设置运行时变量
func (s *skillTool) setActiveModel(ctx context.Context, modelName string) {
    adk.SetRunLocalValue(ctx, activeModelKey, modelName)
}

// WrapModel 读取并切换模型
func (h *skillHandler) WrapModel(ctx context.Context, m model.BaseChatModel, ...) (model.BaseChatModel, error) {
    modelName, found, _ := adk.GetRunLocalValue(ctx, activeModelKey)
    if found {
        newModel, _ := h.tool.modelHub.Get(ctx, modelName.(string))
        return newModel, nil
    }
    return m, nil
}

AgentHub - 子 agent 获取

go 复制代码
type AgentHub interface {
    Get(ctx context.Context, name string, opts *AgentHubOptions) (adk.Agent, error)
}

type AgentHubOptions struct {
    Model model.ToolCallingChatModel  // skill 指定的模型
}

10. Info 方法 - 生成工具描述

go 复制代码
func (s *skillTool) Info(ctx context.Context) (*schema.ToolInfo, error) {
    // 1. 获取所有 skill 的元数据
    skills, err := s.b.List(ctx)

    // 2. 生成工具描述(列出所有可用 skill)
    var fullDesc string
    if s.customToolDesc != nil {
        fullDesc = s.customToolDesc(ctx, skills)
    } else {
        desc, _ := renderToolDescription(skills)
        fullDesc = toolDescriptionBase + desc
    }

    // 3. 构建参数 schema
    oneOf, err := s.buildParamsOneOf(ctx)

    return &schema.ToolInfo{
        Name:        s.toolName,  // "skill"
        Desc:        fullDesc,
        ParamsOneOf: oneOf,
    }, nil
}

11. 核心执行流程

go 复制代码
func (s *skillTool) InvokableRun(ctx context.Context, argumentsInJSON string, ...) (string, error) {
    // 1. 解析参数
    args := &inputArguments{}  // {"skill": "pdf"}
    json.Unmarshal([]byte(argumentsInJSON), args)

    // 2. 获取 skill(按需加载完整内容)
    skill, err := s.b.Get(ctx, args.Skill)

    // 3. 根据 context 模式执行
    switch skill.Context {
    case ContextModeForkWithContext:
        return s.runAgentMode(ctx, skill, true, argumentsInJSON)
    case ContextModeFork:
        return s.runAgentMode(ctx, skill, false, argumentsInJSON)
    default:  // inline
        if skill.Model != "" {
            s.setActiveModel(ctx, skill.Model)
        }
        return s.buildSkillResult(ctx, skill, argumentsInJSON)
    }
}

总结

Skill 机制的核心设计

  1. 渐进式加载:List 只返回元数据,Get 按需加载完整内容
  2. 三种执行模式:inline/fork/fork_with_context,满足不同场景
  3. 丰富的扩展点:支持自定义参数、内容、消息、结果格式
  4. 组件解耦:Backend/AgentHub/ModelHub 都可替换
  5. 模型切换:通过 run local value 实现运行时模型切换
  6. 开箱即用:框架自带完整实现,无需单独开发
  7. 天然支持 ReAct:通过 ChatModelAgentMiddleware 集成

适用场景

  • Inline:简单的指令注入,增强 agent 能力
  • Fork:需要干净上下文的复杂任务
  • Fork With Context:需要历史上下文的复杂任务
  • 云端存储:通过实现自定义 Backend 支持
相关推荐
吾AI科技2 小时前
Agent的诞生(二):让模型开始调用工具
ai·agent·智能体·deepseek
x-cmd2 小时前
[20260530] Claude Code v2.1.157 发布日报:插件去中心化 + Agent 调度增强 + Worktree 跨会话切换
ai-agent·x-cmd·skill·worktree·marketplace·claude-code
久违 °10 小时前
【AI-Agent】TagMatrix 数据标注工具开发
人工智能·数据分析·go·agent·数据隐私
武雄(小星Ai)13 小时前
2026年AI Agent框架选型指南:LangGraph vs CrewAI vs Claude SDK vs OpenAI SDK
人工智能·aigc·agent
Bioinfo Guy15 小时前
pdf-to-skill|把高分论文里的方法路线与图表逻辑,转化为可反复调用的Codex Skill模板
大语言模型·skill
Sincerelyplz16 小时前
【AI会议纪要实践】mapReduce、RAG 与结构化输出
java·后端·agent
七牛开发者16 小时前
如何从零开发一个工业级的 SKILL
人工智能·程序员·agent
创世宇图17 小时前
Claude Opus 4.8 深度实测:动态多 Agent 协同、Effort Control 与幻觉抑制的工程化解析
ai·llm·agent·claude·ai工程化