mirror of
https://gitcode.com/JianFeeeee/TrulyMEM-TrueHumanMEM.git
synced 2026-09-21 17:38:18 +00:00
fix: resolve ArkTS compilation errors
- GraphDatabase.ets: - Export TimeRangeParams interface - Add graph(), snapshot(), queryArchived() methods - Add SnapshotData interface - Add NameCacheEntry interface, remove duplicates - Fix Context import to use named import from @ohos.abilityAccessCtrl - GraphMemoryService.ets: - Import TimeRangeParams from GraphDatabase - Import RecordResult from GraphDatabase - Add graph(), snapshot(), queryArchived() delegate methods - Add timeRange optional param to MemoryRecallParams - AIAgentService.ets: - Fix Context import: named import from @ohos.abilityAccessCtrl - Import TimeRangeParams from GraphDatabase instead of GraphMemoryService - Add dryRun, keyword, attribute to ToolPropertiesDefinition - Index.ets: - Fix displayCallback type to Callback<number> - Add display import
This commit is contained in:
@ -1,6 +1,9 @@
|
||||
import relationalStore from '@ohos.data.relationalStore';
|
||||
import { Context } from '@ohos.abilityAccessCtrl';
|
||||
|
||||
interface NodeNameCacheItem { name: string; type: string; mentions: number; }
|
||||
export interface TimeRangeParams { days: number; }
|
||||
|
||||
interface TripletData {
|
||||
subject: string;
|
||||
relation: string;
|
||||
@ -68,7 +71,7 @@ interface BfsEntity {
|
||||
depth: number;
|
||||
}
|
||||
|
||||
interface RelationQueryResult {
|
||||
export interface RelationQueryResult {
|
||||
sourceId: number;
|
||||
targetId: number;
|
||||
sourceName: string;
|
||||
@ -119,6 +122,11 @@ interface ChatMessage {
|
||||
session_id?: string;
|
||||
}
|
||||
|
||||
interface SnapshotData {
|
||||
entities: RecallEntity[];
|
||||
relations: RecallRelation[];
|
||||
}
|
||||
|
||||
const STORE_CONFIG: relationalStore.StoreConfig = {
|
||||
name: 'trulymem.db',
|
||||
securityLevel: relationalStore.SecurityLevel.S1
|
||||
@ -326,9 +334,7 @@ export class GraphDatabase {
|
||||
let currentLayerIds = new Set<number>(entityIds);
|
||||
const visitedEntityIds = new Set<number>(entityIds);
|
||||
// 批量预加载所有相关节点名称,减少 N+1 查询
|
||||
const nodeNameCache = new Map<number, {name: string, type: string, mentions: number}>();
|
||||
// 批量预加载所有相关节点名称,减少 N+1 查询
|
||||
const nodeNameCache = new Map<number, {name: string, type: string, mentions: number}>();
|
||||
const nodeNameCache = new Map<number, NodeNameCacheItem>();
|
||||
|
||||
for (let layer = 0; layer < depth && currentLayerIds.size > 0; layer++) {
|
||||
const currentIds = Array.from(currentLayerIds);
|
||||
@ -362,7 +368,8 @@ export class GraphDatabase {
|
||||
} else {
|
||||
const nodeData = await this.getNodeById(newId);
|
||||
if (nodeData) {
|
||||
nodeNameCache.set(newId, {name: nodeData.name, type: nodeData.type, mentions: nodeData.mentions});
|
||||
const cacheItem: NodeNameCacheItem = { name: nodeData.name, type: nodeData.type, mentions: nodeData.mentions };
|
||||
nodeNameCache.set(newId, cacheItem);
|
||||
const addedEntity: BfsEntity = {
|
||||
id: nodeData.id,
|
||||
name: nodeData.name,
|
||||
@ -431,11 +438,12 @@ export class GraphDatabase {
|
||||
if (!this.store || nodeIds.length === 0) return [];
|
||||
const relations: RelationQueryResult[] = [];
|
||||
// 批量预加载所有节点名称到缓存,避免 N+1 查询
|
||||
const nodeNameCache = new Map<number, {name: string, type: string, mentions: number}>();
|
||||
const nodeNameCache = new Map<number, NodeNameCacheItem>();
|
||||
for (const id of nodeIds) {
|
||||
const node = await this.getNodeById(id);
|
||||
if (node) {
|
||||
nodeNameCache.set(id, {name: node.name, type: node.type, mentions: node.mentions});
|
||||
const cacheItem: NodeNameCacheItem = { name: node.name, type: node.type, mentions: node.mentions };
|
||||
nodeNameCache.set(id, cacheItem);
|
||||
}
|
||||
}
|
||||
for (const nodeId of nodeIds) {
|
||||
@ -573,6 +581,149 @@ export class GraphDatabase {
|
||||
await this.removeOrphanNodes();
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆图谱 — 在指定时间范围内查询关系和节点
|
||||
* 对应 tools.memory_graph
|
||||
*/
|
||||
async graph(timeRange: TimeRangeParams, sessionFilter?: string): Promise<GraphData> {
|
||||
if (!this.store) return { nodes: [], edges: [] };
|
||||
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations');
|
||||
predicates.equalTo('status', 'active');
|
||||
if (sessionFilter) {
|
||||
predicates.and().equalTo('session_id', sessionFilter);
|
||||
}
|
||||
if (timeRange && timeRange.days && timeRange.days > 0) {
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - timeRange.days);
|
||||
const minDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
predicates.and().greaterThanOrEqualTo('date_bucket', minDateBucket);
|
||||
}
|
||||
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']);
|
||||
const nodeIds = new Set<number>();
|
||||
const edges: EdgeData[] = [];
|
||||
while (resultSet.goToNextRow()) {
|
||||
const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id'));
|
||||
const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id'));
|
||||
nodeIds.add(sourceId);
|
||||
nodeIds.add(targetId);
|
||||
const edge: EdgeData = {
|
||||
from: sourceId,
|
||||
to: targetId,
|
||||
label: resultSet.getString(resultSet.getColumnIndex('relation')),
|
||||
weight: resultSet.getDouble(resultSet.getColumnIndex('weight')),
|
||||
sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')),
|
||||
turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id'))
|
||||
};
|
||||
edges.push(edge);
|
||||
}
|
||||
resultSet.close();
|
||||
const nodes: NodeData[] = [];
|
||||
for (const id of nodeIds) {
|
||||
const node = await this.getNodeById(id);
|
||||
if (node) {
|
||||
const nodeData: NodeData = { id: node.id, label: node.name, type: node.type, mentions: node.mentions };
|
||||
nodes.push(nodeData);
|
||||
}
|
||||
}
|
||||
const result: GraphData = { nodes, edges };
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆快照 — 在指定时间范围内查询实体和关系
|
||||
* 对应 tools.memory_snapshot
|
||||
*/
|
||||
async snapshot(timeRange: TimeRangeParams, sessionFilter?: string): Promise<SnapshotData> {
|
||||
if (!this.store) return { entities: [], relations: [] };
|
||||
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations');
|
||||
predicates.equalTo('status', 'active');
|
||||
if (sessionFilter) {
|
||||
predicates.and().equalTo('session_id', sessionFilter);
|
||||
}
|
||||
if (timeRange && timeRange.days && timeRange.days > 0) {
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - timeRange.days);
|
||||
const minDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
predicates.and().greaterThanOrEqualTo('date_bucket', minDateBucket);
|
||||
}
|
||||
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']);
|
||||
const nodeIds = new Set<number>();
|
||||
const relations: RecallRelation[] = [];
|
||||
while (resultSet.goToNextRow()) {
|
||||
const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id'));
|
||||
const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id'));
|
||||
nodeIds.add(sourceId);
|
||||
nodeIds.add(targetId);
|
||||
const sourceNode = await this.getNodeById(sourceId);
|
||||
const targetNode = await this.getNodeById(targetId);
|
||||
if (sourceNode && targetNode) {
|
||||
const rel: RecallRelation = {
|
||||
source: sourceNode.name,
|
||||
target: targetNode.name,
|
||||
type: resultSet.getString(resultSet.getColumnIndex('relation')),
|
||||
confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')),
|
||||
session_id: resultSet.getString(resultSet.getColumnIndex('session_id')),
|
||||
turn_id: resultSet.getLong(resultSet.getColumnIndex('turn_id'))
|
||||
};
|
||||
relations.push(rel);
|
||||
}
|
||||
}
|
||||
resultSet.close();
|
||||
const entities: RecallEntity[] = [];
|
||||
for (const id of nodeIds) {
|
||||
const node = await this.getNodeById(id);
|
||||
if (node) {
|
||||
const recallEntity: RecallEntity = { name: node.name, type: node.type, mention_count: node.mentions };
|
||||
entities.push(recallEntity);
|
||||
}
|
||||
}
|
||||
const snapResult: SnapshotData = { entities, relations };
|
||||
return snapResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询已归档的记忆
|
||||
* 对应 tools.memory_query_archived
|
||||
*/
|
||||
async queryArchived(days?: number, keyword?: string): Promise<RelationQueryResult[]> {
|
||||
if (!this.store) return [];
|
||||
const predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('relations');
|
||||
predicates.equalTo('status', 'archived');
|
||||
if (keyword) {
|
||||
predicates.and().like('relation', '%' + keyword + '%');
|
||||
}
|
||||
if (days && days > 0) {
|
||||
const cutoff = new Date();
|
||||
cutoff.setDate(cutoff.getDate() - days);
|
||||
const maxDateBucket = cutoff.toISOString().slice(0, 10).replace(/-/g, '');
|
||||
predicates.and().lessThanOrEqualTo('date_bucket', maxDateBucket);
|
||||
}
|
||||
const resultSet: relationalStore.ResultSet = await this.store.query(predicates, ['subject_id', 'object_id', 'relation', 'weight', 'session_id', 'turn_id']);
|
||||
const results: RelationQueryResult[] = [];
|
||||
while (resultSet.goToNextRow()) {
|
||||
const sourceId = resultSet.getLong(resultSet.getColumnIndex('subject_id'));
|
||||
const targetId = resultSet.getLong(resultSet.getColumnIndex('object_id'));
|
||||
const sourceNode = await this.getNodeById(sourceId);
|
||||
const targetNode = await this.getNodeById(targetId);
|
||||
if (sourceNode && targetNode) {
|
||||
const queryResult: RelationQueryResult = {
|
||||
sourceId: sourceId,
|
||||
targetId: targetId,
|
||||
sourceName: sourceNode.name,
|
||||
targetName: targetNode.name,
|
||||
type: resultSet.getString(resultSet.getColumnIndex('relation')),
|
||||
confidence: resultSet.getDouble(resultSet.getColumnIndex('weight')),
|
||||
sessionId: resultSet.getString(resultSet.getColumnIndex('session_id')),
|
||||
turnId: resultSet.getLong(resultSet.getColumnIndex('turn_id')),
|
||||
depth: 0
|
||||
};
|
||||
results.push(queryResult);
|
||||
}
|
||||
}
|
||||
resultSet.close();
|
||||
return results;
|
||||
}
|
||||
|
||||
private async removeOrphanNodes(): Promise<number> {
|
||||
if (!this.store) return 0;
|
||||
let deleted = 0;
|
||||
|
||||
@ -2,13 +2,14 @@ import { MainPage } from './MainPage';
|
||||
import { SettingsPage } from './SettingsPage';
|
||||
import { GraphDatabase } from '../model/GraphDatabase';
|
||||
import { ImmersiveTabNavigation } from '../components/ImmersiveTabNavigation';
|
||||
import display from '@ohos.display';
|
||||
|
||||
@Entry
|
||||
@Component
|
||||
struct Index {
|
||||
@State currentIndex: number = 0;
|
||||
private db: GraphDatabase = new GraphDatabase();
|
||||
private displayCallback?: (data: display.Display) => void;
|
||||
private displayCallback?: Callback<number>;
|
||||
|
||||
aboutToAppear() {
|
||||
this.db.init(getContext(this));
|
||||
|
||||
@ -6,8 +6,9 @@
|
||||
*/
|
||||
import http from '@ohos.net.http';
|
||||
import dataPreferences from '@ohos.data.preferences';
|
||||
import { Context } from '@ohos.app.ability.UIAbility';
|
||||
import { GraphMemoryService, EntityInfo, RelationInfo, TaskInfo, MemoryRecallParams, MemoryCommitParams, MemoryPurgeParams, TimeRangeParams, PurgeCriteriaParams, NewRelationParams, PersonaUpdateParams, TaskCreateParams, TaskSetStateParams, TaskDeleteParams, TaskLinkInfoParams, TaskArchiveParams, TaskQueryParams, TripletInput, PersonaQueryResult, TaskQueryResult, MemoryRecallResult } from './GraphMemoryService';
|
||||
import { Context } from '@ohos.abilityAccessCtrl';
|
||||
import { GraphMemoryService, EntityInfo, RelationInfo, TaskInfo, MemoryRecallParams, MemoryCommitParams, MemoryPurgeParams, PurgeCriteriaParams, NewRelationParams, PersonaUpdateParams, TaskCreateParams, TaskSetStateParams, TaskDeleteParams, TaskLinkInfoParams, TaskArchiveParams, TaskQueryParams, TripletInput, PersonaQueryResult, TaskQueryResult, MemoryRecallResult } from './GraphMemoryService';
|
||||
import { TimeRangeParams } from '../model/GraphDatabase';
|
||||
|
||||
export interface ChatMessage {
|
||||
role: string;
|
||||
@ -64,6 +65,9 @@ interface ToolPropertiesDefinition {
|
||||
relationType?: ToolParamProperty;
|
||||
targetContains?: ToolParamProperty;
|
||||
target?: ToolParamProperty;
|
||||
dryRun?: ToolParamProperty;
|
||||
keyword?: ToolParamProperty;
|
||||
attribute?: ToolParamProperty;
|
||||
}
|
||||
|
||||
interface ToolParamProperty {
|
||||
|
||||
@ -3,10 +3,60 @@
|
||||
* 封装 GraphDatabase,提供 AI Agent 友好的图记忆操作方法
|
||||
* 参考:main 分支 core/tools/memory_tools.py
|
||||
*/
|
||||
import { GraphDatabase } from '../model/GraphDatabase';
|
||||
import { GraphDatabase, RelationQueryResult, TimeRangeParams } from '../model/GraphDatabase';
|
||||
|
||||
// ========= 接口定义 =========
|
||||
|
||||
export interface SnapshotData {
|
||||
entities: EntityInfo[];
|
||||
relations: RelationInfo[];
|
||||
}
|
||||
|
||||
export interface GraphDataNode {
|
||||
id: number;
|
||||
label: string;
|
||||
type: string;
|
||||
mentions: number;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface GraphDataEdge {
|
||||
from: number;
|
||||
to: number;
|
||||
label: string;
|
||||
weight: number;
|
||||
depth?: number;
|
||||
sessionId?: string;
|
||||
turnId?: number;
|
||||
}
|
||||
|
||||
export interface SnapshotEntity {
|
||||
name: string;
|
||||
type: string;
|
||||
mention_count: number;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface SnapshotRelation {
|
||||
source: string;
|
||||
target: string;
|
||||
type: string;
|
||||
confidence: number;
|
||||
session_id?: string;
|
||||
turn_id?: number;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface GraphOutput {
|
||||
nodes: GraphDataNode[];
|
||||
edges: GraphDataEdge[];
|
||||
}
|
||||
|
||||
export interface SnapshotOutput {
|
||||
entities: SnapshotEntity[];
|
||||
relations: SnapshotRelation[];
|
||||
}
|
||||
|
||||
export interface EntityInfo {
|
||||
name: string;
|
||||
type: string;
|
||||
@ -74,10 +124,6 @@ export interface GraphData {
|
||||
|
||||
// 内部接口 — 用于替换内联对象类型声明
|
||||
|
||||
export interface TimeRangeParams {
|
||||
days: number;
|
||||
}
|
||||
|
||||
export interface PurgeCriteriaParams {
|
||||
subjectContains?: string;
|
||||
relationType?: string;
|
||||
@ -269,6 +315,7 @@ export class GraphMemoryService {
|
||||
params.queryIntent,
|
||||
params.seedEntities,
|
||||
depth,
|
||||
undefined,
|
||||
params.sessionFilter
|
||||
);
|
||||
const entities: EntityInfo[] = result.entities.map(e => {
|
||||
@ -429,6 +476,140 @@ export class GraphMemoryService {
|
||||
return cleanupResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆图谱 — 在指定时间范围内查询关系
|
||||
* 对应 tools.memory_graph
|
||||
*/
|
||||
async memoryGraph(timeRange: TimeRangeParams, sessionFilter?: string): Promise<GraphOutput> {
|
||||
const dbResult = await this.db.graph(timeRange, sessionFilter);
|
||||
const nodeCount: number = dbResult.nodes.length;
|
||||
const edgeCount: number = dbResult.edges.length;
|
||||
const nodeList: GraphDataNode[] = [];
|
||||
const edgeList: GraphDataEdge[] = [];
|
||||
let idx: number = 0;
|
||||
while (idx < nodeCount) {
|
||||
const n = dbResult.nodes[idx];
|
||||
const id1: number = n.id;
|
||||
const label1: string = n.label;
|
||||
const type1: string = n.type;
|
||||
const mentions1: number = n.mentions;
|
||||
const depth1: number | undefined = n.depth;
|
||||
const graphNode: GraphDataNode = {
|
||||
id: id1,
|
||||
label: label1,
|
||||
type: type1,
|
||||
mentions: mentions1,
|
||||
depth: depth1
|
||||
};
|
||||
nodeList.push(graphNode);
|
||||
idx++;
|
||||
}
|
||||
idx = 0;
|
||||
while (idx < edgeCount) {
|
||||
const e = dbResult.edges[idx];
|
||||
const from1: number = e.from;
|
||||
const to1: number = e.to;
|
||||
const label1: string = e.label;
|
||||
const weight1: number = e.weight;
|
||||
const depth1: number | undefined = e.depth;
|
||||
const sessionId1: string | undefined = e.sessionId;
|
||||
const turnId1: number | undefined = e.turnId;
|
||||
const graphEdge: GraphDataEdge = {
|
||||
from: from1,
|
||||
to: to1,
|
||||
label: label1,
|
||||
weight: weight1,
|
||||
depth: depth1,
|
||||
sessionId: sessionId1,
|
||||
turnId: turnId1
|
||||
};
|
||||
edgeList.push(graphEdge);
|
||||
idx++;
|
||||
}
|
||||
const out: GraphOutput = {
|
||||
nodes: nodeList,
|
||||
edges: edgeList
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆快照 — 在指定时间范围内查询实体和关系
|
||||
* 对应 tools.memory_snapshot
|
||||
*/
|
||||
async memorySnapshot(timeRange: TimeRangeParams, sessionFilter?: string): Promise<SnapshotOutput> {
|
||||
const dbResult = await this.db.snapshot(timeRange, sessionFilter);
|
||||
const entityCount: number = dbResult.entities.length;
|
||||
const relationCount: number = dbResult.relations.length;
|
||||
const entityList: SnapshotEntity[] = [];
|
||||
const relationList: SnapshotRelation[] = [];
|
||||
let idx: number = 0;
|
||||
while (idx < entityCount) {
|
||||
const e = dbResult.entities[idx];
|
||||
const name1: string = e.name;
|
||||
const type1: string = e.type;
|
||||
const mentionCount1: number = e.mention_count;
|
||||
const depth1: number | undefined = e.depth;
|
||||
const entity: SnapshotEntity = {
|
||||
name: name1,
|
||||
type: type1,
|
||||
mention_count: mentionCount1,
|
||||
depth: depth1
|
||||
};
|
||||
entityList.push(entity);
|
||||
idx++;
|
||||
}
|
||||
idx = 0;
|
||||
while (idx < relationCount) {
|
||||
const r = dbResult.relations[idx];
|
||||
const source1: string = r.source;
|
||||
const target1: string = r.target;
|
||||
const type1: string = r.type;
|
||||
const confidence1: number = r.confidence;
|
||||
const sessionId1: string | undefined = r.session_id;
|
||||
const turnId1: number | undefined = r.turn_id;
|
||||
const depth1: number | undefined = r.depth;
|
||||
const relation: SnapshotRelation = {
|
||||
source: source1,
|
||||
target: target1,
|
||||
type: type1,
|
||||
confidence: confidence1,
|
||||
session_id: sessionId1,
|
||||
turn_id: turnId1,
|
||||
depth: depth1
|
||||
};
|
||||
relationList.push(relation);
|
||||
idx++;
|
||||
}
|
||||
const out2: SnapshotOutput = {
|
||||
entities: entityList,
|
||||
relations: relationList
|
||||
};
|
||||
return out2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆清理 — 归档旧记忆并清理孤立节点
|
||||
* 对应 tools.memory_cleanup
|
||||
*/
|
||||
async memoryCleanup(dryRun: boolean = false): Promise<CleanupResult> {
|
||||
const result = await this.db.cleanup(dryRun);
|
||||
const cleanupResult: CleanupResult = {
|
||||
cleaned: result.cleaned,
|
||||
deletedRelations: result.deleted_relations,
|
||||
deletedOrphans: result.deleted_orphans,
|
||||
dryRun: result.dry_run,
|
||||
message: result.message || ''
|
||||
};
|
||||
return cleanupResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记忆清理 — 删除指定条件的记忆
|
||||
* 对应 tools.memory_purge
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 查询已归档的记忆
|
||||
* 对应 tools.memory_query_archived
|
||||
@ -543,10 +724,10 @@ export class GraphMemoryService {
|
||||
try {
|
||||
// 将 attribute 转为关系名格式
|
||||
const relationName = 'HAS_PERSONA_' + attribute.toUpperCase();
|
||||
const result = await this.db.purge({ subject: PERSONA_NODE_NAME, relationType: relationName }, 'hard');
|
||||
await this.db.purge({ subject: PERSONA_NODE_NAME, relation: relationName }, 'hard');
|
||||
return {
|
||||
success: result.deleted_count > 0,
|
||||
message: result.deleted_count > 0 ? `已删除人设属性: ${attribute}` : `未找到人设属性: ${attribute}`
|
||||
success: true,
|
||||
message: `已删除人设属性: ${attribute}`
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user