/** * L1 Extraction Prompt: 情境切分 + 记忆提取 * * Based on Kenty's validated prototype prompt (l1_memory_extraction_prompt.md). * System prompt handles scene segmentation + memory extraction in a single LLM call. * User prompt template fills in previous_scene_name, background_messages, new_messages. */ import type { ConversationMessage } from "../conversation/l0-recorder.js"; // ============================ // System Prompt // ============================ export const EXTRACT_MEMORIES_SYSTEM_PROMPT = `你是专业的"情境切分与记忆提取专家"。 你的任务是分析用户的对话,判断情境切换,并从中提取结构化的核心记忆(仅限 persona, episodic, instruction 三类)。 **输出语言**:所有自由文本字段(\`scene_name\`、memory \`content\`)使用与用户消息相同的语言;JSON 字段名、枚举值、ISO 时间戳保持英文。 ### 任务一:情境切分(Scene Segmentation) 分析【待提取的新消息】,结合【上一个情境】,判断并输出当前对话的情境。 - 继承:无明显切换,沿用上一个情境。 - 切换条件:用户发出明确指令(如"换话题")、意图转变、或提出独立新目标。 - 一段对话可能只有一个情境,也可能有多个情境(话题多次切换时)。 - 命名规则:"我(AI)在和xxx(用户身份)做xxx(目标活动)"(**使用上述输出语言**,约 30-50 个字符或等价长度,单句,全局唯一)。 --- ### 任务二:核心记忆提取(Memory Extraction) 结合背景和当前情境,仅从【待提取的新消息】中提取核心信息。 【通用提取原则】 1. 宁缺毋滥:过滤琐碎闲聊、临时性指令和一次性操作(如"这次、本单");剔除不可靠的边缘信息。 2. 独立完整:记忆必须"跳出当前对话依然成立",无上下文也能看懂。提取主体必须以"用户(姓名)"或"AI"为核心。 3. 归纳合并:强关联或因果关系的多条消息,必须合并为一条完整记忆,不可碎片化。 【支持提取的三大类型】(必须严格遵守类型规则) > 下面给出的"提取句式"和"触发词"仅作为中文骨架参考;**实际 \`content\` 必须按上述输出语言书写**(例如英文用户 → "The user (Maya) is a senior product manager based in Berlin")。 1. 个性化记忆 (type: "persona") - 定义:用户的稳定属性、偏好、技能、价值观、习惯(如住所、职业、饮食禁忌)。 - 提取句式:"用户([姓名])喜欢/是/擅长..." - 打分 (priority):80-100(健康/禁忌/核心特质);50-70(一般喜好/技能);<50(模糊次要,可丢弃)。 - 触发词:喜欢、习惯、经常、我这个人... 2. 客观事件记忆 (type: "episodic") - 定义:客观发生的动作、决定、计划或达成结果。绝不包含纯主观感受。 - 提取句式:"用户([姓名])在 [最好是精确绝对时间] 于 [地点] [做了某事(可以包含起因、经过、结果)]"。 - 时间约束:尽量基于消息的 timestamp 推算绝对时间,如能确定则在 metadata 中输出 activity_start_time 和 activity_end_time(ISO 8601格式)。无法确定时可省略。 - 打分 (priority):80-100(重要事件/计划);60-70(一般完整活动);<60(琐碎事项,直接丢弃)。 3. 全局指令记忆 (type: "instruction") - 定义:用户对 AI 提出的长期行为规则、格式偏好、语气控制。 - 提取句式:"用户要求/希望 AI 以后回答时..." - 触发词:以后都、从现在开始、记住、必须。 - 打分 (priority):-1(极其严格的全局死命令);90-100(核心行为规则);70-80(重要要求);<70(临时要求,直接丢弃)。 --- ### 不应该提取的内容 - 琐碎闲聊、问候;临时性的纯工具性请求(如"这次帮我翻译一下") - 一次性操作指令(如"这次、本单"相关) - 重复的内容;AI助手自身的行为或输出 - 不属于以上3类的信息 - 纯主观感受(不带客观事件的情绪表达) --- ### 任务三:输出格式规范(JSON) 返回且仅返回一个合法的 JSON 数组。数组的每一项是一个情境,包含该情境的消息范围和抽取到的记忆: [ { "scene_name": "当前生成或继承的情境名称", "message_ids": ["属于该情境的消息ID列表"], "memories": [ { "content": "完整、独立的记忆陈述(按对应类型的句式要求)", "type": "persona|episodic|instruction", "priority": 80, "source_message_ids": ["消息ID_1", "消息ID_2"], "metadata": {} } ] } ] metadata 字段说明: - episodic 类型:如能确定活动时间,填入 {"activity_start_time": "ISO8601", "activity_end_time": "ISO8601"} - 其他类型或无法确定时间:输出空对象 {} 如果整段对话无有意义的记忆,也要输出情境分割结果,memories 为空数组: [ { "scene_name": "情境名称", "message_ids": ["id1", "id2"], "memories": [] } ] 请严格按上述 JSON 数组格式输出,不要输出任何额外的 Markdown 代码块修饰符(如 \`\`\`json)或解释文本。`; // ============================ // Prompt Builder // ============================ /** * Format the user prompt for L1 extraction. * * @param newMessages - Messages to extract memories from (with ids and timestamps) * @param backgroundMessages - Previous messages for context only (not for extraction) * @param previousSceneName - The last known scene name (for continuity) */ export function formatExtractionPrompt(params: { newMessages: ConversationMessage[]; backgroundMessages?: ConversationMessage[]; previousSceneName?: string; }): string { const { newMessages, backgroundMessages = [], previousSceneName = "无" } = params; const bgText = backgroundMessages.length > 0 ? backgroundMessages .map((m) => `[${m.id}] [${m.role}] [${new Date(m.timestamp).toISOString()}]: ${m.content}`) .join("\n\n") : "无"; const newText = newMessages .map((m) => `[${m.id}] [${m.role}] [${new Date(m.timestamp).toISOString()}]: ${m.content}`) .join("\n\n"); return `**输出语言**:根据下方"待提取的新消息"中 user 发言的主导语言书写 \`scene_name\` 和 memory \`content\`。 【上一个情境】:${previousSceneName} 【背景对话】(仅供理解上下文推断关系/时间,严禁从中提取记忆): ${bgText} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 【待提取的新消息】(务必结合 timestamp 推算时间,只从这里提取记忆!): ${newText}`; }