mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-19 08:02:30 +00:00
fix(cli): buffer reasoning tokens to avoid one-token-per-line display
This commit is contained in:
parent
eb0ff3ad1d
commit
b67205f5aa
@ -22,6 +22,11 @@ if sys.platform == "win32":
|
|||||||
import typer
|
import typer
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
# Buffered reasoning display: accumulate streaming tokens and flush
|
||||||
|
# on sentence/line boundaries so the user sees grouped text instead of
|
||||||
|
# one token per line. The empty string placeholder is the sentinel.
|
||||||
|
_reasoning_buf: str = ""
|
||||||
|
|
||||||
# Remove default handler and re-add with unified nanobot format
|
# Remove default handler and re-add with unified nanobot format
|
||||||
logger.remove()
|
logger.remove()
|
||||||
_log_handler_id = logger.add(
|
_log_handler_id = logger.add(
|
||||||
@ -242,10 +247,14 @@ def _print_cli_progress_line(text: str, thinking: ThinkingSpinner | None, render
|
|||||||
target.print(f" [dim]↳ {text}[/dim]")
|
target.print(f" [dim]↳ {text}[/dim]")
|
||||||
|
|
||||||
|
|
||||||
def _print_cli_reasoning(text: str, thinking: ThinkingSpinner | None, renderer: StreamRenderer | None = None) -> None:
|
def _flush_reasoning(thinking: ThinkingSpinner | None, renderer: StreamRenderer | None = None) -> None:
|
||||||
"""Print reasoning/thinking content in a distinct style."""
|
"""Flush accumulated reasoning buffer to the display."""
|
||||||
if not text.strip():
|
global _reasoning_buf
|
||||||
|
if not _reasoning_buf or not _reasoning_buf.strip():
|
||||||
|
_reasoning_buf = ""
|
||||||
return
|
return
|
||||||
|
text = _reasoning_buf.strip()
|
||||||
|
_reasoning_buf = ""
|
||||||
target = renderer.console if renderer else console
|
target = renderer.console if renderer else console
|
||||||
pause = renderer.pause_spinner() if renderer else (thinking.pause() if thinking else nullcontext())
|
pause = renderer.pause_spinner() if renderer else (thinking.pause() if thinking else nullcontext())
|
||||||
with pause:
|
with pause:
|
||||||
@ -254,6 +263,28 @@ def _print_cli_reasoning(text: str, thinking: ThinkingSpinner | None, renderer:
|
|||||||
target.print(f"[dim italic]✻ {text}[/dim italic]")
|
target.print(f"[dim italic]✻ {text}[/dim italic]")
|
||||||
|
|
||||||
|
|
||||||
|
def _print_cli_reasoning(text: str, thinking: ThinkingSpinner | None, renderer: StreamRenderer | None = None) -> None:
|
||||||
|
"""Accumulate reasoning tokens and flush on sentence / line boundaries.
|
||||||
|
|
||||||
|
Without buffering, each streaming delta (often a single token) would be
|
||||||
|
printed as a separate ``✻`` line. This version groups tokens into
|
||||||
|
natural chunks visible in the terminal.
|
||||||
|
"""
|
||||||
|
global _reasoning_buf
|
||||||
|
if not text:
|
||||||
|
return
|
||||||
|
_reasoning_buf += text
|
||||||
|
|
||||||
|
# Flush on newline, sentence-ending punctuation, or when the chunk is
|
||||||
|
# long enough to wrap meaningfully at typical terminal widths.
|
||||||
|
if (
|
||||||
|
text.endswith("\n")
|
||||||
|
or any(text.rstrip().endswith(p) for p in (".", "!", "?", "。", "!", "?"))
|
||||||
|
or len(_reasoning_buf) >= 60
|
||||||
|
):
|
||||||
|
_flush_reasoning(thinking, renderer)
|
||||||
|
|
||||||
|
|
||||||
async def _print_interactive_progress_line(text: str, thinking: ThinkingSpinner | None, renderer: StreamRenderer | None = None) -> None:
|
async def _print_interactive_progress_line(text: str, thinking: ThinkingSpinner | None, renderer: StreamRenderer | None = None) -> None:
|
||||||
"""Print an interactive progress line, pausing the spinner if needed."""
|
"""Print an interactive progress line, pausing the spinner if needed."""
|
||||||
if not text.strip():
|
if not text.strip():
|
||||||
@ -281,6 +312,11 @@ async def _maybe_print_interactive_progress(
|
|||||||
if not metadata.get("_progress"):
|
if not metadata.get("_progress"):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
# Flush reasoning buffer when the reasoning stream ends (bus path).
|
||||||
|
if metadata.get("_reasoning_end"):
|
||||||
|
_flush_reasoning(thinking, renderer)
|
||||||
|
return True
|
||||||
|
|
||||||
is_tool_hint = metadata.get("_tool_hint", False)
|
is_tool_hint = metadata.get("_tool_hint", False)
|
||||||
is_reasoning = metadata.get("_reasoning", False) or metadata.get("_reasoning_delta", False)
|
is_reasoning = metadata.get("_reasoning", False) or metadata.get("_reasoning_delta", False)
|
||||||
if is_reasoning:
|
if is_reasoning:
|
||||||
@ -1109,6 +1145,12 @@ def agent(
|
|||||||
def _make_progress(renderer: StreamRenderer | None = None):
|
def _make_progress(renderer: StreamRenderer | None = None):
|
||||||
async def _cli_progress(content: str, *, tool_hint: bool = False, reasoning: bool = False, **_kwargs: Any) -> None:
|
async def _cli_progress(content: str, *, tool_hint: bool = False, reasoning: bool = False, **_kwargs: Any) -> None:
|
||||||
ch = agent_loop.channels_config
|
ch = agent_loop.channels_config
|
||||||
|
|
||||||
|
# Flush remaining reasoning buffer when the stream ends.
|
||||||
|
if _kwargs.get("reasoning_end"):
|
||||||
|
_flush_reasoning(_thinking, renderer)
|
||||||
|
return
|
||||||
|
|
||||||
if reasoning:
|
if reasoning:
|
||||||
if ch and not ch.show_reasoning:
|
if ch and not ch.show_reasoning:
|
||||||
return
|
return
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user