H繁中版
文件開發者指南plugin llm access
<!-- Source: https://hermesbible.com/docs/developer-guide/plugin-llm-access -->

ctx.llm 是外掛進行 LLM 呼叫的受支援方式。 聊天補全、結構化擷取、同步、非同步、含或不含圖片 — 相同的介面、相同的信任閘門、相同的主機端認證。

外掛在需要做一些涉及模型但不屬於代理程式對話一部分的工作時會使用它。一個將工具錯誤改寫為非工程師可讀內容的鉤子。一個在佇列前翻譯入站訊息的閘道器適配器。一個摘要化長篇貼上的斜線命令。一個評分昨天活動並在狀態板上寫入一行的排程任務。一個決定訊息是否值得喚醒代理程式的預過濾器。

這些是代理程式不應該參與其中的工作。它們只需要一次 LLM 呼叫、一個有類型的答案,然後完成。

最小的呼叫

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

這就是完整的 API,一行搞定。無需金鑰、無需 provider 設定、無需 SDK 初始化。外掛針對使用者目前使用的 provider 和模型運行 — 當他們切換 provider 時,外掛會自動跟隨。

更完整的聊天範例

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 中,以便運維人員可以看到哪個外掛做了哪個呼叫。選用但建議用於任何頻繁觸發的場景。

結構化輸出

當外掛需要有類型的答案時,切換到結構化通道:

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)

主機向 provider 請求 JSON 輸出,在本地回退解析,如果安裝了 jsonschema 則根據你的 schema 驗證,並將 Python 物件放在 result.parsed 上回傳。如果模型無法產生有效的 JSON,result.parsedNoneresult.text 攜帶原始回應。

這個通道給你什麼

  • 一次呼叫,四種形式。 complete() 用於聊天,complete_structured() 用於有類型的 JSON,acomplete()acomplete_structured() 用於 asyncio。相同的參數,相同的結果物件。
  • 主機端擁有的認證。 OAuth 權杖、刷新流程、認證池、每個任務的輔助覆寫 — Hermes 已有的每個認證概念都適用。外掛永遠看不到權杖;主機透過 result.audit 歸屬呼叫。
  • 有限範圍。 單次同步或非同步呼叫。無串流、無工具迴圈、無需管理的對話狀態。陳述輸入,取得結果,返回。
  • 失敗即關閉的信任。 你從未設定過的外掛無法選擇自己的 provider、模型、代理程式或儲存的認證。預設姿態是「使用使用者正在使用的」。運維人員在 config.yaml 中按外掛選擇特定覆寫。

快速入門

以下是兩個完整的外掛 — 一個聊天,一個結構化。兩者都在單個 register(ctx) 函式內發佈,運行時零額外設定,針對使用者啟用的任何模型。

聊天補全 — /tldr

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 攜帶 token 計數;result.providerresult.model 攜帶歸屬。

結構化擷取 — /paste-to-tasks

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 repo(參考外掛的附屬 repo — 不隨 hermes-agent 本身一起發佈)。關於非同步介面(acomplete() / acomplete_structured() 搭配 asyncio.gather()),請見同 repo 中的 plugin-llm-async-example

何時使用哪個

你需要…使用
自由格式的文字回應(翻譯、摘要、改寫、生成)complete()
多回合提示詞(系統 + 少量範例 + 使用者)complete()
回傳有類型的字典,根據 schema 驗證complete_structured()
圖片或文字輸入,回傳有類型的字典complete_structured()
從非同步程式碼中進行相同的呼叫(閘道器適配器、非同步鉤子)acomplete() / acomplete_structured()

其他一切 — provider 選擇、模型解析、認證、後備、逾時、視覺路由 — 在四者之間都是相同的。

API 介面

ctx.llmagent.plugin_llm.PluginLlm 的實例。

complete()

result = ctx.llm.complete(
    messages=[{"role": "user", "content": "Hi"}],
    provider=None,         # 選用,有門控 — Hermes provider ID(例如 "openrouter")
    model=None,            # 選用,有門控 — 該 provider 期望的任何字串
    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= 以使用使用者的啟用 provider 但使用不同的模型。兩者都設置以完全切換 provider。任何參數在沒有運維人員選擇的情況下會引發 PluginLlmTrustError

complete_structured()

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,       # 在沒有 schema 時設為 True 以請求 JSON
    schema_name=None,      # 選用的人類可讀 schema 名稱
    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)

輸入是有類型的文字或圖片區塊(原始位元組會自動 base64 編碼為 data: URL)。當提供 json_schemajson_mode=True 時,主機透過 response_format 請求 JSON 輸出,在本地回退解析,如果安裝了 jsonschema 則根據你的 schema 驗證。

  • result.content_type == "json"result.parsed 是符合你的 schema 的 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                   # 該 provider 本次呼叫回傳的模型
    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_name

usage 攜帶 input_tokensoutput_tokenstotal_tokenscache_read_tokenscache_write_tokenscost_usd(當 provider 回傳這些欄位時)。

信任閘門

預設行為是失敗即關閉。在沒有 plugins.entries 設定區塊的情況下,外掛可以:

  • 針對使用者的啟用 provider 和模型運行四個方法中的任何一個,
  • 設定請求塑造參數(temperaturemax_tokenstimeoutsystem_promptpurposemessagesinstructionsinputjson_schema),

…就這些了。provider=model=agent_id=profile= 參數在運維人員選擇之前會引發 PluginLlmTrustError

大多數外掛永遠不需要這個區塊。 只要呼叫 ctx.llm.complete(messages=...) 而不帶覆寫的外掛,會針對使用者啟用的任何設定運行且零設定。以下區塊僅在特定外掛想要固定到不同於使用者的模型或 provider 時相關。

plugins:
  entries:
    my-plugin:
      llm:
        # 允許此 plugin 選擇不同的 Hermes provider
        # (必須是 Hermes 已知的 — 與 `hermes model` 和
        # config.yaml model.provider 相同的名稱)。
        allow_provider_override: true

        # 選用:限制哪些 provider。使用 ["*"] 表示任何。
        allowed_providers:
          - openrouter
          - anthropic

        # 允許此 plugin 請求特定的模型。
        allow_model_override: true

        # 選用:限制哪些模型。使用 ["*"] 表示任何。
        # 模型與 plugin 發送的字串完全匹配 — Hermes 不查詢任何內容。
        allowed_models:
          - openai/gpt-4o-mini
          - anthropic/claude-3-5-haiku

        # 允許跨代理程式呼叫(少見)。
        allow_agent_id_override: false

        # 允許 plugin 請求特定的已儲存認證設定檔
        # (例如同一 provider 上的不同 OAuth 帳號)。
        allow_profile_override: false

外掛 ID 是扁平外掛的 manifest 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 — 被信任選擇模型的外掛仍然固定在使用者的啟用 provider 上,除非它也取得 provider 門控。

閘門不需要執行什麼

  • 請求塑造參數 — temperaturemax_tokenstimeoutsystem_promptpurposemessagesinstructionsinputjson_schemaschema_namejson_mode — 始終允許;它們不選擇認證或路由。
  • 預設拒絕姿態意味著未設定的外掛仍然可以做有用的工作 — 它只針對啟用的 provider 和模型運行。運維人員只需要為想要更精細路由的外掛考慮 plugins.entries

主機擁有的內容

ctx.llm 為外掛完成的所有事情的完整清單,這樣你就不必自己做:

  • Provider 解析。 從使用者設定(或受信任時的明確覆寫)讀取 model.provider + model.model
  • 認證。~/.hermes/auth.json / 環境變數取得 API key、OAuth 權杖或刷新權杖,包括設定認證池時。外掛永遠看不到它們。
  • 視覺路由。 當提供圖片輸入而使用者的啟用文字模型僅支援文字時,主機自動回退到設定的視覺模型。
  • 後備鏈。 如果使用者的主要 provider 5xx 或 429,請求會經過 Hermes 通常的聚合器感知後備,然後才向回傳錯誤給外掛。
  • 逾時。 遵循你的 timeout= 參數,回退到 auxiliary.<task>.timeout 設定或全域輔助預設值。
  • JSON 塑造。 當你要求 JSON 時向 provider 發送 response_format,然後如果 provider 回傳了程式碼區塊回應則在本地重新解析。
  • Schema 驗證。 當安裝了 jsonschema 時根據你的 json_schema 驗證;否則記錄偵測行並跳過嚴格驗證。
  • 稽核日誌。 每次呼叫向 agent.log 寫入一行 INFO,包含外掛 ID、provider/model、purpose 和 token 總計。

外掛擁有的內容

  • 請求格式。 聊天用 messages,結構化用 instructions + input。外掛建構提示詞;主機執行它。
  • Schema。 你想要回傳的任何格式。主機不會為你推斷。
  • 錯誤處理。 complete_structured() 在空輸入和 schema 驗證失敗時引發 ValueError。當信任閘門拒絕覆寫時引發 PluginLlmTrustError。其他任何情況(provider 5xx、無認證設定、逾時)引發 auxiliary_client.call_llm() 引發的任何異常。
  • 成本。 每次呼叫都針對使用者付費的 provider 運行。不要在沒有考慮 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

參考



Programmatic Integration