feat(gateway): inject stable human-readable message timestamps
Consolidates these related Amy fork patches: - 429830f39 feat(gateway): inject message timestamps into user messages for LLM context - 3c3d6fac0 fix: handle both ISO string and epoch float timestamps in history replay - 2874f7725 feat: human-friendly timestamp format with weekday and timezone name - 3735f4c8b fix: render gateway message timestamps once
This commit is contained in:
committed by
Teknium
parent
b7f0c9cd52
commit
bd7fc8fdcd
+89
-7
@@ -706,6 +706,12 @@ def _build_gateway_agent_history(
|
||||
the current message behind ``history_offset`` during persistence.
|
||||
"""
|
||||
|
||||
from hermes_time import get_timezone as _get_msg_tz
|
||||
from gateway.message_timestamps import (
|
||||
render_user_content_with_timestamp as _render_msg_ts,
|
||||
)
|
||||
|
||||
_msg_tz = _get_msg_tz()
|
||||
agent_history: List[Dict[str, Any]] = []
|
||||
observed_group_context: List[str] = []
|
||||
separate_observed_context = _uses_telegram_observed_group_context(channel_prompt)
|
||||
@@ -725,6 +731,8 @@ def _build_gateway_agent_history(
|
||||
continue
|
||||
|
||||
content = msg.get("content")
|
||||
if role == "user" and isinstance(content, str):
|
||||
content = _render_msg_ts(content, msg.get("timestamp"), tz=_msg_tz)
|
||||
if separate_observed_context and msg.get("observed") and role == "user" and content:
|
||||
observed_group_context.append(str(content).strip())
|
||||
continue
|
||||
@@ -8378,6 +8386,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
|
||||
# Read privacy.redact_pii from config (re-read per message)
|
||||
_redact_pii = False
|
||||
persist_user_message = None
|
||||
persist_user_timestamp = None
|
||||
try:
|
||||
_pcfg = _load_gateway_config()
|
||||
_redact_pii = bool((_pcfg.get("privacy") or {}).get("redact_pii", False))
|
||||
@@ -8902,6 +8912,34 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
if message_text is None:
|
||||
return
|
||||
|
||||
# Inject message timestamp so the LLM sees when this message was sent.
|
||||
# Keep the persisted transcript clean: timestamps are stored as
|
||||
# metadata and rendered into context exactly once on replay.
|
||||
try:
|
||||
from hermes_time import get_timezone as _get_evt_tz
|
||||
from gateway.message_timestamps import (
|
||||
coerce_message_timestamp as _coerce_msg_ts,
|
||||
render_user_content_with_timestamp as _render_msg_ts,
|
||||
strip_leading_message_timestamps as _strip_msg_ts,
|
||||
)
|
||||
_evt_tz = _get_evt_tz()
|
||||
_evt_ts = getattr(event, "timestamp", None)
|
||||
if message_text and isinstance(message_text, str):
|
||||
_clean_message_text, _embedded_ts = _strip_msg_ts(
|
||||
message_text, tz=_evt_tz)
|
||||
persist_user_message = _clean_message_text
|
||||
_event_epoch = _coerce_msg_ts(_evt_ts, tz=_evt_tz)
|
||||
persist_user_timestamp = (
|
||||
_event_epoch if _event_epoch is not None else _embedded_ts
|
||||
)
|
||||
message_text = _render_msg_ts(
|
||||
_clean_message_text,
|
||||
persist_user_timestamp,
|
||||
tz=_evt_tz,
|
||||
)
|
||||
except Exception as _ts_err:
|
||||
logger.debug("Message timestamp injection failed (non-fatal): %s", _ts_err)
|
||||
|
||||
# Bind this gateway run generation to the adapter's active-session
|
||||
# event so deferred post-delivery callbacks can be released by the
|
||||
# same run that registered them.
|
||||
@@ -8935,6 +8973,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
run_generation=run_generation,
|
||||
event_message_id=self._reply_anchor_for_event(event),
|
||||
channel_prompt=event.channel_prompt,
|
||||
persist_user_message=persist_user_message,
|
||||
persist_user_timestamp=persist_user_timestamp,
|
||||
)
|
||||
|
||||
# Stop persistent typing indicator now that the agent is done
|
||||
@@ -9226,7 +9266,7 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
"Your next message will start a fresh session."
|
||||
)
|
||||
|
||||
ts = datetime.now().isoformat()
|
||||
ts = time.time() # Unix epoch float — consistent with DB storage
|
||||
|
||||
# If this is a fresh session (no history), write the full tool
|
||||
# definitions as the first entry so the transcript is self-describing
|
||||
@@ -9262,7 +9302,19 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
# message so the next message can load a transcript that
|
||||
# reflects what was said. Skip the assistant error text since
|
||||
# it's a gateway-generated hint, not model output. (#7100)
|
||||
_user_entry = {"role": "user", "content": message_text, "timestamp": ts}
|
||||
_user_entry = {
|
||||
"role": "user",
|
||||
"content": (
|
||||
persist_user_message
|
||||
if persist_user_message is not None
|
||||
else message_text
|
||||
),
|
||||
"timestamp": (
|
||||
persist_user_timestamp
|
||||
if persist_user_timestamp is not None
|
||||
else ts
|
||||
),
|
||||
}
|
||||
if event.message_id:
|
||||
_user_entry["message_id"] = str(event.message_id)
|
||||
self.session_store.append_to_transcript(
|
||||
@@ -9276,7 +9328,19 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
|
||||
# If no new messages found (edge case), fall back to simple user/assistant
|
||||
if not new_messages:
|
||||
_user_entry = {"role": "user", "content": message_text, "timestamp": ts}
|
||||
_user_entry = {
|
||||
"role": "user",
|
||||
"content": (
|
||||
persist_user_message
|
||||
if persist_user_message is not None
|
||||
else message_text
|
||||
),
|
||||
"timestamp": (
|
||||
persist_user_timestamp
|
||||
if persist_user_timestamp is not None
|
||||
else ts
|
||||
),
|
||||
}
|
||||
if event.message_id:
|
||||
_user_entry["message_id"] = str(event.message_id)
|
||||
self.session_store.append_to_transcript(
|
||||
@@ -9401,13 +9465,26 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
_recent_transcript = []
|
||||
for _msg in reversed(_recent_transcript[-10:]):
|
||||
if _msg.get("role") == "user":
|
||||
_already_persisted = (_msg.get("content") == message_text)
|
||||
_expected_user_content = (
|
||||
persist_user_message
|
||||
if persist_user_message is not None
|
||||
else message_text
|
||||
)
|
||||
_already_persisted = (_msg.get("content") == _expected_user_content)
|
||||
break
|
||||
if not _already_persisted:
|
||||
_user_entry = {
|
||||
"role": "user",
|
||||
"content": message_text,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"content": (
|
||||
persist_user_message
|
||||
if persist_user_message is not None
|
||||
else message_text
|
||||
),
|
||||
"timestamp": (
|
||||
persist_user_timestamp
|
||||
if persist_user_timestamp is not None
|
||||
else time.time()
|
||||
),
|
||||
}
|
||||
if getattr(event, "message_id", None):
|
||||
_user_entry["message_id"] = str(event.message_id)
|
||||
@@ -13602,6 +13679,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
_interrupt_depth: int = 0,
|
||||
event_message_id: Optional[str] = None,
|
||||
channel_prompt: Optional[str] = None,
|
||||
persist_user_message: Optional[str] = None,
|
||||
persist_user_timestamp: Optional[float] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Run the agent with the given message and context.
|
||||
@@ -15028,7 +15107,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
# Keep real user text separate from API-only recovery guidance. If
|
||||
# an auto-continue note is prepended below, persist the original
|
||||
# message so stale guidance never replays as user-authored text.
|
||||
_persist_user_message_override: Optional[Any] = None
|
||||
_persist_user_message_override: Optional[Any] = persist_user_message
|
||||
_persist_user_timestamp_override: Optional[float] = persist_user_timestamp
|
||||
|
||||
# Prepend pending model switch note so the model knows about the switch
|
||||
_pending_notes = getattr(self, '_pending_model_notes', {})
|
||||
@@ -15168,6 +15248,8 @@ class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, Gatew
|
||||
_conversation_kwargs["persist_user_message"] = _persist_user_message_override
|
||||
elif observed_group_context:
|
||||
_conversation_kwargs["persist_user_message"] = message
|
||||
if _persist_user_timestamp_override is not None:
|
||||
_conversation_kwargs["persist_user_timestamp"] = _persist_user_timestamp_override
|
||||
result = agent.run_conversation(_api_run_message, **_conversation_kwargs)
|
||||
finally:
|
||||
unregister_gateway_notify(_approval_session_key)
|
||||
|
||||
Reference in New Issue
Block a user