mirror of
https://github.com/Youzini-afk/ST-Bionic-Memory-Ecology.git
synced 2026-05-15 22:30:38 +08:00
feat: update smart trigger and graph retrieval
This commit is contained in:
433
schema.js
433
schema.js
@@ -5,8 +5,8 @@
|
||||
* 压缩模式
|
||||
*/
|
||||
export const COMPRESSION_MODE = {
|
||||
NONE: 'none',
|
||||
HIERARCHICAL: 'hierarchical',
|
||||
NONE: "none",
|
||||
HIERARCHICAL: "hierarchical",
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -22,172 +22,189 @@ export const COMPRESSION_MODE = {
|
||||
* - compression: 压缩配置
|
||||
*/
|
||||
export const DEFAULT_NODE_SCHEMA = [
|
||||
{
|
||||
id: 'event',
|
||||
label: '事件',
|
||||
tableName: 'event_table',
|
||||
columns: [
|
||||
{ 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: "event",
|
||||
label: "事件",
|
||||
tableName: "event_table",
|
||||
columns: [
|
||||
{ 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: "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: "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: "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: '将主线节点压缩为阶段性进展摘要。保留关键转折和当前目标。',
|
||||
},
|
||||
},
|
||||
{
|
||||
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: '',
|
||||
},
|
||||
},
|
||||
// ====== 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: "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: "将反思条目合并为高层次的叙事指导原则。",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 规范化的关系类型
|
||||
*/
|
||||
export const RELATION_TYPES = [
|
||||
'related', // 一般关联
|
||||
'involved_in', // 参与事件
|
||||
'occurred_at', // 发生于地点
|
||||
'advances', // 推进主线
|
||||
'updates', // 更新实体状态
|
||||
'contradicts', // 矛盾/冲突(用于抑制边)
|
||||
'evolves', // A-MEM 进化链接(新→旧)
|
||||
'temporal_update', // 时序更新(Graphiti:新状态替代旧状态)
|
||||
"related", // 一般关联
|
||||
"involved_in", // 参与事件
|
||||
"occurred_at", // 发生于地点
|
||||
"advances", // 推进主线
|
||||
"updates", // 更新实体状态
|
||||
"contradicts", // 矛盾/冲突(用于抑制边)
|
||||
"evolves", // A-MEM 进化链接(新→旧)
|
||||
"temporal_update", // 时序更新(Graphiti:新状态替代旧状态)
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -196,46 +213,80 @@ export const RELATION_TYPES = [
|
||||
* @returns {{valid: boolean, errors: string[]}}
|
||||
*/
|
||||
export function validateSchema(schema) {
|
||||
const errors = [];
|
||||
const errors = [];
|
||||
|
||||
if (!Array.isArray(schema) || schema.length === 0) {
|
||||
errors.push('Schema 必须是非空数组');
|
||||
return { valid: false, 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;
|
||||
}
|
||||
|
||||
const ids = new Set();
|
||||
const tableNames = new Set();
|
||||
|
||||
for (const type of schema) {
|
||||
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.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}:至少需要一个列`);
|
||||
}
|
||||
|
||||
const hasRequired = type.columns?.some(c => c.required);
|
||||
if (!hasRequired) {
|
||||
errors.push(`类型 ${type.id}:至少需要一个 required 列`);
|
||||
}
|
||||
if (!type.id || typeof type.id !== "string") {
|
||||
errors.push("每种类型必须有 id");
|
||||
continue;
|
||||
}
|
||||
|
||||
return { valid: errors.length === 0, errors };
|
||||
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 };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,5 +296,5 @@ export function validateSchema(schema) {
|
||||
* @returns {object|null}
|
||||
*/
|
||||
export function getSchemaType(schema, typeId) {
|
||||
return schema.find(t => t.id === typeId) || null;
|
||||
return schema.find((t) => t.id === typeId) || null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user