Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ciris_engine/logic/services/runtime/llm_service/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -1509,7 +1509,7 @@
raise RuntimeError("All endpoints exhausted without success")

@staticmethod
def _build_reasoning_off_extras(base_url: str, model_name: str) -> Dict[str, Any]:

Check failure on line 1512 in ciris_engine/logic/services/runtime/llm_service/service.py

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this function to reduce its Cognitive Complexity from 16 to the 15 allowed.

See more on https://sonarcloud.io/project/issues?id=CIRISAI_CIRISAgent&issues=AZ5fcNGJaFdLqJ2Mx7to&open=AZ5fcNGJaFdLqJ2Mx7to&pullRequest=769
"""Return the ``extra_body`` keys that disable reasoning for this
specific (provider, model) combination.

Expand Down Expand Up @@ -1547,6 +1547,23 @@
model = (model_name or "").lower()

if "openrouter.ai" in base:
# Reasoning-OFF contract: pass `reasoning.enabled=false` so any
# reasoning-capable model that honours it (claude-sonnet-4.6,
# openai/gpt-5-chat, deepseek, etc.) emits zero reasoning tokens
# as before. `effort=minimal` is *not* a disable on those models —
# it's the lowest-effort setting and still emits reasoning tokens.
#
# OpenRouter explicitly rejects `reasoning.enabled=false` for the
# gpt-5 reasoning family with 400 "Reasoning is mandatory for this
# endpoint and cannot be disabled." (verified 2026-05-16 against
# `openai/gpt-5`). For that narrow case fall back to
# `effort=minimal`, which the gpt-5 family honours by emitting
# 0 reasoning tokens (cost ~$0.0002 on a trivial prompt). The
# chat-tuned variant `openai/gpt-5-chat` is reasoning-capable, not
# reasoning-mandatory, and stays on the universal `enabled=false`
# path.
if model.startswith("openai/gpt-5") and "chat" not in model:
return {"reasoning": {"effort": "minimal"}}
return {"reasoning": {"enabled": False}}

if "together" in base:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,10 +325,33 @@ def test_deepinfra_carries_vllm_and_reasoning_enabled_keys(self):
assert extra_body["chat_template_kwargs"] == {"enable_thinking": False}
assert extra_body["reasoning"] == {"enabled": False}

def test_openrouter_carries_reasoning_enabled_false(self):
def test_openrouter_default_carries_reasoning_enabled_false(self):
# Default reasoning-OFF contract on OpenRouter: send `enabled=false`
# so reasoning-capable models (claude, gpt-5-chat, deepseek, …) emit
# zero reasoning tokens. `effort=minimal` is *not* a disable on those
# models, so we only use it for the narrow reasoning-mandatory case
# below.
extra_body = self._build_for("https://openrouter.ai/api/v1", "kimi/k2-0130-preview")["extra_body"]
assert extra_body["reasoning"] == {"enabled": False}

def test_openrouter_gpt5_uses_effort_minimal(self):
# OpenRouter rejects reasoning.enabled=false for the gpt-5 reasoning
# family with 400 "Reasoning is mandatory for this endpoint and cannot
# be disabled." (verified 2026-05-16 against openai/gpt-5). For that
# narrow case we fall back to effort=minimal, which gpt-5 honours by
# emitting 0 reasoning tokens.
for model in ("openai/gpt-5", "openai/gpt-5-mini", "openai/gpt-5-nano"):
extra_body = self._build_for("https://openrouter.ai/api/v1", model)["extra_body"]
assert extra_body["reasoning"] == {"effort": "minimal"}, model

def test_openrouter_gpt5_chat_stays_on_enabled_false(self):
# gpt-5-chat is the chat-tuned (reasoning-capable, not mandatory)
# variant and honours reasoning.enabled=false. It must stay on the
# universal disable path so it doesn't accidentally re-enable
# reasoning at lowest-effort under our reasoning-OFF contract.
extra_body = self._build_for("https://openrouter.ai/api/v1", "openai/gpt-5-chat")["extra_body"]
assert extra_body["reasoning"] == {"enabled": False}

def test_local_endpoint_carries_vllm_key(self):
extra_body = self._build_for("http://localhost:8080/v1", "llama")["extra_body"]
assert extra_body["chat_template_kwargs"] == {"enable_thinking": False}
Expand Down Expand Up @@ -453,9 +476,11 @@ def test_openrouter_includes_provider_config(self):
retry_state=RetryState(),
)

# Verify reasoning is disabled
# gpt-4o is reasoning-capable, not reasoning-mandatory — it honours
# `enabled=false`. Reasoning-mandatory gpt-5 models would take the
# `effort=minimal` fallback instead (covered separately above).
assert "extra_body" in extra_kwargs
extra_body = extra_kwargs["extra_body"]
# Provider config may or may not be present depending on env
assert "reasoning" in extra_body
assert extra_body["reasoning"]["enabled"] is False
assert extra_body["reasoning"] == {"enabled": False}
Loading