This commit is contained in:
Octopus 2026-05-20 18:47:51 -07:00 • committed by GitHub
commit 00550204b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 76 additions and 12 deletions

View file

@ -629,6 +629,7 @@ def main() -> None: # noqa: PLR0912, PLR0915
finally:
tracer = get_global_tracer()
if tracer:
tracer.cleanup()
posthog.end(tracer, exit_reason=exit_reason)
results_path = Path("strix_runs") / args.run_name

View file

@ -263,7 +263,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

@ -99,19 +99,30 @@ def finish_scan(
if active_agents_error:
return active_agents_error
validation_errors = []
_NOT_PROVIDED = "[Not provided by model]"
placeholder_fields = []
if not (executive_summary or "").strip():
placeholder_fields.append("executive_summary")
if not (methodology or "").strip():
placeholder_fields.append("methodology")
if not (technical_analysis or "").strip():
placeholder_fields.append("technical_analysis")
if not (recommendations or "").strip():
placeholder_fields.append("recommendations")
if not executive_summary or not executive_summary.strip():
validation_errors.append("Executive summary cannot be empty")
if not methodology or not methodology.strip():
validation_errors.append("Methodology cannot be empty")
if not technical_analysis or not technical_analysis.strip():
validation_errors.append("Technical analysis cannot be empty")
if not recommendations or not recommendations.strip():
validation_errors.append("Recommendations cannot be empty")
executive_summary = (executive_summary or "").strip() or _NOT_PROVIDED
methodology = (methodology or "").strip() or _NOT_PROVIDED
technical_analysis = (technical_analysis or "").strip() or _NOT_PROVIDED
recommendations = (recommendations or "").strip() or _NOT_PROVIDED
if validation_errors:
return {"success": False, "message": "Validation failed", "errors": validation_errors}
if placeholder_fields:
import logging
logging.warning(
"finish_scan: model omitted required field(s) %s; "
"saving partial report with placeholder text",
placeholder_fields,
)
try:
from strix.telemetry.tracer import get_global_tracer

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"