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:
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user