Skip to content

LLM facade drops opts.op before providers — per-op provider switches (e.g. thinking kill-switch for summarize) can never fire #2308

Description

@hxyinan

Bug Description

Providers cannot apply per-operation (op) behavior because the op tag is dropped before it reaches the provider layer. opts.op is set by every caller (e.g. capture.summarize), but buildCallInput() in the LLM facade doesn't copy it into the input object handed to provider.complete(). Any per-op switch inside a provider (request-body tweaks, routing, budget overrides) therefore silently never fires.

This bit us in practice: OpenRouter's DeepSeek models burn the full token budget and double latency on capture.summarize because the recommended thinking: { type: "disabled" } can't be applied per-op — the condition opts.op === "capture.summarize" inside the provider always evaluates against an op that was never forwarded.

Affected code (2.0.17)

dist/core/llm/client.js:

function buildCallInput(opts, jsonMode) {
    return {
        temperature: opts?.temperature ?? config.temperature,
        maxTokens: opts?.maxTokens ?? config.maxTokens ?? DEFAULT_MAX_TOKENS,
        jsonMode,
        stop: opts?.stop,
        // <-- opts?.op is dropped here
    };
}

Callers pass op only into logging/metrics paths (callWithFallback(messages, input, opts, op)record(...)/facadeLog), not into the provider input.

Suggested fix

One line in buildCallInput:

        stop: opts?.stop,
        op: opts?.op,

Then per-op provider behavior works, e.g. in core/llm/providers/openai.js (both complete and stream request builders):

if (opts.op === "capture.summarize") {
    body.thinking = { type: "disabled" };   // OpenRouter reasoning-model kill-switch
}

Longer-term it may be worth defining which ops are meaningful (capture.summarize, retrieval.filter, skill.evolve, …) and documenting them, so per-op switches don't depend on string matching scattered across providers.

Impact

Without this, summarize-style calls on reasoning models (DeepSeek R-series etc. via OpenRouter) cost ~2× latency and excess tokens on every capture. We verified locally on 2.0.17: with the one-liner + provider switch, summarize latency drops back to non-thinking levels and token usage falls accordingly.

Environment

  • Plugin: @memtensor/memos-local-plugin 2.0.17
  • Agent: Hermes (Windows native); LLM via OpenRouter-compatible endpoint

Metadata

Metadata

Labels

ai:pr-readyAI-created PR is ready for review | AI 生成的 PR 已等待评审area:pluginOpenClaw & Hermesstatus:in-progressSomeone or AI is working on it | 人工或 AI 正在处理types:bugSomething isn't working | 功能异常

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions