feat(envelope,conversations): recall hook under the user text, user_sink for the setup's reply log
This commit is contained in:
@@ -99,6 +99,7 @@ __all__ = [
|
||||
"DistillResult",
|
||||
"ForkResult",
|
||||
"SeedContext",
|
||||
"UserSaid",
|
||||
]
|
||||
|
||||
_log = logging.getLogger("beaver_gateway.core.conversations")
|
||||
@@ -151,6 +152,17 @@ class NewDayContext:
|
||||
moved: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class UserSaid:
|
||||
"""One message from the user as it enters a turn (``origin=user``)."""
|
||||
|
||||
conversation_id: str
|
||||
kind: str
|
||||
title: str | None
|
||||
text: str
|
||||
at: datetime
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class ConversationTexts:
|
||||
"""Texts the gateway cannot invent for a setup.
|
||||
@@ -262,8 +274,10 @@ class Conversations:
|
||||
question_timeout: float = 600.0,
|
||||
envelope: Envelope | None = None,
|
||||
distiller: Distiller | None = None,
|
||||
user_sink: Callable[[UserSaid], Awaitable[None] | None] | None = None,
|
||||
) -> None:
|
||||
self._db = db
|
||||
self._user_sink = user_sink
|
||||
self._distiller = distiller
|
||||
self._agents = agents
|
||||
self._backends = backends
|
||||
@@ -1696,7 +1710,8 @@ class Conversations:
|
||||
if head.priority == "user":
|
||||
origin = "user"
|
||||
prompt = head.text
|
||||
envelope = self._envelope_for(conv)
|
||||
await self._note_user(conv, head.text)
|
||||
envelope = self._envelope_for(conv, head.text)
|
||||
if envelope:
|
||||
prompt += "\n\n" + envelope
|
||||
if len(batch) > 1:
|
||||
@@ -1734,10 +1749,37 @@ class Conversations:
|
||||
text=text,
|
||||
)
|
||||
|
||||
def _envelope_for(self, conv: Conversation) -> str | None:
|
||||
if conv.kind != "master" or self._envelope is None:
|
||||
def _envelope_for(self, conv: Conversation, text: str = "") -> str | None:
|
||||
"""Master gets the whole envelope, a branch only the recall lines (§3.3)."""
|
||||
if self._envelope is None:
|
||||
return None
|
||||
return self._envelope.build()
|
||||
if conv.kind == "master":
|
||||
return self._envelope.build(text=text, kind="master")
|
||||
if conv.kind == "branch":
|
||||
return self._envelope.recall_only(text=text, kind="branch")
|
||||
return None
|
||||
|
||||
async def _note_user(self, conv: Conversation, text: str) -> None:
|
||||
"""Hand the user's text to the setup's sink.
|
||||
|
||||
The setup keeps its own log of what Бобёр said, outside the
|
||||
transcript; a failure there never blocks the turn.
|
||||
"""
|
||||
if self._user_sink is None:
|
||||
return
|
||||
message = UserSaid(
|
||||
conversation_id=conv.external_id,
|
||||
kind=conv.kind,
|
||||
title=conv.title,
|
||||
text=text,
|
||||
at=datetime.now(UTC),
|
||||
)
|
||||
try:
|
||||
result = self._user_sink(message)
|
||||
if inspect.isawaitable(result):
|
||||
await result
|
||||
except Exception: # noqa: BLE001
|
||||
_log.exception("user sink failed for %s", conv.external_id)
|
||||
|
||||
def _observer(
|
||||
self, conv: Conversation, runner: _Runner, turn_id: str, origin: str
|
||||
|
||||
Reference in New Issue
Block a user