mirror of
https://github.com/NCBM/plyngent.git
synced 2026-11-07 23:47:54 +08:00
cli/history: never collapse a turn's ends
The mixed policy previewed the user row, so a long prompt came back truncated even though it is half of what the turn is about. Both ends - the human's message and the model's answer - now print in full at every verbosity level; --preview remains the explicit one-line skimmer that collapses them too. Tests cover the ends staying full in the default and -v views (and the rounds between them staying one-liners) plus a long-prompt regression.
This commit is contained in:
@@ -1440,8 +1440,9 @@ def history_cmd(
|
|||||||
``/history N`` — turn N (numbered from the first turn of this session)
|
``/history N`` — turn N (numbered from the first turn of this session)
|
||||||
``/history last [N]`` — the last N turns (default 1)
|
``/history last [N]`` — the last N turns (default 1)
|
||||||
``/history --message N`` — one message (display number, not a DB row)
|
``/history --message N`` — one message (display number, not a DB row)
|
||||||
``-v`` adds every intermediate row (tool calls/results, notices); ``-vv``
|
The turn's ends are printed in full; ``-v`` adds every intermediate row (tool
|
||||||
prints full bodies for all of them; ``--preview`` collapses to one line each.
|
calls/results, notices) as a one-liner, ``-vv`` prints those in full too, and
|
||||||
|
``--preview`` collapses every row (ends included) to one line.
|
||||||
"""
|
"""
|
||||||
from plyngent.cli import transcript as transcript_mod
|
from plyngent.cli import transcript as transcript_mod
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,9 @@ PREVIEW_CHARS = 200
|
|||||||
# ``-v`` expands a turn's rows; ``-vv`` also prints their full bodies.
|
# ``-v`` expands a turn's rows; ``-vv`` also prints their full bodies.
|
||||||
MAX_VERBOSE = 2
|
MAX_VERBOSE = 2
|
||||||
|
|
||||||
# Detail policy: ``mixed`` = a turn's final assistant answer in full, everything
|
# Detail policy: ``mixed`` = a turn's two ends (the user message and the final
|
||||||
# else on one line; ``full``/``preview`` force the extremes.
|
# answer) in full, the rounds between them on one line; ``full``/``preview``
|
||||||
|
# force the extremes (``--preview`` also collapses the ends).
|
||||||
type Detail = Literal["mixed", "full", "preview"]
|
type Detail = Literal["mixed", "full", "preview"]
|
||||||
# ``/history N`` addresses a turn by number; ``last`` counts from the end.
|
# ``/history N`` addresses a turn by number; ``last`` counts from the end.
|
||||||
type TurnTarget = int | Literal["last"]
|
type TurnTarget = int | Literal["last"]
|
||||||
@@ -281,9 +282,16 @@ def echo_turn(turn: Turn, *, verbose: bool, detail: Detail) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def _row_detail(row: Row, *, turn: Turn, detail: Detail) -> Detail:
|
def _row_detail(row: Row, *, turn: Turn, detail: Detail) -> Detail:
|
||||||
"""Effective detail for *row*: mixed prints the turn's answer in full."""
|
"""Effective detail for *row*.
|
||||||
|
|
||||||
|
Under ``mixed`` the two ends of a turn — the human's message and the model's
|
||||||
|
answer — are the rows a reader actually wants, so they are never collapsed;
|
||||||
|
the rounds between them stay one-liners.
|
||||||
|
"""
|
||||||
if detail != "mixed":
|
if detail != "mixed":
|
||||||
return detail
|
return detail
|
||||||
|
if row is turn.user:
|
||||||
|
return "full"
|
||||||
return "full" if row is turn.last and isinstance(row.message, AssistantChatMessage) else "preview"
|
return "full" if row is turn.last and isinstance(row.message, AssistantChatMessage) else "preview"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,8 @@ async def test_default_shows_the_last_turns_two_ends(
|
|||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "turns=2 messages=11 showing=last 1 turn(s) mode=mixed" in out
|
assert "turns=2 messages=11 showing=last 1 turn(s) mode=mixed" in out
|
||||||
assert "turn 2 (msgs 8-11, rounds=1)" in out
|
assert "turn 2 (msgs 8-11, rounds=1)" in out
|
||||||
assert "8. user: now run the tests" in out
|
# Both ends are printed in full, not as one-line previews.
|
||||||
# The answer is printed in full; the round's tool rows are not.
|
assert "8. user:\nnow run the tests" in out
|
||||||
assert "11. assistant:" in out
|
assert "11. assistant:" in out
|
||||||
assert "Tests pass: **12 passed** in 3.2s." in out
|
assert "Tests pass: **12 passed** in 3.2s." in out
|
||||||
assert "tool(c3)" not in out
|
assert "tool(c3)" not in out
|
||||||
@@ -112,13 +112,16 @@ async def test_verbose_expands_every_row_of_the_turn(
|
|||||||
assert await handle_slash(state, "/history -v 1") is True
|
assert await handle_slash(state, "/history -v 1") is True
|
||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "turn 1 (msgs 1-7, rounds=2)" in out
|
assert "turn 1 (msgs 1-7, rounds=2)" in out
|
||||||
|
# The ends stay full (the human's own words, the model's answer) …
|
||||||
|
assert "1. user:\nread src/a.py and fix the typo" in out
|
||||||
|
assert "7. assistant:" in out
|
||||||
|
assert "- line 12 changed" in out
|
||||||
|
# … while the rounds in between are one-liners.
|
||||||
assert "2. assistant: tool_calls=[read_file]" in out
|
assert "2. assistant: tool_calls=[read_file]" in out
|
||||||
assert "3. tool(c1): L1-30 (+26 lines)" in out
|
assert "3. tool(c1): L1-30 (+26 lines)" in out
|
||||||
assert "4. developer: [DIRECTIVE CHECKPOINT band=1" in out
|
assert "4. developer: [DIRECTIVE CHECKPOINT band=1" in out
|
||||||
assert "6. tool(c2): replaced 1 occurrence" in out
|
assert "6. tool(c2): replaced 1 occurrence" in out
|
||||||
# The turn's final answer stays full even though the row detail is mixed.
|
assert "print('bye')" not in out
|
||||||
assert "7. assistant:" in out
|
|
||||||
assert "- line 12 changed" in out
|
|
||||||
|
|
||||||
|
|
||||||
async def test_double_verbose_prints_full_bodies(
|
async def test_double_verbose_prints_full_bodies(
|
||||||
@@ -146,7 +149,21 @@ async def test_verbose_keeps_local_rows_out_of_the_way(
|
|||||||
out = capsys.readouterr().out
|
out = capsys.readouterr().out
|
||||||
assert "local. system:" not in out
|
assert "local. system:" not in out
|
||||||
# Numbering is over conversation rows only, so the user row is #1.
|
# Numbering is over conversation rows only, so the user row is #1.
|
||||||
assert "1. user: read src/a.py and fix the typo" in out
|
assert "1. user:\nread src/a.py and fix the typo" in out
|
||||||
|
|
||||||
|
|
||||||
|
async def test_long_user_message_is_not_collapsed(
|
||||||
|
state: ReplState,
|
||||||
|
capsys: pytest.CaptureFixture[str],
|
||||||
|
) -> None:
|
||||||
|
"""Regression: both ends of a turn print in full, however long they are."""
|
||||||
|
body = "please review this: " + "x" * 400
|
||||||
|
state.agent.messages = [UserChatMessage(content=body), AssistantChatMessage(content="ok")]
|
||||||
|
assert await handle_slash(state, "/history") is True
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert body in out
|
||||||
|
assert "1. user:\n" in out
|
||||||
|
assert "…" not in out.split("2. assistant:")[0]
|
||||||
|
|
||||||
|
|
||||||
async def test_preview_collapses_every_row(
|
async def test_preview_collapses_every_row(
|
||||||
|
|||||||
Reference in New Issue
Block a user