[07] Hermes Agent 技能进化系统拆解:Skill 的元数据结构、自注册加载与退化机制

人工智能Agent 2026-06-22 12
预计阅读时间:9 分钟

> TL;DR:Skill 不是静态的文档——它是 Agent 的"程序性记忆"系统。SKILL.md 采用 YAML frontmatter + Markdown 的元数据结构,各文件通过 AST 扫描自动被 System Prompt 的 Skills Index 加载。这篇从 skills_tool.py(1545 行)和 skill_commands.py(527 行)源码拆 Skill 的完整链路。

前六篇拆了外围子系统,这篇拆 Hermes Agent 最核心的特性——技能进化(Skills)。 你可能从各种地方听过 Hermes 的"自进化"能力:Agent 用着用着自己长出新技能。这是怎么实现的?不是魔法,是一套完整的"经验→模板→复用"管道。


1. Skill 是什么

Skill 的本质是带元数据的可执行工作流文档。格式是组合式:

---
name: axolotl
description: Axolotl: YAML LLM fine-tuning (LoRA, DPO, GRPO).
version: 1.0.0
category: mlops
metadata:
  hermes:
    tags: [fine-tuning, lora, dpo]
    related_skills: [unsloth, trl]
---
# Axolotl Fine-tuning Skill
使用 Axolotl 进行 LLM 微调的完整工作流。核心操作:
```bash
# 安装
pip install axolotl
# 训练
accelerate launch -m axolotl.cli.train config.yml
每个 Skill 对应 `~/.hermes/skills/<category>/<skill-name>/` 下的一个目录:

~/.hermes/skills/ ├── mlops/ │ ├── axolotl/ │ │ ├── SKILL.md # 主文件(必需) │ │ ├── references/ # 参考文档 │ │ │ └── dataset-formats.md │ │ └── templates/ # 输出模板 │ │ └── config.yaml │ └── unsloth/ │ └── SKILL.md ├── devops/ │ └── csdn-publish/ │ └── SKILL.md └── ...

---
## 2. SKILL.md 元数据结构
SKILL.md 的头部 YAML frontmatter 定义了 Agent 和工具系统需要的全部结构化信息。`skills_tool.py` 中的 `_parse_frontmatter()` 负责解析它但不重复实现——它委托给 `agent.skill_utils.parse_frontmatter()`:
```python
def _parse_frontmatter(content: str) -> Tuple[Dict[str, Any], str]:
    from agent.skill_utils import parse_frontmatter
    return parse_frontmatter(content)

Frontmatter 支持 agentskills.io 兼容格式。关键字段: | 字段 | 必需 | 说明 | 上限 | |------|------|------|------| | name | ✅ | Skill 唯一标识 | 64 字符 | | description | ✅ | 列表展示摘要 | 1024 字符 | | version | ❌ | 语义化版本 | — | | category | ❌ | 分类路径 | — | | platforms | ❌ | 运行平台约束(macos/linux/windows) | — | | required_environment_variables | ❌ | 环境变量需求 | — | | metadata.hermes | ❌ | Hermes 专属元数据(tags/related_skills) | — | Prerequisites(旧版)和 required_environment_variables(新版)的解析:

def _get_required_environment_variables(frontmatter, legacy_env_vars=None):
    required_raw = frontmatter.get("required_environment_variables")
    # 支持三种输入格式:
    # 1. 字符串列表: ["OPENAI_API_KEY"]
    # 2. 带描述的 dict: {"name": "OPENAI_API_KEY", "prompt": "输入 key"}
    # 3. 旧版 prerequisites.env_vars
    for item in required_raw:
        if isinstance(item, str):
            _append_required({"name": item})
        if isinstance(item, dict):
            _append_required(item)
    # 旧版兼容
    if legacy_env_vars is None:
        legacy_env_vars, _ = _collect_prerequisite_values(frontmatter)

3. 加载机制:Skills Index——System Prompt 的循环注入

Skills Index 是连接 Skill 系统和 System Prompt 的桥梁。它不是每次从文件系统扫描,而是用两层缓存。 在 prompt_builder.py(System Prompt 组装层)中:

has_skills_tools = any(name in agent.valid_tool_names for name in ['skills_list', 'skill_view', 'skill_manage'])
if has_skills_tools:
    skills_prompt = _r.build_skills_system_prompt(
        available_tools=agent.valid_tool_names,
        available_toolsets=avail_toolsets,
        compact_categories=_compact_cats or None,
    )

build_skills_system_prompt() 返回的是一个索引——不是全部 Skill 的内容,只是名称和描述。每一行是一条记录,像这样:

tools/skill_name: 简短描述(10-30字)

这个索引通过两层缓存减少 I/O 和 Token: 1. 进程内 LRU 缓存:在内存中缓存 index 的构建结果 2. 磁盘快照~/.hermes/.skills_prompt_snapshot.json——跨进程重启也能恢复 当 Agent 启动时,先按"编码模式"(compact_categories)决定是否要压缩非编码类 Skill 的描述。如果当前目录和平台被识别为 coding 场景,非编码类 Skill 在索引中只显示名称,不显示描述——节省约 60-80% 的 Index Token。


4. 自注册:工具系统中的 Skills 工具

Skill 的操作通过三个工具暴露给 Agent:

# tools.registry 中的注册(在 skills_tool.py 的模块顶层)
registry.register(
    name="skills_list",
    toolset="skills",
    schema={
        "description": "按类别列出所有可用的技能,返回元数据",
        "parameters": {
            "type": "object",
            "properties": {
                "category": {"type": "string", "description": "按类别筛选(可选)"}
            }
        }
    },
    handler=skills_list,
    check_fn=check_skills_requirements,
)
registry.register(
    name="skill_view",
    toolset="skills",
    # ... 读取完整内容或引用文件
)
registry.register(
    name="skill_manage",
    toolset="skills",
    # ... 增删改
)

注册表的 check_fn 是一个常量 check_skills_requirements()——它始终返回 True:

def check_skills_requirements() -> bool:
    """Skills are always available — the directory is created on first use if needed."""
    return True

这意味着 Skills 工具集会一直可用,永远不会被"环境不满足"而禁用。

5. 斜杠命令绑定

skill_commands.py 把 Skill 加载和 CLI 斜杠命令绑定在一起。当用户在终端敲 /my-skill 时:

def _load_skill_payload(skill_identifier, task_id=None):
    raw_identifier = (skill_identifier or "").strip()
    if not raw_identifier:
        return None
    try:
        from tools.skills_tool import SKILLS_DIR, skill_view
        # 支持绝对路径和相对路径
        identifier_path = Path(raw_identifier).expanduser()
        if identifier_path.is_absolute():
            # 安全检测:只允许在 SKILLS_DIR 和 external_skills_dirs 内的路径
            for root in trusted_roots:
                try:
                    normalized = str(identifier_path.relative_to(root))
                    break
                except ValueError:
                    continue
        else:
            normalized = raw_identifier.lstrip("/")
        loaded_skill = json.loads(
            skill_view(normalized, task_id=task_id, preprocess=False)
        )
    except Exception:
        return None

路径安全检测是一个值得关注的设计:skill_view() 在处理绝对路径时,只允许在 SKILLS_DIR 和 external skills dirs 范围内的路径。这意味着你不能用 skill_view("/etc/passwd") 来读取系统文件——这是一个安全边界。

6. 注入检测

skills_tool.py 硬编码了一个 prompt injection 检测模式列表:

_INJECTION_PATTERNS: list = [
    "ignore previous instructions",
    "ignore all previous",
    "you are now",
    "disregard your",
    "forget your instructions",
    "new instructions:",
    "system prompt:",
    "<system>",
    "]]>",
]

这些模式在 skill_view() 加载内容时检查。如果你从某个不可信源复制了 SKILL.md,其中包含"ignore previous instructions",Hermes 会探测到并发出警告。

7. 退化机制:什么时候 Skill 不会加载

Hermes 不会无差别加载所有 Skill。以下情况 Skill 会被跳过或标记为不可用:

7.1 平台不匹配

_PLATFORM_MAP = {
    "macos": "darwin",
    "linux": "linux",
    "windows": "win32",
}
def skill_matches_platform(frontmatter) -> bool:
    from agent.skill_utils import skill_matches_platform as _impl
    return _impl(frontmatter)

如果 SKILL.md 中声明了 platforms: [macos],这台 Linux 机器就不会加载它。

7.2 环境不匹配

def skill_matches_environment(frontmatter) -> bool:
    from agent.skill_utils import skill_matches_environment as _impl
    return _impl(frontmatter)

这个检查是"offer-time"级别的——只在工具发现阶段生效。如果你显式调用 skill_view("my-skill"),它会强制加载,不受环境检查影响。

7.3 所需环境变量缺失

Skill 可以声明 required_environment_variables。如果某个必需的环境变量不存在:

if _is_gateway_surface() and not env_var_enabled("HERMES_INTERACTIVE"):
    return {
        "missing_names": missing_names,
        "setup_skipped": False,
        "gateway_setup_hint": _gateway_setup_hint(),
    }

Gateway 上(Telegram/Discord 等非交互界面)会返回一条提示,告诉用户去 CLI 配置这个环境变量。CLI 上则会直接弹窗让用户输入。

8. 一条完整的 Skill 链路

从零到使用一个 Skill 的完整路径:

用户创建 SKILL.md
    
文件写入 ~/.hermes/skills/<category>/<name>/
    
Agent 启动  System Prompt 构建
    
prompt_builder 调用 build_skills_system_prompt()
     扫描 skills/ 目录
     读取每个 SKILL.md  frontmatter
     两层缓存(LRU + disk snapshot
     生成 Skills Index(名称+描述列表)
    
System Prompt 注入  Agent 看到可用工具
    
用户:"/check-server"
    
skill_commands._load_skill_payload("check-server")
     skill_view() 读取 SKILL.md
     注入检测扫描
     平台/环境检查
     环境变量检查
    
Agent 执行 Skill 中定义的工作流
    
完成后  Hindsight 自动 retain 总结
     如果够复杂,新建/更新 SKILL.md(技能进化)

本系列基于 Hermes Agent v0.15.2 源码。技能系统文件:tools/skills_tool.py(1545 行)、agent/skill_commands.py(527 行)、agent/skill_preprocessing.pyagent/skill_utils.py


本文由 admin 原创,转载请注明出处。

相关推荐

评论

0
暂无评论,来发表第一条评论吧

发表评论

登录 后发表评论

发现更多