feat(plugins): adaptive-reasoning — context-aware effort routing with closed-loop feedback - #88522
feat(plugins): adaptive-reasoning — context-aware effort routing with closed-loop feedback#88522zons-zhaozhy wants to merge 2 commits into
Conversation
… closed-loop feedback Addresses NousResearch#40306 / NousResearch#74725 as a zero-core-change plugin (issues NousResearch#74725, NousResearch#13663). Routes per-turn reasoning effort through the existing llm_request middleware: - v3 classifier: text prior + conversation context (request.messages work depth) — brevity mid-task inherits session difficulty, cold acks stay minimal - v4 closed loop: llm_execution observer records the previous call's real reasoning_tokens/finish_reason; mid-loop feedback corrects the prior (starvation-first, monotone — measured glm-5.3 distribution showed hard+high always hits rt~2000/finish=length, so escalate-on-rt rules oscillate on 3-tier native scales) - provider-native scales: reuses get_provider_profile().build_api_kwargs_extras() so wire values always match what the transport itself would send - floor/ceiling clamps via user config; tool-error rescue path unchanged Measured evidence (glm-5.3, coding endpoint): in-context brevity burns 1022 rt vs isolated 151; hard-task rt grid refuted the two-threshold feedback design; observer handles all three response shapes (pydantic, relay dict, SimpleNamespace). 42 unit tests.
… profiles
The three provider-scale tests were written against a fork-local zai
profile that clamps glm-5.3 reasoning_effort to {low, high, max}.
Upstream zai has no glm-5.3 handling (glm-5.2 only: {high, max}), so the
tests failed on the PR target repo.
Re-anchor to upstream-real behavior:
- kimi-coding _K3_EFFORT_MAP (low/high/max) for the three-tier cases
- zai glm-5.2 two-tier collapse for the no-op case (already-high request
yields None — assert the invariant, not the wrapper shape)
The invariant under test is unchanged: top-level reasoning_effort must
only ever hold values the provider profile itself would emit.
Review of "feat(plugins): adaptive-reasoning" (sampled: classifier/config/middleware wiring in the 777-line module plus the test file; the closed-loop llm_execution observer was reviewed at its contract seams). Standout plugin engineering: the docstring reports MEASURED A/B data instead of vibes, states its own blind spot (short-and-hard prompts route low; real accuracy loss observed) alongside built-in mitigations (tool-error escalation, floor=medium escape hatch), keeps the prompt-cache prefix untouched by rewriting only the request-scoped effort field, handles the CJK \b-boundary pitfall explicitly, adds a closed-loop observer with an anti-oscillation guard for 3-tier scales, and forgets stale turn counters. Config caching on (mtime_ns, size) matches core conventions; floor>ceiling self-corrects. Suggestions:
|
What does this PR do?
Adaptive reasoning-effort routing as a standalone plugin — zero core changes. Addresses #40306 / #74725 as the edge-capability alternative: no new env vars, no core tool, no schema changes.
Routes per-turn reasoning effort through the existing
llm_requestmiddleware:\bon CJK).llm_executionobserver records reasoning_tokens + finish_reason per turn; monotone feedback rules adjust the next turn —lengthtruncation → medium (never down),rt=0+ clean stop → low,rt>=600+ clean stop → high. A staleness guard (turn_id) rejects cross-turn leakage.reasoning_effortis rewritten ONLY with the value the provider profile itself emits (_translate_effortre-runs the same profile mapping). Hermes-only levels never leak onto the wire.plugins.entries.adaptive-reasoning.enabled: falsein config.yaml.Related Issue
Addresses #40306 and its duplicate #74725 (also #13663). Not using
Closes— those issues also cover per-turnnoneand an integrated reasoning display, which are outside this PR's scope.Related prior art: open #82578 takes a core approach (19 files: restoration, delegation semantics, Desktop UI); open #61410 also uses deterministic local heuristics; closed #58305 explored a classifier-model call. This PR differs on placement: the same per-turn routing delivered entirely at the edge via
llm_requestmiddleware, so the core stays narrow and the feature is opt-in per install.Type of Change
Changes Made
plugins/adaptive-reasoning/plugin.yaml— manifest (middleware: llm_request; hooks: post_tool_call, llm_execution)plugins/adaptive-reasoning/__init__.py— classifier, feedback controller, profile-scale translation, middlewaretests/plugins/test_adaptive_reasoning.py— 42 testsHow to Test
plugins: {entries: {adaptive-reasoning: {enabled: true}}}in config.yamlscripts/run_tests.sh tests/plugins/test_adaptive_reasoning.py— 42 passChecklist
Code
tests/plugins/: 1587 passed, 1 pre-existing a2a order-sensitive failure reproducible on pure upstreammain, unrelated to this PR) — full-suite run pending CIDocumentation & Housekeeping
cli-config.yaml.example— N/A (no new config keys outsideplugins.entries)CONTRIBUTING.md/AGENTS.md— N/A (no architecture change)Evidence