fix: apply assistant-prefill guard to Anthropic models in interactive mode (fixes #416)

When interactive mode is enabled (TUI), the guard that prevents sending a
trailing assistant message to the Anthropic API was incorrectly skipped. The
Anthropic API rejects such requests with
"This model does not support assistant message prefill." regardless of
whether the caller is in interactive or non-interactive mode.

Updated the condition to also apply the guard when `_is_anthropic()` is
true, so Anthropic models are always protected while non-Anthropic models
that may support assistant prefill continue to be unaffected in interactive
mode.

Added three unit tests covering all three branches of the new condition.

Co-Authored-By: Octopus <liyuan851277048@icloud.com>
This commit is contained in:
octo-patch 2026-04-28 12:34:24 +08:00
parent 8e25743c44
commit da472ca0d5
2 changed files with 53 additions and 1 deletions

View file

@ -239,7 +239,9 @@ class LLM:
conversation_history.extend(compressed)
messages.extend(compressed)
if messages[-1].get("role") == "assistant" and not self.config.interactive:
if messages[-1].get("role") == "assistant" and (
not self.config.interactive or self._is_anthropic()
):
messages.append({"role": "user", "content": "<meta>Continue the task.</meta>"})
if self._is_anthropic() and self.config.enable_prompt_caching:

View file

@ -0,0 +1,50 @@
"""Tests for LLM._prepare_messages trailing-assistant-message handling."""
from strix.llm.config import LLMConfig
from strix.llm.llm import LLM
def _make_llm(monkeypatch, model_name: str, interactive: bool) -> LLM:
monkeypatch.setenv("STRIX_LLM", model_name)
config = LLMConfig(model_name=model_name, interactive=interactive, enable_prompt_caching=False)
return LLM(config, agent_name=None)
def _history_ending_with_assistant() -> list[dict]:
return [
{"role": "user", "content": "Scan this target."},
{"role": "assistant", "content": "I found a vulnerability."},
]
def test_non_interactive_anthropic_adds_user_message(monkeypatch) -> None:
"""Non-interactive mode always appends a user message when history ends with assistant."""
llm = _make_llm(monkeypatch, "claude-sonnet-4-6", interactive=False)
history = _history_ending_with_assistant()
messages = llm._prepare_messages(history)
assert messages[-1]["role"] == "user"
assert messages[-1]["content"] == "<meta>Continue the task.</meta>"
def test_interactive_anthropic_adds_user_message(monkeypatch) -> None:
"""Interactive mode with Anthropic model must also append a user message.
Anthropic API rejects messages where the last entry has role 'assistant'
(no assistant prefill support). This should hold regardless of interactive mode.
"""
llm = _make_llm(monkeypatch, "claude-sonnet-4-6", interactive=True)
history = _history_ending_with_assistant()
messages = llm._prepare_messages(history)
assert messages[-1]["role"] == "user"
assert messages[-1]["content"] == "<meta>Continue the task.</meta>"
def test_interactive_non_anthropic_does_not_add_user_message(monkeypatch) -> None:
"""Interactive mode with a non-Anthropic model keeps the trailing assistant message.
Non-Anthropic models may support assistant prefill; in interactive mode the
caller (TUI) is responsible for appending the next user message.
"""
llm = _make_llm(monkeypatch, "openai/gpt-5.4", interactive=True)
history = _history_ending_with_assistant()
messages = llm._prepare_messages(history)
assert messages[-1]["role"] == "assistant"