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