feat(agents,backends,conversations,frontends,ui): progress thinking is told apart by its agent, raycast reasoning stays collapsed, read_conversation keeps progress
This commit is contained in:
1 parent
13873f857b
commit
120985deab
18 files changed
+374
-110
No files matched your search
@@ -1,7 +1,11 @@
|
||||
from pathlib import Path
|
||||
|
||||
from anthropic.types import Message, TextBlock, ThinkingBlock, ToolUseBlock, Usage
|
||||
|
||||
from beaver_gateway.frontends.markdown import history, parser, renderer
|
||||
from beaver_gateway.frontends.markdown.crossfront import CrossFrontendLogger
|
||||
from beaver_gateway.frontends.markdown.parser import TextSegment, ToolSegment
|
||||
from beaver_gateway.frontends.turn_record import TurnRecord
|
||||
|
||||
|
||||
def _message(*blocks: object) -> Message:
|
||||
@@ -25,9 +29,10 @@ def test_tool_calls_leave_no_blank_lines() -> None:
|
||||
ToolUseBlock(type="tool_use", id="t3", name="Grep", input={"pattern": "y"}),
|
||||
TextBlock(type="text", text="second"),
|
||||
)
|
||||
assert renderer.render_assistant_message(message) == (
|
||||
"### Assistant:\n\nfirst\n\nsecond\n"
|
||||
)
|
||||
for progress in (True, False):
|
||||
assert renderer.render_assistant_message(
|
||||
message, thinking_is_progress=progress
|
||||
) == ("### Assistant:\n\nfirst\n\nsecond\n")
|
||||
|
||||
|
||||
def _progress_turn() -> Message:
|
||||
@@ -44,38 +49,88 @@ def _progress_turn() -> Message:
|
||||
)
|
||||
|
||||
|
||||
def _reasoning_turn() -> Message:
|
||||
return _message(
|
||||
ThinkingBlock(type="thinking", thinking="one", signature=""),
|
||||
ToolUseBlock(type="tool_use", id="t1", name="web_search", input={}),
|
||||
ThinkingBlock(type="thinking", thinking="two", signature=""),
|
||||
ToolUseBlock(type="tool_use", id="t2", name="read_page", input={}),
|
||||
ThinkingBlock(type="thinking", thinking="three", signature=""),
|
||||
TextBlock(type="text", text="answer"),
|
||||
ThinkingBlock(type="thinking", thinking="four\nfive", signature=""),
|
||||
TextBlock(type="text", text="more"),
|
||||
)
|
||||
|
||||
|
||||
REASONING_TURN_AS_BEFORE = (
|
||||
"### Assistant:\n\n> [!thinking]-\n> one\n\nanswer\n\n"
|
||||
"> [!thinking]-\n> four\n> five\n\nmore\n"
|
||||
)
|
||||
|
||||
|
||||
def _full_circle(
|
||||
message: Message, *, thinking_is_progress: bool
|
||||
) -> history.ForkOutcome:
|
||||
body = renderer.render_user_text("status?") + renderer.render_assistant_message(
|
||||
message, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
turns = parser.parse(body + renderer.render_user_text("thanks")).turns
|
||||
stored = [
|
||||
{"role": "user", "content": "status?"},
|
||||
{"role": "assistant", "content": [b.model_dump() for b in message.content]},
|
||||
]
|
||||
outcome = history.diff_and_fork(
|
||||
stored=stored, incoming=turns, thinking_is_progress=thinking_is_progress
|
||||
)
|
||||
assert outcome.divergence_index is None
|
||||
assert not outcome.edited
|
||||
assert outcome.messages[1] == stored[1]
|
||||
return outcome
|
||||
|
||||
|
||||
def test_progress_renders_as_text_in_place_and_empty_thinking_vanishes() -> None:
|
||||
assert renderer.render_assistant_message(_progress_turn()) == (
|
||||
assert renderer.render_assistant_message(
|
||||
_progress_turn(), thinking_is_progress=True
|
||||
) == (
|
||||
"### Assistant:\n\nChecking the containers.\n\nAll up, reading logs.\n\n"
|
||||
"Found it.\nOne more.\n\nThe stack is healthy.\n"
|
||||
)
|
||||
|
||||
|
||||
def test_reasoning_renders_as_collapsed_callouts_as_before() -> None:
|
||||
assert (
|
||||
renderer.render_assistant_message(_reasoning_turn(), thinking_is_progress=False)
|
||||
== REASONING_TURN_AS_BEFORE
|
||||
)
|
||||
|
||||
|
||||
def test_progress_round_trips_through_the_parser_and_the_stored_history() -> None:
|
||||
message = _progress_turn()
|
||||
body = renderer.render_user_text("status?") + renderer.render_assistant_message(
|
||||
message
|
||||
)
|
||||
turns = parser.parse(body + renderer.render_user_text("thanks")).turns
|
||||
assert turns[1].structure == (
|
||||
body = renderer.render_assistant_message(message, thinking_is_progress=True)
|
||||
assert parser.parse_assistant_structure(body.removeprefix("### Assistant:")) == [
|
||||
TextSegment(
|
||||
text="Checking the containers.\n\nAll up, reading logs.\n\n"
|
||||
"Found it.\nOne more.\n\nThe stack is healthy."
|
||||
),
|
||||
)
|
||||
stored = [
|
||||
{"role": "user", "content": "status?"},
|
||||
{"role": "assistant", "content": [b.model_dump() for b in message.content]},
|
||||
)
|
||||
]
|
||||
outcome = history.diff_and_fork(stored=stored, incoming=turns)
|
||||
assert outcome.divergence_index is None
|
||||
assert not outcome.edited
|
||||
assert outcome.messages[1] == stored[1]
|
||||
_full_circle(message, thinking_is_progress=True)
|
||||
|
||||
|
||||
def test_reasoning_round_trips_through_the_parser_and_the_stored_history() -> None:
|
||||
message = _reasoning_turn()
|
||||
body = renderer.render_assistant_message(message, thinking_is_progress=False)
|
||||
assert parser.parse_assistant_structure(body.removeprefix("### Assistant:")) == [
|
||||
TextSegment(text="answer"),
|
||||
TextSegment(text="more"),
|
||||
]
|
||||
_full_circle(message, thinking_is_progress=False)
|
||||
|
||||
|
||||
def test_an_edited_progress_turn_rebuilds_instead_of_rewriting_signed_blocks() -> None:
|
||||
message = _progress_turn()
|
||||
body = renderer.render_assistant_message(message).replace("Found it", "Fixed it")
|
||||
body = renderer.render_assistant_message(
|
||||
message, thinking_is_progress=True
|
||||
).replace("Found it", "Fixed it")
|
||||
turns = parser.parse(
|
||||
renderer.render_user_text("status?") + body + renderer.render_user_text("ok")
|
||||
).turns
|
||||
@@ -83,7 +138,9 @@ def test_an_edited_progress_turn_rebuilds_instead_of_rewriting_signed_blocks() -
|
||||
{"role": "user", "content": "status?"},
|
||||
{"role": "assistant", "content": [b.model_dump() for b in message.content]},
|
||||
]
|
||||
outcome = history.diff_and_fork(stored=stored, incoming=turns)
|
||||
outcome = history.diff_and_fork(
|
||||
stored=stored, incoming=turns, thinking_is_progress=True
|
||||
)
|
||||
assert outcome.edited
|
||||
assert outcome.messages[1] == {
|
||||
"role": "assistant",
|
||||
@@ -91,6 +148,22 @@ def test_an_edited_progress_turn_rebuilds_instead_of_rewriting_signed_blocks() -
|
||||
}
|
||||
|
||||
|
||||
async def test_a_raycast_archive_with_reasoning_renders_as_before(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
logger = CrossFrontendLogger(vault_path=tmp_path, logged_subdir="logged")
|
||||
record = TurnRecord(
|
||||
agent_name="beaver-gemini-pro-high",
|
||||
input_messages=[{"role": "user", "content": "q"}],
|
||||
output_message=_reasoning_turn(),
|
||||
thinking_is_progress=False,
|
||||
)
|
||||
await logger.handle(record)
|
||||
[written] = (tmp_path / "logged" / "beaver-gemini-pro-high").glob("*.md")
|
||||
body = parser.parse(written.read_text()).body
|
||||
assert f"---\n\n{REASONING_TURN_AS_BEFORE}\n---\n\n### User:" in body
|
||||
|
||||
|
||||
def test_a_file_with_collapsed_thinking_callouts_parses_as_before() -> None:
|
||||
body = (
|
||||
"### User:\n\nq\n\n---\n\n### Assistant:\n\n"
|
||||
|
||||
Reference in new issue
Block a user