fix(agent): match Ollama "-cloud" tags; correct echo-family docs

Local Ollama exposes cloud models both as "<model>:cloud" and as
"<model>:<size>-cloud" (gpt-oss:120b-cloud). Treat the second form like
the first, requiring a ":" so provider ids such as "gmi-cloud" do not
match. Update the require-side comment and docstrings that still listed
only DeepSeek/Kimi/MiMo or claimed every member 400s without the field.
This commit is contained in:
kshitijk4poor
2026-10-10 14:57:55 +05:30
committed by kshitij
parent ce3f83088a
commit 8e33fdd7af
3 changed files with 10 additions and 6 deletions

View File

@@ -625,7 +625,7 @@ def _set_provider_tool_id(tc: Any, key: str, value: str) -> None:
# -- reasoning_content policy: single owner of strip-vs-re-pad; adapters keep only SYNTAX --
# Require side (echo-back enforced; replays 400 without the field): the families below. Kimi
# Require side (echo-back enforced or consumed by the template): the families below. Kimi
# is host-driven on purpose (aggregators re-exporting kimi reject it); DeepSeek V4 rejects
# empty-string pads → " ". Strict side (400/422 "Extra inputs are not permitted"): everyone
# else — Mistral, Cerebras, Groq, SambaNova, … Strip the key entirely, even a one-space pad.
@@ -636,12 +636,12 @@ def _set_provider_tool_id(tc: Any, key: str, value: str) -> None:
# (2b3a4f0af8 strip for strict providers, b5495db701 re-pad for require-side, 94b3131be7/9a9f8a6d99 kimi
# pad). The POLICY — which provider direction gets which treatment — lives here as one rule table + apply
# functions; adapters keep only SYNTAX mapping (e.g. anthropic_adapter turning reasoning_content into a
# thinking block). Direction table: require-side (echo-back enforced; replays 400 without the field): kimi
# thinking block). Direction table: require-side (echo-back enforced, or consumed by the template): kimi
# — provider kimi-coding/kimi-coding-cn, or host api.kimi.com / moonshot.ai / moonshot.cn. Host-driven on
# purpose: aggregators re-exporting kimi models reject the echo. deepseek — provider "deepseek", model
# contains "deepseek", or host api.deepseek.com (#15250; V4 rejects empty-string pads, hence the " "
# single-space pad, #17341). mimo — provider "xiaomi", model contains "mimo", or host *.xiaomimimo.com.
# ollama — provider "ollama-cloud", host ollama.com, or a local ":cloud" model (Ollama's /v1 feeds the
# ollama — provider "ollama-cloud", host ollama.com, or a local ":cloud"/"-cloud" tag (Ollama's /v1 feeds the
# field back into the template's thinking slot; stripping it drops the model's own CoT mid tool loop).
# strict side (field rejected with 400/422 "Extra inputs are not permitted"): everyone else — Mistral,
# Cerebras, Groq, SambaNova, … (#45655). Strip the key entirely, even a single-space pad.
@@ -662,6 +662,8 @@ def matches_reasoning_echo_family(family: str, provider: Any, model: Any, base_u
_, raw_providers, lowered_providers, model_subs, hosts = _REASONING_ECHO_RULE_BY_FAMILY[family]
model_lower = (model or "").lower()
if ":" in model_lower and model_lower.endswith("-cloud"):
model_lower += ":cloud" # Ollama's "<size>-cloud" tag (gpt-oss:120b-cloud) == ":cloud"
return (
provider in raw_providers or (provider or "").lower() in lowered_providers
or any(sub in model_lower for sub in model_subs) or any(base_url_host_matches(base_url, host) for host in hosts)
@@ -669,7 +671,7 @@ def matches_reasoning_echo_family(family: str, provider: Any, model: Any, base_u
def reasoning_echo_family(provider: Any, model: Any, base_url: Any) -> str | None:
"""``"kimi"`` / ``"deepseek"`` / ``"mimo"`` (first match in table order) when the
"""``"kimi"`` / ``"deepseek"`` / ``"mimo"`` / ``"ollama"`` (first match in table order) when the
endpoint enforces reasoning_content echo-back, else ``None`` (strip side)."""
families = (rule[0] for rule in _REASONING_ECHO_RULES)
return next((f for f in families if matches_reasoning_echo_family(f, provider, model, base_url)), None)

View File

@@ -156,7 +156,7 @@ class ReasoningParamsMixin:
def _needs_thinking_reasoning_pad(self) -> bool:
"""True when the provider enforces ``reasoning_content`` echo-back on tool-call replays (DeepSeek, Kimi,
MiMo thinking all 400 without it). Cached per (provider, model, base_url), invalidated by
MiMo thinking all 400 without it; Ollama Cloud feeds it to the template). Cached per (provider, model, base_url), invalidated by
``switch_model()`` / ``_try_activate_fallback()`` — called ~16× per turn.
DeepSeek v4 thinking and Kimi / Moonshot thinking both reject replays of assistant tool-call
@@ -207,7 +207,7 @@ class ReasoningParamsMixin:
return matches_reasoning_echo_family("mimo", (self.provider or "").lower(), self.model, self.base_url)
def _needs_ollama_tool_reasoning(self) -> bool:
"""True for Ollama Cloud (provider, host, or a local ``:cloud`` model)."""
"""True for Ollama Cloud (provider, host, or a local ``:cloud``/``-cloud`` tag)."""
return matches_reasoning_echo_family("ollama", (self.provider or "").lower(), self.model, self.base_url)
_copy_reasoning_content_for_api = _forward("agent.agent_runtime_helpers", "copy_reasoning_content_for_api")

View File

@@ -166,6 +166,8 @@ class TestReasoningEchoFamily:
("ollama-cloud", "kimi-k3", "https://x", "ollama"),
("custom", "kimi-k3", "https://ollama.com/v1", "ollama"),
("custom", "kimi-k3:cloud", "http://127.0.0.1:11434/v1", "ollama"),
("custom", "gpt-oss:120b-cloud", "http://127.0.0.1:11434/v1", "ollama"),
("gmi", "gmi-cloud", "https://x", None),
("custom", "qwen3:8b", "http://127.0.0.1:11434/v1", None),
("openai", "gpt-5", "https://api.openai.com/v1", None),
("mistral", "mistral-large", "https://api.mistral.ai/v1", None),