ByteNoteByteNote

字节笔记本

2026年6月21日

hermes教程-插件 LLM 访问

API中转
¥120

ctx.llm 是插件进行 LLM 调用的受支持方式。 聊天补全、结构化提取、同步、异步、带或不带 图像——相同的接口、相同的信任门、相同的主机拥有的凭据。

当插件需要执行涉及模型但不属于代理对话的操作时,它会使用此接口。一个将工具错误重写为非工程师可读内容的钩子。一个在入站消息排队前进行转换的网关适配器。一个总结长粘贴内容的斜杠命令。一个定时任务,对昨天的活动进行评分并在状态板上写入一行。一个预过滤器,决定一条消息是否值得唤醒代理。

这些是代理不应参与的工作。它们只需要一次 LLM 调用、一个类型化的答案,然后结束。

最简单的调用

python
result = ctx.llm.complete(messages=[{"role": "user", "content": "ping"}])
return result.text

这就是一行完整的 API。无需密钥、无需提供商配置、无需 SDK 初始化。插件会使用用户当前使用的任何提供商和模型运行——当用户切换提供商时,插件会自动跟随。

更完整的聊天示例

python
result = ctx.llm.complete(
    messages=[
        {"role": "system", "content": "Rewrite errors as one short sentence a non-engineer can act on."},
        {"role": "user",   "content": traceback_text},
    ],
    max_tokens=64,
    purpose="hooks.error-rewrite",
)
return result.text

purpose 是一个自由格式的审计字符串——它会出现在 agent.logresult.audit 中,以便操作员查看哪个插件进行了哪个调用。可选,但对于频繁触发的内容推荐使用。

结构化输出

当插件需要类型化答案时,切换到结构化通道:

python
result = ctx.llm.complete_structured(
    instructions="Score this support reply for urgency (0–1) and pick a category.",
    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.parsedNoneresult.text 携带原始响应。

此通道提供的内容

  • 一次调用,四种形式。 complete() 用于聊天,complete_structured() 用于类型化 JSON,acomplete()acomplete_structured() 用于 asyncio。相同的参数,相同的结果对象。
  • 主机拥有的凭据。 OAuth 令牌、刷新流程、凭据池、每个任务的辅助覆盖——Hermes 已有的每个凭据概念都适用。插件永远不会看到令牌;主机通过 result.audit 将调用归因回去。
  • 有界。 单个同步或异步调用。无流式传输、无工具循环、无对话状态管理。陈述输入,获取结果,返回。
  • 故障关闭信任。 你从未配置过的插件不能选择自己的提供商、模型、代理或存储的凭据。默认姿态是“使用用户正在使用的”。操作员在 config.yaml 中为每个插件选择特定的覆盖。

快速入门

下面两个完整的插件——一个聊天,一个结构化。两者都包含在单个 register(ctx) 函数中,并且无需任何外部配置即可针对用户激活的任何模型运行。

聊天补全 — /tldr

python
def register(ctx):
    ctx.register_command(
        name="tldr",
        handler=lambda raw: _tldr(ctx, raw),
        description="Summarise the supplied text in one paragraph.",
        args_hint="<text>",
    )

def _tldr(ctx, raw_args: str) -> str:
    text = raw_args.strip()
    if not text:
        return "Usage: /tldr <text to summarise>"
    result = ctx.llm.complete(
        messages=[
            {"role": "system",
             "content": "Summarise the user's text in one tight paragraph. No preamble."},
            {"role": "user", "content": text},
        ],
        max_tokens=256,
        temperature=0.3,
        purpose="tldr",
    )
    return result.text

result.text 是模型的响应;result.usage 携带令牌计数;result.providerresult.model 携带归属信息。

结构化提取 — /paste-to-tasks

python
def register(ctx):
    ctx.register_command(
        name="paste-to-tasks",
        handler=lambda raw: _paste_to_tasks(ctx, raw),
        description="Turn freeform meeting notes into structured tasks.",
        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 date or empty"},
                },
                "required": ["action"],
            },
        },
    },
    "required": ["tasks"],
}

def _paste_to_tasks(ctx, raw_args: str) -> str:
    if not raw_args.strip():
        return "Usage: /paste-to-tasks <meeting notes>"
    result = ctx.llm.complete_structured(
        instructions=(
            "Extract concrete action items from these meeting notes. "
            "One task per actionable line. If no owner is named, leave 'owner' blank."
        ),
        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"Couldn't parse a response. Raw output:\n{result.text}"
    lines = [f"- [{t.get('owner') or '?'}] {t['action']}" for t in result.parsed["tasks"]]
    return "\n".join(lines) or "(no tasks found)"

第三个带有图像输入的示例位于 hermes-example-plugins 仓库中(参考插件的配套仓库——不随 hermes-agent 本身捆绑)。关于异步接口(acomplete() / acomplete_structured()asyncio.gather()),请参见同一仓库中的 plugin-llm-async-example

何时使用哪种

你想要…使用
自由格式文本响应(翻译、摘要、重写、生成)complete()
多轮提示(系统 + 少样本示例 + 用户)complete()
返回类型化字典,并根据模式验证complete_structured()
图像或文本输入,返回类型化字典complete_structured()
从异步代码进行相同调用(网关适配器、异步钩子)acomplete() / acomplete_structured()

其他所有内容——提供商选择、模型解析、认证、回退、超时、视觉路由——在所有四种方法中都是相同的。

API 接口

ctx.llmagent.plugin_llm.PluginLlm 的一个实例。

complete()

python
result = ctx.llm.complete(
    messages=[{"role": "user", "content": "Hi"}],
    provider=None,         # 可选,受门控 — Hermes 提供商 ID(例如 "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": "..."} 字典列表。多轮提示(系统 + 少样本用户/助手对 + 最终用户)的工作方式与 OpenAI SDK 完全相同。

provider=model= 是独立的,遵循与主机主配置相同的格式(model.provider + model.model)。仅设置 model= 以使用用户当前提供商的不同模型。同时设置两者以完全切换提供商。如果没有操作员选择加入,任何一个参数都会引发 PluginLlmTrustError

complete_structured()

python
result = ctx.llm.complete_structured(
    instructions="What you want extracted.",
    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_schemajson_mode=True 时,主机通过 response_format 请求 JSON 输出,本地解析作为回退,如果安装了 jsonschema 则根据你的模式进行验证。

  • result.content_type == "json"result.parsed 是一个与你的模式匹配的 Python 对象。
  • result.content_type == "text" — 解析或验证失败;检查 result.text 以获取原始模型响应。

异步

python
result = await ctx.llm.acomplete(messages=...)
result = await ctx.llm.acomplete_structured(instructions=..., input=...)

参数和结果类型与其同步对应项相同。在网关适配器、异步钩子或任何已在 asyncio 循环上运行的插件代码中使用这些。

结果属性

python
@dataclass
class PluginLlmCompleteResult:
    text: str                    # 助手的响应
    provider: str                # 例如 "openrouter", "anthropic"
    model: str                   # 提供商为此调用返回的任何内容
    agent_id: str                # 使用了谁的模型/认证
    usage: PluginLlmUsage        # 令牌 + 缓存 + 成本估算
    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_name 时也会携带

usage 携带 input_tokensoutput_tokenstotal_tokenscache_read_tokenscache_write_tokenscost_usd(当提供商返回这些字段时)。

信任门

默认行为是故障关闭。如果没有 plugins.entries 配置块,插件可以:

  • 针对用户当前活动的提供商和模型运行四种方法中的任何一种,
  • 设置请求整形参数(temperaturemax_tokenstimeoutsystem_promptpurposemessagesinstructionsinputjson_schema),

……仅此而已。provider=model=agent_id=profile= 参数会引发 PluginLlmTrustError,直到操作员选择加入。

大多数插件永远不需要此部分。 一个仅调用 ctx.llm.complete(messages=...) 且没有覆盖的插件会针对用户当前活动的任何内容运行,并且零配置即可工作。下面的块仅当插件特别想固定到与用户不同的模型或提供商时才相关。

yaml
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/openaimemory/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——一个被信任选择模型的插件仍然被固定到用户当前活动的提供商,除非它也获得了提供商门。

门不需要强制执行的内容

  • 请求整形参数——temperaturemax_tokenstimeoutsystem_promptpurposemessagesinstructionsinputjson_schemaschema_namejson_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、提供商/模型、目的和令牌总数。

插件拥有的内容

  • 请求形状。 聊天使用 messages,结构化使用 instructions + input。插件构建提示;主机运行它。
  • 模式。 你想要的任何形状。主机不会为你推断。
  • 错误处理。 complete_structured() 在空输入和模式验证失败时引发 ValueError。当信任门拒绝覆盖时,PluginLlmTrustError 触发。其他任何情况(提供商 5xx、未配置凭据、超时)都会引发 auxiliary_client.call_llm() 引发的任何异常。
  • 成本。 每次调用都针对用户付费的提供商运行。不要在不考虑令牌花费的情况下,为每个网关消息循环调用 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

参考


分享: