Files
hermes-agent/tests/agent/test_side_question.py
T
Teknium 74a95a3ddf feat: /btw now answers side questions with conversation context; /background renamed to /bg
/bg (formerly /background, which is retired) keeps the existing semantics:
spawn a fresh, independent agent session in the background.

/btw is now its own command matching the convention other harnesses use:
ask a quick side question ABOUT the current conversation without
interrupting it. A one-shot auxiliary LLM call (main model by default,
overridable via auxiliary.side_question.* in config.yaml) answers from a
read-only transcript snapshot — the live session's history, role
alternation, and prompt cache are untouched, and the current turn keeps
running.

Surfaces wired: CLI (inline mid-run dispatch), gateway (all messengers,
busy-dispatch table + idle dispatch, i18n across all 17 locales), TUI
(prompt.btw RPC + btw.complete event), Discord native slash, relay
command manifest, desktop exec routing, docs (EN + zh-Hans).
2026-08-29 07:25:17 -07:00

93 lines
3.6 KiB
Python

"""Tests for agent/side_question.py — the /btw context-aware side question engine."""
from unittest.mock import patch
from agent.side_question import (
SIDE_QUESTION_TASK,
answer_side_question,
render_history_for_side_question,
)
class TestRenderHistory:
def test_empty_history(self):
assert render_history_for_side_question([]) == "(no prior conversation)"
assert render_history_for_side_question(None) == "(no prior conversation)"
def test_basic_roles(self):
history = [
{"role": "system", "content": "SYSTEM PROMPT — must not appear"},
{"role": "user", "content": "fix the bug in foo.py"},
{
"role": "assistant",
"content": "Looking now.",
"tool_calls": [
{"function": {"name": "read_file"}},
{"function": {"name": "patch"}},
],
},
{"role": "tool", "content": "Traceback: ValueError in foo.py line 3"},
{"role": "assistant", "content": "Fixed it."},
]
out = render_history_for_side_question(history)
assert "SYSTEM PROMPT" not in out
assert "USER: fix the bug in foo.py" in out
assert "ASSISTANT [called tools: read_file, patch]" in out
assert "TOOL RESULT: Traceback: ValueError in foo.py line 3" in out
assert "ASSISTANT: Fixed it." in out
def test_structured_content_blocks(self):
history = [
{"role": "user", "content": [{"type": "text", "text": "hello there"}]},
]
out = render_history_for_side_question(history)
assert "USER: hello there" in out
def test_newest_biased_truncation(self):
history = [
{"role": "user", "content": f"message number {i} " + "x" * 400}
for i in range(200)
]
out = render_history_for_side_question(history, char_budget=3000)
# Newest messages survive; oldest are dropped with a marker.
assert "message number 199" in out
assert "message number 0 " not in out
assert out.startswith("[...older conversation omitted...]")
assert len(out) < 4000
def test_non_dict_entries_ignored(self):
out = render_history_for_side_question(["garbage", None, 42, {"role": "user", "content": "hi"}])
assert "USER: hi" in out
class TestAnswerSideQuestion:
def test_empty_question_raises(self):
try:
answer_side_question(" ", [])
except ValueError:
pass
else:
raise AssertionError("expected ValueError for empty question")
def test_calls_oneshot_with_snapshot_and_task(self):
captured = {}
def fake_run_oneshot(**kwargs):
captured.update(kwargs)
return "the error was in foo.py"
history = [{"role": "user", "content": "run the tests"}]
runtime = {"model": "m", "provider": "p", "base_url": "u", "api_key": "k", "api_mode": "chat_completions"}
with patch("agent.oneshot.run_oneshot", side_effect=fake_run_oneshot):
answer = answer_side_question(
"which file had the error?", history, main_runtime=runtime
)
assert answer == "the error was in foo.py"
assert captured["task"] == SIDE_QUESTION_TASK
assert captured["main_runtime"] is runtime
assert "USER: run the tests" in captured["user_input"]
assert "Side question: which file had the error?" in captured["user_input"]
# The instructions steer the model to answer only the side question.
assert "side" in captured["instructions"].lower()