title: Hermes Agent 高级技能(Skill)开发实战:打造你的专属智能体工具链
date: 2026-06-04
tags: [Hermes Agent, AI, 自动化, Skill开发, 智能体]
Hermes Agent 高级技能(Skill)开发实战:打造你的专属智能体工具链
引言
当部署好 Hermes Agent 并体验到其强大的自动化能力后,下一个问题自然浮现:如何让智能体做我们想做的事?
答案就在 Skill(技能) 中。Skill 是 Hermes Agent 的核心扩展机制——就像给 AI 配备了一把瑞士军刀,每个技能文件就是一把功能独特的刀片。无论是文件批处理、数据库查询、代码审查,还是对接企业内部 API,都可以通过编写一个 Skill 来实现。
本文将带你从零开始,手写三个实用 Skill:文件清理工具、PostgreSQL 查询助手、以及自定义 Slack 通知器。全程代码实战,无需任何框架知识,仅需基础的 Python 和 YAML 理解。
核心概念:Skill 到底是什么?
Hermes Agent 中的 Skill 本质上是一个定义了 工具(tools) 和 指令(instructions) 的模块。它告诉 Agent:
- 什么时候调用这个技能(通过 triggers 或 instruction 描述)
- 有哪些工具可用(shell 命令、Python 函数、API 调用等)
- 工具的输入输出格式(参数类型、返回结构)
Skill 的目录结构
每个 Skill 存放在独立的目录中,典型结构如下:
~/.hermes/skills/my-custom-skill/
├── skill.yaml # 技能元数据与工具定义
├── tools/
│ ├── cleanup.py # Python 工具实现
│ └── slack_notify.py
└── templates/ # 可选:提示词模板
└── report.md.j2
skill.yaml 核心字段
# ~/.hermes/skills/my-custom-skill/skill.yaml
name: "my-custom-skill"
description: "自定义技能集合:文件清理、数据库查询、Slack通知"
version: "1.0.0"
author: "your-name"
# 当用户提到以下关键词时,优先调用该技能
triggers:
- "清理"
- "数据库查询"
- "Slack通知"
# 工具定义
tools:
- name: "cleanup_temp_files"
description: "清理指定目录下的临时文件(.tmp, .log, .cache)"
command: "python3 {{SKILL_DIR}}/tools/cleanup.py"
args:
directory:
type: string
description: "目标目录路径"
required: true
days_old:
type: integer
description: "保留最近 N 天的文件(默认 7 天)"
default: 7
- name: "query_postgres"
description: "执行 PostgreSQL 查询并返回结果"
command: "python3 {{SKILL_DIR}}/tools/query_db.py"
args:
query:
type: string
description: "SQL 查询语句"
required: true
limit:
type: integer
description: "返回行数限制"
default: 10
实战步骤:从零编写三个 Skill
Skill 1:文件清理工具
这是最实用的入门级 Skill。它能根据文件年龄、类型和大小,安全地清理指定目录。
第一步:创建目录结构
mkdir -p ~/.hermes/skills/file-cleanup/tools
第二步:编写 skill.yaml
# ~/.hermes/skills/file-cleanup/skill.yaml
name: "file-cleanup"
description: "智能文件清理:按类型、年龄、大小批量清理"
version: "1.0.0"
triggers:
- "清理文件"
- "释放空间"
- "删除临时文件"
tools:
- name: "cleanup_by_pattern"
description: "按文件模式清理(如 *.tmp, *.log)"
command: "python3 {{SKILL_DIR}}/tools/cleanup.py --action pattern"
args:
directory:
type: string
description: "要清理的目录"
required: true
pattern:
type: string
description: "文件匹配模式(如 '*.tmp')"
required: true
dry_run:
type: boolean
description: "预览模式,不实际删除"
default: true
- name: "cleanup_by_age"
description: "清理超过指定天数的文件"
command: "python3 {{SKILL_DIR}}/tools/cleanup.py --action age"
args:
directory:
type: string
description: "要清理的目录"
required: true
days:
type: integer
description: "保留最近 N 天内的文件"
default: 30
dry_run:
type: boolean
description: "预览模式,不实际删除"
default: true
第三步:编写 Python 工具脚本
#!/usr/bin/env python3
# ~/.hermes/skills/file-cleanup/tools/cleanup.py
"""Hermes Agent Skill: 智能文件清理工具"""
import argparse
import os
import time
from pathlib import Path
from datetime import datetime, timedelta
def cleanup_by_pattern(directory: str, pattern: str, dry_run: bool = True):
"""按文件模式匹配并清理"""
path = Path(directory).expanduser().resolve()
if not path.exists():
return {"error": f"目录不存在: {directory}"}
deleted_count = 0
deleted_size = 0
results = []
for f in path.rglob(pattern):
if not f.is_file():
continue
size = f.stat().st_size
if dry_run:
results.append(f"[预览] 将删除: {f} ({size} bytes)")
else:
f.unlink()
results.append(f"[已删除] {f} ({size} bytes)")
deleted_count += 1
deleted_size += size
return {
"status": "preview" if dry_run else "cleaned",
"directory": str(path),
"pattern": pattern,
"files_matched": deleted_count,
"total_size_bytes": deleted_size,
"total_size_human": human_size(deleted_size),
"details": results,
}
def cleanup_by_age(directory: str, days: int = 30, dry_run: bool = True):
"""清理超过指定天数的旧文件"""
path = Path(directory).expanduser().resolve()
if not path.exists():
return {"error": f"目录不存在: {directory}"}
cutoff = time.time() - days * 86400
deleted_count = 0
deleted_size = 0
results = []
for f in path.rglob("*"):
if not f.is_file():
continue
if f.stat().st_mtime < cutoff:
size = f.stat().st_size
mtime = datetime.fromtimestamp(f.stat().st_mtime).isoformat()
if dry_run:
results.append(f"[预览] 将删除: {f} (修改于 {mtime}, {size} bytes)")
else:
f.unlink()
results.append(f"[已删除] {f} (修改于 {mtime}, {size} bytes)")
deleted_count += 1
deleted_size += size
return {
"status": "preview" if dry_run else "cleaned",
"directory": str(path),
"older_than_days": days,
"files_matched": deleted_count,
"total_size_bytes": deleted_size,
"total_size_human": human_size(deleted_size),
"details": results,
}
def human_size(bytes_size: int) -> str:
"""将字节转换为人类可读格式"""
for unit in ["B", "KB", "MB", "GB", "TB"]:
if bytes_size < 1024:
return f"{bytes_size:.2f} {unit}"
bytes_size /= 1024
return f"{bytes_size:.2f} PB"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Hermes 文件清理工具")
parser.add_argument("--action", required=True, choices=["pattern", "age"])
parser.add_argument("--directory", required=True)
parser.add_argument("--pattern", default="*.tmp")
parser.add_argument("--days", type=int, default=30)
parser.add_argument("--dry-run", action="store_true", default=True)
parser.add_argument("--no-dry-run", action="store_false", dest="dry_run")
args = parser.parse_args()
if args.action == "pattern":
result = cleanup_by_pattern(args.directory, args.pattern, args.dry_run)
else:
result = cleanup_by_age(args.directory, args.days, args.dry_run)
print(json.dumps(result, indent=2, ensure_ascii=False))
Skill 2:PostgreSQL 查询助手
这个 Skill 让你的 Hermes Agent 能直接查询数据库并返回结构化结果。
skill.yaml 配置:
# ~/.hermes/skills/db-query/skill.yaml
name: "db-query"
description: "PostgreSQL 数据库查询助手"
version: "1.0.0"
triggers:
- "查询数据库"
- "执行SQL"
- "数据库"
tools:
- name: "run_sql_query"
description: "执行 SQL 查询并返回 JSON 结果"
command: "python3 {{SKILL_DIR}}/tools/query_db.py"
args:
query:
type: string
description: "完整 SQL 查询语句"
required: true
db_host:
type: string
description: "数据库主机地址(默认从环境变量读取)"
db_name:
type: string
description: "数据库名称(默认从环境变量读取)"
limit:
type: integer
description: "最大返回行数"
default: 50
对应的 Python 实现:
#!/usr/bin/env python3
# ~/.hermes/skills/db-query/tools/query_db.py
"""Hermes Agent Skill: PostgreSQL 查询工具"""
import argparse
import json
import os
import psycopg2
from psycopg2.extras import RealDictCursor
def query_database(
sql: str,
host: str = None,
port: int = 5432,
dbname: str = None,
user: str = None,
password: str = None,
limit: int = 50,
) -> dict:
"""执行查询并返回结构化结果"""
# 从环境变量获取默认值
host = host or os.environ.get("PGHOST", "localhost")
dbname = dbname or os.environ.get("PGDATABASE", "postgres")
user = user or os.environ.get("PGUSER", "postgres")
password = password or os.environ.get("PGPASSWORD", "")
# 安全限制:添加 LIMIT 防止全表扫描
limited_sql = sql.rstrip().rstrip(";")
if "limit" not in limited_sql.lower():
limited_sql += f" LIMIT {limit}"
conn = psycopg2.connect(
host=host,
port=port,
dbname=dbname,
user=user,
password=password,
)
try:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
start = time.time()
cur.execute(limited_sql)
rows = cur.fetchall()
elapsed = time.time() - start
return {
"status": "success",
"query": limited_sql,
"row_count": len(rows),
"execution_time_ms": round(elapsed * 1000, 2),
"columns": list(rows[0].keys()) if rows else [],
"rows": rows,
}
except Exception as e:
return {"status": "error", "query": sql, "error": str(e)}
finally:
conn.close()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--query", required=True)
parser.add_argument("--db-host")
parser.add_argument("--db-name")
parser.add_argument("--limit", type=int, default=50)
args = parser.parse_args()
result = query_database(
sql=args.query,
host=args.db_host,
dbname=args.db_name,
limit=args.limit,
)
print(json.dumps(result, indent=2, ensure_ascii=False, default=str))
Skill 3:自定义 Slack 通知器
将 Agent 的自动化结果直接推送到 Slack 频道。
skill.yaml 配置:
# ~/.hermes/skills/slack-notify/skill.yaml
name: "slack-notify"
description: "发送通知到 Slack 频道"
version: "1.0.0"
triggers:
- "发Slack"
- "通知团队"
- "推送消息"
tools:
- name: "send_slack_message"
description: "发送 Markdown 格式消息到指定 Slack 频道"
command: "python3 {{SKILL_DIR}}/tools/slack_notify.py"
args:
channel:
type: string
description: "Slack 频道名称(如 #general, #dev-alerts)"
required: true
message:
type: string
description: "消息内容(支持 Markdown 格式)"
required: true
title:
type: string
description: "消息标题(可选)"
default: "Hermes Agent 通知"
color:
type: string
description: "消息颜色:good(绿)、warning(黄)、danger(红)、#自定义十六进制"
default: "good"
Python 实现:
#!/usr/bin/env python3
# ~/.hermes/skills/slack-notify/tools/slack_notify.py
"""Hermes Agent Skill: Slack 消息推送"""
import argparse
import json
import os
import requests
def send_slack_webhook(
webhook_url: str,
message: str,
title: str = "Hermes Agent 通知",
color: str = "good",
channel: str = None,
) -> dict:
"""通过 Slack Webhook 发送富文本消息"""
# 如果没有指定 webhook_url,从环境变量读取
webhook_url = webhook_url or os.environ.get("SLACK_WEBHOOK_URL")
if not webhook_url:
return {"status": "error", "error": "未设置 SLACK_WEBHOOK_URL 环境变量"}
# 构建 Slack 消息 payload
payload = {
"attachments": [
{
"color": color,
"title": title,
"text": message,
"mrkdwn_in": ["text", "fields"],
"footer": "Hermes Agent",
"ts": int(__import__("time").time()),
}
]
}
if channel:
payload["channel"] = channel
try:
resp = requests.post(
webhook_url,
json=payload,
timeout=10,
headers={"Content-Type": "application/json"},
)
if resp.status_code == 200:
return {"status": "success", "channel": channel or "default", "title": title}
else:
return {
"status": "error",
"http_code": resp.status_code,
"response": resp.text,
}
except Exception as e:
return {"status": "error", "error": str(e)}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--channel")
parser.add_argument("--message", required=True)
parser.add_argument("--title", default="Hermes Agent 通知")
parser.add_argument("--color", default="good")
parser.add_argument("--webhook-url", default=None)
args = parser.parse_args()
result = send_slack_webhook(
webhook_url=args.webhook_url,
channel=args.channel,
message=args.message,
title=args.title,
color=args.color,
)
print(json.dumps(result, indent=2, ensure_ascii=False))
使用与调试技巧
编写完 Skill 后,如何确认它被正确加载了?
# 1. 重新加载 Skill(在 Hermes 会话中)
reload_skills
# 2. 查看当前加载的所有 Skill
list_skills
# 3. 实时查看日志,确认 Skill 是否正确注册
tail -f ~/.hermes/logs/hermes.log | grep -i "skill"
调试三板斧
| 问题 | 排查方法 |
|---|---|
| Skill 未加载 | 检查 skill.yaml 格式(YAML 对缩进极其敏感,用空格而非 Tab) |
| 工具找不到 | 确认 command 路径中的 {{SKILL_DIR}} 变量是否正确展开 |
| 参数解析失败 | 在终端直接运行 Python 脚本测试参数传入:python3 tools/cleanup.py --help |
最佳实践
- 安全优先:文件操作类工具默认开启
dry_run=true,让用户确认后再实际执行 - 参数验证:Python 脚本内对输入参数做二次校验,不要完全信赖 Agent 的传参
- 错误处理:所有工具函数都返回结构化的 dict(包含
status和error字段),方便 Agent 理解 - 环境变量:敏感信息(数据库密码、API Key)通过
~/.hermes/config.yaml或操作系统环境变量传递,绝不硬编码
常见问题
Q: Skill 写好但 Hermes 说找不到这个工具?
A: 执行 hermes skill reload 重新加载配置。然后检查日志 tail -f ~/.hermes/logs/hermes.log 看 YAML 解析是否报错。
Q: 一个 Skill 可以包含多个工具吗?
A: 当然可以。上面的文件清理 Skill 就一口气定义了 cleanup_by_pattern 和 cleanup_by_age 两个工具。合理组织可大幅降低技能数量。
Q: Python 工具脚本里的依赖包怎么处理?
A: 在 Skill 目录下创建 requirements.txt 文件,然后运行 hermes skill install。Hermes 会自动为每个 Skill 创建独立的虚拟环境。
Q: 能让 Skill 调用其他 Skill 的工具吗?
A: 不能直接跨 Skill 调用。但 Agent 本身可以依次调用多个工具——你只需要在对话中描述完整的流程,Hermes 会自动编排。
总结
Skill 是 Hermes Agent 生态中最强大的扩展点。通过本文的三个实战案例,你已经掌握了:
- ✅ Skill 的目录结构和
skill.yaml配置规范 - ✅ 文件清理工具的完整编写流程(含 dry-run 安全机制)
- ✅ 数据库查询工具的数据处理和返回格式化
- ✅ Slack 通知工具的外部 API 对接模式
- ✅ 调试、日志和最佳实践
从今天起,不必再等待官方发布新功能——任何你需要的自动化能力,都可以通过编写 Skill 来实现。
下一步可以挑战更复杂的 Skill:对接 Jira API 自动创建工单、解析 Docker 日志并智能告警、甚至让 Hermes 帮你管理 K8s 集群。欢迎在评论区分享你写的 Skill!















暂无评论内容