NousResearch/hermes-agent

Temperature handling for Kimi and gpt-5.5 auxiliary/vision calls

Cluster 4661 · agent. Data as of

2 open items. Fix merged in #12773 on 20 Apr 2026. Check it covers your case.

The short answer

1open issue
1open PR
21items grouped
Known?
Yes. 21 related items grouped here, 5 issues already closed.
Fix event
A fix was merged to main in PR #12773. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this partially resolved, deciding PRs #12773, #12861, #12862, #115850. That is separate from the fix event.
Still open
1 issue and 1 PR.

Most members concern temperature values sent to Kimi/Moonshot models (kimi-for-coding, kimi-k2.5, kimi-k2.7-code) in auxiliary and vision calls, including endpoint-specific overrides and honoring config. A smaller part covers omitting temperature for the OpenAI gpt-5.5 family, so the cluster is mostly one theme with an adjacent provider-specific item.

Open issues1

  • Issue#53455vision_analyze tool passes invalid temperature to kimi-k2.7-code model

Open PRs1

  • PR#53495fix(temperature): set temperature=1 for Kimi coding models on /coding/ endpoint (#53455)

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #11834 fix(kimi): force kimi-for-coding temperature to 0.6

    merged to main

    files: agent/auxiliary_client.py, run_agent.py, tests/agent/test_auxiliary_client.py

    Show the diff
    • agent/auxiliary_client.py
      @@ -94,6 +94,17 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:
               return "custom"
           return _PROVIDER_ALIASES.get(normalized, normalized)
       
      +
      +_FIXED_TEMPERATURE_MODELS: Dict[str, float] = {
      +    "kimi-for-coding": 0.6,
      +}
      +
      +
      +def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
      +    """Return a required temperature override for models with strict contracts."""
      +    normalized = (model or "").strip().lower()
      +    return _FIXED_TEMPERATURE_MODELS.get(normalized)
      +
       # Default auxiliary models for direct API-key providers (cheap/fast for side tasks)
       _API_KEY_PROVIDER_AUX_MODELS: [redacted:secret-assignment] str] = {
           "gemini": "gemini-3-flash-preview",
      @@ -2293,6 +2304,10 @@ def _build_call_kwargs(
               "timeout": timeout,
           }
       
      +    fixed_temperature = _fixed_temperature_for_model(model)
      +    if fixed_temperature is not None:
      +        temperature = fixed_temperature
      +
           # Opus 4.7+ rejects any non-default temperature/top_p/top_k — silently
           # drop here so auxiliary callers that hardcode temperature (e.g. 0.3 on
           # flush_memories, 0 on structured-JSON extraction) don't 400 the moment
    • run_agent.py
      @@ -7207,14 +7207,22 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
       
                   # Use auxiliary client for the flush call when available --
                   # it's cheaper and avoids Codex Responses API incompatibility.
      -            from agent.auxiliary_client import call_llm as _call_llm
      +            from agent.auxiliary_client import (
      +                call_llm as _call_llm,
      +                _fixed_temperature_for_model,
      +            )
                   _aux_available = True
      +            # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if
      +            # the model has a strict contract; otherwise the historical 0.3 default.
      +            _flush_temperature = _fixed_temperature_for_model(self.model)
      +            if _flush_temperature is None:
      +                _flush_temperature = 0.3
                   try:
                       response = _call_llm(
                           task="flush_memories",
                           messages=api_messages,
                           tools=[memory_tool_def],
      -                    temperature=0.3,
      +                    temperature=_flush_temperature,
                           max_tokens=5120,
                           # timeout resolved from auxiliary.flush_memories.timeout config
                       )
      @@ -7226,7 +7234,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                       # No auxiliary client -- use the Codex Responses path directly
                       codex_kwargs = self._build_api_kwargs(api_messages)
                       codex_kwargs["tools"] = self._responses_tools([memory_tool_def])
      -                codex_kwargs["temperature"] = 0.3
      +                codex_kwargs["temperature"] = _flush_temperature
                       if "max_output_tokens" in codex_kwargs:
                           codex_kwargs["max_output_tokens"] = 5120
                       response = self._run_codex_stream(codex_kwargs)
      @@ -7245,7 +7253,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                           "model": self.model,
                           "messages": api_messages,
                           "tools": [memory_tool_def],
      -                    "temperature": 0.3,
      +                    "temperature": _flush_temperature,
                           **self._max_tokens_param(5120),
                       }
                       from agent.auxiliary_client import _get_task_timeout
    • tests/agent/test_auxiliary_client.py
      @@ -696,6 +696,95 @@ def test_500_not_connection(self):
               assert _is_connection_error(err) is False
       
       
      +class TestKimiForCodingTemperature:
      +    """kimi-for-coding now requires temperature=0.6 exactly."""
      +
      +    def test_build_call_kwargs_forces_fixed_temperature(self):
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-for-coding",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.3,
      +        )
      +
      +        assert kwargs["temperature"] == 0.6
      +
      +    def test_build_call_kwargs_injects_temperature_when_missing(self):
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-for-coding",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=None,
      +        )
      +
      +        assert kwargs["temperature"] == 0.6
      +
      +    def test_auto_routed_kimi_for_coding_sync_call_uses_fixed_temperature(self):
      +        client = MagicMock()
      +        client.base_url = "https://api.kimi.com/coding/v1"
      +        response = MagicMock()
      +        client.chat.completions.create.return_value = response
      +
      +        with patch(
      +            "agent.auxiliary_client._get_cached_client",
      +            return_value=(client, "kimi-for-coding"),
      +        ), patch(
      +            "agent.auxiliary_client._resolve_task_provider_model",
      +            return_value=("auto", "kimi-for-coding", None, None, None),
      +        ):
      +            result = call_llm(
      +                task="session_search",
      +                messages=[{"role": "user", "content": "hello"}],
      +                temperature=0.1,
      +            )
      +
      +        assert result is response
      +        kwargs = client.chat.completions.create.call_args.kwargs
      +        assert kwargs["model"] == "kimi-for-coding"
      +        assert kwargs["temperature"] == 0.6
      +
      +    @pytest.mark.asyncio
      +    async def test_auto_routed_kimi_for_coding_async_call_uses_fixed_temperature(self):
      +        client = MagicMock()
      +        client.base_url = "https://api.kimi.com/coding/v1"
      +        response = MagicMock()
      +        client.chat.completions.create = AsyncMock(return_value=response)
      +
      +        with patch(
      +            "agent.auxiliary_client._get_cached_client",
      +            return_value=(client, "kimi-for-coding"),
      +        ), patch(
      +            "agent.auxiliary_client._resolve_task_provider_model",
      +            return_value=("auto", "kimi-for-coding", None, None, None),
      +        ):
      +            result = await async_call_llm(
      +                task="session_search",
      +                messages=[{"role": "user", "content": "hello"}],
      +                temperature=0.1,
      +            )
      +
      +        assert result is response
      +        kwargs = client.chat.completions.create.call_args.kwargs
      +        assert kwargs["model"] == "kimi-for-coding"
      +        assert kwargs["temperature"] == 0.6
      +
      +    def test_non_kimi_model_still_preserves_temperature(self):
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-k2.5",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.3,
      +        )
      +
      +        assert kwargs["temperature"] == 0.3
      +
      +
       # ---------------------------------------------------------------------------
       # async_call_llm payment / connection fallback (#7512 bug 2)
       # ---------------------------------------------------------------------------
  • #12773 fix(kimi): honor public Moonshot temperature contract across direct call paths

    merged to main

    files: agent/auxiliary_client.py, mini_swe_runner.py, run_agent.py, scripts/release.py, tests/agent/test_auxiliary_client.py, tests/run_agent/test_run_agent.py, tests/test_mini_swe_runner.py, tests/test_trajectory_compressor.py, tests/test_trajectory_compressor_async.py, trajectory_compressor.py

    Show the diff
    • agent/auxiliary_client.py
      @@ -116,8 +116,25 @@ def _normalize_aux_provider(provider: Optional[str]) -> str:
           "kimi-k2-thinking-turbo",
       })
       
      +# Moonshot's public chat endpoint (api.moonshot.ai/v1) enforces a different
      +# temperature contract than the Coding Plan endpoint above.  Empirically,
      +# `kimi-k2.5` on the public API rejects 0.6 with HTTP 400
      +# "invalid temperature: only 1 is allowed for this model" — the Coding Plan
      +# lock (0.6 for non-thinking) does not apply.  `kimi-k2-turbo-preview` and the
      +# thinking variants already match the Coding Plan contract on the public
      +# endpoint, so we only override the models that diverge.
      +# Users hit this endpoint when `KIMI_API_KEY` is a legacy `sk-*` key (the
      +# `sk-kimi-*` prefix routes to api.kimi.com/coding/v1 instead — see
      +# hermes_cli/auth.py:_kimi_base_url_for_key).
      +_KIMI_PUBLIC_API_OVERRIDES: Dict[str, float] = {
      +    "kimi-k2.5": 1.0,
      +}
      +
       
      -def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
      +def _fixed_temperature_for_model(
      +    model: Optional[str],
      +    base_url: Optional[str] = None,
      +) -> Optional[float]:
           """Return a required temperature override for models with strict contracts.
       
           Moonshot's kimi-for-coding endpoint rejects any non-approved temperature on
      @@ -125,15 +142,31 @@ def _fixed_temperature_for_model(model: Optional[str]) -> Optional[float]:
           variants require 1.0.  An optional ``vendor/`` prefix (e.g.
           ``moonshotai/kimi-k2.5``) is tolerated for aggregator routings.
       
      +    When ``base_url`` points to Moonshot's public chat endpoint
      +    (``api.moonshot.ai``), the contract changes for ``kimi-k2.5``: the public
      +    API only accepts ``temperature=1``, not 0.6.  That override takes precedence
      +    over the Coding Plan defaults above.
      +
           Returns ``None`` for every other model, including ``kimi-k2-instruct*``
           which is the separate non-coding K2 family with variable temperature.
           """
           normalized = (model or "").strip().lower()
      +    bare = normalized.rsplit("/", 1)[-1]
      +
      +    # Public Moonshot API has a stricter contract for some models than the
      +    # Coding Plan endpoint — check it first so it wins on conflict.
      +    if base_url and "api.moonshot.ai" in base_url.lower():
      +        public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)
      +        if public is not None:
      +            logger.debug(
      +                "Forcing temperature=%s for %r on public Moonshot API", public, model
      +            )
      +            return public
      +
           fixed = _FIXED_TEMPERATURE_MODELS.get(normalized)
           if fixed is not None:
               logger.debug("Forcing temperature=%s for model %r (fixed map)", fixed, model)
               return fixed
      -    bare = normalized.rsplit("/", 1)[-1]
           if bare in _KIMI_THINKING_MODELS:
               logger.debug("Forcing temperature=1.0 for kimi thinking model %r", model)
               return 1.0
      @@ -2417,7 +2450,7 @@ def _build_call_kwargs(
               "timeout": timeout,
           }
       
      -    fixed_temperature = _fixed_temperature_for_model(model)
      +    fixed_temperature = _fixed_temperature_for_model(model, base_url)
           if fixed_temperature is not None:
               temperature = fixed_temperature
       
      @@ -2598,11 +2631,14 @@ def call_llm(
                            task, resolved_provider or "auto", final_model or "default",
                            f" at {_base_info}" if _base_info and "openrouter" not in _base_info else "")
       
      +    # Pass the client's actual base_url (not just resolved_base_url) so
      +    # endpoint-specific temperature overrides can distinguish
      +    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.
           kwargs = _build_call_kwargs(
               resolved_provider, final_model, messages,
               temperature=temperature, max_tokens=[redacted:secret-assignment]
               tools=tools, timeout=effective_timeout, extra_body=extra_body,
      -        base_url=resolved_base_url)
      +        base_url=_base_info or resolved_base_url)
       
           # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
           _client_base = str(getattr(client, "base_url", "") or "")
      @@ -2656,7 +2692,8 @@ def call_llm(
                           fb_label, fb_model, messages,
                           temperature=temperature, max_tokens=[redacted:secret-assignment]
                           tools=tools, timeout=effective_timeout,
      -                    extra_body=extra_body)
      +                    extra_body=extra_body,
      +                    base_url=str(getattr(fb_client, "base_url", "") or ""))
                       return _validate_llm_response(
                           fb_client.chat.completions.create(**fb_kwargs), task)
               raise
      @@ -2791,14 +2828,17 @@ async def async_call_llm(
       
           effective_timeout = timeout if timeout is not None else _get_task_timeout(task)
       
      +    # Pass the client's actual base_url (not just resolved_base_url) so
      +    # endpoint-specific temperature overrides can distinguish
      +    # api.moonshot.ai vs api.kimi.com/coding even on auto-detected routes.
      +    _client_base = str(getattr(client, "base_url", "") or "")
           kwargs = _build_call_kwargs(
               resolved_provider, final_model, messages,
               temperature=temperature, max_tokens=[redacted:secret-assignment]
               tools=tools, timeout=effective_timeout, extra_body=extra_body,
      -        base_url=resolved_base_url)
      +        base_url=_client_base or resolved_base_url)
       
           # Convert image blocks for Anthropic-compatible endpoints (e.g. MiniMax)
      -    _client_base = str(getattr(client, "base_url", "") or "")
           if _is_anthropic_compat_endpoint(resolved_provider, _client_base):
               kwargs["messages"] = _convert_openai_images_to_anthropic(kwargs["messages"])
       
      @@ -2834,7 +2874,8 @@ async def async_call_llm(
                           fb_label, fb_model, messages,
                           temperature=temperature, max_tokens=[redacted:secret-assignment]
                           tools=tools, timeout=effective_timeout,
      -                    extra_body=extra_body)
      +                    extra_body=extra_body,
      +                    base_url=str(getattr(fb_client, "base_url", "") or ""))
                       # Convert sync fallback client to async
                       async_fb, async_fb_model = _to_async_client(fb_client, fb_model or "")
                       if async_fb_model and async_fb_model != fb_kwargs.get("model"):
    • mini_swe_runner.py
      @@ -43,13 +43,16 @@
       load_dotenv()
       
       
      -def _effective_temperature_for_model(model: str) -> Optional[float]:
      +def _effective_temperature_for_model(
      +    model: str,
      +    base_url: Optional[str] = None,
      +) -> Optional[float]:
           """Return a fixed temperature for models with strict sampling contracts."""
           try:
               from agent.auxiliary_client import _fixed_temperature_for_model
           except Exception:
               return None
      -    return _fixed_temperature_for_model(model)
      +    return _fixed_temperature_for_model(model, base_url)
       
       
       
      @@ -457,7 +460,10 @@ def run_task(self, task: str) -> Dict[str, Any]:
                               "tools": self.tools,
                               "timeout": 300.0,
                           }
      -                    fixed_temperature = _effective_temperature_for_model(self.model)
      +                    fixed_temperature = _effective_temperature_for_model(
      +                        self.model,
      +                        str(getattr(self.client, "base_url", "") or ""),
      +                    )
                           if fixed_temperature is not None:
                               api_kwargs["temperature"] = fixed_temperature
       
    • run_agent.py
      @@ -7173,7 +7173,7 @@ def _build_api_kwargs(self, api_messages: list) -> dict:
               except Exception:
                   _fixed_temperature_for_model = None
               if _fixed_temperature_for_model is not None:
      -            fixed_temperature = _fixed_temperature_for_model(self.model)
      +            fixed_temperature = _fixed_temperature_for_model(self.model, self.base_url)
                   if fixed_temperature is not None:
                       api_kwargs["temperature"] = fixed_temperature
               if self._is_qwen_portal():
      @@ -7619,7 +7619,7 @@ def flush_memories(self, messages: list = None, min_turns: int = None):
                   _aux_available = True
                   # Use the fixed-temperature override (e.g. kimi-for-coding → 0.6) if
                   # the model has a strict contract; otherwise the historical 0.3 default.
      -            _flush_temperature = _fixed_temperature_for_model(self.model)
      +            _flush_temperature = _fixed_temperature_for_model(self.model, self.base_url)
                   if _flush_temperature is None:
                       _flush_temperature = 0.3
                   try:
      @@ -8675,7 +8675,7 @@ def _handle_max_iterations(self, messages: list, api_call_count: int) -> str:
                   except Exception:
                       _fixed_temperature_for_model = None
                   _summary_temperature = (
      -                _fixed_temperature_for_model(self.model)
      +                _fixed_temperature_for_model(self.model, self.base_url)
                       if _fixed_temperature_for_model is not None
                       else None
                   )
    • scripts/release.py
      @@ -107,6 +107,7 @@
           "[email protected]": "handsdiff",
           "[redacted:email]": "etherman-os",
           "[redacted:email]": "mark-ramsell",
      +    "[redacted:email]": "taeng0204",
           "[redacted:email]": "gpickett00",
           "[redacted:email]": "wakamex",
           "[redacted:email]": "clawdia-nash",
    • tests/agent/test_auxiliary_client.py
      @@ -832,6 +832,92 @@ def test_non_restricted_model_preserves_temperature(self, model):
       
               assert kwargs["temperature"] == 0.3
       
      +    # ── Endpoint-aware overrides: api.moonshot.ai vs api.kimi.com/coding ──
      +    # The public Moonshot chat endpoint and the Coding Plan endpoint enforce
      +    # different temperature contracts for the same model name.  `kimi-k2.5` on
      +    # api.moonshot.ai rejects 0.6 with HTTP 400 "only 1 is allowed for this
      +    # model", while the Coding Plan docs mandate 0.6.  Override must pick the
      +    # right value per base_url.
      +
      +    @pytest.mark.parametrize(
      +        "base_url",
      +        [
      +            "https://api.moonshot.ai/v1",
      +            "https://api.moonshot.ai/v1/",
      +            "https://API.MOONSHOT.AI/v1",
      +        ],
      +    )
      +    def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):
      +        """kimi-k2.5 on the public Moonshot API only accepts temperature=1."""
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-k2.5",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.1,
      +            base_url=base_url,
      +        )
      +
      +        assert kwargs["temperature"] == 1.0
      +
      +    def test_kimi_k2_5_coding_plan_keeps_temperature_0_6(self):
      +        """kimi-k2.5 on api.kimi.com/coding keeps the Coding Plan's 0.6 lock."""
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-k2.5",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.1,
      +            base_url="https://api.kimi.com/coding/v1",
      +        )
      +
      +        assert kwargs["temperature"] == 0.6
      +
      +    def test_kimi_k2_5_no_base_url_falls_back_to_coding_plan_lock(self):
      +        """Without a base_url hint, the Coding Plan default (0.6) applies.
      +
      +        Preserves PR #12144 backward compatibility for callers that don't thread
      +        the client's base_url through.
      +        """
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model="kimi-k2.5",
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.1,
      +        )
      +
      +        assert kwargs["temperature"] == 0.6
      +
      +    @pytest.mark.parametrize(
      +        "model,expected",
      +        [
      +            # Only kimi-k2.5 diverges on api.moonshot.ai; the rest keep the
      +            # Coding Plan lock (empirically verified against Moonshot in April
      +            # 2026: turbo-preview accepts 0.6, thinking-turbo accepts 1.0).
      +            ("kimi-k2-turbo-preview", 0.6),
      +            ("kimi-k2-0905-preview", 0.6),
      +            ("kimi-k2-thinking", 1.0),
      +            ("kimi-k2-thinking-turbo", 1.0),
      +            ("moonshotai/kimi-k2-thinking-turbo", 1.0),
      +        ],
      +    )
      +    def test_other_kimi_k2_family_unchanged_on_public_api(self, model, expected):
      +        from agent.auxiliary_client import _build_call_kwargs
      +
      +        kwargs = _build_call_kwargs(
      +            provider="kimi-coding",
      +            model=model,
      +            messages=[{"role": "user", "content": "hello"}],
      +            temperature=0.1,
      +            base_url="https://api.moonshot.ai/v1",
      +        )
      +
      +        assert kwargs["temperature"] == expected
      +
       
       # ---------------------------------------------------------------------------
       # async_call_llm payment / connection fallback (#7512 bug 2)
    • tests/run_agent/test_run_agent.py
      @@ -918,6 +918,26 @@ def test_basic_kwargs(self, agent):
               assert kwargs["messages"] is messages
               assert kwargs["timeout"] == 1800.0
       
      +    def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):
      +        agent.base_url = "https://api.moonshot.ai/v1"
      +        agent._base_url_lower = agent.base_url.lower()
      +        agent.model = "kimi-k2.5"
      +        messages = [{"role": "user", "content": "hi"}]
      +
      +        kwargs = agent._build_api_kwargs(messages)
      +
      +        assert kwargs["temperature"] == 1.0
      +
      +    def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):
      +        agent.base_url = "https://api.kimi.com/coding/v1"
      +        agent._base_url_lower = agent.base_url.lower()
      +        agent.model = "kimi-k2.5"
      +        messages = [{"role": "user", "content": "hi"}]
      +
      +        kwargs = agent._build_api_kwargs(messages)
      +
      +        assert kwargs["temperature"] == 0.6
      +
           def test_provider_preferences_injected(self, agent):
               agent.base_url = "https://openrouter.ai/api/v1"
               agent.providers_allowed = ["Anthropic"]
    • tests/test_mini_swe_runner.py
      @@ -26,3 +26,30 @@ def test_run_task_forces_kimi_fixed_temperature():
       
           assert result["completed"] is True
           assert client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
      +
      +
      +def test_run_task_public_moonshot_kimi_k2_5_forces_temperature_1():
      +    with patch("openai.OpenAI") as mock_openai:
      +        client = MagicMock()
      +        client.base_url = "https://api.moonshot.ai/v1"
      +        client.chat.completions.create.return_value = SimpleNamespace(
      +            choices=[SimpleNamespace(message=SimpleNamespace(content="done", tool_calls=[]))]
      +        )
      +        mock_openai.return_value = client
      +
      +        from mini_swe_runner import MiniSWERunner
      +
      +        runner = MiniSWERunner(
      +            model="kimi-k2.5",
      +            base_url="https://api.moonshot.ai/v1",
      +            api_key="[redacted:secret-assignment]",
      +            env_type="local",
      +            max_iterations=1,
      +        )
      +        runner._create_env = MagicMock()
      +        runner._cleanup_env = MagicMock()
      +
      +        result = runner.run_task("2+2")
      +
      +    assert result["completed"] is True
      +    assert client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    • tests/test_trajectory_compressor.py
      @@ -54,6 +54,30 @@ def test_generate_summary_custom_client_forces_kimi_temperature():
           assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
       
       
      +def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():
      +    config = CompressionConfig(
      +        summarization_model="kimi-k2.5",
      +        base_url="https://api.moonshot.ai/v1",
      +        temperature=0.3,
      +        summary_target_tokens=100,
      +        max_retries=1,
      +    )
      +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
      +    compressor.config = config
      +    compressor.logger = MagicMock()
      +    compressor._use_call_llm = False
      +    compressor.client = MagicMock()
      +    compressor.client.chat.completions.create.return_value = SimpleNamespace(
      +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
      +    )
      +
      +    metrics = TrajectoryMetrics()
      +    result = compressor._generate_summary("tool output", metrics)
      +
      +    assert result.startswith("[CONTEXT SUMMARY]:")
      +    assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
      +
      +
       # ---------------------------------------------------------------------------
       # CompressionConfig
       # ---------------------------------------------------------------------------
    • tests/test_trajectory_compressor_async.py
      @@ -141,3 +141,31 @@ async def test_generate_summary_async_custom_client_forces_kimi_temperature():
       
           assert result.startswith("[CONTEXT SUMMARY]:")
           assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 0.6
      +
      +
      [redacted:email]
      +async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperature_1():
      +    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics
      +
      +    config = CompressionConfig(
      +        summarization_model="kimi-k2.5",
      +        base_url="https://api.moonshot.ai/v1",
      +        temperature=0.3,
      +        summary_target_tokens=100,
      +        max_retries=1,
      +    )
      +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
      +    compressor.config = config
      +    compressor.logger = MagicMock()
      +    compressor._use_call_llm = False
      +    async_client = MagicMock()
      +    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(
      +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
      +    ))
      +    compressor._get_async_client = MagicMock(return_value=async_client)
      +
      +    metrics = TrajectoryMetrics()
      +    result = await compressor._generate_summary_async("tool output", metrics)
      +
      +    assert result.startswith("[CONTEXT SUMMARY]:")
      +    assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
    • trajectory_compressor.py
      @@ -54,14 +54,18 @@
       load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env)
       
       
      -def _effective_temperature_for_model(model: str, requested_temperature: float) -> float:
      +def _effective_temperature_for_model(
      +    model: str,
      +    requested_temperature: float,
      +    base_url: Optional[str] = None,
      +) -> float:
           """Apply fixed model temperature contracts to direct client calls."""
           try:
               from agent.auxiliary_client import _fixed_temperature_for_model
           except Exception:
               return requested_temperature
       
      -    fixed_temperature = _fixed_temperature_for_model(model)
      +    fixed_temperature = _fixed_temperature_for_model(model, base_url)
           if fixed_temperature is not None:
               return fixed_temperature
           return requested_temperature
      @@ -583,6 +587,7 @@ def _generate_summary(self, content: str, metrics: TrajectoryMetrics) -> str:
                       summary_temperature = _effective_temperature_for_model(
                           self.config.summarization_model,
                           self.config.temperature,
      +                    self.config.base_url,
                       )
                       
                       if getattr(self, '_use_call_llm', False):
      @@ -649,6 +654,7 @@ async def _generate_summary_async(self, content: str, metrics: TrajectoryMetrics
                       summary_temperature = _effective_temperature_for_model(
                           self.config.summarization_model,
                           self.config.temperature,
      +                    self.config.base_url,
                       )
                       
                       if getattr(self, '_use_call_llm', False):
  • #12861 fix(kimi): include api.moonshot.cn in public temperature override

    merged to main

    files: agent/auxiliary_client.py, tests/agent/test_auxiliary_client.py, tests/run_agent/test_run_agent.py, tests/test_trajectory_compressor.py, tests/test_trajectory_compressor_async.py

    Show the diff
    • agent/auxiliary_client.py
      @@ -155,7 +155,7 @@ def _fixed_temperature_for_model(
       
           # Public Moonshot API has a stricter contract for some models than the
           # Coding Plan endpoint — check it first so it wins on conflict.
      -    if base_url and "api.moonshot.ai" in base_url.lower():
      +    if base_url and ("api.moonshot.ai" in base_url.lower() or "api.moonshot.cn" in base_url.lower()):
               public = _KIMI_PUBLIC_API_OVERRIDES.get(bare)
               if public is not None:
                   logger.debug(
    • tests/agent/test_auxiliary_client.py
      @@ -845,6 +845,8 @@ def test_non_restricted_model_preserves_temperature(self, model):
                   "https://api.moonshot.ai/v1",
                   "https://api.moonshot.ai/v1/",
                   "https://API.MOONSHOT.AI/v1",
      +            "https://api.moonshot.cn/v1",
      +            "https://api.moonshot.cn/v1/",
               ],
           )
           def test_kimi_k2_5_public_api_forces_temperature_1(self, base_url):
    • tests/run_agent/test_run_agent.py
      @@ -928,6 +928,16 @@ def test_public_moonshot_kimi_k2_5_forces_temperature_1(self, agent):
       
               assert kwargs["temperature"] == 1.0
       
      +    def test_public_moonshot_cn_kimi_k2_5_forces_temperature_1(self, agent):
      +        agent.base_url = "https://api.moonshot.cn/v1"
      +        agent._base_url_lower = agent.base_url.lower()
      +        agent.model = "kimi-k2.5"
      +        messages = [{"role": "user", "content": "hi"}]
      +
      +        kwargs = agent._build_api_kwargs(messages)
      +
      +        assert kwargs["temperature"] == 1.0
      +
           def test_kimi_coding_endpoint_keeps_kimi_k2_5_at_0_6(self, agent):
               agent.base_url = "https://api.kimi.com/coding/v1"
               agent._base_url_lower = agent.base_url.lower()
    • tests/test_trajectory_compressor.py
      @@ -78,6 +78,30 @@ def test_generate_summary_public_moonshot_kimi_k2_5_forces_temperature_1():
           assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
       
       
      +def test_generate_summary_public_moonshot_cn_kimi_k2_5_forces_temperature_1():
      +    config = CompressionConfig(
      +        summarization_model="kimi-k2.5",
      +        base_url="https://api.moonshot.cn/v1",
      +        temperature=0.3,
      +        summary_target_tokens=100,
      +        max_retries=1,
      +    )
      +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
      +    compressor.config = config
      +    compressor.logger = MagicMock()
      +    compressor._use_call_llm = False
      +    compressor.client = MagicMock()
      +    compressor.client.chat.completions.create.return_value = SimpleNamespace(
      +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
      +    )
      +
      +    metrics = TrajectoryMetrics()
      +    result = compressor._generate_summary("tool output", metrics)
      +
      +    assert result.startswith("[CONTEXT SUMMARY]:")
      +    assert compressor.client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
      +
      +
       # ---------------------------------------------------------------------------
       # CompressionConfig
       # ---------------------------------------------------------------------------
    • tests/test_trajectory_compressor_async.py
      @@ -169,3 +169,32 @@ async def test_generate_summary_async_public_moonshot_kimi_k2_5_forces_temperatu
       
           assert result.startswith("[CONTEXT SUMMARY]:")
           assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
      +
      +
      +
      [redacted:email]
      +async def test_generate_summary_async_public_moonshot_cn_kimi_k2_5_forces_temperature_1():
      +    from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics
      +
      +    config = CompressionConfig(
      +        summarization_model="kimi-k2.5",
      +        base_url="https://api.moonshot.cn/v1",
      +        temperature=0.3,
      +        summary_target_tokens=100,
      +        max_retries=1,
      +    )
      +    compressor = TrajectoryCompressor.__new__(TrajectoryCompressor)
      +    compressor.config = config
      +    compressor.logger = MagicMock()
      +    compressor._use_call_llm = False
      +    async_client = MagicMock()
      +    async_client.chat.completions.create = MagicMock(return_value=SimpleNamespace(
      +        choices=[SimpleNamespace(message=SimpleNamespace(content="[CONTEXT SUMMARY]: summary"))]
      +    ))
      +    compressor._get_async_client = MagicMock(return_value=async_client)
      +
      +    metrics = TrajectoryMetrics()
      +    result = await compressor._generate_summary_async("tool output", metrics)
      +
      +    assert result.startswith("[CONTEXT SUMMARY]:")
      +    assert async_client.chat.completions.create.call_args.kwargs["temperature"] == 1.0
  • #12862 fix(vision): honor auxiliary.vision.temperature from config

    merged to main

    files: scripts/release.py, tests/tools/test_browser_camofox.py, tests/tools/test_browser_console.py, tests/tools/test_vision_tools.py, tools/browser_camofox.py, tools/browser_tool.py, tools/vision_tools.py

  • #115850 fix(auxiliary): vision and other auxiliary calls to gpt-5.x / o-series no longer pay a temperature 400 round-trip (#51083, salvage #51157)

    merged to main

    files: agent/auxiliary_client.py, tests/agent/test_auxiliary_client.py, tests/agent/test_auxiliary_parameter_rung_chaining.py, tests/agent/test_injected_param_strip_retry_registry.py, tests/agent/test_unsupported_temperature_retry.py

9 PRs closed without merging

From the close comments: merged via another change 4 · stale or abandoned 2 · done elsewhere 1 · superseded by another PR 1 · other 1

  • #4967 fix: vision tool respects auxiliary.vision.temperature from config · done elsewhere → 088bf90, #4661
  • #9191 fix(auxiliary): omit temperature for auto-routed kimi calls · stale or abandoned → #9125
  • #11825 fix(kimi): force kimi-for-coding temperature to 0.6 · merged via another change → #11834
  • #12668 fix(kimi): kimi-k2.5 needs temperature=1.0 on api.moonshot.ai endpoint · merged via another change → #12773
  • #12680 fix(kimi): honor public Moonshot temperature contract across direct call paths · merged via another change → #12773
Show 4 more
  • #12760 fix: move kimi-k2.5 from instant to thinking models · superseded by another PR → #12773
  • #12772 fix: move kimi-k2.5 to thinking models · stale or abandoned
  • #12806 fix: include api.moonshot.cn in public API temperature override (#12745) · other
  • #51157 fix(auxiliary): OMIT temperature for gpt-5.5 family (#51083) · merged via another change → ada97e3, #115850, #51083, #51157
Closed issues · 5
  • Closed#4661[Bug]: kimi-coding vision provider fails with 400 error on second call due to hardcoded temperature
  • Closed#9125[Bug]: kimi-coding auxiliary calls fail with 400 "invalid temperature" when provider=auto
  • Closed#12404kimi-k2.5 fails with invalid temperature error
  • Closed#12745[Bug]: kimi-k2.5 incorrectly classified as instant mode (temperature=0.6) instead of thinking mode (temperature=1.0)
  • Closed#51083[Bug]: auxiliary vision sends unsupported temperature to OpenAI gpt-5.5
How this cluster was grouped
  • Link evidence (partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text.
  • Members outside that structural core may belong to a different underlying problem.
  • Pairwise link density 0.09: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#11834, #12773, #12861, #12862, #115850
Closed issues
5
Attempts
9 PRs closed without merging
Fix link checked
19 Sep 2026

For your agent

The same facts, typed.