Hermes Agent 的插件系统及插件开发
Hermes Agent 的插件系统不算庞大,但设计得相当工整。最近为了接入一个新的图像生成后端,我把它的插件机制从头到尾走了一遍——从目录结构、注册流程,到调试时的各种踩坑。这篇文章是我沿途的记录,希望能帮你少走些弯路。
1. 概述
Hermes 的插件系统采用 共享源码 + Profile 符号链接 的架构。所有 Profile 共用同一份插件代码(位于 ~/.hermes/profiles/plugins/),各 Profile 通过符号链接引用,并在自身的 config.yaml 中按需启用。
三个核心设计原则值得先记住:
- 动态 import,无需自建 venv:插件由 Hermes 运行时动态加载,使用的是 Hermes 自身的 Python 环境(
~/.hermes/hermes-agent/venv/)。插件目录下面不需要搞一个自己的虚拟环境。 - Last-writer-wins 注册:同名 provider 后注册的会覆盖先注册的——这是个重要的行为细节,调试时容易忽略。
- 三层发现顺序:Bundled → User → Pip 包,依次扫描,同名覆盖。这意味着你写的 user 插件天然可以覆盖官方内置的同名 provider。

2. 八种插件类型
Hermes 支持八种插件类型,每种都有独立的 ABC 契约和注册 API:
| 类型 | 目录路径 | 注册方式 | 用途 |
|---|---|---|---|
| 通用工具 | plugins/<name>/ | registry.register() | 自定义工具、slash 命令、生命周期 hook |
| 图像生成 | plugins/image_gen/<name>/ | ctx.register_image_gen_provider() | 替换/扩展 image_generate 工具 |
| 视频生成 | plugins/video_gen/<name>/ | ctx.register_video_gen_provider() | 替换/扩展视频生成工具 |
| 模型 Provider | plugins/model-providers/<name>/ | register_provider(Profile) | 新增 LLM 推理端点 |
| 记忆后端 | plugins/memory/<name>/ | MemoryProvider 子类 | 替换长期记忆存储 |
| 上下文引擎 | plugins/context_engine/<name>/ | ContextEngine 子类 | 替换上下文压缩策略 |
| 平台适配 | plugins/platforms/<name>/ | ctx.register_platform() | 新增聊天通道 |
| MCP Server | 外部进程 | MCP 协议(非 Python) | 接入任意 MCP 工具源 |
Agent 主循环是 Hermes 的固定核心,不可替换。你不能写一个插件把 Hermes 的主循环换成 CrewAI 的——但可以在 generate() 内部 import crewai 作为库来调用(后面第 6.3 节会展开讲)。
3. 环境架构
理解这套目录结构是后面一切操作的基础。我第一次配的时候就因为没搞清哪个 Python 环境在干活,绕了不少路。
~/.hermes/
├── hermes-agent/ # Hermes 本体
│ └── venv/ # ★ 运行时 Python 环境(hermes 命令自动激活)
├── profiles/
│ ├── plugins/ # ★ 共享插件源(所有 profile 共用)
│ │ ├── pyproject.toml # uv workspace 根配置
│ │ ├── pyrightconfig.json # LSP 指向 hermes-agent/venv
│ │ └── <category>/<name>/ # 各插件代码
│ └── <profile>/
│ ├── config.yaml # 插件启用/禁用 + provider 配置
│ ├── .env # API keys
│ └── plugins -> ../plugins # ★ 符号链接到共享源
└── plugins -> profiles/plugins # 全局链接(无 profile 模式用)
三种 Python 环境的角色分工:
| 角色 | 使用的 Python | 用途 |
|---|---|---|
| Hermes 运行时 | ~/.hermes/hermes-agent/venv/ | hermes 命令 import 插件 |
| 编辑器 / LSP | ~/.hermes/hermes-agent/venv/ | pyrightconfig.json 指向此处,提供代码补全 |
uv lock | 系统 Python 3.11 | 仅锁定 workspace 依赖版本,不创建 .venv |
插件代码里能 import 什么,取决于 hermes-agent/venv 里装了什么。如果你在插件里用了 some-obscure-lib,记得:
~/.hermes/hermes-agent/venv/bin/pip install some-obscure-lib
好消息是 requests、openai、pydantic、httpx、Pillow 这些常用的已经内置了,大多数情况不需要额外安装。
4. 首次配置:建立符号链接
如果你是新装的环境,第一步是把符号链接搭好:
# 全局链接(无 profile 模式时 Hermes 扫描此路径)
# 在 ~/.hermes/ 下执行
ln -sr profiles/plugins plugins
# 每个 profile 也需要链接到共享源
# 在 ~/.hermes/profiles/<profile>/ 下执行
unlink plugins 2>/dev/null
ln -sr ../plugins plugins
rm -rf plugins/末尾的斜杠会让 shell 跟随符号链接,直接删掉原始目录里的所有插件代码。用 unlink 来移除符号链接——它只删链接本身,不会误伤原始数据。
5. 开发实战
下面走一遍完整的开发流程。以图像生成 Provider 为例——这是最常见也最有代表性的插件类型。
5.1. 创建插件目录
cd ~/.hermes/profiles/plugins
# Provider 类插件需要按类别建子目录
mkdir -p image_gen/my-backend
5.2. 加入 uv workspace
在插件目录下创建 pyproject.toml:
[project]
name = "my-backend"
version = "0.1.0"
requires-python = ">=3.11,<3.14"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
确保 workspace 根 pyproject.toml 的 members 数组里包含了你的路径:
[tool.uv.workspace]
members = ["image_gen/*", "model-providers/*", ...]
然后:
uv lock # 解析依赖,不创建 venv
5.3. 编写 plugin.yaml
name: my-backend # 唯一标识符,必须与 provider.name 一致
version: 1.0.0
description: "接入某个图像生成后端"
author: Your Name
kind: backend # Provider 类需要;通用工具可省略
requires_env: # 声明需要的环境变量
- MY_API_KEY
5.4. 编写插件代码
这是重头戏。一个 ImageGenProvider 的骨架长这样:
from agent.image_gen_provider import (
ImageGenProvider, success_response, error_response,
save_b64_image, save_url_image,
resolve_aspect_ratio, normalize_reference_images,
DEFAULT_ASPECT_RATIO,
)
from typing import Any, Dict, List, Optional
import os, logging, requests
logger = logging.getLogger(__name__)
class MyProvider(ImageGenProvider):
@property
def name(self) -> str:
return "my-backend" # 必须与 plugin.yaml 的 name 一致
@property
def display_name(self) -> str:
return "My Backend"
def is_available(self) -> bool:
return bool(os.environ.get("MY_API_KEY"))
def list_models(self) -> List[Dict[str, Any]]:
return [{"id": "model-a", "display": "Model A", "speed": "~5s"}]
def capabilities(self) -> Dict[str, Any]:
return {
"modalities": ["text", "image"],
"max_reference_images": 1
}
def get_setup_schema(self) -> dict:
return {
"name": "My Backend",
"env_vars": [
{"key": "MY_API_KEY", "prompt": "API key", "url": "https://..."},
],
}
def generate(self, prompt, aspect_ratio=DEFAULT_ASPECT_RATIO, *,
image_url=None, reference_image_urls=None, **kwargs):
# 1. 校验 prompt
if not prompt or not prompt.strip():
return error_response("prompt 不能为空")
# 2. 检查 API key
api_key = os.environ.get("MY_API_KEY")
if not api_key:
return error_response("MY_API_KEY 未设置")
# 3. 路由 text-to-image vs image-to-image
# 4. 调用后端 API
# 5. save_b64_image() 或 save_url_image() 保存到缓存
# 6. 返回 success_response(image=..., provider=self.name)
return success_response(image="/path/to/output.png", provider=self.name)
def register(ctx) -> None:
ctx.register_image_gen_provider(MyProvider())
**kwargsgenerate() 的签名末尾必须接受 **kwargs。工具层会传入前向兼容的参数——如果你的函数因为不认识某个 kwarg 而抛出 TypeError,整个工具就崩了。这是最常见的坑,没有之一。
如果是通用工具(非 Provider 类),注册方式稍有不同:
from . import schemas, tools
def register(ctx):
ctx.register_tool(
name="my_tool",
toolset="my-plugin",
schema=schemas.MY_TOOL_SCHEMA,
handler=tools.my_tool_handler,
)
5.5. 配置 Profile
编辑 ~/.hermes/profiles/<profile>/config.yaml:
plugins:
enabled:
- image_gen/my-backend # 嵌套布局(带类别前缀)
- my-tool # 扁平布局
在对应的 .env 里加上 API key:
MY_API_KEY=your-key-here
5.6. 激活与验证
# 启用插件(user 插件需要此步;bundled 插件自动加载)
hermes plugins enable my-backend
# 设置活跃 provider
hermes config set image_gen.provider my-backend
hermes config set image_gen.model model-a
# 重启(config 在 session 启动时快照——不重启不生效)
/reset
# 验证
HERMES_PLUGINS_DEBUG=1 hermes plugins list | grep my-backend
hermes tools

到此为止,一个可以工作的图像生成插件就完成了。我坐在电脑前,长长舒了一口气——但转念一想,这只是开始。我们可以做的还有很多:引入其它 Agent 开发框架作为后端、建造更加强大的工具箱……下面聊聊三种包装策略。
6. 三种包装策略
6.1. OpenAI 兼容适配(最常见)
这是最省力的路径。很多厂商和自托管系统都提供 OpenAI 兼容的端点:
- ComfyUI + OpenAI 兼容节点
- vLLM 部署的图像模型
- Replicate 的 OpenAI 兼容镜像
- 企业内部代理
模式非常直白:requests.post(f"{base}/v1/images/generations", ...),然后解析 data[0].b64_json 或 data[0].url 即可。
6.2. 厂商原生协议
不兼容 OpenAI 的后端就得自己写协议翻译了。几个常见厂商的对接方式:
| 厂商 | 对接方式 |
|---|---|
| Replicate | POST → poll urls.get → 等待 succeeded → 取 output[0] |
| Google Imagen | POST with ?key= → 取 generatedImages[0].bytes.base64 |
| xAI Grok | 已内置为 xai,可作为参考实现研究 |
无论后端协议怎么变,插件侧的套路是一致的:调 API → 取 b64 或 url → 调 success_response()。分离关注点做得好,切换后端不会伤筋动骨。
6.3. 内嵌第三方 Agent SDK
这是最有想象空间的一种:把 CrewAI / LangGraph / AutoGen 作为库在 generate() 内部调用。
def generate(self, prompt, **kwargs):
from crewai import Agent, Crew, Task
from langchain_openai import ChatOpenAI
designer = Agent(role="设计师", goal="生成图片 URL",
backstory="你是一个创意设计师",
llm=ChatOpenAI(model="gpt-4o"))
task = Task(description=f"根据描述生成图片: {prompt}", agent=designer)
result = Crew(agents=[designer], tasks=[task]).kickoff()
image_url = parse_url_from(result.raw)
return success_response(image=image_url, provider=self.name)
- ✅ 在
generate()内部调用任意 Python 库——CrewAI、LangGraph、AutoGen,随便你 - ✅
delegate_task只能 spawn Hermes 子 agent(不是 CrewAI agent) - ❌ 替换 Hermes 的 Agent 主循环——这不是插件能干的事
- ❌
ProviderProfile只适用于 OpenAI/Anthropic/Bedrock 形状的 LLM 端点,不能用于编排框架
7. 目录结构速查
开发时放在手边参考。
通用插件
plugins/<name>/
├── plugin.yaml # 声明 name, version, provides_tools/hooks
├── __init__.py # def register(ctx): 注册工具和 hook
├── schemas.py # 工具的 JSON Schema(LLM 读取)
└── tools.py # 工具的实际处理逻辑
Provider 类插件
plugins/<category>/<name>/
├── plugin.yaml # kind: backend, requires_env
├── __init__.py # Provider 子类 + register()
└── pyproject.toml # 声明依赖
8. 排错手册
开发过程中最可能遇到的几个问题:
| 症状 | 可能原因 | 解决 |
|---|---|---|
| 插件不显示 | Profile 的 plugins/ 不是符号链接或路径错误 | ls -l plugins,确认指向 ../plugins |
provider <name> 未注册 | plugins.enabled 缺少 <category>/<name> | 确认 config.yaml 中 enabled 列表包含完整路径 |
ModuleNotFoundError | 第三方依赖未安装到 hermes-agent venv | ~/.hermes/hermes-agent/venv/bin/pip install <pkg> |
TypeError: unexpected keyword | generate() 未接受 **kwargs | 在签名末尾加上 **kwargs |
| 同名 bundled 插件优先 | 注册顺序:bundled → user,后者覆盖前者 | user 插件注册同名 provider 即可覆盖 |
9. 参考文档
- Build a Hermes Plugin
- Image Generation Provider
- Model Provider
- Memory Provider
- Context Engine
- Web Search Provider
- Browser Provider
- Secret Source
- Event Hooks Reference
- Official Example Plugins
写于一个深夜调试 session 之后。插件本身不复杂,但细节不少——希望这篇文章能帮你跳过那些我踩过的坑。
