Skip to content

Commit 6d0f187

Browse files
committed
Render chat responses as Markdown using rich Live display
Use rich's Live + Markdown to progressively render LLM responses in the chat REPL with proper formatting (headings, bold, code blocks, lists, etc.) instead of raw text output. Falls back to plain text when --no-color is set. Tool call lines still use prompt_toolkit styled output.
1 parent 5c103c4 commit 6d0f187

2 files changed

Lines changed: 43 additions & 11 deletions

File tree

openkb/agent/chat.py

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,10 @@ def _make_prompt_session(session: ChatSession, style: Style, use_color: bool) ->
189189
)
190190

191191

192-
async def _run_turn(agent: Any, session: ChatSession, user_input: str, style: Style) -> None:
192+
async def _run_turn(
193+
agent: Any, session: ChatSession, user_input: str, style: Style,
194+
*, use_color: bool = True,
195+
) -> None:
193196
"""Run one agent turn with streaming output and persist the new history."""
194197
from agents import (
195198
RawResponsesStreamEvent,
@@ -202,39 +205,67 @@ async def _run_turn(agent: Any, session: ChatSession, user_input: str, style: St
202205

203206
result = Runner.run_streamed(agent, new_input, max_turns=MAX_TURNS)
204207

205-
sys.stdout.write("\n")
206-
sys.stdout.flush()
208+
print()
207209
collected: list[str] = []
208210
last_was_text = False
209211
need_blank_before_text = False
212+
213+
if use_color:
214+
from rich.console import Console
215+
from rich.live import Live
216+
from rich.markdown import Markdown
217+
218+
console = Console()
219+
live = Live(console=console, vertical_overflow="visible")
220+
live.start()
221+
else:
222+
live = None
223+
210224
try:
211225
async for event in result.stream_events():
212226
if isinstance(event, RawResponsesStreamEvent):
213227
if isinstance(event.data, ResponseTextDeltaEvent):
214228
text = event.data.delta
215229
if text:
216230
if need_blank_before_text:
217-
sys.stdout.write("\n")
231+
if live:
232+
live.stop()
233+
print()
234+
live.start()
235+
else:
236+
sys.stdout.write("\n")
218237
need_blank_before_text = False
219-
sys.stdout.write(text)
220-
sys.stdout.flush()
221238
collected.append(text)
222239
last_was_text = True
240+
if live:
241+
live.update(Markdown("".join(collected)))
242+
else:
243+
sys.stdout.write(text)
244+
sys.stdout.flush()
223245
elif isinstance(event, RunItemStreamEvent):
224246
item = event.item
225247
if item.type == "tool_call_item":
226248
if last_was_text:
227-
sys.stdout.write("\n")
228-
sys.stdout.flush()
249+
if live:
250+
live.stop()
251+
live.start()
252+
else:
253+
sys.stdout.write("\n")
254+
sys.stdout.flush()
229255
last_was_text = False
230256
raw = item.raw_item
231257
name = getattr(raw, "name", "?")
232258
args = getattr(raw, "arguments", "") or ""
259+
if live:
260+
live.stop()
233261
_fmt(style, ("class:tool", _format_tool_line(name, args) + "\n"))
262+
if live:
263+
live.start()
234264
need_blank_before_text = True
235265
finally:
236-
sys.stdout.write("\n\n")
237-
sys.stdout.flush()
266+
if live:
267+
live.stop()
268+
print("\n")
238269

239270
answer = "".join(collected).strip()
240271
if not answer:
@@ -371,7 +402,7 @@ async def run_chat(
371402

372403
append_log(kb_dir / "wiki", "query", user_input)
373404
try:
374-
await _run_turn(agent, session, user_input, style)
405+
await _run_turn(agent, session, user_input, style, use_color=use_color)
375406
except KeyboardInterrupt:
376407
_fmt(style, ("class:error", "\n[aborted]\n"))
377408
except Exception as exc:

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ dependencies = [
3636
"python-dotenv",
3737
"json-repair",
3838
"prompt_toolkit>=3.0",
39+
"rich>=13.0",
3940
]
4041

4142
[project.urls]

0 commit comments

Comments
 (0)