Skip to content

feat(agent): per-platform request_overrides via platform_request_overrides - #34007

Closed
juk80x wants to merge 1 commit into
NousResearch:mainfrom
juk80x:feat/platform-request-overrides
Closed

feat(agent): per-platform request_overrides via platform_request_overrides#34007
juk80x wants to merge 1 commit into
NousResearch:mainfrom
juk80x:feat/platform-request-overrides

Conversation

@juk80x

@juk80x juk80x commented May 28, 2026

Copy link
Copy Markdown

What does this PR do?

Adds a top-level platform_request_overrides config key that lets users layer OpenAI-compatible chat-completion request fields per platform. When the same AIAgent configuration drives multiple surfaces (cli, telegram, api_server, etc.), each platform can now carry its own extra_body, reasoning_effort, and service_tier overrides — without forking the global config or running a separate process per surface.

Resolution order (high → low):

  1. Caller-supplied request_overrides (highest — preserves the existing contract for auxiliary clients, kanban workers, delegated subagents).
  2. platform_request_overrides[<platform>] (new layer).
  3. custom_providers[].extra_body (existing global, resolved earlier by _merge_custom_provider_extra_body).

extra_body is shallow-merged at the second level so a platform setting one nested key (e.g. chat_template_kwargs) doesn't erase siblings (e.g. reasoning_effort) a custom-provider entry already supplied. Top-level keys (service_tier, reasoning_effort) replace wholesale.

No-op when platform_request_overrides is absent or the current platform key has no entry — untouched configs behave exactly as before.

Scope of the override: applies to the main conversational LLM call (OpenAI chat completions / Anthropic messages / Codex responses) for every platform that has a matching entry. Does NOT cover auxiliary model calls (those have their own auxiliary.<task>.extra_body knob) or non-conversational tool requests (embeddings, image generation, TTS / STT). Documented inline in cli-config.yaml.example.

Related Issue

Fixes #34006

Adjacent (open) PRs reviewed before designing this, in case reviewers want context:

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Changes Made

  • agent/agent_init.py — new _platform_request_overrides_for_agent resolver (keyword-only args, defensive isinstance guards, returns a copy so callers can't corrupt parsed config) + _merge_platform_request_overrides applier; hooked in at agent init immediately after _merge_custom_provider_extra_body. Naming + signature shape mirror the existing _custom_provider_extra_body_for_agent / _merge_custom_provider_extra_body pair.
  • tests/agent/test_platform_request_overrides.py — 18 new tests mirroring tests/agent/test_custom_provider_extra_body.py style: resolver edge cases (missing block, unknown platform, empty/non-string key, case-insensitive matching, non-dict config tolerance, copy semantics), _merge_* correctness (no-op paths, extra_body shallow-merge, top-level passthrough, layering on top of a custom-provider entry, caller-precedence for both nested and top-level keys, malformed extra_body tolerance, no-empty-dict policy).
  • cli-config.yaml.example — new "Platform Request Overrides" section between Platform Toolsets and Gateway Platform Settings. Includes resolution-order doc, scope clarification (what's covered vs what isn't), and three concrete examples (per-platform reasoning_effort, per-platform service_tier, per-platform chat_template_kwargs.enable_thinking).

Diffstat: 3 files changed, 396 insertions(+)+94 prod (agent_init.py), +251 tests, +59 docs.

How to Test

Automated:

scripts/run_tests.sh tests/agent/test_platform_request_overrides.py
# 18 passed in <1s

scripts/run_tests.sh tests/agent/test_custom_provider_extra_body.py \
                    tests/agent/test_platform_request_overrides.py \
                    tests/agent/test_auxiliary_client.py \
                    tests/hermes_cli/test_runtime_provider_resolution.py
# 321 passed, 0 failed in 2.2s (adjacent code paths to confirm no regression)

Manual (config + behavior):

  1. Add to ~/.hermes/config.yaml:

    platform_request_overrides:
      api_server:
        extra_body:
          chat_template_kwargs:
            enable_thinking: false
  2. Start the gateway with an api_server platform pointed at a local llama.cpp running a hybrid-thinking model (e.g. Qwen3-derived).

  3. POST a chat completion to the api_server endpoint and observe usage.completion_tokens and round-trip time. With the override, both drop substantially because the model skips its hidden thinking block. CLI and Telegram surfaces (no entry for them) keep the default behaviour.

  4. Verify CLI is unaffected: hermes chat -q "what is the capital of France?" — same thinking-mode behaviour as before the change.

Tested on: macOS 15.5 (Darwin 25.5), Python 3.11.15.

Concrete measurement (Qwen3.6-35B-A3B + llama.cpp + the api_server platform via an OpenAI-compatible client): a "what time is it?" turn that requires one tool call (GetDateTime via Home Assistant MCP) drops from ~10.1s / 244 completion tokens to ~1.5s / 25 completion tokens with chat_template_kwargs.enable_thinking: false set only for api_server. CLI and Telegram (no entry) unaffected.

Checklist

Code

Documentation & Housekeeping

  • I've updated relevant documentation — added a new section in cli-config.yaml.example with usage examples and a scope note
  • I've updated cli-config.yaml.example because this adds a new config key
  • N/A — no architecture or workflow changes that need CONTRIBUTING.md / AGENTS.md updates
  • I've considered cross-platform impact — pure dict manipulation in Python; no syscalls, no shell, no path handling. scripts/check-windows-footguns.py reports clean on the diff.
  • N/A — no tool descriptions/schemas changed

…rides

Adds a top-level config key that lets users layer OpenAI-compatible
chat-completion request fields per platform. Concrete cases that
motivated this: tuning reasoning_effort lower on a latency-sensitive
api_server integration while keeping CLI at the default, sending a
chat_template_kwargs.enable_thinking flag to a hybrid-thinking model
(Qwen3 / GLM-4.6 / Hunyuan family on llama.cpp / vLLM) for one
endpoint but not others, or switching service_tier per surface.

Resolution order (high -> low):
  1. caller-supplied request_overrides (highest)
  2. platform_request_overrides[<platform>] (this layer)
  3. custom_providers[].extra_body (existing global, resolved earlier)

extra_body is shallow-merged at the second level so a platform can set
one nested key (e.g. chat_template_kwargs) without erasing siblings
(e.g. reasoning_effort) that a custom-provider entry already supplied.
Top-level keys (service_tier, reasoning_effort) replace wholesale.

Caller-supplied keys always win - the platform layer fills only keys
the caller did not pass explicitly. This preserves the existing
contract for auxiliary clients, kanban workers, and delegated subagents
that already thread request_overrides through.

No-op when platform_request_overrides is absent or the current platform
key has no entry, so untouched configs behave exactly as before.

Changes:
  * agent/agent_init.py - _platform_request_overrides_for_agent
    resolver + _merge_platform_request_overrides applier, hooked in
    immediately after _merge_custom_provider_extra_body
  * tests/agent/test_platform_request_overrides.py - 18 tests mirroring
    test_custom_provider_extra_body.py (resolver edge cases, top-level
    + extra_body merge semantics, caller-precedence, malformed config
    tolerance)
  * cli-config.yaml.example - new commented section with three usage
    examples and a Scope note clarifying that the override covers the
    agent's main conversational LLM call only, not auxiliary models or
    non-conversational tools

Measured locally with Qwen3.6-35B-A3B on llama.cpp via the api_server
platform: a "what time is it?" turn that requires one tool call drops
from ~10.1s / 244 completion tokens to ~1.5s / 25 completion tokens
with chat_template_kwargs.enable_thinking: false set only for
api_server. CLI and Telegram surfaces are unaffected.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/config Config system, migrations, profiles labels May 28, 2026
@juk80x juk80x closed this Jun 3, 2026
@DasBootU96

Copy link
Copy Markdown

I’m using Hermes with a local Qwen model served by llama.cpp.

Setup:

  • Hermes Agent v0.17.0
  • Custom OpenAI-compatible provider
  • API mode: chat_completions
  • Base URL: http://192.168.1.2:8080/v1
  • Model: qwen36-27b-unsloth

/no_think does not disable thinking for this backend. It is treated as normal prompt text.

This still returns reasoning_content:

curl -s http://192.168.1.2:8080/v1/chat/completions   -H "Content-Type: application/json"   -d '{
    "model": "qwen36-27b-unsloth",
    "messages": [{"role": "user", "content": "hello /no_think"}],
    "max_tokens": 120
  }' | jq '.choices[0].message'

But this works:

curl -s http://192.168.1.2:8080/v1/chat/completions   -H "Content-Type: application/json"   -d '{
    "model": "qwen36-27b-unsloth",
    "messages": [{"role": "user", "content": "hello"}],
    "chat_template_kwargs": {"enable_thinking": false},
    "max_tokens": 120
  }' | jq '.choices[0].message'

Request: allow Hermes to apply chat_template_kwargs.enable_thinking=false per request/turn/platform, so /no_think or low-latency surfaces can disable thinking without disabling thinking globally for the provider.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: per-platform LLM request_overrides (extra_body / reasoning_effort / service_tier)

3 participants