refactor(chat): update datetime prefixing logic for user messages and improve formatting
This commit is contained in:
@@ -350,28 +350,28 @@ export const useChatOrchestratorStore = defineStore('chat-orchestrator', () => {
|
||||
],
|
||||
})
|
||||
|
||||
// Per-message datetime injection (replaces the old `<context>` XML block):
|
||||
// every user/assistant message gets a `[YYYY-MM-DD HH:MM]` prefix
|
||||
// derived from its persisted `createdAt`. The full date appears on every
|
||||
// turn so the model can read "today" from the most recent message; the
|
||||
// system prompt itself stays 100% static for permanent KV-cache reuse.
|
||||
// Legacy entries without a persisted `createdAt` fall back to "now"
|
||||
// rather than a fabricated older timestamp.
|
||||
// Inject `[YYYY-MM-DD HH:MM]` prefix only into user messages, derived
|
||||
// from their persisted `createdAt`. Assistant messages stay clean —
|
||||
// otherwise the model learns the format and mirrors it back into its
|
||||
// own replies (e.g. `[2026-05-09 00:23] > ...`). The model still reads
|
||||
// "today" from the latest user message, and the system prompt stays
|
||||
// 100% static for permanent KV-cache reuse. Legacy entries without a
|
||||
// persisted `createdAt` fall back to "now" rather than a fabricated
|
||||
// older timestamp.
|
||||
// See `./chat/datetime-prefix.ts` for the rationale.
|
||||
const nowTs = Date.now()
|
||||
|
||||
const newMessages = sessionMessagesForSend.map((msg) => {
|
||||
const { context: _context, id: _id, createdAt, ...withoutContext } = msg
|
||||
const rawMessage = toRaw(withoutContext)
|
||||
const ts = createdAt ?? nowTs
|
||||
|
||||
if (rawMessage.role === 'user') {
|
||||
return prependTextToContent(rawMessage, formatTimePrefix(ts))
|
||||
return prependTextToContent(rawMessage, formatTimePrefix(createdAt ?? nowTs))
|
||||
}
|
||||
|
||||
if (rawMessage.role === 'assistant') {
|
||||
const { slices: _slices, tool_results: _toolResults, categorization: _categorization, ...rest } = rawMessage as ChatAssistantMessage
|
||||
return prependTextToContent(toRaw(rest), formatTimePrefix(ts))
|
||||
return toRaw(rest)
|
||||
}
|
||||
|
||||
return rawMessage
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
* invalidated KV-cache prefixes on every send).
|
||||
*
|
||||
* Strategy:
|
||||
* - Each user/assistant message is prefixed with `[YYYY-MM-DD HH:MM]` derived
|
||||
* from its persisted `createdAt`. Stored timestamps never change, so the
|
||||
* prefixed history stays byte-stable across turns and accumulates KV-cache
|
||||
* prefix matches.
|
||||
* - The full date is included on every message so the model can infer "today"
|
||||
* from the most recent message — there is no separate system-prompt date
|
||||
* anchor, which keeps the system prompt 100% static and permanently
|
||||
* cacheable across turns and across day boundaries.
|
||||
* - Only user messages are prefixed with `[YYYY-MM-DD HH:MM]` derived from
|
||||
* their persisted `createdAt`. Assistant messages stay clean — prefixing
|
||||
* them caused models to learn the format and emit `[date] > ...` in their
|
||||
* own replies.
|
||||
* - Stored timestamps never change, so the prefixed user history stays
|
||||
* byte-stable across turns and accumulates KV-cache prefix matches.
|
||||
* - The full date is included on every user message so the model can infer
|
||||
* "today" from the most recent user turn — there is no separate
|
||||
* system-prompt date anchor, which keeps the system prompt 100% static and
|
||||
* permanently cacheable across turns and across day boundaries.
|
||||
*
|
||||
* Format choice:
|
||||
* - `[YYYY-MM-DD HH:MM]` is ISO-like, structurally compact (~17 chars), and
|
||||
@@ -26,23 +28,18 @@
|
||||
* correlates with verbatim copy-back.
|
||||
*/
|
||||
|
||||
const DATE_TIME = new Intl.DateTimeFormat('en-CA', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false,
|
||||
})
|
||||
import { format } from 'date-fns'
|
||||
|
||||
/**
|
||||
* Formats a timestamp as `[YYYY-MM-DD HH:MM] ` in the user's local timezone.
|
||||
*
|
||||
* Use when:
|
||||
* - Annotating user/assistant messages so the model has a concrete time
|
||||
* anchor on every turn — historic and current alike use the same shape so
|
||||
* that prefix-cache stays valid when a "current" turn becomes "historic" on
|
||||
* - Annotating user messages so the model has a concrete time anchor on
|
||||
* every turn — historic and current user turns use the same shape so that
|
||||
* prefix-cache stays valid when a "current" turn becomes "historic" on
|
||||
* the next send.
|
||||
* - Not used for assistant messages — that caused the model to mirror the
|
||||
* prefix into its own output.
|
||||
*
|
||||
* Returns:
|
||||
* - String including a trailing space, e.g. `"[2026-04-25 18:47] "`.
|
||||
@@ -54,8 +51,5 @@ const DATE_TIME = new Intl.DateTimeFormat('en-CA', {
|
||||
* - "[2026-04-25 18:47] "
|
||||
*/
|
||||
export function formatTimePrefix(createdAt: number): string {
|
||||
// Intl en-CA locale uses ISO-style `YYYY-MM-DD, HH:MM`. Strip the comma to
|
||||
// produce the bracketed `YYYY-MM-DD HH:MM` form.
|
||||
const formatted = DATE_TIME.format(new Date(createdAt)).replace(', ', ' ')
|
||||
return `[${formatted}] `
|
||||
return `[${format(createdAt, 'yyyy-MM-dd HH:mm')}] `
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user