docs(plugins): clarify tool description sources

Explain that schema.description is model-facing while register_tool(description=...) only populates ToolEntry metadata, and remove the duplicated hello-world description in English and zh-Hans docs.

Refs #60735

Co-authored-by: Shiki <132348332+songshikang0111@users.noreply.github.com>
This commit is contained in:
Teknium 2026-08-08 15:03:57 -07:00
parent 934546fd5a
commit 26b3918dd9
4 changed files with 12 additions and 6 deletions

View File

@ -30,18 +30,20 @@ Every tool file in `tools/` calls `registry.register()` at module level to decla
registry.register(
name="terminal", # Unique tool name (used in API schemas)
toolset="terminal", # Toolset this tool belongs to
schema={...}, # OpenAI function-calling schema (description, parameters)
schema={...}, # Model-facing schema (description, parameters)
handler=handle_terminal, # The function that executes when the tool is called
check_fn=check_terminal, # Optional: returns True/False for availability
requires_env=["SOME_VAR"], # Optional: env vars needed (for UI display)
is_async=False, # Whether the handler is an async coroutine
description="Run commands", # Human-readable description
description="Run commands", # Optional ToolEntry registry metadata
emoji="💻", # Emoji for spinner/progress display
)
```
Each call creates a `ToolEntry` stored in the singleton `ToolRegistry._tools` dict keyed by tool name. A registration that would shadow an existing tool from a **different** toolset is rejected (with an error log) unless the caller passes `override=True`; plugin overrides of built-in tools additionally require the operator opt-in `plugins.entries.<plugin_id>.allow_tool_override: true` in `config.yaml`.
`schema["description"]` is the authoritative model-facing description. The separate `description=` argument populates `ToolEntry.description`; when it is omitted, the registry metadata falls back to the schema description. `get_definitions()` builds the OpenAI function definition from `entry.schema` and does not copy `entry.description` into a schema that lacks `description`. Therefore, `description=` alone does not describe the tool to the model, and when both values differ the model sees the schema value. Prefer defining the description once in the schema unless a registry consumer intentionally needs different metadata.
### Discovery: `discover_builtin_tools()`
When `model_tools.py` is imported, it calls `discover_builtin_tools()` from `tools/registry.py`. This function scans every `tools/*.py` file using AST parsing to find modules that contain top-level `registry.register()` calls, then imports them:

View File

@ -77,7 +77,6 @@ def register(ctx):
toolset="hello_world",
schema=schema,
handler=handle_hello,
description="Return a friendly greeting for the given name.",
)
# --- Hook: log every tool call ---
@ -89,6 +88,8 @@ def register(ctx):
Drop both files into `~/.hermes/plugins/hello-world/`, restart Hermes, and the model can immediately call `hello_world`. The hook prints a log line after every tool invocation.
The model-facing tool description belongs in `schema["description"]`. The optional `ctx.register_tool(description=...)` value is separate `ToolEntry` registry metadata: when omitted, it defaults to the schema description, but Hermes does not copy it back into a schema that lacks `description`. Prefer defining the text once in the schema. If you provide both values, keep them synchronized; the model sees the schema value.
Project-local plugins under `./.hermes/plugins/` are disabled by default. Enable them only for trusted repositories by setting `HERMES_ENABLE_PROJECT_PLUGINS=true` before starting Hermes.
## What plugins can do

View File

@ -30,18 +30,20 @@ Hermes 工具是自注册函数,按 toolset工具集分组并通过
registry.register(
name="terminal", # 唯一工具名称(用于 API schema
toolset="terminal", # 该工具所属的 toolset
schema={...}, # OpenAI function-calling schema描述、参数
schema={...}, # 面向模型的 schema描述、参数
handler=handle_terminal, # 工具被调用时执行的函数
check_fn=check_terminal, # 可选:返回 True/False 表示是否可用
requires_env=["SOME_VAR"], # 可选:所需的环境变量(用于 UI 显示)
is_async=False, # handler 是否为异步协程
description="Run commands", # 人类可读的描述
description="Run commands", # 可选的 ToolEntry 注册表元数据
emoji="💻", # 用于 spinner/进度显示的 emoji
)
```
每次调用都会创建一个 `ToolEntry`,以工具名称为键存储在单例 `ToolRegistry._tools` 字典中。若不同 toolset 之间出现名称冲突,会记录警告,后注册的条目覆盖前者。
`schema["description"]` 是面向模型的权威描述。独立的 `description=` 参数用于填充 `ToolEntry.description`;省略该参数时,注册表元数据会回退到 schema 中的描述。`get_definitions()` 根据 `entry.schema` 构建 OpenAI function definition并不会在 schema 缺少 `description` 时把 `entry.description` 复制进去。因此,只提供 `description=` 不会向模型描述该工具;如果两个值不同,模型看到的是 schema 中的值。除非某个注册表消费者确实需要不同的元数据,否则应只在 schema 中定义一次描述。
### 发现机制:`discover_builtin_tools()`
`model_tools.py` 被导入时,会调用 `tools/registry.py` 中的 `discover_builtin_tools()`。该函数使用 AST 解析扫描所有 `tools/*.py` 文件,找出包含顶层 `registry.register()` 调用的模块,然后导入它们:

View File

@ -75,7 +75,6 @@ def register(ctx):
toolset="hello_world",
schema=schema,
handler=handle_hello,
description="Return a friendly greeting for the given name.",
)
# --- Hook: log every tool call ---
@ -87,6 +86,8 @@ def register(ctx):
将两个文件放入 `~/.hermes/plugins/hello-world/`,重启 Hermes模型即可立即调用 `hello_world`。每次工具调用后hook 会打印一行日志。
面向模型的工具描述应写在 `schema["description"]` 中。可选的 `ctx.register_tool(description=...)` 值是独立的 `ToolEntry` 注册表元数据:省略时,它会默认使用 schema 中的描述;但如果 schema 缺少 `description`Hermes 不会把该元数据反向复制到 schema。建议只在 schema 中定义一次描述。如果同时提供两个值,请保持同步;模型看到的是 schema 中的值。
`./.hermes/plugins/` 下的项目本地插件默认禁用。仅对可信仓库启用,方法是在启动 Hermes 前设置 `HERMES_ENABLE_PROJECT_PLUGINS=true`
## 插件能做什么