From 9b365403d7f3a9ddee90359c0233aee1cb401503 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Tue, 22 Sep 2026 23:05:59 +0800 Subject: [PATCH] cli/history: print a row's reasoning before its answer A reasoning trace was appended after the body it produced, which read as if the answer had come first and the thinking had followed it. It now leads the row, under its own `reasoning:` label with a blank line before the answer - the order the live stream uses. The trace only prints where it already did: a turn's ends (the default view) and -vv; previews stay silent. --- src/plyngent/cli/transcript.py | 23 +++++++++++++++----- tests/test_cli/test_history.py | 38 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/plyngent/cli/transcript.py b/src/plyngent/cli/transcript.py index be6acdc..b60dd00 100644 --- a/src/plyngent/cli/transcript.py +++ b/src/plyngent/cli/transcript.py @@ -243,23 +243,36 @@ def echo_row(row: Row, *, detail: Detail) -> None: click.echo() +def _echo_reasoning(message: AssistantChatMessage) -> None: + """Print the model's reasoning trace for *message*, when it has one.""" + shown = message.reasoning_content + if not isinstance(shown, str) or not shown.strip(): + return + click.secho("reasoning:", fg="bright_black") + click.echo(shown) + click.echo() + + def _echo_assistant(row: Row, *, reasoning: bool) -> None: + """Print an assistant row: the thinking first, then the answer it produced. + + A transcript reads in the order the model produced it, and the live stream + prints the reasoning before the answer; printing it after the body made the + answer look like it had come before the thinking behind it. + """ from plyngent.cli.display import markdown_render_available, print_markdown message = row.message assert isinstance(message, AssistantChatMessage) click.echo(row.prefix()) + if reasoning: + _echo_reasoning(message) content = message.content if isinstance(content, str) and content.strip(): if markdown_render_available(): print_markdown(content, label="") else: click.echo(content) - if reasoning: - shown = message.reasoning_content - if isinstance(shown, str) and shown.strip(): - click.secho("reasoning:", fg="bright_black") - click.echo(shown) names = _tool_call_names(message) if names: click.secho(f" tool_calls=[{', '.join(names)}]", fg="yellow") diff --git a/tests/test_cli/test_history.py b/tests/test_cli/test_history.py index 68aeb79..081f50e 100644 --- a/tests/test_cli/test_history.py +++ b/tests/test_cli/test_history.py @@ -140,6 +140,44 @@ async def test_double_verbose_prints_full_bodies( assert "You are plyngent." in out +async def test_reasoning_prints_before_the_answer( + state: ReplState, + capsys: pytest.CaptureFixture[str], +) -> None: + """A reasoning trace leads the answer it produced, as it does live.""" + state.agent.messages = [ + UserChatMessage(content="why is the sky blue?"), + AssistantChatMessage(content="Rayleigh scattering.", reasoning_content="Light scatters off air."), + ] + assert await handle_slash(state, "/history") is True + out = capsys.readouterr().out + assert "2. assistant:" in out + assert "reasoning:\nLight scatters off air." in out + assert out.index("reasoning:") < out.index("Rayleigh scattering.") + assert out.index("2. assistant:") < out.index("reasoning:") + + +async def test_reasoning_is_absent_without_one( + state: ReplState, + capsys: pytest.CaptureFixture[str], +) -> None: + state.agent.messages = [UserChatMessage(content="hi"), AssistantChatMessage(content="hello")] + assert await handle_slash(state, "/history -vv") is True + assert "reasoning:" not in capsys.readouterr().out + + +async def test_preview_never_prints_reasoning( + state: ReplState, + capsys: pytest.CaptureFixture[str], +) -> None: + state.agent.messages = [ + UserChatMessage(content="why?"), + AssistantChatMessage(content="Because.", reasoning_content="Because the docs say so."), + ] + assert await handle_slash(state, "/history --preview") is True + assert "Because the docs say so." not in capsys.readouterr().out + + async def test_verbose_keeps_local_rows_out_of_the_way( state: ReplState, capsys: pytest.CaptureFixture[str],