插件 LLM 访问
ctx.llm 是插件进行 LLM 调用的推荐方式。
Chat 补全、结构化提取、同步、异步、带或不带
图像——相同的接口,相同的信任门控,相同的宿主拥有凭证。
当插件需要做一些涉及模型但不属于代理对话 的事情时,就会用到它。一个重写工具错误让非工程师也能 看懂的钩子。一个在消息入队前进行翻译的网关 适配器。一个总结长文本的斜杠命令。一个定期任务 对昨天的活动打分并将一行结果写入状态面板。一个预过滤器 决定一条消息是否值得唤醒代理。
这些都是代理不应该参与的任务。它们只需要一次 LLM 调用,一个类型化的答案,然后结束。
最简单的调用
result = ctx.llm.complete(messages=[{"role": "user", "content": "ping"}])
return result.text这就是完整的 API,一行搞定。无需密钥,无需提供商配置,无需 SDK 初始化。该插件使用用户当前使用的提供商和 模型运行——当用户切换提供商时,插件会自动跟随。
更完整的聊天示例
result = ctx.llm.complete(
messages=[
{"role": "system", "content": "将错误重写为非工程师能理解的一句话。"},
{"role": "user", "content": traceback_text},
],
max_tokens=64,
purpose="hooks.error-rewrite",
)
return result.textpurpose 是一个自由格式的审计字符串——它会出现在 agent.log
和 result.audit 中,以便运维人员查看哪个插件发起了哪个
调用。可选,但对于频繁触发的调用推荐使用。
结构化输出
当插件需要类型化答案时,切换到结构化通道:
result = ctx.llm.complete_structured(
instructions="对此支持回复进行紧急程度评分(0–1)并选择一个类别。",
input=[{"type": "text", "text": message_body}],
json_schema=TRIAGE_SCHEMA,
purpose="support.triage",
temperature=0.0,
max_tokens=128,
)
if result.parsed["urgency"] > 0.8:
await dispatch_to_oncall(result.parsed["category"], message_body)宿主要求提供商输出 JSON,本地解析作为
备用,如果安装了 jsonschema 则验证您的模式,并在 result.parsed 上返回
Python 对象。如果模型无法生成有效的 JSON,result.parsed 为 None,
result.text 携带原始响应。
此通道提供的功能
- 一次调用,四种形态。
complete()用于聊天,complete_structured()用于类型化 JSON,acomplete()和acomplete_structured()用于 asyncio。相同的参数,相同的结果 对象。 - 宿主拥有的凭证。 OAuth 令牌、刷新流程、
凭据池、每任务辅助覆盖——Hermes 已有的每个凭证概念
都适用。插件永远看不到令牌;宿主通过
result.audit将调用归属回去。 - 有界的。 单个同步或异步调用。无流式传输,无工具 循环,无需管理对话状态。陈述输入,获取 结果,返回。
- 默认关闭的信任。 一个您从未配置过的插件
不能选择自己的提供商、模型、代理或存储凭证。
默认姿态是”使用用户当前使用的。“运维人员
在
config.yaml中为特定插件选择加入特定的覆盖。
快速入门
下面两个完整插件——一个是聊天,一个是结构化。两者都包含在
一个 register(ctx) 函数内,无需任何外部
配置即可针对用户当前激活的模型运行。
聊天补全——/tldr
def register(ctx):
ctx.register_command(
name="tldr",
handler=lambda raw: _tldr(ctx, raw),
description="用一段话总结所提供的文本。",
args_hint="<text>",
)
def _tldr(ctx, raw_args: str) -> str:
text = raw_args.strip()
if not text:
return "用法: /tldr <要总结的文本>"
result = ctx.llm.complete(
messages=[
{"role": "system",
"content": "用一段紧凑的文字总结用户的文本。不要有前言。"},
{"role": "user", "content": text},
],
max_tokens=256,
temperature=0.3,
purpose="tldr",
)
return result.textresult.text 是模型的响应;result.usage 携带 token
计数;result.provider 和 result.model 携带归属信息。
结构化提取——/paste-to-tasks
def register(ctx):
ctx.register_command(
name="paste-to-tasks",
handler=lambda raw: _paste_to_tasks(ctx, raw),
description="将自由格式的会议笔记转换为结构化任务。",
args_hint="<text>",
)
_TASKS_SCHEMA = {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"action": {"type": "string"},
"due": {"type": "string", "description": "ISO 日期或留空"},
},
"required": ["action"],
},
},
},
"required": ["tasks"],
}
def _paste_to_tasks(ctx, raw_args: str) -> str:
if not raw_args.strip():
return "用法: /paste-to-tasks <会议笔记>"
result = ctx.llm.complete_structured(
instructions=(
"从这些会议笔记中提取具体的行动项。"
"每个可操作的条目作为一个任务。如果未指定负责人,将 'owner' 留空。"
),
input=[{"type": "text", "text": raw_args}],
json_schema=_TASKS_SCHEMA,
schema_name="meeting.tasks",
purpose="paste-to-tasks",
temperature=0.0,
max_tokens=512,
)
if result.parsed is None:
return f"无法解析响应。原始输出:\n{result.text}"
lines = [f"- [{t.get('owner') or '?'}] {t['action']}" for t in result.parsed["tasks"]]
return "\n".join(lines) or "(未找到任务)"第三个带图像输入的完整示例位于
hermes-example-plugins
仓库(参考插件的配套仓库——未随 hermes-agent 本身
捆绑)。有关异步接口(acomplete() /
acomplete_structured() 配合 asyncio.gather()),请参阅
同一仓库中的
plugin-llm-async-example。
何时使用哪种
| 您想要…… | 使用 |
|---|---|
| 自由格式的文本响应(翻译、摘要、重写、生成) | complete() |
| 多轮提示(系统 + few-shot 示例 + 用户) | complete() |
| 类型化字典返回,根据模式验证 | complete_structured() |
| 图像或文本输入,返回类型化字典 | complete_structured() |
| 从异步代码中进行的相同调用(网关适配器、异步钩子) | acomplete() / acomplete_structured() |
其他所有内容——提供商选择、模型解析、认证、备用、 超时、视觉路由——在所有四种方法中都是相同的。
API 接口
ctx.llm 是 agent.plugin_llm.PluginLlm 的一个实例。
complete()
result = ctx.llm.complete(
messages=[{"role": "user", "content": "Hi"}],
provider=None, # 可选,受门控——Hermes 提供商标识(如 "openrouter")
model=None, # 可选,受门控——该提供商期望的任意字符串
temperature=None,
max_tokens=None,
timeout=None, # 秒
agent_id=None, # 可选,受门控
profile=None, # 可选,受门控——显式认证配置文件名称
purpose="optional-audit-string",
)
# → PluginLlmCompleteResult(text, provider, model, agent_id, usage, audit)纯聊天补全。messages 是标准的 OpenAI 格式——
由 {"role": "...", "content": "..."} 字典组成的列表。多轮
提示(系统 + few-shot 用户/助手对 + 最终用户)的用法
与 OpenAI SDK 完全相同。
provider= 和 model= 是独立的,遵循与宿主主配置
(model.provider + model.model)相同的格式。仅设置
model= 可以使用用户激活的提供商但使用不同的模型。
同时设置两者则完全切换提供商。任一参数
在没有运维人员选择加入的情况下会引发 PluginLlmTrustError。
complete_structured()
result = ctx.llm.complete_structured(
instructions="您想要提取的内容。",
input=[
{"type": "text", "text": "..."},
{"type": "image", "data": b"...", "mime_type": "image/png"},
{"type": "image", "url": "https://..."},
],
json_schema={...}, # 可选——触发解析结果 + 验证
json_mode=False, # 设置为 True 可以在没有模式的情况下仍然要求 JSON 输出
schema_name=None, # 可选的人类可读模式名称
system_prompt=None,
provider=None, # 可选,受门控
model=None, # 可选,受门控
temperature=None,
max_tokens=None,
timeout=None,
agent_id=None,
profile=None,
purpose=None,
)
# → PluginLlmStructuredResult(text, provider, model, agent_id,
# usage, parsed, content_type, audit)输入是类型化的文本或图像块(原始字节自动作为 data: URL
进行 base64 编码)。当提供了 json_schema 或
json_mode=True 时,宿主通过 response_format 请求 JSON 输出,
本地解析作为备用,并在安装了 jsonschema 时
根据您的模式进行验证。
result.content_type == "json"—result.parsed是一个与您的模式 匹配的 Python 对象。result.content_type == "text"— 解析或验证失败; 查看result.text以获取原始模型响应。
异步
result = await ctx.llm.acomplete(messages=...)
result = await ctx.llm.acomplete_structured(instructions=..., input=...)与同步版本相同的参数和结果类型。在 网关适配器、异步钩子或任何已在 asyncio 循环上运行的插件代码中 使用它们。
结果属性
@dataclass
class PluginLlmCompleteResult:
text: str # 助手的响应
provider: str # 例如 "openrouter"、"anthropic"
model: str # 提供商为此调用返回的模型名称
agent_id: str # 使用了谁的模型/认证
usage: PluginLlmUsage # token + 缓存 + 成本估算
audit: Dict[str, Any] # plugin_id、purpose、profile
@dataclass
class PluginLlmStructuredResult(PluginLlmCompleteResult):
parsed: Optional[Any] # 当 content_type == "json" 时的 JSON 对象
content_type: str # "json" 或 "text"
# audit 在提供时也携带 schema_nameusage 携带 input_tokens、output_tokens、total_tokens、
cache_read_tokens、cache_write_tokens 和 cost_usd(当提供商
返回这些字段时)。
信任门控
默认行为是默认关闭。在没有 plugins.entries
配置块的情况下,插件可以:
- 针对用户激活的提供商和模型运行任意四种方法,
- 设置请求塑形参数(
temperature、max_tokens、timeout、system_prompt、purpose、messages、instructions、input、json_schema),
……仅此而已。provider=、model=、agent_id= 和 profile=
参数会引发 PluginLlmTrustError,直到运维人员选择加入。
大多数插件不需要此部分。 一个仅使用
ctx.llm.complete(messages=...) 且无覆盖的插件使用用户当前
激活的任何配置运行,零配置即可工作。下面的块
仅当插件特别想要固定到与用户不同的
模型或提供商时才相关。
plugins:
entries:
my-plugin:
llm:
# 允许此插件选择不同的 Hermes 提供商
# (必须是 Hermes 已知的提供商——与
# `hermes model` 和 config.yaml model.provider 相同的名称)。
allow_provider_override: true
# 可选地限制允许的提供商。使用 ["*"] 表示任意。
allowed_providers:
- openrouter
- anthropic
# 允许此插件请求特定模型。
allow_model_override: true
# 可选地限制允许的模型。使用 ["*"] 表示任意。
# 模型按照插件发送的字符串进行字面匹配——
# Hermes 不会进行任何查找。
allowed_models:
- openai/gpt-4o-mini
- anthropic/claude-3-5-haiku
# 允许跨代理调用(罕见)。
allow_agent_id_override: false
# 允许插件请求特定的存储认证配置文件
# (例如同一提供商上的不同 OAuth 账户)。
allow_profile_override: false插件 ID 是平面插件的清单 name: 字段,或
嵌套插件的路径派生键(image_gen/openai、
memory/honcho 等)。
门控强制执行的内容
| 覆盖 | 默认 | 配置键 |
|---|---|---|
provider= | 拒绝 | allow_provider_override: true |
| ↳ 允许列表 | — | allowed_providers: [...] |
model= | 拒绝 | allow_model_override: true |
| ↳ 允许列表 | — | allowed_models: [...] |
agent_id= | 拒绝 | allow_agent_id_override: true |
profile= | 拒绝 | allow_profile_override: true |
每个覆盖都是独立门控的。授予 allow_model_override
并不同时授予 allow_provider_override——一个被信任可以选择
模型的插件仍然固定到用户的激活提供商,除非它
也获得了提供商门控。
门控不需要强制执行的内容
- 请求塑形参数——
temperature、max_tokens、timeout、system_prompt、purpose、messages、instructions、input、json_schema、schema_name、json_mode——始终 允许;它们不选择凭证或路由。 - 默认拒绝姿态意味着未配置的插件仍然可以做
有用的工作——它只是针对激活的提供商和模型运行。
运维人员只需要为那些想要更精细路由的插件
考虑
plugins.entries。
宿主拥有的职责
ctx.llm 为插件完成的事情的完整列表,
让您无需自己处理:
- 提供商解析。 读取用户配置中的
model.provider+model.model(或在受信任时的显式覆盖)。 - 认证。 从
~/.hermes/auth.json/ 环境变量获取 API 密钥、OAuth 令牌 或刷新令牌,包括配置了凭据池时的处理。 插件永远看不到它们。 - 视觉路由。 当提供了图像输入且用户的 激活文本模型仅为文本时,宿主自动回退到 配置的视觉模型。
- 备用链。 如果用户的主要提供商 5xx 或 429, 请求在向插件返回错误之前会通过 Hermes 的 常用聚合器感知备用。
- 超时。 遵循您的
timeout=参数,回退到auxiliary.<task>.timeout配置或全局辅助默认值。 - JSON 塑形。 当您请求 JSON 时向提供商发送
response_format, 然后在提供商返回代码围栏响应时 从本地重新解析。 - 模式验证。 在安装了
jsonschema时根据您的json_schema进行验证;否则记录调试行并跳过严格 验证。 - 审计日志。 每次调用向
agent.log写入一条 INFO 行, 包含插件 ID、提供商/模型、用途和 token 总数。
插件的职责
- 请求形状。 聊天的
messages,结构化的instructions+input。 插件构建提示词;宿主运行它。 - 模式。 您想要返回的任何形状。宿主编译 它不会为您推断。
- 错误处理。 空输入和模式验证失败时
complete_structured()引发ValueError。当信任 门控拒绝覆盖时触发PluginLlmTrustError。其他任何情况 (提供商 5xx、未配置凭证、超时)引发auxiliary_client.call_llm()引发的任何异常。 - 成本。 每次调用都针对用户的付费提供商运行。不要
在不考虑 token 消耗的情况下为每条网关消息循环调用
complete()。
这在整个插件接口中的位置
现有的 ctx.* 方法扩展了 Hermes 的某个子系统:
| 方法 | 功能 |
|---|---|
ctx.register_tool | 添加代理可以调用的工具 |
ctx.register_platform | 连接新的网关适配器 |
ctx.register_image_gen_provider | 替换图像生成后端 |
ctx.register_memory_provider | 替换内存后端 |
ctx.register_context_engine | 替换上下文压缩器 |
ctx.register_hook | 观察生命周期事件 |
ctx.llm 是第一个让插件能够运行用户正在与之对话的
相同模型带外而不需要以上任何功能的接口。
这就是它的唯一职责。如果您的插件需要注册一个
代理调用的工具,请使用 register_tool。如果需要响应
生命周期事件,请使用 register_hook。如果需要执行
自己的模型调用——无论出于何种原因,结构化与否——使用 ctx.llm。
参考
- 实现:
agent/plugin_llm.py - 测试:
tests/agent/test_plugin_llm.py - 参考插件(配套仓库):
plugin-llm-example— 带图像输入的同步结构化提取plugin-llm-async-example— 配合asyncio.gather()的异步调用
- 辅助客户端(底层引擎):请参阅 提供商运行时。