mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(ai): enrich session data snapshot prompt
This commit is contained in:
1 parent
f578c3597a
commit
383af0b13a
6 files changed
+242
-12
No files matched your search
@@ -21,6 +21,36 @@ const mockT: TranslateFn = (key, options) => {
|
||||
const opts = options as Record<string, unknown>
|
||||
return `[snapshot:${opts.name}:${opts.totalMessages}:${opts.lastMessageDate}]`
|
||||
}
|
||||
if (key === 'ai.agent.dataSnapshotContext') {
|
||||
const opts = options as Record<string, unknown>
|
||||
return `[snapshot-context]
|
||||
- name: ${opts.name}
|
||||
- platform: ${opts.platform}
|
||||
- type: ${opts.type}
|
||||
- total_messages: ${opts.totalMessages}
|
||||
- total_members: ${opts.totalMembers}
|
||||
- first_message_ts: ${opts.firstMessageTs}
|
||||
- first_message_time: ${opts.firstMessageTime}
|
||||
- last_message_ts: ${opts.lastMessageTs}
|
||||
- last_message_time: ${opts.lastMessageTime}
|
||||
- segment_summaries_available: ${opts.segmentSummaryCount}
|
||||
|
||||
${opts.memberHintTitle}
|
||||
${opts.memberHintLines}
|
||||
|
||||
${opts.usageRules}`
|
||||
}
|
||||
if (key === 'ai.agent.dataSnapshotMemberHintsAll') return '活跃成员查询提示(全部成员):'
|
||||
if (key === 'ai.agent.dataSnapshotMemberHintsTop') return '活跃成员查询提示(按历史总消息量 Top 10):'
|
||||
if (key === 'ai.agent.dataSnapshotMemberHintsUnavailable') return '活跃成员查询提示:'
|
||||
if (key === 'ai.agent.dataSnapshotMemberHintsEmpty') return '无可用成员提示。'
|
||||
if (key === 'ai.agent.dataSnapshotUsageRules') {
|
||||
return `- member_id 是工具查询提示;display_name 仅用于人类识别,可能不唯一。
|
||||
- 不要在最终回答中主动暴露 member_id 或启动上下文本身,除非用户明确要求技术细节。
|
||||
- 活跃成员排行只代表历史总消息量,不代表最近活跃情况。
|
||||
- 相对时间表达以真实当前日期为基准,而不是数据库最后消息时间。
|
||||
- 不要只为了重新发现 min/max timestamp 调用工具。`
|
||||
}
|
||||
if (key === 'ai.agent.evidencePolicy') return '[evidence-policy]'
|
||||
if (key === 'ai.agent.responseInstruction') return '[response-instruction]'
|
||||
if (key === 'ai.agent.mentionedMembersNote') return 'Mentioned members:'
|
||||
@@ -108,7 +138,42 @@ describe('buildSystemPrompt', () => {
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(result.includes('[snapshot:Team Chat:1234:'))
|
||||
assert.ok(result.includes('[snapshot-context]'))
|
||||
assert.ok(result.includes('total_messages: 1234'))
|
||||
assert.ok(result.includes('segment_summaries_available: 0'))
|
||||
assert.ok(result.includes('[evidence-policy]'))
|
||||
})
|
||||
|
||||
it('renders extended data snapshot startup context and rules', () => {
|
||||
const result = buildSystemPrompt({
|
||||
t: mockT,
|
||||
locale: 'zh-CN',
|
||||
dataSnapshot: {
|
||||
version: 2,
|
||||
name: 'Team Chat',
|
||||
platform: 'wechat',
|
||||
type: 'group',
|
||||
totalMessages: 100,
|
||||
totalMembers: 2,
|
||||
firstMessageTs: 1735689600,
|
||||
lastMessageTs: 1767225599,
|
||||
activeMemberHints: [
|
||||
{ memberId: 1, displayName: 'Alice', messageCount: 60, share: 60 },
|
||||
{ memberId: 2, displayName: 'Bob', messageCount: 40, share: 40 },
|
||||
],
|
||||
segmentSummaries: { availableCount: 12 },
|
||||
},
|
||||
})
|
||||
|
||||
assert.ok(result.includes('first_message_ts: 1735689600'))
|
||||
assert.ok(result.includes('last_message_ts: 1767225599'))
|
||||
assert.ok(result.includes('segment_summaries_available: 12'))
|
||||
assert.ok(result.includes('member_id=1 | display_name=Alice | messages=60 | share=60%'))
|
||||
assert.ok(result.includes('member_id=2 | display_name=Bob | messages=40 | share=40%'))
|
||||
assert.ok(result.includes('历史总消息量'))
|
||||
assert.ok(result.includes('真实当前日期'))
|
||||
assert.ok(result.includes('不要只为了重新发现 min/max timestamp 调用工具'))
|
||||
assert.ok(!result.includes('platform_id='))
|
||||
assert.ok(!result.includes('aliases='))
|
||||
})
|
||||
})
|
||||
@@ -23,7 +23,15 @@ export interface SkillContext {
|
||||
skillMenu?: string
|
||||
}
|
||||
|
||||
export interface ActiveMemberHint {
|
||||
memberId: number
|
||||
displayName: string
|
||||
messageCount: number
|
||||
share: number
|
||||
}
|
||||
|
||||
export interface DataSnapshot {
|
||||
version?: 2
|
||||
name: string
|
||||
platform: string
|
||||
type: string
|
||||
@@ -31,7 +39,11 @@ export interface DataSnapshot {
|
||||
totalMembers: number
|
||||
firstMessageTs: number | null
|
||||
lastMessageTs: number | null
|
||||
capturedAt: number
|
||||
capturedAt?: number
|
||||
activeMemberHints?: ActiveMemberHint[]
|
||||
segmentSummaries?: {
|
||||
availableCount: number
|
||||
}
|
||||
}
|
||||
|
||||
export type TranslateFn = (key: string, options?: Record<string, unknown>) => string
|
||||
@@ -51,6 +63,56 @@ function agentT(t: TranslateFn, key: string, locale: string, options?: Record<st
|
||||
return t(key, { lng: locale, ...options })
|
||||
}
|
||||
|
||||
function formatNullableTimestamp(timestamp: number | null | undefined): string {
|
||||
return typeof timestamp === 'number' && Number.isFinite(timestamp) ? String(timestamp) : 'null'
|
||||
}
|
||||
|
||||
function formatSharePercent(share: number): string {
|
||||
if (!Number.isFinite(share)) return '0%'
|
||||
const rounded = Math.round(share * 10) / 10
|
||||
return Number.isInteger(rounded) ? `${rounded}%` : `${rounded.toFixed(1)}%`
|
||||
}
|
||||
|
||||
function normalizeInlineText(text: string): string {
|
||||
return text.replace(/\s+/g, ' ').trim()
|
||||
}
|
||||
|
||||
export function formatDataSnapshotContext(t: TranslateFn, dataSnapshot: DataSnapshot, locale: string): string {
|
||||
const memberHints = dataSnapshot.activeMemberHints ?? []
|
||||
const memberHintTitle =
|
||||
memberHints.length === 0
|
||||
? agentT(t, 'ai.agent.dataSnapshotMemberHintsUnavailable', locale)
|
||||
: dataSnapshot.totalMembers <= 10
|
||||
? agentT(t, 'ai.agent.dataSnapshotMemberHintsAll', locale)
|
||||
: agentT(t, 'ai.agent.dataSnapshotMemberHintsTop', locale)
|
||||
|
||||
// 中文注释:启动上下文会进入每轮系统提示词,字段顺序和标签必须稳定,便于模型缓存和后续 smoke 对比。
|
||||
const memberHintLines =
|
||||
memberHints.length > 0
|
||||
? memberHints
|
||||
.map((member, index) => {
|
||||
return `${index + 1}. member_id=${member.memberId} | display_name=${normalizeInlineText(member.displayName)} | messages=${member.messageCount} | share=${formatSharePercent(member.share)}`
|
||||
})
|
||||
.join('\n')
|
||||
: agentT(t, 'ai.agent.dataSnapshotMemberHintsEmpty', locale)
|
||||
|
||||
return agentT(t, 'ai.agent.dataSnapshotContext', locale, {
|
||||
name: dataSnapshot.name,
|
||||
platform: dataSnapshot.platform,
|
||||
type: dataSnapshot.type,
|
||||
totalMessages: dataSnapshot.totalMessages,
|
||||
totalMembers: dataSnapshot.totalMembers,
|
||||
firstMessageTs: formatNullableTimestamp(dataSnapshot.firstMessageTs),
|
||||
firstMessageTime: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
||||
lastMessageTs: formatNullableTimestamp(dataSnapshot.lastMessageTs),
|
||||
lastMessageTime: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
||||
segmentSummaryCount: dataSnapshot.segmentSummaries?.availableCount ?? 0,
|
||||
memberHintTitle,
|
||||
memberHintLines,
|
||||
usageRules: agentT(t, 'ai.agent.dataSnapshotUsageRules', locale),
|
||||
})
|
||||
}
|
||||
|
||||
function getLockedPromptSection(
|
||||
t: TranslateFn,
|
||||
chatType: 'group' | 'private',
|
||||
@@ -94,16 +156,7 @@ function getLockedPromptSection(
|
||||
: ''
|
||||
|
||||
const year = now.getFullYear()
|
||||
const dataSnapshotNote = dataSnapshot
|
||||
? `${agentT(t, 'ai.agent.dataSnapshotNote', locale, {
|
||||
name: dataSnapshot.name,
|
||||
platform: dataSnapshot.platform,
|
||||
totalMessages: dataSnapshot.totalMessages,
|
||||
totalMembers: dataSnapshot.totalMembers,
|
||||
firstMessageDate: formatTimestamp(dataSnapshot.firstMessageTs, locale),
|
||||
lastMessageDate: formatTimestamp(dataSnapshot.lastMessageTs, locale),
|
||||
})}\n`
|
||||
: ''
|
||||
const dataSnapshotNote = dataSnapshot ? `${formatDataSnapshotContext(t, dataSnapshot, locale)}\n` : ''
|
||||
|
||||
return `${agentT(t, 'ai.agent.currentDateIs', locale)} ${currentDate}。
|
||||
${ownerNote}
|
||||
|
||||
@@ -297,6 +297,34 @@ Returned summaries are brief descriptions of each segment, helping quickly locat
|
||||
defaultYearNote: 'When no time range is specified, queries default to all data. Current year is {{year}}.',
|
||||
dataSnapshotNote:
|
||||
'Current chat database snapshot: {{name}} ({{platform}}), {{totalMessages}} total messages, {{totalMembers}} members, time range {{firstMessageDate}} ~ {{lastMessageDate}}. This snapshot only helps judge data coverage; concrete chat facts still require tool retrieval from the current database.',
|
||||
dataSnapshotContext: `Current chat database startup context:
|
||||
- name: {{name}}
|
||||
- platform: {{platform}}
|
||||
- type: {{type}}
|
||||
- total_messages: {{totalMessages}}
|
||||
- total_members: {{totalMembers}}
|
||||
- first_message_ts: {{firstMessageTs}}
|
||||
- first_message_time: {{firstMessageTime}}
|
||||
- last_message_ts: {{lastMessageTs}}
|
||||
- last_message_time: {{lastMessageTime}}
|
||||
- segment_summaries_available: {{segmentSummaryCount}}
|
||||
|
||||
{{memberHintTitle}}
|
||||
{{memberHintLines}}
|
||||
|
||||
Usage rules:
|
||||
{{usageRules}}`,
|
||||
dataSnapshotMemberHintsAll: 'Active member lookup hints (all members):',
|
||||
dataSnapshotMemberHintsTop: 'Active member lookup hints (top 10 by historical total messages):',
|
||||
dataSnapshotMemberHintsUnavailable: 'Active member lookup hints:',
|
||||
dataSnapshotMemberHintsEmpty: 'No member hints available.',
|
||||
dataSnapshotUsageRules: `- member_id is a tool lookup hint; display_name is only for human recognition and may not be unique.
|
||||
- Do not proactively expose member_id or the startup context in the final answer unless the user explicitly asks for technical details.
|
||||
- Active member ranking only reflects historical total message volume, not recent activity; it is also not evidence for influence, relationships, or recent trends.
|
||||
- Interpret relative time expressions using the real current date, not the database's last message time.
|
||||
- "recent year" / "past year" means one year back from the real current date to today; "last year" means the previous calendar year.
|
||||
- Database time bounds are only for explaining coverage, not for redefining the user's requested time range.
|
||||
- Do not call tools only to rediscover min/max timestamp; concrete chat facts, statistics, and conclusions still require tool evidence.`,
|
||||
evidencePolicy: `Evidence policy:
|
||||
- AI conversation history, prior AI replies, and compressed summaries are only for understanding the user's intent; they are not evidence for chat-record facts.
|
||||
- Whenever the user asks about chat content, recent topics, what someone said, rankings/statistics, whether a topic appeared, or asks for exact quotes, first call an appropriate data tool to retrieve the current database.
|
||||
|
||||
@@ -296,6 +296,34 @@ export default {
|
||||
defaultYearNote: '時間範囲を指定しない場合は全期間が対象。現在の年は{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'現在のチャットデータベースのスナップショット:{{name}}({{platform}})、総メッセージ {{totalMessages}} 件、メンバー {{totalMembers}} 人、期間 {{firstMessageDate}} ~ {{lastMessageDate}}。このスナップショットはデータ範囲の判断にのみ使い、具体的なチャット事実の回答には必ず現在のデータベースをツールで検索する。',
|
||||
dataSnapshotContext: `現在のチャットデータベース起動コンテキスト:
|
||||
- name: {{name}}
|
||||
- platform: {{platform}}
|
||||
- type: {{type}}
|
||||
- total_messages: {{totalMessages}}
|
||||
- total_members: {{totalMembers}}
|
||||
- first_message_ts: {{firstMessageTs}}
|
||||
- first_message_time: {{firstMessageTime}}
|
||||
- last_message_ts: {{lastMessageTs}}
|
||||
- last_message_time: {{lastMessageTime}}
|
||||
- segment_summaries_available: {{segmentSummaryCount}}
|
||||
|
||||
{{memberHintTitle}}
|
||||
{{memberHintLines}}
|
||||
|
||||
使用ルール:
|
||||
{{usageRules}}`,
|
||||
dataSnapshotMemberHintsAll: 'アクティブメンバー検索ヒント(全メンバー):',
|
||||
dataSnapshotMemberHintsTop: 'アクティブメンバー検索ヒント(過去の総メッセージ数 Top 10):',
|
||||
dataSnapshotMemberHintsUnavailable: 'アクティブメンバー検索ヒント:',
|
||||
dataSnapshotMemberHintsEmpty: '利用可能なメンバーヒントはありません。',
|
||||
dataSnapshotUsageRules: `- member_id はツール検索用のヒントです。display_name は人間が識別するためだけのもので、一意とは限りません。
|
||||
- ユーザーが技術的詳細を明示的に求めない限り、最終回答で member_id や起動コンテキスト自体を積極的に開示しないでください。
|
||||
- アクティブメンバー順位は過去の総メッセージ量だけを示し、最近の活発さを示すものではありません。また、影響力、関係性、最近の傾向の結論証拠にもなりません。
|
||||
- 相対的な時間表現は、データベースの最終メッセージ時刻ではなく、実際の現在日付を基準に解釈してください。
|
||||
- 「最近一年/過去一年」は実際の現在日付から今日までの1年間を指し、「去年」は前の暦年を指します。
|
||||
- データベースの時間境界はカバレッジ説明にのみ使い、ユーザーが求めた時間範囲を再定義するために使わないでください。
|
||||
- min/max timestamp を再発見するためだけにツールを呼ばないでください。ただし、具体的なチャット事実、統計、結論には引き続きツール証拠が必要です。`,
|
||||
evidencePolicy: `証拠ポリシー:
|
||||
- AI 会話履歴、過去の AI 回答、圧縮要約はユーザー意図の理解にのみ使用し、チャット記録の事実証拠として扱わない。
|
||||
- ユーザーがチャット内容、最近の話題、誰かの発言、ランキング/統計、ある話題が出たかどうか、または原文引用を求めた場合は、必ず先に適切なデータツールで現在のデータベースを検索する。
|
||||
|
||||
@@ -286,6 +286,34 @@ export default {
|
||||
defaultYearNote: '未指定时间范围时默认查询全部。当前年份为{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'当前聊天数据库快照:{{name}}({{platform}}),总消息 {{totalMessages}} 条,成员 {{totalMembers}} 人,时间范围 {{firstMessageDate}} ~ {{lastMessageDate}}。该快照只用于判断数据范围;回答具体聊天事实仍必须调用工具检索当前数据库。',
|
||||
dataSnapshotContext: `当前聊天数据库启动上下文:
|
||||
- name: {{name}}
|
||||
- platform: {{platform}}
|
||||
- type: {{type}}
|
||||
- total_messages: {{totalMessages}}
|
||||
- total_members: {{totalMembers}}
|
||||
- first_message_ts: {{firstMessageTs}}
|
||||
- first_message_time: {{firstMessageTime}}
|
||||
- last_message_ts: {{lastMessageTs}}
|
||||
- last_message_time: {{lastMessageTime}}
|
||||
- segment_summaries_available: {{segmentSummaryCount}}
|
||||
|
||||
{{memberHintTitle}}
|
||||
{{memberHintLines}}
|
||||
|
||||
使用规则:
|
||||
{{usageRules}}`,
|
||||
dataSnapshotMemberHintsAll: '活跃成员查询提示(全部成员):',
|
||||
dataSnapshotMemberHintsTop: '活跃成员查询提示(按历史总消息量 Top 10):',
|
||||
dataSnapshotMemberHintsUnavailable: '活跃成员查询提示:',
|
||||
dataSnapshotMemberHintsEmpty: '无可用成员提示。',
|
||||
dataSnapshotUsageRules: `- member_id 是工具查询提示;display_name 仅用于人类识别,可能不唯一。
|
||||
- 不要在最终回答中主动暴露 member_id 或启动上下文本身,除非用户明确要求技术细节。
|
||||
- 活跃成员排行只代表历史总消息量,不代表最近活跃情况;也不足以作为影响力、关系或近期趋势结论的证据。
|
||||
- 相对时间表达以真实当前日期为基准,而不是数据库最后消息时间。
|
||||
- “最近一年/过去一年”表示从真实当前日期回推一年到今天;“去年”表示上一自然年。
|
||||
- 数据库时间边界只用于说明覆盖范围,不用于重定义用户要求的时间范围。
|
||||
- 不要只为了重新发现 min/max timestamp 调用工具;但回答具体聊天事实、统计和结论仍必须调用工具获取证据。`,
|
||||
evidencePolicy: `证据策略:
|
||||
- AI 对话历史、历史 AI 回复和压缩摘要只用于理解用户意图,不能作为聊天记录事实证据。
|
||||
- 只要用户询问聊天记录内容、最近聊什么、某人说过什么、统计排行、是否出现过某话题或要求引用原话,必须先调用合适的数据工具检索当前数据库。
|
||||
|
||||
@@ -286,6 +286,34 @@ export default {
|
||||
defaultYearNote: '未指定時間範圍時預設查詢全部。當前年份為{{year}}年',
|
||||
dataSnapshotNote:
|
||||
'目前聊天資料庫快照:{{name}}({{platform}}),總訊息 {{totalMessages}} 條,成員 {{totalMembers}} 人,時間範圍 {{firstMessageDate}} ~ {{lastMessageDate}}。該快照只用於判斷資料範圍;回答具體聊天事實仍必須呼叫工具檢索目前資料庫。',
|
||||
dataSnapshotContext: `目前聊天資料庫啟動上下文:
|
||||
- name: {{name}}
|
||||
- platform: {{platform}}
|
||||
- type: {{type}}
|
||||
- total_messages: {{totalMessages}}
|
||||
- total_members: {{totalMembers}}
|
||||
- first_message_ts: {{firstMessageTs}}
|
||||
- first_message_time: {{firstMessageTime}}
|
||||
- last_message_ts: {{lastMessageTs}}
|
||||
- last_message_time: {{lastMessageTime}}
|
||||
- segment_summaries_available: {{segmentSummaryCount}}
|
||||
|
||||
{{memberHintTitle}}
|
||||
{{memberHintLines}}
|
||||
|
||||
使用規則:
|
||||
{{usageRules}}`,
|
||||
dataSnapshotMemberHintsAll: '活躍成員查詢提示(全部成員):',
|
||||
dataSnapshotMemberHintsTop: '活躍成員查詢提示(按歷史總訊息量 Top 10):',
|
||||
dataSnapshotMemberHintsUnavailable: '活躍成員查詢提示:',
|
||||
dataSnapshotMemberHintsEmpty: '無可用成員提示。',
|
||||
dataSnapshotUsageRules: `- member_id 是工具查詢提示;display_name 僅用於人類識別,可能不唯一。
|
||||
- 不要在最終回答中主動暴露 member_id 或啟動上下文本身,除非使用者明確要求技術細節。
|
||||
- 活躍成員排行只代表歷史總訊息量,不代表近期活躍情況;也不足以作為影響力、關係或近期趨勢結論的證據。
|
||||
- 相對時間表達以真實目前日期為基準,而不是資料庫最後訊息時間。
|
||||
-「最近一年/過去一年」表示從真實目前日期回推一年到今天;「去年」表示上一自然年。
|
||||
- 資料庫時間邊界只用於說明覆蓋範圍,不用於重定義使用者要求的時間範圍。
|
||||
- 不要只為了重新發現 min/max timestamp 呼叫工具;但回答具體聊天事實、統計和結論仍必須呼叫工具取得證據。`,
|
||||
evidencePolicy: `證據策略:
|
||||
- AI 對話歷史、歷史 AI 回覆和壓縮摘要只用於理解使用者意圖,不能作為聊天紀錄事實證據。
|
||||
- 只要使用者詢問聊天紀錄內容、最近聊什麼、某人說過什麼、統計排行、是否出現過某話題或要求引用原話,必須先呼叫合適的資料工具檢索目前資料庫。
|
||||
|
||||
Reference in new issue
Block a user