Xây dựng plugin Hermes
Hướng dẫn này hướng dẫn cách xây dựng một plugin Hermes hoàn chỉnh từ đầu. Cuối cùng, bạn sẽ có một plugin hoạt động với nhiều công cụ, móc vòng đời, tệp dữ liệu được vận chuyển và kỹ năng đi kèm — mọi thứ mà hệ thống plugin hỗ trợ.
Hermes có một số giao diện có thể cắm riêng biệt — một số sử dụng API Python `register_*
, một số khác là các thư mục thả vào hoặc điều khiển theo cấu hình. Trước tiên hãy sử dụng bản đồ này:
| Nếu bạn muốn thêm… | Đọc |
|---|---|
| Công cụ tùy chỉnh, hook, lệnh gạch chéo, kỹ năng hoặc lệnh phụ CLI | Hướng dẫn này (bề mặt plugin chung) |
| Một LLM / phụ trợ suy luận (nhà cung cấp mới) | Model Provider Plugins |
| Một kênh cổng (Discord/Telegram/IRC/Teams/etc.) | Adding Platform Adapters |
| Phần phụ trợ bộ nhớ (Honcho/Mem0/Supermemory/etc.) | Memory Provider Plugins |
| Một công cụ nén ngữ cảnh | Context Engine Plugins |
| Một phụ trợ tạo hình ảnh | Image Generation Provider Plugins |
| Phần phụ trợ tạo video | Video Generation Provider Plugins |
| Phần phụ trợ TTS (bất kỳ CLI nào — piper, VoxCPM, Kokoro, nhân bản giọng nói,…) | TTS custom command providers — dựa trên cấu hình, không cần Python |
| Phần phụ trợ STT (thì thầm tùy chỉnh / ASR CLI) | Voice Message Transcription - đặt |
| `Hermes_LOCAL_STT_COMMAND | |
| ` thành mẫu shell | |
| Các công cụ bên ngoài thông qua MCP (hệ thống tệp, GitHub, Linear, bất kỳ máy chủ MCP nào) | MCP — khai báo |
| `MCP_servers.<name | |
| ` trong | |
| `config.yaml | |
| ` | |
| Móc sự kiện cổng (kích hoạt khi khởi động, sự kiện phiên, lệnh) | Event Hooks — thả |
| `HOOK.YAML |
handler.py vào
~/.Hermes/hooks/<name/
| | **Shell hooks** (chạy lệnh shell trên các sự kiện) | [Shell Hooks](/docs/user-guide/features/hooks#shell-hooks) — khai báo theo hooks:
trong config.yaml
| | **Các nguồn kỹ năng bổ sung** (kho GitHub tùy chỉnh, chỉ mục kỹ năng riêng) | [Skills](/docs/user-guide/features/skills) — Hermes skills tap add <repo
` · Publishing a tap |
| Nhà cung cấp suy luận cốt lõi hạng nhất (không phải plugin) | Adding Providers |
Xem toàn bộ Pluggable interfaces table để có cái nhìn tổng hợp về mọi bề mặt mở rộng bao gồm các kiểu định hướng cấu hình (TTS, STT, MCP, shell hook) và kiểu thư mục thả vào (móc cổng).
Những gì bạn đang xây dựng
Một plugin máy tính có hai công cụ:
calculate — đánh giá các biểu thức toán học (
`2**16
, `sqrt(144)
, `pi * 5**2
)
`unit_convert
- chuyển đổi giữa các đơn vị ( `100 F → 37.78 C
, `5 km → 3.11 mi
)
Cộng với một cái móc ghi lại mọi lệnh gọi công cụ và một tệp kỹ năng đi kèm.
Bước 1: Tạo thư mục plugin
mkdir -p ~/.Hermes/plugins/calculator
cd ~/.Hermes/plugins/calculator
`
## Bước 2: Viết bảng kê khai
Tạo
`plugin.YAML
:
`YAML
name: calculator
version: 1.0.0
description: Math calculator — evaluate expressions and convert units
provides_tools:
- calculate
- unit_convert
provides_hooks:
- post_tool_call
`
``Điều này nói với Hermes: "Tôi là một plugin có tên là máy tính, tôi cung cấp các công cụ và móc nối." Các trường
`provides_tools
` và
`provides_hooks
` là danh sách những gì plugin đăng ký.
Các trường tùy chọn bạn có thể thêm:
`
``` yaml
author: Your Name
requires_env: # gate loading on env vars; prompted during install
- SOME_API_KEY # simple format — plugin disabled if missing
- name: OTHER_KEY # rich format — shows description/url during install
description: "Key for the Other service"
url: "https://other.com/keys"
secret: true
`
## Bước 3: Viết lược đồ công cụ
Tạo
`schemas.py
- đây là nội dung LLM đọc để quyết định thời điểm gọi công cụ của bạn:
``` python
"""Tool schemas — what the LLM sees."""
CALCULATE = {
"name": "calculate",
"description": (
"Evaluate a mathematical expression and return the result. "
"Supports arithmetic (+, -, *, /, **), functions (sqrt, sin, cos, "
"log, abs, round, floor, ceil), and constants (pi, e). "
"Use this for any math the user asks about."
),
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression to evaluate (e.g., '2**10', 'sqrt(144)')",
},
},
"required": ["expression"],
},
}
UNIT_CONVERT = {
"name": "unit_convert",
"description": (
"Convert a value between units. Supports length (m, km, mi, ft, in), "
"weight (kg, lb, oz, g), temperature (C, F, K), data (B, KB, MB, GB, TB), "
"and time (s, min, hr, day)."
),
"parameters": {
"type": "object",
"properties": {
"value": {
"type": "number",
"description": "The numeric value to convert",
},
"from_unit": {
"type": "string",
"description": "Source unit (e.g., 'km', 'lb', 'F', 'GB')",
},
"to_unit": {
"type": "string",
"description": "Target unit (e.g., 'mi', 'kg', 'C', 'MB')",
},
},
"required": ["value", "from_unit", "to_unit"],
},
}
`
``**Tại sao lược đồ lại quan trọng:** Trường
`description
` là cách LLM quyết định thời điểm sử dụng công cụ của bạn. Hãy cụ thể về những gì nó làm và khi nào sử dụng nó.
`parameters
` xác định những đối số mà LLM chuyển qua.
## Bước 4: Viết các trình xử lý công cụ
Tạo
`tools.py
- đây là mã thực sự thực thi khi LLM gọi công cụ của bạn:
`Python
"""Tool handlers — the code that runs when the LLM calls each tool."""`import JSON
import math
# Safe globals for expression evaluation — no file/network access
_SAFE_MATH = {
"abs": abs, "round": round, "min": min, "max": max,
"pow": pow, "sqrt": math.sqrt, "sin": math.sin, "cos": math.cos,
"tan": math.tan, "log": math.log, "log2": math.log2, "log10": math.log10,
"floor": math.floor, "ceil": math.ceil,
"pi": math.pi, "e": math.e,
"factorial": math.factorial,
}
def calculate(args: dict, **kwargs) -> str:
"""Evaluate a math expression safely.
Rules for handlers:
1. Receive args (dict) — the parameters the LLM passed
2. Do the work
3. Return a JSON string — ALWAYS, even on error
4. Accept **kwargs for forward compatibility
"""
expression = args.get("expression", "").strip()
if not expression:
return JSON.dumps(\{"error": "No expression provided"})
try:
result = eval(expression, \{"__builtins__": \{}}, _SAFE_MATH)
return JSON.dumps(\{"expression": expression, "result": result})
except ZeroDivisionError:
return JSON.dumps(\{"expression": expression, "error": "Division by zero"})
except Exception as e:
return JSON.dumps(\{"expression": expression, "error": f"Invalid: \{e}"})
# Conversion tables — values are in base units
_LENGTH = \{"m": 1, "km": 1000, "mi": 1609.34, "ft": 0.3048, "in": 0.0254, "cm": 0.01}
_WEIGHT = \{"kg": 1, "g": 0.001, "lb": 0.453592, "oz": 0.0283495}
_DATA = \{"B": 1, "KB": 1024, "MB": 1024**2, "GB": 1024**3, "TB": 1024**4}
_TIME = \{"s": 1, "ms": 0.001, "min": 60, "hr": 3600, "day": 86400}
def _convert_temp(value, from_u, to_u):
# Normalize to Celsius
c = \{"F": (value - 32) * 5/9, "K": value - 273.15}.get(from_u, value)
# Convert to target
return \{"F": c * 9/5 + 32, "K": c + 273.15}.get(to_u, c)
def unit_convert(args: dict, **kwargs) -> str:
"""Convert between units."""
value = args.get("value")
from_unit = args.get("from_unit", "").strip()
to_unit = args.get("to_unit", "").strip()
if value is None or not from_unit or not to_unit:
return JSON.dumps(\{"error": "Need value, from_unit, and to_unit"})
try:
# Temperature
if from_unit.upper() in \{"C","F","K"} and to_unit.upper() in \{"C","F","K"}:
result = _convert_temp(float(value), from_unit.upper(), to_unit.upper())
return JSON.dumps(\{"input": f"\{value} \{from_unit}", "result": round(result, 4),
"output": f"\{round(result, 4)} \{to_unit}"})
# Ratio-based conversions
for table in (_LENGTH, _WEIGHT, _DATA, _TIME):
lc = \{k.lower(): v for k, v in table.items()}
if from_unit.lower() in lc and to_unit.lower() in lc:
result = float(value) * lc[from_unit.lower()] / lc[to_unit.lower()]
return JSON.dumps(\{"input": f"\{value} \{from_unit}",
"result": round(result, 6),
"output": f"\{round(result, 6)} \{to_unit}"})
return JSON.dumps(\{"error": f"Cannot convert \{from_unit} → \{to_unit}"})
except Exception as e:
return JSON.dumps(\{"error": f"Conversion failed: \{e}"})
`
``**Các quy tắc chính dành cho người xử lý:**
1. **Chữ ký:**
`def my_handler(args: dict, **kwargs) -> str
2. **Trả về:** Luôn là chuỗi JSON. Thành công và sai sót như nhau.
3. **Không bao giờ tăng:** Bắt tất cả các ngoại lệ, thay vào đó hãy trả về lỗi JSON.
4. **Chấp nhận
**kwargs
:** Hermes có thể chuyển ngữ cảnh bổ sung trong tương lai.
## Bước 5: Viết thông tin đăng ký
Tạo
`__init__.py
- điều này kết nối các lược đồ với trình xử lý:
``` python
"""Calculator plugin — registration."""`import logging`from . import schemas, tools`logger = logging.getLogger(__name__)
# Track tool usage via hooks
_call_log = []
def _on_post_tool_call(tool_name, args, result, task_id, **kwargs):
"""Hook: runs after every tool call (not just ours)."""
_call_log.append(\{"tool": tool_name, "session": task_id})
if len(_call_log) > 100:
_call_log.pop(0)
logger.debug("Tool called: %s (session %s)", tool_name, task_id)
def register(ctx):
"""Wire schemas to handlers and register hooks."""
ctx.register_tool(name="calculate", toolset="calculator",
schema=schemas.CALCULATE, handler=tools.calculate)
ctx.register_tool(name="unit_convert", toolset="calculator",
schema=schemas.UNIT_CONVERT, handler=tools.unit_convert)
# This hook fires for ALL tool calls, not just ours
ctx.register_hook("post_tool_call", _on_post_tool_call)
`
`**
`register()
` làm gì:**
- Được gọi đúng 1 lần khi khởi động
-
`ctx.register_tool()
` đưa công cụ của bạn vào sổ đăng ký - mô hình sẽ nhìn thấy nó ngay lập tức
-
`ctx.register_hook()
` đăng ký các sự kiện trong vòng đời
-
`ctx.register_CLI_command()
` đăng ký lệnh con CLI (ví dụ
`Hermes my-plugin <subcommand
)
-
`ctx.register_command()
` đăng ký lệnh gạch chéo trong phiên (ví dụ:
/myplugin <args
` bên trong trò chuyện CLI / cổng) - xem [Register slash commands](#register-slash-commands) bên dưới
-
`ctx.dispatch_tool(name, arguments)
` — gọi bất kỳ công cụ nào khác (tích hợp sẵn hoặc từ plugin khác) với bối cảnh của tác nhân mẹ (phê duyệt, thông tin xác thực, task_id) được tự động kết nối. Hữu ích từ các trình xử lý lệnh gạch chéo cần gọi
`terminal
,
`read_file
` hoặc bất kỳ công cụ nào khác như thể mô hình đã gọi nó trực tiếp.
- Nếu chức năng này gặp sự cố, plugin bị vô hiệu hóa nhưng Hermes vẫn hoạt động bình thường`**Ví dụ
`dispatch_tool
` — lệnh gạch chéo chạy một công cụ:**
``` python
def handle_scan(ctx, argstr):
"""Implement /scan by invoking the terminal tool through the registry."""
result = ctx.dispatch_tool("terminal", \{"command": f"find . -name '\{argstr}'"})
return result # returned to the caller's chat UI`def register(ctx):
ctx.register_command("scan", handle_scan, help="Find files matching a glob")
`
``Công cụ được điều phối sẽ trải qua các quy trình phê duyệt, biên tập và ngân sách thông thường — đó là một lệnh gọi công cụ thực sự chứ không phải là một lối tắt xung quanh chúng.
## Bước 6: Kiểm tra
Bắt đầu Hermes:
`bash
Hermes
`
``Bạn sẽ thấy
`calculator: calculate, unit_convert
` trong danh sách công cụ của biểu ngữ.
Hãy thử những lời nhắc sau:
`
What's 2 to the power of 16?
Convert 100 fahrenheit to celsius
What's the square root of 2 times pi?
How many gigabytes is 1.5 terabytes?
`
``Kiểm tra trạng thái plugin:
`
/plugins
`
``Đầu ra:
`
Plugins (1):
✓ calculator v1.0.0 (2 tools, 1 hooks)
`
### Phát hiện plugin gỡ lỗi
Nếu plugin của bạn không hiển thị - hoặc hiển thị nhưng không tải - hãy đặt
`Hermes_PLUGINS_DEBUG=1
` để nhận nhật ký khám phá chi tiết trên stderr:
`bash
Hermes_PLUGINS_DEBUG=1 Hermes plugins list
`
``Bạn sẽ thấy, đối với mọi nguồn plugin (theo gói, người dùng, dự án, điểm vào):
- thư mục nào đã được quét và mỗi thư mục mang lại bao nhiêu bảng kê khai
- trên mỗi bảng kê khai: khóa, tên, loại, nguồn, đường dẫn trên đĩa đã được giải quyết
- lý do bỏ qua:
`disabled via config
,
`not enabled in config
,
`exclusive plugin
,
`no plugin.YAML, depth cap reached
- khi tải: plugin đang được nhập, cùng với bản tóm tắt một dòng về những gì
`register(ctx)
` đã đăng ký (công cụ, hook, lệnh gạch chéo, lệnh CLI)
- về lỗi phân tích cú pháp: truy nguyên đầy đủ cho ngoại lệ (lỗi trình quét YAML, v.v.)
- đối với lỗi
`register()
: một dấu vết đầy đủ trỏ vào dòng trong
`__init__.py
` của bạn đã nâng lên
Các nhật ký tương tự luôn được ghi vào
~/.Hermes/logs/agent.log
` ở cấp độ CẢNH BÁO (chỉ lỗi) và cấp độ DEBUG (mọi thứ) khi biến env được đặt. Vì vậy, nếu bạn không thể chạy với env var (ví dụ: từ bên trong cổng), thay vào đó hãy theo đuôi tệp nhật ký:
``` bash
Hermes logs --level WARNING | grep -i plugin
`
``Những lý do phổ biến khiến plugin không xuất hiện:
- **Không được bật trong cấu hình** — các plugin được chọn tham gia. Chạy
`Hermes plugins enable <name
` (tên xuất phát từ đầu ra
`plugins list
, có thể là
<category/<plugin
` cho bố cục lồng nhau).
- **Bố cục thư mục sai** — phải là
~/.Hermes/plugins/<plugin-name/plugin.YAML
` (phẳng) hoặc
~/.Hermes/plugins/<category/<plugin-name/plugin.YAML
` (tối đa một cấp độ lồng danh mục). Bất cứ điều gì sâu sắc hơn đều bị bỏ qua.
- **Thiếu
`__init__.py
** — thư mục plugin cần cả
`plugin.YAML
` và
`__init__.py
` có chức năng
`register(ctx)
.
- **Sai
`kind
** — bộ điều hợp cổng cần
`kind: platform
` trong bảng kê khai của chúng. Các nhà cung cấp bộ nhớ được tự động phát hiện dưới dạng
`kind: exclusive
` và được định tuyến thông qua cấu hình
`memory.provider
` thay vì
`plugins.enabled
.
## Cấu trúc cuối cùng của plugin của bạn
`
~/.Hermes/plugins/calculator/
├── plugin.YAML # "I'm calculator, I provide tools and hooks"
├── __init__.py # Wiring: schemas → handlers, register hooks
├── schemas.py # What the LLM reads (descriptions + parameter specs)
└── tools.py # What runs (calculate, unit_convert functions)
`
``Bốn tập tin, tách biệt rõ ràng:
- **Manifest** khai báo plugin là gì
- **Lược đồ** mô tả các công cụ dành cho LLM
- **Trình xử lý** triển khai logic thực tế
- **Đăng ký** kết nối mọi thứ
## Plugin có thể làm gì khác?
### Gửi tệp dữ liệu
Đặt bất kỳ tệp nào vào thư mục plugin của bạn và đọc chúng tại thời điểm nhập:
``` python
# In tools.py or __init__.py
from pathlib import Path`_PLUGIN_DIR = Path(__file__).parent
_DATA_FILE = _PLUGIN_DIR / "data" / "languages.YAML"`with open(_DATA_FILE) as f:
_DATA = YAML.safe_load(f)
`
### Gói kỹ năng
Các plugin có thể gửi các tệp kỹ năng mà tác nhân tải qua
`skill_view("plugin:skill")
. Đăng ký chúng trong
`__init__.py
` của bạn:
`
~/.Hermes/plugins/my-plugin/
├── __init__.py
├── plugin.YAML
└── skills/
├── my-workflow/
│ └── SKILL.md
└── my-checklist/
└── SKILL.md
`
`
``` python
from pathlib import Path`def register(ctx):
skills_dir = Path(__file__).parent / "skills"
for child in sorted(skills_dir.iterdir()):
skill_md = child / "SKILL.md"
if child.is_dir() and skill_md.exists():
ctx.register_skill(child.name, skill_md)
`
``Giờ đây, đại lý có thể tải các kỹ năng của bạn với tên được đặt tên của họ:
`Python
skill_view("my-plugin:my-workflow") # → plugin's version
skill_view("my-workflow") # → built-in version (unchanged)
`
``**Thuộc tính chính:**
- Các kỹ năng của plugin **chỉ đọc** — chúng không nhập
~/.Hermes/skills/
` và không thể chỉnh sửa qua
`skill_manage
.
- Các kỹ năng của plugin **không** được liệt kê trong chỉ mục
<available_skills>
` của lời nhắc hệ thống — chúng là các tải rõ ràng được chọn tham gia.
- Tên kỹ năng đơn giản không bị ảnh hưởng - không gian tên ngăn ngừa xung đột với các kỹ năng tích hợp.
- Khi tác nhân tải một kỹ năng bổ trợ, một biểu ngữ ngữ cảnh gói sẽ được thêm vào trước để liệt kê các kỹ năng tương tự từ cùng một plugin.:::tip Legacy pattern
Mẫu
`shutil.copy2
` cũ (sao chép kỹ năng vào
~/.Hermes/skills/
) vẫn hoạt động nhưng tạo ra nguy cơ xung đột tên với các kỹ năng tích hợp sẵn. Ưu tiên
`ctx.register_skill()
` cho các plugin mới.
:::
### Cổng vào các biến môi trường
Nếu plugin của bạn cần khóa API:
``` yaml
# plugin.YAML — simple format (backwards-compatible)
requires_env:
- WEATHER_API_KEY
`
``Nếu
`WEATHER_API_KEY
` không được đặt, plugin sẽ bị tắt với thông báo rõ ràng. Không có sự cố, không có lỗi trong tác nhân — chỉ là "Plugin Weather bị vô hiệu hóa (thiếu: WEATHER_API_KEY)".
Khi người dùng chạy
`Hermes plugins install
, họ **được nhắc tương tác** về mọi biến
`requires_env
` bị thiếu. Các giá trị được lưu tự động vào
.env
.
Để có trải nghiệm cài đặt tốt hơn, hãy sử dụng định dạng phong phú có mô tả và URL đăng ký:
``` yaml
# plugin.YAML — rich format
requires_env:
- name: WEATHER_API_KEY
description: "API key for OpenWeather"
url: "https://openweathermap.org/API"
secret: true
`
| Lĩnh vực | Bắt buộc | Mô tả |
|-------|----------|-------------|
|
`name
` | Có | Tên biến môi trường |
|
`description
` | Không | Hiển thị cho người dùng trong khi nhắc cài đặt |
|
`url
` | Không | Lấy chứng chỉ ở đâu |
|
`secret
` | Không | Nếu
`true
, đầu vào bị ẩn (như trường mật khẩu) |
Cả hai định dạng có thể được trộn lẫn trong cùng một danh sách. Các biến đã được đặt sẽ được bỏ qua một cách âm thầm.
### Lazy-install tùy chọn phụ thuộc Python
Nếu plugin của bạn bao gồm một SDK mà không phải người dùng nào cũng cài đặt (SDK của nhà cung cấp, thư viện ML nặng, gói dành riêng cho nền tảng), thì đừng đặt
`import
` ở đầu mô-đun. Sử dụng trình trợ giúp
`tools.lazy_deps.ensure(...)
` bên trong trình xử lý công cụ - Hermes sẽ cài đặt gói trong lần sử dụng đầu tiên, được kiểm soát bởi cấu hình
`security.allow_lazy_installs
` của người dùng.
``` python
# tools.py
from tools.lazy_deps import ensure, FeatureUnavailable`def my_tool_handler(args, **kwargs):
try:
ensure("my-plugin.my-backend") # key must be in LAZY_DEPS
except FeatureUnavailable as exc:
return \{"error": str(exc)}
import my_backend_SDK # safe now
...
`
``Hai quy tắc từ mô hình bảo mật trong
`tools/lazy_deps.py
:
| Quy tắc | Tại sao |
|---|---|
| Khóa tính năng của bạn phải xuất hiện trong danh sách cho phép
`LAZY_DEPS
` trong cây | Ngăn chặn cấu hình độc hại dụ Hermes cài đặt các gói tùy ý - chỉ thông số kỹ thuật mà chính tàu Hermes mới đủ điều kiện |
| Thông số kỹ thuật chỉ có PyPI theo tên | Không có
--index-url
,
`git+https://
` hoặc tệp: đường dẫn. Ghim các phiên bản có PEP 440 (
"my-SDK>=1.2,<2"
) vào mục danh sách cho phép |
Đối với các plugin của bên thứ ba được phân phối qua pip, hãy khai báo các phần phụ tùy chọn dưới dạng phần bổ sung
`pyproject.TOML
` trong
`pyproject.TOML
` của riêng bạn và thông báo cho người dùng về
`pip install your-plugin[backend]
` — đường dẫn đó không đi qua
`lazy_deps
. Vũ điệu cài đặt lười biếng hữu ích nhất cho các plugin **đi kèm** trong đó việc vận chuyển phần phụ thuộc cứng vào mỗi lần cài đặt sẽ làm tăng dấu chân của Hermes cơ bản.
Khi
`security.allow_lazy_installs: false
` được đặt trên toàn cầu,
`ensure()
` sẽ tăng
`FeatureUnavailable
` ngay lập tức kèm theo gợi ý khắc phục — plugin của bạn sẽ phát hiện được nó và xuống cấp một cách nhẹ nhàng (trả về kết quả lỗi chứ không làm hỏng vòng lặp công cụ).
### Tính khả dụng của công cụ có điều kiện
Đối với các công cụ phụ thuộc vào thư viện tùy chọn:
``` python
ctx.register_tool(
name="my_tool",
schema=\{...},
handler=my_handler,
check_fn=lambda: _has_optional_lib(), # False = tool hidden from model
)
`
### Ghi đè công cụ tích hợp
Để thay thế một công cụ tích hợp bằng cách triển khai của riêng bạn (ví dụ: hoán đổi
công cụ trình duyệt mặc định cho phần phụ trợ CDP của Chrome hoặc thay thế
`web_search
` với chỉ mục công ty tùy chỉnh), vượt qua
`override=True
:
`Python
def register(ctx):
ctx.register_tool(
name="browser_navigate", # same name as the built-in
toolset="plugin_my_browser", # your own toolset namespace
schema=\{...},
handler=my_custom_navigate,
override=True, # explicit opt-in
)
`
``Nếu không có
`override=True
, cơ quan đăng ký sẽ từ chối mọi đăng ký có thể
theo dõi một công cụ hiện có từ một bộ công cụ khác - điều này ngăn cản
ghi đè ngẫu nhiên. Việc ghi đè được ghi lại ở cấp độ INFO nên nó
có thể kiểm tra được trong
~/.Hermes/logs/agent.log
. Tải plugin sau khi tích hợp sẵn
tools, do đó thứ tự đăng ký là chính xác: trình xử lý của bạn thay thế
một cái tích hợp sẵn.
### Đăng ký nhiều hook
`Python
def register(ctx):
ctx.register_hook("pre_tool_call", before_any_tool)
ctx.register_hook("post_tool_call", after_any_tool)
ctx.register_hook("pre_LLM_call", inject_memory)
ctx.register_hook("on_session_start", on_new_session)
ctx.register_hook("on_session_end", on_session_end)
`
### Tham khảo móc
Mỗi hook đều được ghi lại đầy đủ trên **[Event Hooks reference](/docs/user-guide/features/hooks#plugin-hooks)** — chữ ký gọi lại, bảng tham số, chính xác thời điểm mỗi hook được kích hoạt và ví dụ. Đây là bản tóm tắt:| Móc | Kích hoạt khi | Chữ ký gọi lại | Trả về |
|------|-------------|-------------------|--------|
| [XPROTECTX152XPROTECTX](/docs/user-guide/features/hooks#pre_tool_call) | Trước khi bất kỳ công cụ nào thực thi |
tool_name: str, args: dict, task_id: str
` | bỏ qua |
| [XPROTECTX154XPROTECTX](/docs/user-guide/features/hooks#post_tool_call) | Sau khi bất kỳ công cụ nào trả về |
tool_name: str, args: dict, result: str, task_id: str, duration_ms: int
` | bỏ qua |
| [XPROTECTX156XPROTECTX](/docs/user-guide/features/hooks#pre_LLM_call) | Mỗi lượt một lần, trước vòng lặp gọi công cụ |
session_id: str, user_message: str, conversation_history: list, is_first_turn: bool, model: str, platform: str
` | [context injection](#pre_LLM_call-context-injection) |
| [XPROTECTX158XPROTECTX](/docs/user-guide/features/hooks#post_LLM_call) | Một lần trong lượt, sau vòng gọi công cụ (chỉ những lượt thành công) |
session_id: str, user_message: str, assistant_response: str, conversation_history: list, model: str, platform: str
` | bỏ qua |
| [XPROTECTX160XPROTECTX](/docs/user-guide/features/hooks#on_session_start) | Đã tạo phiên mới (chỉ lượt đầu tiên) |
session_id: str, model: str, platform: str
` | bỏ qua |
| [XPROTECTX162XPROTECTX](/docs/user-guide/features/hooks#on_session_end) | Kết thúc mọi cuộc gọi
`run_conversation
+ thoát CLI |
`session_id: str, completed: bool, interrupted: bool, model: str, platform: str
` | bỏ qua |
| [XPROTECTX165XPROTECTX](/docs/user-guide/features/hooks#on_session_finalize) | CLI/gateway phá bỏ phiên hoạt động |
session_id: str \| None, platform: str
` | bỏ qua |
| [XPROTECTX167XPROTECTX](/docs/user-guide/features/hooks#on_session_reset) | Hoán đổi cổng trong khóa phiên mới (
/new
,
/reset
) |
`session_id: str, platform: str
` | bỏ qua |
Hầu hết các hook đều có chức năng quan sát kiểu fire-and-forget — giá trị trả về của chúng bị bỏ qua. Ngoại lệ là
`pre_LLM_call
, có thể đưa ngữ cảnh vào cuộc trò chuyện.
Tất cả các cuộc gọi lại phải chấp nhận
`
**kwargs
` để tương thích về phía trước. Nếu một lệnh gọi lại hook gặp sự cố, nó sẽ được ghi lại và bị bỏ qua. Các hook khác và đại lý vẫn tiếp tục bình thường.
### Chèn ngữ cảnh
`pre_LLM_call
``Đây là hook duy nhất có giá trị trả về quan trọng. Khi lệnh gọi lại
`pre_LLM_call
` trả về một lệnh có khóa
"context"
` (hoặc một chuỗi đơn giản), Hermes sẽ đưa văn bản đó vào **thông báo người dùng của lượt hiện tại**. Đây là cơ chế dành cho các plugin bộ nhớ, tích hợp RAG, lan can và bất kỳ plugin nào cần cung cấp ngữ cảnh bổ sung cho mô hình.
#### Định dạng trả về
``` python
# Dict with context key
return \{"context": "Recalled memories:\n- User prefers dark mode\n- Last project: Hermes-agent"}
# Plain string (equivalent to the dict form above)
return "Recalled memories:\n- User prefers dark mode"
# Return None or don't return → no injection (observer-only)
return None
`
``Bất kỳ kết quả trả về không trống, không trống nào có khóa
"context"
` (hoặc một chuỗi đơn giản không trống) đều được thu thập và thêm vào thông báo người dùng cho lượt hiện tại.
#### Cách hoạt động của quá trình tiêm
Ngữ cảnh được chèn sẽ được thêm vào **thông báo của người dùng** chứ không phải lời nhắc của hệ thống. Đây là một sự lựa chọn thiết kế có chủ ý:
- **Bảo quản bộ nhớ đệm theo lời nhắc** — lời nhắc của hệ thống vẫn giống nhau qua các lượt. Anthropic và OpenRouter lưu vào bộ đệm tiền tố lời nhắc hệ thống, do đó, việc giữ ổn định sẽ tiết kiệm hơn 75% mã thông báo đầu vào trong các cuộc hội thoại nhiều lượt. Nếu các plugin sửa đổi lời nhắc hệ thống thì mỗi lượt sẽ là một lỗi bộ đệm.
- **Tạm thời** — việc tiêm chỉ diễn ra tại thời điểm gọi API. Tin nhắn ban đầu của người dùng trong lịch sử hội thoại không bao giờ bị thay đổi và không có gì được lưu lại trong cơ sở dữ liệu phiên.
- **Lời nhắc hệ thống là lãnh thổ của Hermes** — nó chứa hướng dẫn dành riêng cho từng mô hình, quy tắc thực thi công cụ, hướng dẫn tính cách và nội dung kỹ năng được lưu trong bộ nhớ đệm. Các plugin đóng góp ngữ cảnh cùng với thông tin đầu vào của người dùng chứ không phải bằng cách thay đổi các hướng dẫn cốt lõi của tác nhân.
#### Ví dụ: Plugin thu hồi bộ nhớ
``` python
"""Memory plugin — recalls relevant context from a vector store."""`import httpx
MEMORY_API = "https://your-memory-API.example.com"`def recall_context(session_id, user_message, is_first_turn, **kwargs):
"""Called before each LLM turn. Returns recalled memories."""
try:
resp = httpx.post(f"\{MEMORY_API}/recall", JSON={
"session_id": session_id,
"query": user_message,
}, timeout=3)
memories = resp.JSON().get("results", [])
if not memories:
return None # nothing to inject`text = "Recalled context from previous sessions:\n"
text += "\n".join(f"- \{m['text']}" for m in memories)
return \{"context": text}
except Exception:
return None # fail silently, don't break the agent`def register(ctx):
ctx.register_hook("pre_LLM_call", recall_context)
`
#### Ví dụ: Plugin Guardrails
`Python
"""Guardrails plugin — enforces content policies."""
POLICY = """You MUST follow these content policies for this session:
- Never generate code that accesses the filesystem outside the working directory
- Always warn before executing destructive operations
- Refuse requests involving personal data extraction"""`def inject_guardrails(**kwargs):
"""Injects policy text into every turn."""
return \{"context": POLICY}
def register(ctx):
ctx.register_hook("pre_LLM_call", inject_guardrails)
`
#### Ví dụ: Hook chỉ dành cho người quan sát (không tiêm)
``` python
"""Analytics plugin — tracks turn metadata without injecting context."""`import logging
logger = logging.getLogger(__name__)
def log_turn(session_id, user_message, model, is_first_turn, **kwargs):
"""Fires before each LLM call. Returns None — no context injected."""
logger.info("Turn: session=%s model=%s first=%s msg_len=%d",
session_id, model, is_first_turn, len(user_message or ""))
# No return → no injection`def register(ctx):
ctx.register_hook("pre_LLM_call", log_turn)
`
#### Nhiều plugin trả về bối cảnh
Khi nhiều plugin trả về ngữ cảnh từ
`pre_LLM_call
, kết quả đầu ra của chúng sẽ được nối với hai dòng mới và được gắn vào thông báo của người dùng cùng nhau. Thứ tự tuân theo thứ tự khám phá plugin (theo bảng chữ cái theo tên thư mục plugin).
### Đăng ký lệnh CLI
Các plugin có thể thêm cây lệnh con
`Hermes <plugin>
` của riêng mình:
``` python
def _my_command(args):
"""Handler for Hermes my-plugin <subcommand>."""
sub = getattr(args, "my_command", None)
if sub == "status":
print("All good!")
elif sub == "config":
print("Current config: ...")
else:
print("Usage: Hermes my-plugin <status|config>")
def _setup_argparse(subparser):
"""Build the argparse tree for Hermes my-plugin."""
subs = subparser.add_subparsers(dest="my_command")
subs.add_parser("status", help="Show plugin status")
subs.add_parser("config", help="Show plugin config")
subparser.set_defaults(func=_my_command)
def register(ctx):
ctx.register_tool(...)
ctx.register_CLI_command(
name="my-plugin",
help="Manage my plugin",
setup_fn=_setup_argparse,
handler_fn=_my_command,
)
`
``Sau khi đăng ký, người dùng có thể chạy
`Hermes my-plugin status
,
`Hermes my-plugin config
, v.v.
**Các plugin của nhà cung cấp bộ nhớ** thay vào đó sử dụng cách tiếp cận dựa trên quy ước: thêm chức năng
`register_CLI(subparser)
` vào tệp
`CLI.py
` của plugin của bạn. Hệ thống phát hiện plugin bộ nhớ tự động tìm thấy nó - không cần lệnh gọi
`ctx.register_CLI_command()
. Xem [Memory Provider Plugin guide](/docs/developer-guide/memory-provider-plugin#adding-CLI-commands) để biết chi tiết.
**Gating nhà cung cấp hoạt động:** Các lệnh CLI của plugin bộ nhớ chỉ xuất hiện khi nhà cung cấp của họ là
`memory.provider
` đang hoạt động trong config. Nếu người dùng chưa thiết lập nhà cung cấp của bạn, các lệnh CLI của bạn sẽ không làm lộn xộn kết quả trợ giúp.
### Đăng ký lệnh gạch chéo
Các plugin có thể đăng ký các lệnh gạch chéo trong phiên — các lệnh mà người dùng nhập trong cuộc trò chuyện (như
/lcm status
` hoặc
/ping
). Chúng hoạt động trong cả CLI và cổng (Telegram, Discord, v.v.).
`Python
def _handle_status(raw_args: str) -> str:
"""Handler for /mystatus — called with everything after the command name."""
if raw_args.strip() == "help":
return "Usage: /mystatus [help|check]"
return "Plugin status: all systems nominal"`def register(ctx):
ctx.register_command(
"mystatus",
handler=_handle_status,
description="Show plugin status",
)
`
``Sau khi đăng ký, người dùng có thể gõ
/mystatus
` trong bất kỳ phiên nào. Lệnh xuất hiện trong tự động hoàn thành, đầu ra
/help
` và menu bot Telegram.
**Chữ ký:**
`ctx.register_command(name: str, handler: Callable, description: str = "")
| Tham số | Loại | Mô tả |
|----------|------|-------------|
|
`name
` |
`str
` | Tên lệnh không có dấu gạch chéo ở đầu (ví dụ
"lcm"
,
"mystatus"
) |
|
`handler
` |
`Callable[[str], str \| None]
` | Được gọi với chuỗi đối số thô. Cũng có thể là
`async
. |
|
`description
` |
`str
` | Hiển thị trong menu bot
/help
, tự động hoàn thành và Telegram bot |`**Sự khác biệt chính so với
`register_CLI_command()
:**
| |
register_command()
` |
`register_CLI_command()
` |
|---|---|---|
| Được gọi là |
/name
` trong một phiên |
`Hermes name
` trong terminal |
| Nó hoạt động ở đâu | Phiên CLI, Telegram, Discord, v.v. | Chỉ terminal |
| Người xử lý nhận | Chuỗi đối số thô | argparse
`Namespace
` |
| Trường hợp sử dụng | Chẩn đoán, trạng thái, hành động nhanh | Cây lệnh con phức tạp, trình hướng dẫn thiết lập |
**Bảo vệ xung đột:** Nếu plugin cố đăng ký tên xung đột với lệnh tích hợp (
`help
,
`model
,
`new
, v.v.), đăng ký sẽ bị từ chối âm thầm kèm theo cảnh báo nhật ký. Các lệnh tích hợp luôn được ưu tiên.
**Trình xử lý không đồng bộ:** Công văn cổng tự động phát hiện và chờ trình xử lý không đồng bộ, do đó bạn có thể sử dụng chức năng đồng bộ hóa hoặc không đồng bộ:
`Python
async def _handle_check(raw_args: str) -> str:
result = await some_async_operation()
return f"Check result: \{result}"`def register(ctx):
ctx.register_command("check", handler=_handle_check, description="Run async check")
`
### Gửi công cụ từ lệnh gạch chéo
Trình xử lý lệnh gạch chéo cần sắp xếp các công cụ (sinh ra một tác nhân phụ thông qua
`delegate_task
, gọi
`file_edit
, v.v.) nên sử dụng
`ctx.dispatch_tool()
` thay vì tiếp cận các phần bên trong khung. Bối cảnh tác nhân gốc (gợi ý không gian làm việc, công cụ quay vòng, kế thừa mô hình) được tự động kết nối.
`Python
def register(ctx):
def _handle_deliver(raw_args: str):
result = ctx.dispatch_tool(
"delegate_task",
{
"goal": raw_args,
"toolsets": ["terminal", "file", "web"],
},
)
return result`ctx.register_command(
"deliver",
handler=_handle_deliver,
description="Delegate a goal to a subagent",
)
`
``**Chữ ký:**
`ctx.dispatch_tool(name: str, args: dict, *, parent_agent=None) -> str
| Tham số | Loại | Mô tả |
|----------|------|-------------|
|
`name
` |
`str
` | Tên công cụ đã được đăng ký trong sổ đăng ký công cụ (ví dụ:
"delegate_task"
,
"file_edit"
) |
|
`args
` |
`dict
` | Đối số công cụ, hình dạng giống như mô hình sẽ gửi |
|
`parent_agent
` |
`Agent \| None
` | Ghi đè tùy chọn. Khi bị bỏ qua, sẽ giải quyết khỏi tác nhân CLI hiện tại (hoặc xuống cấp một cách nhẹ nhàng ở chế độ cổng) |
**Hành vi trong thời gian chạy:**
- **Chế độ CLI:**
`parent_agent
` được phân giải từ tác nhân CLI đang hoạt động nên các gợi ý về không gian làm việc, công cụ quay vòng và lựa chọn mô hình sẽ kế thừa như mong đợi.
- **Chế độ cổng:** Không có tác nhân CLI nên các công cụ xuống cấp một cách nhẹ nhàng — không gian làm việc được đọc từ
`TERMINAL_CWD
` và không có vòng quay nào được hiển thị.
- **Ghi đè rõ ràng:** Nếu người gọi chuyển
`parent_agent=
` một cách rõ ràng, nó sẽ được tôn trọng và không bị ghi đè.
Đây là giao diện công khai, ổn định để gửi công cụ từ các lệnh plugin. Các plugin không được tiếp cận
`ctx._CLI_ref.agent
` hoặc trạng thái riêng tư tương tự.
:::tip
Hướng dẫn này bao gồm **plugin chung** (công cụ, hook, lệnh gạch chéo, lệnh CLI). Các phần bên dưới phác thảo mẫu tác giả cho từng loại plugin chuyên dụng; mỗi liên kết đến hướng dẫn đầy đủ để tham khảo trường và ví dụ.
:::
## Các loại plugin chuyên dụng
Hermes có năm loại plugin chuyên dụng ngoài bề mặt chung. Mỗi gói được gửi dưới dạng một thư mục trong
`plugins/<category>/<name>/
` (theo gói) hoặc
~/.Hermes/plugins/<category>/<name>/
` (người dùng). Hợp đồng này khác nhau tùy theo danh mục — hãy chọn hợp đồng bạn cần, sau đó đọc hướng dẫn đầy đủ về hợp đồng đó.
### Plugin của nhà cung cấp mô hình - thêm phần phụ trợ LLM
Thả hồ sơ vào
`plugins/model-providers/<name>/
:
``` python
# plugins/model-providers/acme/__init__.py
from providers import register_provider
from providers.base import ProviderProfile`register_provider(ProviderProfile(
name="acme",
aliases=("acme-inference",),
display_name="Acme Inference",
env_vars=("ACME_API_KEY", "ACME_BASE_URL"),
base_url="https://API.acme.example.com/v1",
auth_type="API_key",
default_aux_model="acme-small-fast",
fallback_models=("acme-large-v3", "acme-medium-v3"),
))
`
`
``` yaml
# plugins/model-providers/acme/plugin.YAML
name: acme-provider
kind: model-provider
version: 1.0.0
description: Acme Inference — OpenAI-compatible direct API
`
``Lười phát hiện ra lần đầu tiên bất cứ thứ gì gọi
`get_provider_profile()
` hoặc
`list_providers()
-
`auth.py
,
`config.py
,
`doctor.py
,
`models.py
,
`runtime_provider.py
` và chat_completions vận chuyển tự động kết nối tới nó. Các plugin của người dùng sẽ ghi đè các plugin được gói theo tên.
**Hướng dẫn đầy đủ:** [Model Provider Plugins](/docs/developer-guide/model-provider-plugin) — tham chiếu trường, các móc có thể ghi đè (
`prepare_messages
,
`build_extra_body
,
`build_API_kwargs_extras
,
`fetch_models
), lựa chọn API_mode, loại xác thực, thử nghiệm.
### Plugin nền tảng - thêm kênh cổng
Thả bộ chuyển đổi vào
`plugins/platforms/<name/
:
``` python
# plugins/platforms/myplatform/adapter.py
from gateway.platforms.base import BasePlatformAdapter`class MyPlatformAdapter(BasePlatformAdapter):
async def connect(self): ...
async def send(self, chat_id, text): ...
async def disconnect(self): ...
def check_requirements():
import os
return bool(os.environ.get("MYPLATFORM_TOKEN"))
def _env_enablement():
import os
tok = os.getenv("MYPLATFORM_TOKEN", "").strip()
if not tok:
return None
return \{"token": tok}`def register(ctx):
ctx.register_platform(
name="myplatform",
label="MyPlatform",
adapter_factory=lambda cfg: MyPlatformAdapter(cfg),
check_fn=check_requirements,
required_env=["MYPLATFORM_TOKEN"],
# Auto-populate PlatformConfig.extra from env so env-only setups
# show up in
`Hermes gateway status
` without SDK instantiation.
env_enablement_fn=_env_enablement,
# Opt in to cron delivery:
`deliver=myplatform
` routes to this var.
cron_deliver_env_var="MYPLATFORM_HOME_CHANNEL",
emoji="💬",
platform_hint="You are chatting via MyPlatform. Keep responses concise.",
)
`
`
``` yaml
# plugins/platforms/myplatform/plugin.YAML
name: myplatform-platform
label: MyPlatform
kind: platform
version: 1.0.0
description: MyPlatform gateway adapter
requires_env:
- name: MYPLATFORM_TOKEN
description: "Bot token from the MyPlatform console"
password: true
optional_env:
- name: MYPLATFORM_HOME_CHANNEL
description: "Default channel for cron delivery"
password: false
`
``**Hướng dẫn đầy đủ:** [Adding Platform Adapters](/docs/developer-guide/adding-platform-adapters) — hợp đồng
`BasePlatformAdapter
` hoàn chỉnh, định tuyến tin nhắn, xác thực cổng, tích hợp trình hướng dẫn thiết lập. Hãy xem
`plugins/platforms/irc/
` để biết ví dụ hoạt động chỉ dành cho stdlib.
### Plugin nhà cung cấp bộ nhớ — thêm phần phụ trợ kiến thức giữa các phiênThả phần triển khai
`MemoryProvider
` vào
`plugins/memory/<name>/
:
``` python
# plugins/memory/my-memory/__init__.py
from agent.memory_provider import MemoryProvider`class MyMemoryProvider(MemoryProvider):
@property
def name(self) -> str:
return "my-memory"`def is_available(self) -> bool:
import os
return bool(os.environ.get("MY_MEMORY_API_KEY"))
def initialize(self, session_id: str, **kwargs) -> None:
self._session_id = session_id`def sync_turn(self, user_message, assistant_response, **kwargs) -> None:
...
def prefetch(self, query: str, **kwargs) -> str | None:
...
def register(ctx):
ctx.register_memory_provider(MyMemoryProvider())
`
``Các nhà cung cấp bộ nhớ được chọn một lần — mỗi lần chỉ có một nhà cung cấp hoạt động, được chọn thông qua
`memory.provider
` trong
`config.yaml
.
**Hướng dẫn đầy đủ:** [Memory Provider Plugins](/docs/developer-guide/memory-provider-plugin) —
`MemoryProvider
` ABC đầy đủ, hợp đồng phân luồng, cách ly hồ sơ, đăng ký lệnh CLI qua
`CLI.py
.
### Plugin công cụ ngữ cảnh - thay thế trình nén ngữ cảnh
``` python
# plugins/context_engine/my-engine/__init__.py
from agent.context_engine import ContextEngine`class MyContextEngine(ContextEngine):
@property
def name(self) -> str:
return "my-engine"`def should_compress(self, messages, model) -> bool: ...
def compress(self, messages, model) -> list[dict]: ...
def register(ctx):
ctx.register_context_engine(MyContextEngine())
`
``Công cụ ngữ cảnh được chọn một lần — được chọn thông qua
`context.engine
` trong
`config.yaml
.
**Hướng dẫn đầy đủ:** [Context Engine Plugins](/docs/developer-guide/context-engine-plugin).
### Phần phụ trợ tạo hình ảnh
Thả nhà cung cấp vào
`plugins/image_gen/<name>/
:
``` python
# plugins/image_gen/my-imggen/__init__.py
from agent.image_gen_provider import ImageGenProvider`class MyImageGenProvider(ImageGenProvider):
@property
def name(self) -> str:
return "my-imggen"`def is_available(self) -> bool: ...
def generate(self, prompt: str, **kwargs) -> str: ... # returns image path`def register(ctx):
ctx.register_image_gen_provider(MyImageGenProvider())
`
`
``` yaml
# plugins/image_gen/my-imggen/plugin.YAML
name: my-imggen
kind: backend
version: 1.0.0
description: Custom image generation backend
`
``**Hướng dẫn đầy đủ:** [Image Generation Provider Plugins](/docs/developer-guide/image-gen-provider-plugin) — đầy đủ các trình trợ giúp
`ImageGenProvider
` ABC,
`list_models()
` /
`get_setup_schema()
, siêu dữ liệu
`success_response()
/
`error_response()
, đầu ra base64 so với URL, ghi đè người dùng, phân phối pip.
**Ví dụ tham khảo:**
`plugins/image_gen/OpenAI/
` (DALL-E / GPT-Image qua OpenAI SDK),
`plugins/image_gen/OpenAI-Codex/
,
`plugins/image_gen/xai/
` (gen hình ảnh Grok).
## Bề mặt mở rộng không phải Python
Hermes cũng chấp nhận các tiện ích mở rộng hoàn toàn không phải là plugin Python. Những điều này được hiển thị trong [Pluggable interfaces table](/docs/user-guide/features/plugins#pluggable-interfaces--where-to-go-for-each); các phần bên dưới phác họa ngắn gọn từng phong cách tác giả.
### Máy chủ MCP - đăng ký các công cụ bên ngoài
Các máy chủ Model Context Protocol (MCP) đăng ký các công cụ của riêng họ vào Hermes mà không cần bất kỳ plugin Python nào. Khai báo chúng trong
~/.Hermes/config.yaml
:
``` yaml
MCP_servers:
filesystem:
command: "npx"
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/projects"]
timeout: 120`linear:
url: "https://MCP.linear.app/sse"
auth:
type: "OAuth"
`
``Hermes kết nối với từng máy chủ khi khởi động, liệt kê các công cụ của nó và đăng ký chúng cùng với các phần mềm tích hợp sẵn. LLM nhìn nhận chúng giống hệt như bất kỳ công cụ nào khác. **Hướng dẫn đầy đủ:** [MCP](/docs/user-guide/features/MCP).
### Móc nối sự kiện cổng — kích hoạt các sự kiện trong vòng đời
Thả bảng kê khai + trình xử lý vào
~/.Hermes/hooks/<name/
:
`YAML
# ~/.Hermes/hooks/long-task-alert/HOOK.YAML
name: long-task-alert
description: Send a push notification when a long task finishes
events:
- agent:end
`
`
``` python
# ~/.Hermes/hooks/long-task-alert/handler.py
async def handle(event_type: str, context: dict) -> None:
if context.get("duration_seconds", 0) > 120:
# send notification …
pass
`
``Các sự kiện bao gồm
`gateway:startup
,
`session:start
,
`session:end
,
`session:reset
,
`agent:start
,
`agent:step
,
`agent:end
` và ký tự đại diện
`command:*
. Các lỗi trong hook được phát hiện và ghi lại — chúng không bao giờ chặn đường dẫn chính.
**Hướng dẫn đầy đủ:** [Gateway Event Hooks](/docs/user-guide/features/hooks#gateway-event-hooks).
### Shell hooks — chạy lệnh shell khi gọi công cụ
Nếu bạn chỉ muốn chạy tập lệnh khi một công cụ kích hoạt (thông báo, nhật ký kiểm tra, cảnh báo trên màn hình, trình định dạng tự động), hãy sử dụng móc shell trong
`config.yaml
- không cần Python:
``` yaml
hooks:
- event: post_tool_call
command: "notify-send 'Tool ran: \{tool_name}'"
when:
tools: [terminal, patch, write_file]
`
``Hỗ trợ tất cả các sự kiện tương tự như hook plugin Python (
`pre_tool_call
,
`post_tool_call
,
`pre_LLM_call
,
`post_LLM_call
,
`on_session_start
,
`on_session_end
,
`pre_gateway_dispatch
) cùng với đầu ra JSON có cấu trúc cho các quyết định chặn
`pre_tool_call
.
**Hướng dẫn đầy đủ:** [Shell Hooks](/docs/user-guide/features/hooks#shell-hooks).
### Nguồn kỹ năng - thêm sổ đăng ký kỹ năng tùy chỉnh
Nếu bạn duy trì kho kỹ năng GitHub (hoặc muốn lấy từ chỉ mục cộng đồng ngoài các nguồn tích hợp), hãy thêm nó dưới dạng **nhấn**:
``` bash
Hermes skills tap add myorg/skills-repo
Hermes skills search my-workflow --source myorg/skills-repo
Hermes skills install myorg/skills-repo/my-workflow
`
``Việc xuất bản nhấn của riêng bạn chỉ là một kho lưu trữ GitHub với các thư mục
`skills/<skill-name>/SKILL.md
` — không cần đăng ký máy chủ hoặc đăng ký.
**Hướng dẫn đầy đủ:** [Skills Hub](/docs/user-guide/features/skills#skills-hub) · [Publishing a custom tap](/docs/user-guide/features/skills#publishing-a-custom-skill-tap) (bố cục repo, ví dụ tối thiểu, đường dẫn không mặc định, mức độ tin cậy).
### TTS/STT qua mẫu lệnh
Bất kỳ CLI nào đọc/ghi âm thanh hoặc văn bản đều có thể được cắm thông qua
`config.yaml
- không cần mã Python:
`YAML
tts:
provider: voxcpm
providers:
voxcpm:
type: command
command: "voxcpm --ref ~/voice.wav --text-file \{input_path} --out \{output_path}"
output_format: mp3
voice_compatible: true
`
``Đối với STT, hãy trỏ
`Hermes_LOCAL_STT_COMMAND
` vào mẫu shell. Trình giữ chỗ được hỗ trợ:
\{input_path}
,
\{output_path}
,
\{format}
,
\{voice}
,
\{model}
,
\{speed}
` (TTS);
\{input_path}
,
\{output_dir}
,
\{language}
,
\{model}
` (STT). Bất kỳ CLI tương tác với đường dẫn nào cũng tự động là một plugin.
**Hướng dẫn đầy đủ:** [TTS custom command providers](/docs/user-guide/features/tts#custom-command-providers) · [STT](/docs/user-guide/features/tts#voice-message-transcription-stt).
## Phân phối qua pip
Để chia sẻ plugin công khai, hãy thêm điểm vào gói Python của bạn:
`TOML
# pyproject.TOML
[project.entry-points."Hermes_agent.plugins"]
my-plugin = "my_plugin_package"
`
`
``` bash
pip install Hermes-plugin-calculator
# Plugin auto-discovered on next Hermes startup
`
## Phân phối cho NixOS
Người dùng NixOS có thể cài đặt plugin của bạn theo cách khai báo nếu bạn cung cấp
`pyproject.TOML
` với các điểm vào:
**Plugin điểm đầu vào** (được khuyến nghị để phân phối):
`
`nix
# User's configuration.nix
services.Hermes-agent.extraPythonPackages = [
(pkgs.Python312Packages.buildPythonPackage {
pname = "my-plugin";
version = "1.0.0";
src = pkgs.fetchFromGitHub {
owner = "you";
repo = "Hermes-my-plugin";
rev = "v1.0.0";
hash = "sha256-..."; # nix-prefetch-url --unpack
};
format = "pyproject";
build-system = [ pkgs.Python312Packages.setuptools ];
})
];
`
``**Plugin thư mục** (không cần
`pyproject.TOML
):
`
`nix
services.Hermes-agent.extraPlugins = [
(pkgs.fetchFromGitHub {
owner = "you";
repo = "Hermes-my-plugin";
rev = "v1.0.0";
hash = "sha256-...";
})
];
`
``Xem [Nix Setup guide](/docs/getting-started/nix-setup#plugins) để biết tài liệu đầy đủ bao gồm việc sử dụng lớp phủ và kiểm tra va chạm.`##Những lỗi thường gặp`**Trình xử lý không trả về chuỗi JSON:**
`
``` python
# Wrong — returns a dict
def handler(args, **kwargs):
return \{"result": 42}
# Right — returns a JSON string
def handler(args, **kwargs):
return JSON.dumps(\{"result": 42})
`
`**Thiếu
**kwargs
` trong chữ ký trình xử lý:**
`
``` python
# Wrong — will break if Hermes passes extra context
def handler(args):
...
# Right
def handler(args, **kwargs):
...
`
``**Trình xử lý đưa ra các ngoại lệ:**
`
``` python
# Wrong — exception propagates, tool call fails
def handler(args, **kwargs):
result = 1 / int(args["value"]) # ZeroDivisionError!
return JSON.dumps(\{"result": result})
# Right — catch and return error JSON
def handler(args, **kwargs):
try:
result = 1 / int(args.get("value", 0))
return JSON.dumps(\{"result": result})
except Exception as e:
return JSON.dumps(\{"error": str(e)})
`
``**Mô tả lược đồ quá mơ hồ:**
`
``` python
# Bad — model doesn't know when to use it
"description": "Does stuff"
# Good — model knows exactly when and how
"description": "Evaluate a mathematical expression. Use for arithmetic, trig, logarithms. Supports: +, -, *, /, **, sqrt, sin, cos, log, pi, e."
`
`