mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
feat: 优化默认提示词(HARD GATE约束) + 扩展prompt变量(Phase1+2)
Phase 1: 重写 DEFAULT_TASK_BLOCKS 全部6个任务的 role/format/rules - 统一应用 HARD GATE 约束段 + 常见错误负例 - compress/synopsis 增加自检清单 - 增强 JSON 稳定性约束 Phase 2: 扩展 prompt 内置变量 - 新建 st-context.js: 统一读取 ST 上下文 - 新增变量: charName/userName/charDescription/userPersona/currentTime - 更新 extractor/retriever/compressor/consolidator 共6处调用端 - 更新 BUILTIN_BLOCK_DEFINITIONS 帮助文案(多轮对话指引)
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from "./graph.js";
|
||||
import { callLLMForJSON } from "./llm.js";
|
||||
import { buildTaskPrompt } from "./prompt-builder.js";
|
||||
import { getSTContextForPrompt } from "./st-context.js";
|
||||
import { applyTaskRegex } from "./task-regex.js";
|
||||
import { isDirectVectorConfig } from "./vector-index.js";
|
||||
|
||||
@@ -234,6 +235,7 @@ async function summarizeBatch(
|
||||
candidateNodes: nodeDescriptions,
|
||||
currentRange: `${nodes[0]?.seq ?? "?"} ~ ${nodes[nodes.length - 1]?.seq ?? "?"}`,
|
||||
graphStats: `node_count=${nodes.length}, node_type=${typeDef.id}`,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
const systemPrompt = applyTaskRegex(
|
||||
settings,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { embedBatch, searchSimilar } from "./embedding.js";
|
||||
import { addEdge, createEdge, getActiveNodes, getNode } from "./graph.js";
|
||||
import { callLLMForJSON } from "./llm.js";
|
||||
import { buildTaskPrompt } from "./prompt-builder.js";
|
||||
import { getSTContextForPrompt } from "./st-context.js";
|
||||
import { applyTaskRegex } from "./task-regex.js";
|
||||
import {
|
||||
buildNodeVectorText,
|
||||
@@ -298,6 +299,7 @@ export async function consolidateMemories({
|
||||
candidateNodes: userPrompt,
|
||||
candidateText: userPrompt,
|
||||
graphStats: `new_entries=${newEntries.length}, threshold=${conflictThreshold}`,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
try {
|
||||
decision = await callLLMForJSON({
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ensureEventTitle, getNodeDisplayName } from "./node-labels.js";
|
||||
import { buildTaskPrompt } from "./prompt-builder.js";
|
||||
import { RELATION_TYPES } from "./schema.js";
|
||||
import { applyTaskRegex } from "./task-regex.js";
|
||||
import { getSTContextForPrompt } from "./st-context.js";
|
||||
import { buildNodeVectorText, isDirectVectorConfig } from "./vector-index.js";
|
||||
|
||||
function createAbortError(message = "操作已终止") {
|
||||
@@ -117,6 +118,7 @@ export async function extractMemories({
|
||||
graphStats: graphOverview,
|
||||
graphOverview,
|
||||
currentRange,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
|
||||
// 系统提示词
|
||||
@@ -633,6 +635,7 @@ export async function generateSynopsis({
|
||||
characterSummary: charSummary || "(无)",
|
||||
threadSummary: threadSummary || "(无)",
|
||||
graphStats: `event=${eventNodes.length}, character=${characterNodes.length}, thread=${threadNodes.length}`,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
const synopsisSystemPrompt = applyTaskRegex(
|
||||
settings,
|
||||
@@ -746,6 +749,7 @@ export async function generateReflection({
|
||||
threadSummary: threadSummary || "(无)",
|
||||
contradictionSummary: contradictionSummary || "(无)",
|
||||
graphStats: `event=${recentEvents.length}, character=${recentCharacters.length}, thread=${recentThreads.length}`,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
const reflectionSystemPrompt = applyTaskRegex(
|
||||
settings,
|
||||
|
||||
@@ -47,7 +47,7 @@ const BUILTIN_BLOCK_DEFINITIONS = [
|
||||
sourceKey: "systemInstruction",
|
||||
name: "系统说明",
|
||||
role: "system",
|
||||
description: "注入任务级系统指令。可用于添加通用约束或全局规则,所有任务类型均可使用。",
|
||||
description: "注入任务级系统指令。可用于添加通用约束或全局规则。提示:可创建多个自定义块并设置不同角色(system/user/assistant)来实现多轮对话式 prompt 编排,利用 few-shot 引导 LLM 遵守格式。可用变量:{{charName}}、{{userName}}、{{charDescription}}、{{userPersona}}、{{currentTime}}。",
|
||||
},
|
||||
{
|
||||
sourceKey: "outputRules",
|
||||
@@ -141,82 +141,213 @@ const LEGACY_PROMPT_FIELD_MAP = {
|
||||
|
||||
const DEFAULT_TASK_BLOCKS = {
|
||||
extract: {
|
||||
role: "你是一个记忆提取分析器。从对话中提取结构化记忆节点并存入知识图谱。",
|
||||
role: [
|
||||
"你是记忆提取执行 AI。从对话中提取结构化记忆节点,写入知识图谱。",
|
||||
`必须按「分析(thought)→ 操作(operations)」架构工作。`,
|
||||
].join("\n"),
|
||||
format: [
|
||||
"输出格式为严格 JSON:",
|
||||
"{",
|
||||
' \"thought\": \"你对本段对话的分析(事件/角色变化/新信息)\",',
|
||||
' \"operations\": [',
|
||||
' "thought": "你对本段对话的分析(事件/角色变化/新信息)",',
|
||||
' "operations": [',
|
||||
" {",
|
||||
' \"action\": \"create\",',
|
||||
' \"type\": \"event\",',
|
||||
' \"fields\": {\"title\": \"简短事件名\", \"summary\": \"...\", \"participants\": \"...\", \"status\": \"ongoing\"},',
|
||||
' \"importance\": 6,',
|
||||
' \"ref\": \"evt1\",',
|
||||
' \"links\": [',
|
||||
' {\"targetNodeId\": \"existing-id\", \"relation\": \"involved_in\", \"strength\": 0.9}',
|
||||
' "action": "create",',
|
||||
' "type": "event",',
|
||||
' "fields": {"title": "简短事件名", "summary": "...", "participants": "...", "status": "ongoing"},',
|
||||
' "importance": 6,',
|
||||
' "ref": "evt1",',
|
||||
' "links": [',
|
||||
' {"targetNodeId": "existing-id", "relation": "involved_in", "strength": 0.9}',
|
||||
" ]",
|
||||
" },",
|
||||
" {",
|
||||
' \"action\": \"update\",',
|
||||
' \"nodeId\": \"existing-node-id\",',
|
||||
' \"fields\": {\"state\": \"新的状态\"}',
|
||||
' "action": "update",',
|
||||
' "nodeId": "existing-node-id",',
|
||||
' "fields": {"state": "新的状态"}',
|
||||
" }",
|
||||
" ]",
|
||||
"}",
|
||||
].join("\n"),
|
||||
rules: [
|
||||
"- 每批对话最多创建 1 个事件节点,多个子事件合并为一条",
|
||||
"- 角色/地点节点:如果图中已有同名节点,用 update 而非 create",
|
||||
"- 不要虚构内容,只提取对话中有证据支持的信息",
|
||||
"- importance 范围 1-10,普通事件 5,关键转折 8+",
|
||||
"- event.fields.title 需要是简短事件名,建议 6-18 字,只用于图谱和列表显示",
|
||||
"- summary 应该是摘要抽象,不要复制原文",
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"一、提取原则",
|
||||
"1. 唯一来源:只从对话正文中提取,绝对禁止虚构未出现的信息",
|
||||
"2. 合并策略:每批对话最多 1 个事件节点,多个子事件合并",
|
||||
"3. 去重检查:图中已有同名角色/地点节点时,用 update 而非 create",
|
||||
"4. importance:1-10,日常交互 3-5,关键转折 7-8,改变格局 9-10",
|
||||
"",
|
||||
"二、字段约束",
|
||||
"- event.fields.title:简短事件名,6-18 字,用于图谱列表显示",
|
||||
"- summary:摘要抽象,不复制原文,150 字以内",
|
||||
"- participants:所有参与者名字,逗号分隔",
|
||||
"",
|
||||
"三、JSON 稳定性",
|
||||
"- 字符串中的双引号必须转义",
|
||||
"- 禁止尾随逗号、单引号、注释",
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 虚构对话中未出现的事件或角色",
|
||||
`- 图中已有「张三」节点时仍 create 新「张三"`,
|
||||
"- title 写成整段叙述而非简短事件名",
|
||||
"- summary 直接复制原文对话",
|
||||
"- importance 全部给 5(应区分轻重)",
|
||||
].join("\n"),
|
||||
},
|
||||
recall: {
|
||||
role: "你是一个记忆召回分析器。\n根据用户最新输入和对话上下文,从候选记忆节点中选择最相关的节点。",
|
||||
format: '输出严格的 JSON 格式:\n{\"selected_ids\": [\"id1\", \"id2\", ...], \"reason\": \"简要说明选择理由\"}',
|
||||
rules: "优先选择:\n (1) 直接相关的当前场景节点\n (2) 因果关系连续性节点\n (3) 有潜在影响的背景节点",
|
||||
role: [
|
||||
"你是记忆召回执行 AI。从候选记忆节点中选择与当前对话最相关的节点。",
|
||||
"必须先推测剧情走向,再按相关性排序选择。",
|
||||
].join("\n"),
|
||||
format: '输出严格的 JSON 格式:\n{"selected_ids": ["id1", "id2", ...], "reason": "简要说明选择理由"}',
|
||||
rules: [
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"一、召回优先级(从高到低)",
|
||||
"1. 直接相关:当前场景正在发生的事件/在场角色",
|
||||
"2. 因果链接:与当前事件构成因果关系的前序事件",
|
||||
"3. 情感关联:涉及相同角色的情感/关系变化",
|
||||
"4. 背景补充:可能影响当前决策的世界观或状态信息",
|
||||
"",
|
||||
"二、选择原则",
|
||||
"- 不要因为 importance 高就选择,必须与当前对话相关",
|
||||
"- 每个选中节点必须在 reason 中说明选择理由",
|
||||
"- 宁缺毋滥:无关节点不要选",
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 选择全部候选节点(应有取舍)",
|
||||
"- 选择 0 个节点(除非候选列表确实全部无关)",
|
||||
`- reason 写成「这些节点相关」(应具体说明每个节点为何相关)`,
|
||||
"- 选择已被标记为 archived 的过时信息",
|
||||
].join("\n"),
|
||||
},
|
||||
consolidation: {
|
||||
role: "你是一个记忆整合分析器。当新记忆加入知识图谱时,你需要同时完成两项任务:",
|
||||
role: [
|
||||
"你是记忆整合执行 AI。当新记忆加入知识图谱时,执行冲突检测与进化分析。",
|
||||
`必须按「冲突检测 → 进化分析」双任务架构工作。`,
|
||||
].join("\n"),
|
||||
format: [
|
||||
"输出严格 JSON:",
|
||||
'{ \"results\": [',
|
||||
' { \"node_id\": \"新记忆节点ID\",',
|
||||
' \"action\": \"keep\"|\"merge\"|\"skip\",',
|
||||
' \"merge_target_id\": \"旧节点ID (仅merge)\",',
|
||||
' \"reason\": \"理由\",',
|
||||
' \"evolution\": { \"should_evolve\": true/false, \"connections\": [\"旧记忆ID\"], \"neighbor_updates\": [...] }',
|
||||
'{ "results": [',
|
||||
' { "node_id": "新记忆节点ID",',
|
||||
' "action": "keep"|"merge"|"skip",',
|
||||
' "merge_target_id": "旧节点ID (仅merge)",',
|
||||
' "reason": "理由",',
|
||||
' "evolution": { "should_evolve": true/false, "connections": ["旧记忆ID"], "neighbor_updates": [...] }',
|
||||
" }",
|
||||
"] }",
|
||||
].join("\n"),
|
||||
rules: [
|
||||
"任务一:冲突检测",
|
||||
" - skip: 新记忆与已有记忆完全重复",
|
||||
" - merge: 新记忆是对旧记忆的修正/补充",
|
||||
" - keep: 新记忆是全新信息",
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"任务二:进化分析(仅 action=keep 时)",
|
||||
" - 建立关联连接",
|
||||
" - 反向更新旧记忆",
|
||||
"一、冲突检测(每个新节点必须判定)",
|
||||
"- skip:新记忆与已有记忆完全重复(信息量无增益)",
|
||||
"- merge:新记忆是对旧记忆的修正、补充或更新",
|
||||
"- keep:新记忆包含全新信息,不与已有记忆冲突",
|
||||
"",
|
||||
"二、进化分析(仅 action=keep 时执行)",
|
||||
"- 检查新记忆是否与旧记忆存在因果/时序/角色关联",
|
||||
"- 建立 connections(记忆间的关联边)",
|
||||
"- 判断是否需要反向更新旧记忆状态",
|
||||
"",
|
||||
"三、判定标准",
|
||||
`- 「完全重复」= 核心事实相同,不只是措辞相似`,
|
||||
`- 「修正」= 新信息明确否定或更正旧信息`,
|
||||
`- 「补充」= 新信息为旧信息增加细节但不矛盾`,
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 对所有节点都返回 keep(应认真检测重复)",
|
||||
"- merge 时未指定 merge_target_id",
|
||||
`- 把「只是措辞不同」的记忆判定为 keep(应 skip 或 merge)`,
|
||||
"- keep 时 connections 为空(应尝试建立关联)",
|
||||
].join("\n"),
|
||||
},
|
||||
compress: {
|
||||
role: "你是一个记忆压缩器。将多个同类型节点总结为一条更高层级的压缩节点。",
|
||||
format: '输出格式为严格 JSON:\n{\"fields\": {\"summary\": \"...\", ...}}',
|
||||
rules: "- 保留关键信息:因果关系、不可逆结果、未解决伏笔\n- 去除重复和低信息密度内容\n- 压缩后文本应精炼,目标 150 字左右",
|
||||
role: [
|
||||
"你是记忆压缩执行 AI。将多个同类记忆节点合并为一条精炼的高层摘要。",
|
||||
`必须按「分析 → 压缩 → 自检」流程工作。`,
|
||||
].join("\n"),
|
||||
format: '输出格式为严格 JSON:\n{"fields": {"summary": "...", ...}}',
|
||||
rules: [
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"一、保留优先级(从高到低)",
|
||||
"1. 不可逆结果:死亡、永久变化、无法撤销的决定",
|
||||
"2. 因果关系:事件 A 导致事件 B 的逻辑链",
|
||||
"3. 未解决伏笔:尚未揭示的悬念、未完成的任务",
|
||||
"4. 关键情感转折:角色关系的重大变化",
|
||||
"5. 去除:重复描述、日常寒暄、低信息密度内容",
|
||||
"",
|
||||
"二、压缩约束",
|
||||
"- 目标 150 字左右,不超过 300 字",
|
||||
"- 第三方客观视角,不加主观判断",
|
||||
"- 保留时间线信息(先后顺序不可错乱)",
|
||||
"",
|
||||
"三、自检(压缩后逐项核查)",
|
||||
"□ 关键因果链是否完整保留?",
|
||||
"□ 是否有重要信息被遗漏?",
|
||||
"□ 时间顺序是否正确?",
|
||||
"□ 是否引入了原文没有的信息?",
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 丢失关键因果关系",
|
||||
"- 混淆不同角色的经历",
|
||||
"- 加入原始节点中不存在的推测",
|
||||
"- 输出超过 300 字",
|
||||
].join("\n"),
|
||||
},
|
||||
synopsis: {
|
||||
role: "你是故事概要生成器。根据事件线、角色和主线生成简洁的前情提要。",
|
||||
format: '输出 JSON:{\"summary\": \"前情提要文本(200字以内)\"}',
|
||||
rules: "要求:涵盖核心冲突、关键转折、主要角色当前状态。",
|
||||
role: [
|
||||
"你是故事概要生成执行 AI。根据事件线、角色状态和主线信息,生成简洁的前情提要。",
|
||||
"必须覆盖核心冲突、关键转折和角色当前状态。",
|
||||
].join("\n"),
|
||||
format: '输出 JSON:{"summary": "前情提要文本(200字以内)"}',
|
||||
rules: [
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"一、覆盖要素(缺一不可)",
|
||||
"1. 核心冲突:当前故事的主要矛盾是什么",
|
||||
"2. 关键转折:近期发生的改变局势的事件",
|
||||
"3. 角色状态:主要角色当前的处境和关系",
|
||||
"",
|
||||
"二、写作约束",
|
||||
"- 200 字以内",
|
||||
"- 按时间线顺序组织",
|
||||
"- 使用第三方叙述视角",
|
||||
"- 不要罗列事件清单,要有叙事连贯性",
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 超过 200 字",
|
||||
"- 遗漏核心冲突或主要角色",
|
||||
"- 写成事件列表而非连贯叙述",
|
||||
"- 加入个人评价或预测",
|
||||
].join("\n"),
|
||||
},
|
||||
reflection: {
|
||||
role: "你是 RP 长期记忆系统的反思生成器。",
|
||||
format: '输出严格 JSON:{\"insight\":\"...\",\"trigger\":\"...\",\"suggestion\":\"...\",\"importance\":1-10}',
|
||||
rules: "- insight 应总结最近情节中最值得长期保留的变化、关系趋势或潜在线索\n- trigger 说明触发这条反思的关键事件或矛盾\n- suggestion 给出后续检索或叙事上值得关注的提示\n- 不要复述全部事件,要提炼高层结论",
|
||||
role: [
|
||||
"你是长期记忆反思执行 AI。从近期事件中提炼长期趋势、潜在线索和值得关注的变化。",
|
||||
"重点关注:角色关系走向、未解悬念、可能的伏笔。",
|
||||
].join("\n"),
|
||||
format: '输出严格 JSON:{"insight":"...","trigger":"...","suggestion":"...","importance":1-10}',
|
||||
rules: [
|
||||
"============ 【核心规则 - HARD GATE】============",
|
||||
"",
|
||||
"一、反思维度",
|
||||
"1. insight:最值得长期保留的变化/关系趋势/潜在线索",
|
||||
"2. trigger:触发这条反思的关键事件或矛盾",
|
||||
"3. suggestion:后续叙事中值得关注或检索的方向",
|
||||
"",
|
||||
"二、写作约束",
|
||||
"- 不要复述事件详情,要提炼高层结论",
|
||||
"- insight 应具有长期参考价值(数十轮后仍有意义)",
|
||||
"- importance 严格按影响范围评分,不要全给高分",
|
||||
"",
|
||||
"============ 【常见错误(绝对禁止)】============",
|
||||
"- 复述全部事件而非提炼结论",
|
||||
"- insight 写成事件摘要而非趋势分析",
|
||||
"- importance 全部给 8+(应区分轻重)",
|
||||
"- trigger 为空或过于笼统",
|
||||
].join("\n"),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { callLLMForJSON } from "./llm.js";
|
||||
import { buildTaskPrompt } from "./prompt-builder.js";
|
||||
import { applyTaskRegex } from "./task-regex.js";
|
||||
import { getSTContextForPrompt } from "./st-context.js";
|
||||
import { findSimilarNodesByText, validateVectorConfig } from "./vector-index.js";
|
||||
|
||||
function createAbortError(message = "操作已终止") {
|
||||
@@ -425,6 +426,7 @@ async function llmRecall(
|
||||
candidateNodes: candidateDescriptions,
|
||||
candidateText: candidateDescriptions,
|
||||
graphStats: `candidate_count=${candidates.length}`,
|
||||
...getSTContextForPrompt(),
|
||||
});
|
||||
const systemPrompt = applyTaskRegex(
|
||||
settings,
|
||||
|
||||
45
st-context.js
Normal file
45
st-context.js
Normal file
@@ -0,0 +1,45 @@
|
||||
// ST-BME: SillyTavern 上下文数据读取辅助
|
||||
// 为 prompt 变量扩展(Phase 2)提供统一的 ST 上下文数据接口
|
||||
|
||||
import { getContext } from "../../../extensions.js";
|
||||
|
||||
/**
|
||||
* 从 SillyTavern 的 getContext() 提取当前上下文数据,
|
||||
* 返回的字段可直接展开传入 buildTaskPrompt 的 context 参数,
|
||||
* 用户在自定义 prompt 块中可通过 {{key}} 引用。
|
||||
*
|
||||
* @returns {object} 上下文字段映射
|
||||
*/
|
||||
export function getSTContextForPrompt() {
|
||||
try {
|
||||
const ctx = getContext?.() || {};
|
||||
const charId = ctx.characterId;
|
||||
const char =
|
||||
ctx.characters?.[Number(charId)] ||
|
||||
ctx.characters?.[charId] ||
|
||||
null;
|
||||
|
||||
return {
|
||||
userPersona:
|
||||
ctx.powerUserSettings?.persona_description ||
|
||||
ctx.name1_description ||
|
||||
"",
|
||||
charDescription:
|
||||
char?.description ||
|
||||
char?.data?.description ||
|
||||
"",
|
||||
charName: ctx.name2 || "",
|
||||
userName: ctx.name1 || "",
|
||||
currentTime: new Date().toLocaleString("zh-CN"),
|
||||
};
|
||||
} catch (e) {
|
||||
console.warn("[ST-BME] getSTContextForPrompt 失败:", e);
|
||||
return {
|
||||
userPersona: "",
|
||||
charDescription: "",
|
||||
charName: "",
|
||||
userName: "",
|
||||
currentTime: new Date().toLocaleString("zh-CN"),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user