feat(telegram): reasoning rides under the draft status while the turn runs
This commit is contained in:
1 parent
712c24de4e
commit
0fec15fb3b
3 files changed
+72
-10
No files matched your search
@@ -9,6 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import html
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
@@ -26,6 +27,7 @@ __all__ = ["Draft"]
|
|||||||
_log = logging.getLogger("beaver_gateway.frontends.telegram.drafts")
|
_log = logging.getLogger("beaver_gateway.frontends.telegram.drafts")
|
||||||
|
|
||||||
_TAIL = 3500
|
_TAIL = 3500
|
||||||
|
_REASONING_TAIL = 400
|
||||||
_KEEPALIVE = 20.0
|
_KEEPALIVE = 20.0
|
||||||
|
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ class Draft:
|
|||||||
self._interval = interval
|
self._interval = interval
|
||||||
self.status = status
|
self.status = status
|
||||||
self.text = ""
|
self.text = ""
|
||||||
|
self.reasoning = ""
|
||||||
self._paused = False
|
self._paused = False
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
self._broken = False
|
self._broken = False
|
||||||
@@ -80,9 +83,20 @@ class Draft:
|
|||||||
self.text = self.text.rstrip() + "\n\n" + text.lstrip()
|
self.text = self.text.rstrip() + "\n\n" + text.lstrip()
|
||||||
else:
|
else:
|
||||||
self.text += text
|
self.text += text
|
||||||
|
self.reasoning = ""
|
||||||
self._paused = False
|
self._paused = False
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
|
def think(self, text: str) -> None:
|
||||||
|
if text:
|
||||||
|
self.reasoning += text
|
||||||
|
self._dirty = True
|
||||||
|
|
||||||
|
def rethink(self) -> None:
|
||||||
|
if self.reasoning:
|
||||||
|
self.reasoning = ""
|
||||||
|
self._dirty = True
|
||||||
|
|
||||||
async def stop(self) -> None:
|
async def stop(self) -> None:
|
||||||
"""Stop pushing and wait for the push in flight.
|
"""Stop pushing and wait for the push in flight.
|
||||||
|
|
||||||
@@ -146,8 +160,21 @@ class Draft:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _render(self) -> tuple[str, str]:
|
def _render(self) -> tuple[str, str]:
|
||||||
|
rendered, plain = [to_html(self.status)], [self.status]
|
||||||
|
thought = _tail(self.reasoning.strip(), _REASONING_TAIL)
|
||||||
|
if thought:
|
||||||
|
rendered.append(f"<blockquote>{html.escape(thought)}</blockquote>")
|
||||||
|
plain.append(thought)
|
||||||
tail = self.text[-_TAIL:]
|
tail = self.text[-_TAIL:]
|
||||||
if not tail.strip():
|
if tail.strip():
|
||||||
return to_html(self.status), self.status
|
rendered.append(to_html_tail(self.text, _TAIL))
|
||||||
rendered = f"{to_html(self.status)}\n\n{to_html_tail(self.text, _TAIL)}"
|
plain.append(tail)
|
||||||
return rendered, f"{self.status}\n\n{tail}"
|
return "\n\n".join(rendered), "\n\n".join(plain)
|
||||||
|
|
||||||
|
|
||||||
|
def _tail(text: str, limit: int) -> str:
|
||||||
|
if len(text) <= limit:
|
||||||
|
return text
|
||||||
|
cut = text[-limit:]
|
||||||
|
_, _, rest = cut.partition(" ")
|
||||||
|
return f"… {rest or cut}"
|
||||||
@@ -994,10 +994,13 @@ class TelegramFrontend(Frontend):
|
|||||||
draft.append(progress)
|
draft.append(progress)
|
||||||
else:
|
else:
|
||||||
draft.set_status(self.texts.thinking)
|
draft.set_status(self.texts.thinking)
|
||||||
|
draft.think(progress)
|
||||||
elif kind == "content_block_start":
|
elif kind == "content_block_start":
|
||||||
block = raw.get("content_block") or {}
|
block = raw.get("content_block") or {}
|
||||||
if block.get("type") == "tool_use":
|
if block.get("type") == "tool_use":
|
||||||
draft.set_status(self._tool_status(block.get("name") or "", None))
|
draft.set_status(self._tool_status(block.get("name") or "", None))
|
||||||
|
elif block.get("type") == "thinking":
|
||||||
|
draft.rethink()
|
||||||
|
|
||||||
def _tool_status(self, name: Any, tool_input: Any) -> str:
|
def _tool_status(self, name: Any, tool_input: Any) -> str:
|
||||||
label = status_label(str(name), tool_input, self.texts.tool_labels)
|
label = status_label(str(name), tool_input, self.texts.tool_labels)
|
||||||
|
|||||||
+38
-6
@@ -1228,18 +1228,50 @@ def test_progress_updates_reach_the_draft_but_hidden_reasoning_does_not() -> Non
|
|||||||
assert draft.status == texts.writing
|
assert draft.status == texts.writing
|
||||||
|
|
||||||
|
|
||||||
def test_reasoning_from_an_agent_whose_thinking_is_not_progress_stays_hidden() -> None:
|
def test_reasoning_rides_under_the_status_and_never_reaches_the_text() -> None:
|
||||||
draft = _progress_draft()
|
draft = _progress_draft()
|
||||||
texts = TelegramTexts()
|
texts = TelegramTexts()
|
||||||
stub = SimpleNamespace(_drafts={"k": draft}, texts=texts)
|
stub = SimpleNamespace(_drafts={"k": draft}, texts=texts)
|
||||||
TelegramFrontend._feed_draft(
|
feed = functools.partial(
|
||||||
cast("Any", stub),
|
TelegramFrontend._feed_draft, cast("Any", stub), "k", thinking_is_progress=False
|
||||||
"k",
|
|
||||||
_thinking_delta("weighing it"),
|
|
||||||
thinking_is_progress=False,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
feed(_thinking_delta("weighing <it> "))
|
||||||
|
feed(_thinking_delta("against the note"))
|
||||||
assert draft.text == ""
|
assert draft.text == ""
|
||||||
assert draft.status == texts.thinking
|
assert draft.status == texts.thinking
|
||||||
|
assert draft._render() == (
|
||||||
|
f"{texts.thinking}\n\n"
|
||||||
|
"<blockquote>weighing <it> against the note</blockquote>",
|
||||||
|
f"{texts.thinking}\n\nweighing <it> against the note",
|
||||||
|
)
|
||||||
|
|
||||||
|
feed(
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"type": "content_block_start",
|
||||||
|
"index": 2,
|
||||||
|
"content_block": {"type": "thinking"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert draft.reasoning == ""
|
||||||
|
feed(_thinking_delta("word " * 200))
|
||||||
|
shown = draft._render()[1].removeprefix(f"{texts.thinking}\n\n")
|
||||||
|
assert shown.startswith("… word")
|
||||||
|
assert len(shown) <= 402
|
||||||
|
|
||||||
|
feed(
|
||||||
|
{
|
||||||
|
"event": {
|
||||||
|
"type": "content_block_delta",
|
||||||
|
"index": 3,
|
||||||
|
"delta": {"type": "text_delta", "text": "Done."},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert draft.reasoning == ""
|
||||||
|
assert draft._render()[1] == f"{texts.writing}\n\nDone."
|
||||||
|
|
||||||
|
|
||||||
def test_a_tool_call_between_progress_updates_starts_a_new_paragraph() -> None:
|
def test_a_tool_call_between_progress_updates_starts_a_new_paragraph() -> None:
|
||||||
|
|||||||
Reference in new issue
Block a user