From 67c6b38e50da25e18c16fc7dbef7be48bf9ed0ef Mon Sep 17 00:00:00 2001 From: root Date: Thu, 30 Apr 2026 17:41:02 +0800 Subject: [PATCH] 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 - Add display import --- entry/src/main/ets/model/GraphDatabase.ets | 165 ++++++++++++++- entry/src/main/ets/pages/Index.ets | 3 +- .../src/main/ets/services/AIAgentService.ets | 8 +- .../main/ets/services/GraphMemoryService.ets | 197 +++++++++++++++++- 4 files changed, 355 insertions(+), 18 deletions(-) diff --git a/entry/src/main/ets/model/GraphDatabase.ets b/entry/src/main/ets/model/GraphDatabase.ets index b1c4a8e..58e96e3 100644 --- a/entry/src/main/ets/model/GraphDatabase.ets +++ b/entry/src/main/ets/model/GraphDatabase.ets @@ -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(entityIds); const visitedEntityIds = new Set(entityIds); // 批量预加载所有相关节点名称,减少 N+1 查询 - const nodeNameCache = new Map(); - // 批量预加载所有相关节点名称,减少 N+1 查询 - const nodeNameCache = new Map(); + const nodeNameCache = new Map(); 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(); + const nodeNameCache = new Map(); 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 { + 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(); + 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 { + 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(); + 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 { + 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 { if (!this.store) return 0; let deleted = 0; diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 41561f1..28d5381 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -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; aboutToAppear() { this.db.init(getContext(this)); diff --git a/entry/src/main/ets/services/AIAgentService.ets b/entry/src/main/ets/services/AIAgentService.ets index 3472600..030a9a5 100644 --- a/entry/src/main/ets/services/AIAgentService.ets +++ b/entry/src/main/ets/services/AIAgentService.ets @@ -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 { diff --git a/entry/src/main/ets/services/GraphMemoryService.ets b/entry/src/main/ets/services/GraphMemoryService.ets index fd53bc6..a74323d 100644 --- a/entry/src/main/ets/services/GraphMemoryService.ets +++ b/entry/src/main/ets/services/GraphMemoryService.ets @@ -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 { + 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 { + 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 { + 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 {