hermes-agent/tests/agent/test_kimi_coding_anthropic_thinking.py
Teknium 6b81590c55
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00

153 lines
6.1 KiB
Python

"""Kimi / Moonshot thinking behavior on the Anthropic-Messages wire.
Contract (changed from the original #13848 mitigation):
- Kimi-family endpoints receive ``thinking`` in **adaptive** form
(``thinking.type="adaptive"`` + ``output_config.effort``) — never manual
``budget_tokens``. Their Anthropic-compatible endpoints
(``api.moonshot.cn/anthropic``, ``api.kimi.com/coding``) accept the
field set, and the replay-validation 400s that originally motivated
dropping the parameter (#13848) no longer occur.
- ``convert_messages_to_anthropic`` still preserves unsigned
reasoning_content-derived thinking blocks on replay for this family, so
multi-turn tool-call history round-trips.
Kimi on the chat_completions route handles ``thinking`` via ``extra_body``
in ``ChatCompletionsTransport`` (#13503).
"""
from __future__ import annotations
import pytest
class TestKimiCodingAnthropicThinking:
"""Kimi-family thinking on the Anthropic wire (incl. /coding)."""
def test_kimi_coding_with_explicit_disabled_omits_thinking(self) -> None:
from agent.anthropic_adapter import build_anthropic_kwargs
kwargs = build_anthropic_kwargs(
model="kimi-k2.5",
messages=[{"role": "user", "content": "hello"}],
tools=None,
max_tokens=4096,
reasoning_config={"enabled": False},
base_url="https://api.kimi.com/coding",
)
assert "thinking" not in kwargs
def test_non_kimi_third_party_still_gets_thinking(self) -> None:
"""MiniMax and other third-party Anthropic endpoints must retain thinking."""
from agent.anthropic_adapter import build_anthropic_kwargs
kwargs = build_anthropic_kwargs(
model="MiniMax-M2.7",
messages=[{"role": "user", "content": "hello"}],
tools=None,
max_tokens=4096,
reasoning_config={"enabled": True, "effort": "medium"},
base_url="https://api.minimax.io/anthropic",
)
assert "thinking" in kwargs
assert kwargs["thinking"]["type"] == "enabled"
def test_native_anthropic_still_gets_thinking(self) -> None:
from agent.anthropic_adapter import build_anthropic_kwargs
kwargs = build_anthropic_kwargs(
model="claude-sonnet-4-20250514",
messages=[{"role": "user", "content": "hello"}],
tools=None,
max_tokens=4096,
reasoning_config={"enabled": True, "effort": "medium"},
base_url=None,
)
assert "thinking" in kwargs
class TestKimiFamilyGetsAdaptiveThinking:
"""Kimi-family endpoints get adaptive thinking + output_config.effort."""
@pytest.mark.parametrize(
"base_url,model",
[
# Official Kimi / Moonshot hosts (all URL shapes)
("https://api.kimi.com/coding", "kimi-k2.5"),
("https://api.kimi.com/coding/v1", "kimi-k2.5"),
("https://api.kimi.com/coding/anthropic", "kimi-k2.5"),
("https://api.kimi.com/v1", "kimi-k2.5"),
("https://api.moonshot.ai/anthropic", "moonshot-v1-32k"),
("https://api.moonshot.cn/anthropic", "moonshot-v1-32k"),
("https://api.moonshot.cn/anthropic/v1", "kimi-0714-preview"),
# Custom / proxied hosts with a Kimi-family model (#17057)
("http://my-kimi-proxy.internal", "kimi-2.6"),
("https://llm.example.com/anthropic", "moonshotai/kimi-k2.5"),
],
)
def test_kimi_family_endpoint_gets_adaptive_thinking(
self, base_url: str, model: str
) -> None:
from agent.anthropic_adapter import build_anthropic_kwargs
kwargs = build_anthropic_kwargs(
model=model,
messages=[{"role": "user", "content": "hello"}],
tools=None,
max_tokens=4096,
reasoning_config={"enabled": True, "effort": "high"},
base_url=base_url,
)
assert kwargs.get("thinking", {}).get("type") == "adaptive", (
f"Kimi-family endpoint ({base_url}, {model}) must receive "
f"adaptive thinking, got {kwargs.get('thinking')!r}"
)
assert "budget_tokens" not in kwargs["thinking"]
assert kwargs["output_config"] == {"effort": "high"}
# Adaptive mode must not force temperature or inflate max_tokens
# (those are manual-budget-mode side effects).
assert "temperature" not in kwargs
assert kwargs["max_tokens"] == 4096
def test_kimi_family_replay_preserves_unsigned_thinking(self) -> None:
"""On a custom Kimi endpoint, unsigned reasoning_content thinking
blocks must survive the third-party signature-stripping pass so
the upstream's message-history validation passes.
"""
from agent.anthropic_adapter import convert_messages_to_anthropic
messages = [
{"role": "user", "content": "hi"},
{
"role": "assistant",
"reasoning_content": "planning the tool call",
"tool_calls": [
{
"id": "call_1",
"type": "function",
"function": {"name": "skill_view", "arguments": "{}"},
}
],
},
{"role": "tool", "tool_call_id": "call_1", "content": "ok"},
]
_, converted = convert_messages_to_anthropic(
messages,
base_url="http://my-kimi-proxy.internal",
model="kimi-2.6",
)
# The assistant message still carries the unsigned thinking block
# synthesised from reasoning_content (required by Kimi's history
# validation). A plain third-party endpoint would have stripped it.
assistant_msg = next(m for m in converted if m["role"] == "assistant")
assistant_blocks = assistant_msg["content"]
thinking_blocks = [
b for b in assistant_blocks
if isinstance(b, dict) and b.get("type") == "thinking"
]
assert len(thinking_blocks) == 1
assert thinking_blocks[0]["thinking"] == "planning the tool call"