feat(llm): route reasoning models to reasoning-aware provider clients #106

Merged
CoreRasurae merged 1 commits from feature/m2-reasoning-model-provider-support into master 2026-08-05 22:19:58 +00:00

1 Commits

Author SHA1 Message Date
CoreRasurae 01ab2c8e9b feat(llm): route reasoning models to reasoning-aware provider clients
CI / lint (pull_request) Successful in 35s
CI / security (pull_request) Successful in 1m6s
CI / quality (pull_request) Successful in 35s
CI / typecheck (pull_request) Successful in 1m38s
CI / build (pull_request) Successful in 1m50s
CI / integration_tests (pull_request) Successful in 2m36s
CI / unit_tests (pull_request) Successful in 4m47s
CI / coverage (pull_request) Successful in 4m3s
CI / status-check (pull_request) Successful in 8s
CI / lint (push) Successful in 42s
CI / typecheck (push) Successful in 1m13s
CI / security (push) Successful in 1m12s
CI / quality (push) Successful in 1m47s
CI / build (push) Successful in 1m40s
CI / integration_tests (push) Successful in 4m48s
CI / unit_tests (push) Successful in 6m8s
CI / benchmark (pull_request) Failing after 22m54s
CI / coverage (push) Successful in 4m30s
CI / status-check (push) Successful in 6s
CI / benchmark (push) Failing after 17m0s
Non-native providers (openai_compatible and every named additional
provider) were routed unconditionally to a bare
langchain_openai.ChatOpenAI(base_url=...) client. That client silently
drops reasoning_content on both the response and request legs, so a
reasoning/"thinking" model behind an OpenAI-compatible endpoint that
requires the reasoning block to be echoed back on the assistant turn
preceding a tool result rejected the follow-up call with a
non-transient 400 error, aborting the actor-graph execution.

Adds an optional `reasoning` boolean field to the LLM agent
configuration (default false, byte-identical behavior when
absent/false or for native providers). When true on a non-native
provider, build_chat_model now constructs
cleveractors.agents.llm_reasoning.ReasoningChatModel, a thin
langchain_deepseek.ChatDeepSeek subclass that overrides
_get_request_payload to re-inject reasoning_content onto assistant
turns, closing the round-trip that ChatDeepSeek alone leaves open.
LLMAgent._execute_tool_loop needed no change: it already appends the
returned AIMessage by reference, so additional_kwargs survives replay.

ADR-2036 (approved) extends ADR-2028 with the full design record
(client-selection strategy, response/request-leg responsibilities,
alternatives considered). docs/index.md §4.4/§4.4.1 documents the new
`reasoning` field as Version 1.3.0, with a §21.1 Revision History row
attributing the change to ADR-2036 and a normative note that the
routing applies regardless of single-response vs. streaming invocation.

Adds a new mkdocs user guide, docs/guides/reasoning-aware-llm-agents.md
(registered under a new "Guides" nav section in mkdocs.yml), covering
practical usage: enabling the field, combining it with tool calling,
and the concrete difference between Executor.execute() and
Executor.execute_stream() — the latter runs the identical multi-turn
tool-call loop and reasoning_content round-trip when tools are
configured, differing only in delivering the finished answer as one
chunk instead of token-by-token, which is a pre-existing property of
streaming with tools in general, not something specific to reasoning.

Addresses PR #106 review feedback from rui.hu:
- Reformat features/steps/reasoning_provider_routing_steps.py with
  ruff so the lint CI job (and downstream coverage job) run again.
- Clarify in ReasoningRoutingTestLib.py that stubbing only the OpenAI
  network boundary matches established precedent across every other
  *.robot suite in this project (ToolCallingTestLib, TokenBudgetTestLib,
  SkillLoadingTestLib all mock only the LLM API call for the same
  reason: integration tests must run without a real LLM API key).
- Add a Behave scenario/steps mirroring the existing google-genai
  import-guard coverage test for the new _REASONING_AVAILABLE guard in
  llm_imports.py, closing a coverage gap the new guard introduced.

Also corrects a rebase artifact in docs/index.md: this branch had
regressed the Version header to 1.1.0 and silently dropped master's
1.2.0 `tools_max_timeout` tool-agent field (ADR-2030 D-9) along with
its §21.1 Revision History row. Restored that content and the
`reasoning` field now lands as its own 1.3.0 revision on top of it.

ISSUES CLOSED: #101
2026-08-05 22:00:08 +00:00