Files
Youzini-afk 47ffd5413d refactor(terminology): 层级总结为主路径,synopsis 标记为旧式全局概要
- README: 明确 summaryState 为主总结体系,legacy synopsis 为兼容兜底
- prompt-profiles: synopsis 任务标签改为「小总结」
- schema/task-graph-stats/panel: synopsis 节点显示为「全局概要(旧)」
- index.js: fallback 状态文案改为「旧式全局概要生成/更新中」
- p0-regressions: 同步更新断言字符串
2026-04-12 17:58:05 +08:00

331 lines
9.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ST-BME: 节点类型 Schema 定义
// 定义图谱中支持的节点类型、字段、注入策略和压缩配置
/**
* 压缩模式
*/
export const COMPRESSION_MODE = {
NONE: "none",
HIERARCHICAL: "hierarchical",
};
/**
* 默认节点类型 Schema
* 每种类型定义了:
* - id: 唯一标识
* - label: 显示名称
* - tableName: 注入时的表名
* - columns: 字段列表 [{name, hint, required}]
* - alwaysInject: 是否常驻注入true=Core, false=需要召回)
* - latestOnly: 是否只保留最新版本(用于角色/地点等随时间更新的实体)
* - forceUpdate: 每次提取是否必须产出此类型节点
* - compression: 压缩配置
*/
export const DEFAULT_NODE_SCHEMA = [
{
id: "event",
label: "事件",
tableName: "event_table",
columns: [
{ name: "title", hint: "简短事件名(建议 6-10 字,用于图谱显示)", required: false },
{ name: "summary", hint: "事件摘要,包含因果关系和结果", required: true },
{ name: "participants", hint: "参与角色名,逗号分隔", required: false },
{
name: "status",
hint: "事件状态ongoing/resolved/blocked",
required: false,
},
],
alwaysInject: true,
latestOnly: false,
forceUpdate: true,
compression: {
mode: COMPRESSION_MODE.HIERARCHICAL,
threshold: 9,
fanIn: 3,
maxDepth: 10,
keepRecentLeaves: 6,
instruction:
"将事件节点压缩为高价值的剧情里程碑摘要。保留因果关系、不可逆结果和未解决的伏笔。",
},
},
{
id: "character",
label: "角色",
tableName: "character_table",
columns: [
{ name: "name", hint: "角色名(仅规范名称)", required: true },
{ name: "traits", hint: "稳定的性格特征和外貌标记", required: false },
{ name: "state", hint: "当前状态或处境", required: false },
{ name: "goal", hint: "当前目标或动机", required: false },
{ name: "inventory", hint: "携带或拥有的关键物品", required: false },
{ name: "core_note", hint: "值得长期记住的关键备注", required: false },
],
alwaysInject: false,
latestOnly: true,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.NONE,
threshold: 0,
fanIn: 0,
maxDepth: 0,
keepRecentLeaves: 0,
instruction: "",
},
},
{
id: "location",
label: "地点",
tableName: "location_table",
columns: [
{ name: "name", hint: "地点名称(仅规范名称)", required: true },
{ name: "state", hint: "当前状态或环境条件", required: false },
{ name: "features", hint: "重要特征、资源或服务", required: false },
{ name: "danger", hint: "危险等级或威胁", required: false },
],
alwaysInject: false,
latestOnly: true,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.NONE,
threshold: 0,
fanIn: 0,
maxDepth: 0,
keepRecentLeaves: 0,
instruction: "",
},
},
{
id: "rule",
label: "规则",
tableName: "rule_table",
columns: [
{ name: "title", hint: "简短规则名", required: true },
{ name: "constraint", hint: "不可违反的规则内容", required: true },
{ name: "scope", hint: "适用范围/场景", required: false },
{
name: "status",
hint: "当前有效性active/suspended/revoked",
required: false,
},
],
alwaysInject: true,
latestOnly: false,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.NONE,
threshold: 0,
fanIn: 0,
maxDepth: 0,
keepRecentLeaves: 0,
instruction: "",
},
},
{
id: "thread",
label: "主线",
tableName: "thread_table",
columns: [
{ name: "title", hint: "主线名称", required: true },
{ name: "summary", hint: "当前进展摘要", required: false },
{
name: "status",
hint: "状态active/completed/abandoned",
required: false,
},
],
alwaysInject: true,
latestOnly: false,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.HIERARCHICAL,
threshold: 6,
fanIn: 3,
maxDepth: 5,
keepRecentLeaves: 3,
instruction: "将主线节点压缩为阶段性进展摘要。保留关键转折和当前目标。",
},
},
// ====== v2 新增节点类型 ======
{
id: "synopsis",
label: "全局概要(旧)",
tableName: "synopsis_table",
columns: [
{
name: "summary",
hint: "旧式单条全局前情提要(兼容 / 迁移兜底)",
required: true,
},
{ name: "scope", hint: "该旧式概要覆盖的楼层范围", required: false },
],
alwaysInject: true, // 常驻注入MemoRAG 启发)
latestOnly: true, // 只保留最新版本
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.NONE,
threshold: 0,
fanIn: 0,
maxDepth: 0,
keepRecentLeaves: 0,
instruction: "",
},
},
{
id: "reflection",
label: "反思",
tableName: "reflection_table",
columns: [
{ name: "insight", hint: "对角色行为或情节的元认知反思", required: true },
{ name: "trigger", hint: "触发反思的事件/矛盾", required: false },
{ name: "suggestion", hint: "对后续叙事的建议", required: false },
],
alwaysInject: false, // 需要被召回
latestOnly: false,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.HIERARCHICAL,
threshold: 6,
fanIn: 3,
maxDepth: 3,
keepRecentLeaves: 3,
instruction: "将反思条目合并为高层次的叙事指导原则。",
},
},
{
id: "pov_memory",
label: "主观记忆",
tableName: "pov_memory_table",
columns: [
{ name: "summary", hint: "这个视角如何记住这件事", required: true },
{ name: "belief", hint: "她/他认为发生了什么", required: false },
{ name: "emotion", hint: "主观情绪反应", required: false },
{ name: "attitude", hint: "对人物或事件的态度", required: false },
{
name: "certainty",
hint: "确定度certain/unsure/mistaken",
required: false,
},
{ name: "about", hint: "关联对象或引用标签", required: false },
],
alwaysInject: false,
latestOnly: false,
forceUpdate: false,
compression: {
mode: COMPRESSION_MODE.HIERARCHICAL,
threshold: 8,
fanIn: 3,
maxDepth: 4,
keepRecentLeaves: 4,
instruction:
"将同一视角、同一角色归属下的主观记忆压缩成更稳定的第一视角记忆摘要,保留误解、情绪和态度变化。",
},
},
];
/**
* 规范化的关系类型
*/
export const RELATION_TYPES = [
"related", // 一般关联
"involved_in", // 参与事件
"occurred_at", // 发生于地点
"advances", // 推进主线
"updates", // 更新实体状态
"contradicts", // 矛盾/冲突(用于抑制边)
"evolves", // A-MEM 进化链接(新→旧)
"temporal_update", // 时序更新Graphiti新状态替代旧状态
];
/**
* 验证 Schema 配置的合法性
* @param {Array} schema
* @returns {{valid: boolean, errors: string[]}}
*/
export function validateSchema(schema) {
const errors = [];
if (!Array.isArray(schema) || schema.length === 0) {
errors.push("Schema 必须是非空数组");
return { valid: false, errors };
}
const ids = new Set();
const tableNames = new Set();
for (const type of schema) {
if (!type || typeof type !== "object") {
errors.push("Schema 类型定义必须是对象");
continue;
}
if (!type.id || typeof type.id !== "string") {
errors.push("每种类型必须有 id");
continue;
}
if (ids.has(type.id)) {
errors.push(`类型 ID 重复:${type.id}`);
}
ids.add(type.id);
if (!type.label || typeof type.label !== "string") {
errors.push(`类型 ${type.id}:缺少 label`);
}
if (!type.tableName || typeof type.tableName !== "string") {
errors.push(`类型 ${type.id}:缺少 tableName`);
} else if (tableNames.has(type.tableName)) {
errors.push(`表名重复:${type.tableName}`);
} else {
tableNames.add(type.tableName);
}
if (!Array.isArray(type.columns) || type.columns.length === 0) {
errors.push(`类型 ${type.id}:至少需要一个列`);
continue;
}
const columnNames = new Set();
for (const column of type.columns) {
if (!column?.name || typeof column.name !== "string") {
errors.push(`类型 ${type.id}:存在缺少 name 的列定义`);
continue;
}
if (columnNames.has(column.name)) {
errors.push(`类型 ${type.id}:列名重复 ${column.name}`);
}
columnNames.add(column.name);
}
const hasRequired = type.columns.some((c) => c?.required);
if (!hasRequired) {
errors.push(`类型 ${type.id}:至少需要一个 required 列`);
}
if (type.latestOnly) {
const hasPrimaryLikeField = ["name", "title", "summary"].some(
(fieldName) =>
type.columns.some((column) => column?.name === fieldName),
);
if (!hasPrimaryLikeField) {
errors.push(
`类型 ${type.id}latestOnly 类型至少需要 name/title/summary 之一作为主标识字段`,
);
}
}
}
return { valid: errors.length === 0, errors };
}
/**
* 获取 Schema 中某个类型的定义
* @param {Array} schema
* @param {string} typeId
* @returns {object|null}
*/
export function getSchemaType(schema, typeId) {
return schema.find((t) => t.id === typeId) || null;
}