Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 150e2d5607 | |||
| 38941ff2b5 | |||
| 53d6940994 | |||
| 16327dec72 | |||
| 9e2f390332 | |||
| 56e787a5a8 | |||
| 13e3c662ac |
@ -1,117 +0,0 @@
|
|||||||
# TrulyMEM → WaterFlow 迁移计划
|
|
||||||
|
|
||||||
> ⚠️ **修改只在 TrulyMEM 的 waterflow 分支执行** ⚠️
|
|
||||||
>
|
|
||||||
> 所有代码修改仅应用于 TrulyMEM 仓库的 `waterflow` 分支,作为 WaterFlow 框架的适配版本。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 迁移目标
|
|
||||||
|
|
||||||
将 TrulyMEM 的图记忆能力从 Python 迁移到 TypeScript,适配 WaterFlow 框架。
|
|
||||||
|
|
||||||
**代码位置**: `/home/program/TrulyMEM-TrueHumanMEM/` (waterflow 分支)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 迁移策略
|
|
||||||
|
|
||||||
将图记忆能力作为 TypeScript 模块添加到 waterflow 分支:
|
|
||||||
|
|
||||||
| TrulyMEM (Python) | WaterFlow (TypeScript) |
|
|
||||||
|-------------------|------------------------|
|
|
||||||
| `EmbeddedGraphDB` | `GraphDatabase` |
|
|
||||||
| `GraphMemoryClient` | `MemoryService` |
|
|
||||||
| 12 个记忆工具 | `GraphMemoryTool` + Skills |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 实施步骤
|
|
||||||
|
|
||||||
### Phase 1: 项目结构
|
|
||||||
|
|
||||||
- [x] 1.1 创建 `ts/` 目录 - TypeScript 项目
|
|
||||||
- [x] 1.2 创建 `package.json` - 项目配置
|
|
||||||
- [x] 1.3 创建 `tsconfig.json` - TypeScript 配置
|
|
||||||
|
|
||||||
### Phase 2: 核心库
|
|
||||||
|
|
||||||
- [x] 2.1 创建 `ts/src/runtime/core/graph_memory/types.ts` - 类型定义
|
|
||||||
- [x] 2.2 创建 `ts/src/runtime/core/graph_memory/graph_database.ts` - 图数据库
|
|
||||||
- [x] 2.3 创建 `ts/src/runtime/core/graph_memory/memory_service.ts` - 记忆服务
|
|
||||||
- [x] 2.4 创建 `ts/src/runtime/core/graph_memory/index.ts` - 模块导出
|
|
||||||
|
|
||||||
### Phase 3: Tool 接口
|
|
||||||
|
|
||||||
- [x] 3.1 创建 `ts/src/runtime/core/tools/builtin/graph_memory_tool.ts` - Tool 实现
|
|
||||||
- [x] 3.2 注册 Tool (作为独立模块导出)
|
|
||||||
|
|
||||||
### Phase 4: Skill 定义
|
|
||||||
|
|
||||||
- [x] 4.1 创建 `ts/bundled-skills/graph_memory/SKILL.md` - 主 Skill
|
|
||||||
- [x] 4.2 创建 `ts/bundled-skills/graph_memory/persona/SKILL.md` - Persona
|
|
||||||
- [x] 4.3 创建 `ts/bundled-skills/graph_memory/task/SKILL.md` - 任务管理
|
|
||||||
|
|
||||||
### Phase 5: 验证
|
|
||||||
|
|
||||||
- [x] 5.1 编译 TypeScript - 无错误
|
|
||||||
- [ ] 5.2 运行测试
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 目录结构 (在 TrulyMEM waterflow 分支)
|
|
||||||
|
|
||||||
```
|
|
||||||
TrulyMEM-TrueHumanMEM/
|
|
||||||
├── ts/ # TypeScript 项目 (保留)
|
|
||||||
│ ├── src/
|
|
||||||
│ │ └── runtime/core/
|
|
||||||
│ │ ├── graph_memory/ # 图记忆模块
|
|
||||||
│ │ │ ├── index.ts
|
|
||||||
│ │ │ ├── types.ts
|
|
||||||
│ │ │ ├── graph_database.ts
|
|
||||||
│ │ │ └── memory_service.ts
|
|
||||||
│ │ └── tools/
|
|
||||||
│ │ └── builtin/
|
|
||||||
│ │ └── graph_memory_tool.ts
|
|
||||||
│ ├── bundled-skills/
|
|
||||||
│ │ └── graph_memory/
|
|
||||||
│ │ ├── SKILL.md
|
|
||||||
│ │ ├── persona/SKILL.md
|
|
||||||
│ │ └── task/SKILL.md
|
|
||||||
│ ├── package.json
|
|
||||||
│ └── tsconfig.json
|
|
||||||
│
|
|
||||||
├── docs/integration/waterflow-design.md # 迁移设计文档 (保留)
|
|
||||||
│
|
|
||||||
└── (其他文件迁移后删除)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 迁移后清理
|
|
||||||
|
|
||||||
迁移完成后,waterflow 分支将删除以下文件:
|
|
||||||
|
|
||||||
- `core/` - Python 核心代码
|
|
||||||
- `ui/` - Python UI 代码
|
|
||||||
- `tests/` - Python 测试
|
|
||||||
- `tools/` - Python 工具
|
|
||||||
- `trulymem_entry.py` - Python 入口
|
|
||||||
- `build/` - 构建脚本
|
|
||||||
- `pic/` - 图片资源 (除图标外)
|
|
||||||
- `requirements.txt` - Python 依赖
|
|
||||||
- `TrulyMEM.spec` - Python 打包配置
|
|
||||||
|
|
||||||
只保留:
|
|
||||||
- `ts/` - TypeScript 源码
|
|
||||||
- `docs/integration/waterflow-design.md` - 迁移文档
|
|
||||||
- `.gitignore`, `LICENSE`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 工作追踪
|
|
||||||
|
|
||||||
工作进度记录在: `todo_progress.md`
|
|
||||||
|
|
||||||
每次修改文件前后请查看此文件并更新进度。
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
# 迁移工作进度追踪
|
|
||||||
|
|
||||||
> ⚠️ **修改只在 TrulyMEM 的 waterflow 分支执行** ⚠️
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 当前状态
|
|
||||||
|
|
||||||
- **开始时间**: 2026-04-15
|
|
||||||
- **当前任务**: 迁移完成,等待测试
|
|
||||||
- **最后更新**: 2026-04-15
|
|
||||||
- **状态**: TypeScript 编译通过
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 完成状态
|
|
||||||
|
|
||||||
### Phase 1: 项目结构
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|------|------|------|
|
|
||||||
| 1.1 ts/ 目录 | ✅ done | |
|
|
||||||
| 1.2 package.json | ✅ done | |
|
|
||||||
| 1.3 tsconfig.json | ✅ done | |
|
|
||||||
|
|
||||||
### Phase 2: 核心库
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|------|------|------|
|
|
||||||
| 2.1 types.ts | ✅ done | |
|
|
||||||
| 2.2 graph_database.ts | ✅ done | |
|
|
||||||
| 2.3 memory_service.ts | ✅ done | |
|
|
||||||
| 2.4 index.ts | ✅ done | |
|
|
||||||
|
|
||||||
### Phase 3: Tool 接口
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|------|------|------|
|
|
||||||
| 3.1 graph_memory_tool.ts | ✅ done | |
|
|
||||||
| 3.2 tool_interface.ts | ✅ done | |
|
|
||||||
|
|
||||||
### Phase 4: Skill 定义
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|------|------|------|
|
|
||||||
| 4.1 SKILL.md (主) | ✅ done | |
|
|
||||||
| 4.2 persona/SKILL.md | ✅ done | |
|
|
||||||
| 4.3 task/SKILL.md | ✅ done | |
|
|
||||||
|
|
||||||
### Phase 5: 验证
|
|
||||||
|
|
||||||
| 任务 | 状态 | 备注 |
|
|
||||||
|------|------|------|
|
|
||||||
| 5.1 编译 | ✅ done | TypeScript 编译通过 |
|
|
||||||
| 5.2 测试 | ⏳ pending | |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 创建的文件
|
|
||||||
|
|
||||||
| 文件 | 说明 |
|
|
||||||
|------|------|
|
|
||||||
| `ts/package.json` | 项目配置 |
|
|
||||||
| `ts/tsconfig.json` | TypeScript 配置 |
|
|
||||||
| `ts/src/runtime/core/graph_memory/types.ts` | 类型定义 |
|
|
||||||
| `ts/src/runtime/core/graph_memory/graph_database.ts` | 图数据库 |
|
|
||||||
| `ts/src/runtime/core/graph_memory/memory_service.ts` | 记忆服务 |
|
|
||||||
| `ts/src/runtime/core/graph_memory/index.ts` | 模块导出 |
|
|
||||||
| `ts/src/runtime/core/tools/tool_interface.ts` | Tool 接口 |
|
|
||||||
| `ts/src/runtime/core/tools/builtin/graph_memory_tool.ts` | GraphMemory Tool |
|
|
||||||
| `ts/bundled-skills/graph_memory/SKILL.md` | 主 Skill |
|
|
||||||
| `ts/bundled-skills/graph_memory/persona/SKILL.md` | Persona Skill |
|
|
||||||
| `ts/bundled-skills/graph_memory/task/SKILL.md` | Task Skill |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 说明
|
|
||||||
|
|
||||||
- 每次修改文件前后更新此文件
|
|
||||||
- 记录每次修改的文件和操作
|
|
||||||
- 方便意外终止后恢复任务
|
|
||||||
187
README.md
187
README.md
@ -48,130 +48,103 @@ ts/
|
|||||||
|
|
||||||
## 在 WaterFlow 中使用
|
## 在 WaterFlow 中使用
|
||||||
|
|
||||||
本模块支持两种使用方式:**作为模块直接引用** 或 **作为 Skill 调用**。
|
本模块完全不动 WaterFlow 源码,只需在你的入口文件中注册即可。
|
||||||
|
|
||||||
### 方式一:作为模块直接引用(适合开发者集成)
|
### 快速开始(推荐)
|
||||||
|
|
||||||
#### 步骤 1:复制源码
|
#### 步骤 1:安装依赖
|
||||||
|
|
||||||
将本项目的 `ts/` 目录复制到你的 WaterFlow 项目中,例如:
|
|
||||||
|
|
||||||
```
|
|
||||||
你的WaterFlow项目/
|
|
||||||
├── src/
|
|
||||||
│ └── runtime/
|
|
||||||
│ └── core/
|
|
||||||
│ └── graph_memory/ # 从 ts/src/runtime/core/ 复制
|
|
||||||
└── ts/ # 或直接放在项目根目录
|
|
||||||
└── bundled-skills/ # Skill 文件
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 步骤 2:编译 TypeScript
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ts/
|
npm install /path/to/TrulyMEM-TrueHumanMEM/ts
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
编译后的文件会输出到 `ts/dist/` 目录。
|
或在 `package.json` 中添加:
|
||||||
|
|
||||||
#### 步骤 3:在代码中引用
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"trulymem-waterflow": "file:../TrulyMEM-TrueHumanMEM/ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后运行:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 步骤 2:在你的入口文件中注册
|
||||||
|
|
||||||
|
只需两行代码,完全不动 WaterFlow 源码:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createGraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool';
|
import { getPlatform } from 'waterflow-ts/dist/platform/index.js';
|
||||||
|
import { installTrulyMEM } from 'trulymem/tools';
|
||||||
|
|
||||||
// 创建工具实例,可以传入 sessionId 来区分不同会话
|
// 一行安装,返回配置好的 ToolRegistry
|
||||||
const tool = createGraphMemoryTool('my-session-id');
|
const registry = await installTrulyMEM(getPlatform(), 'my-session-id');
|
||||||
|
|
||||||
// 准备执行上下文
|
// 继续组装 WaterFlow...
|
||||||
const context = {
|
const toolExecutor = new ToolExecutor(registry);
|
||||||
toolCallId: 'call-123',
|
|
||||||
workingDirectory: '/project',
|
|
||||||
abortController: { signal: {} },
|
|
||||||
config: { timeout: 30000 },
|
|
||||||
logger: {
|
|
||||||
info: console.log,
|
|
||||||
warn: console.warn,
|
|
||||||
error: console.error,
|
|
||||||
debug: console.debug
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 写入记忆示例
|
|
||||||
const commitResult = await tool.handler({
|
|
||||||
action: 'commit',
|
|
||||||
params: {
|
|
||||||
triplets: [
|
|
||||||
{ subject: '用户', relation: '喜欢', object: '编程' },
|
|
||||||
{ subject: '用户', relation: '正在学习', object: 'TypeScript' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}, context);
|
|
||||||
|
|
||||||
console.log(commitResult);
|
|
||||||
// 输出: {"success":true,"data":{"createdEntities":4,"createdRelations":2}}
|
|
||||||
|
|
||||||
// 检索记忆示例
|
|
||||||
const recallResult = await tool.handler({
|
|
||||||
action: 'recall',
|
|
||||||
params: {
|
|
||||||
queryIntent: '用户 编程'
|
|
||||||
}
|
|
||||||
}, context);
|
|
||||||
|
|
||||||
console.log(recallResult);
|
|
||||||
// 输出: {"success":true,"data":{"entities":[...],"relations":[...],"message":"找到 X 个实体, Y 条关系"}}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 方式二:使用 Skill(推荐,适合 AI Agent 调用)
|
### 手动注册(更灵活)
|
||||||
|
|
||||||
|
如果你想自己控制 ToolRegistry 的创建:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getPlatform } from 'waterflow-ts/dist/platform/index.js';
|
||||||
|
import { initializeToolRegistry } from 'waterflow-ts/dist/runtime/core/tools/builtin/index.js';
|
||||||
|
import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||||
|
|
||||||
|
const platform = getPlatform();
|
||||||
|
const registry = initializeToolRegistry(platform);
|
||||||
|
|
||||||
|
// 注册图记忆工具
|
||||||
|
registerGraphMemoryTool(registry, 'my-session-id');
|
||||||
|
|
||||||
|
// 继续组装...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用 Skill(AI Agent 调用)
|
||||||
|
|
||||||
#### 步骤 1:配置 Skill 来源
|
#### 步骤 1:配置 Skill 来源
|
||||||
|
|
||||||
在你的 WaterFlow 项目中,找到 Skill 配置文件,添加 bundled 来源指向本项目的 Skill 目录:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// skill_interface.ts 或配置文件中
|
|
||||||
import { DEFAULT_SKILL_LOADER_CONFIG } from './skill_interface';
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
...DEFAULT_SKILL_LOADER_CONFIG,
|
...DEFAULT_SKILL_LOADER_CONFIG,
|
||||||
sources: {
|
sources: {
|
||||||
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
||||||
bundled: './ts/bundled-skills' // 指向本项目的 Skill 目录
|
bundled: './node_modules/trulymem-waterflow/bundled-skills'
|
||||||
},
|
},
|
||||||
enabledSources: ['project', 'bundled']
|
enabledSources: ['project', 'bundled']
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 步骤 2:通过 Agent 调用 Skill
|
#### 步骤 2:通过 Agent 调用
|
||||||
|
|
||||||
在你的 Agent 或 Workflow 中,通过 Tool 调用 Skill:
|
AI Agent 会自动读取 SKILL.md 并调用 `builtin:graph_memory` 工具。
|
||||||
|
|
||||||
```
|
|
||||||
使用 skill:graph_memory 进行以下操作:
|
|
||||||
|
|
||||||
1. 写入记忆: 我喜欢编程,正在学习 TypeScript
|
|
||||||
2. 检索记忆: 找出我和编程相关的记忆
|
|
||||||
```
|
|
||||||
|
|
||||||
或者通过代码调用:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 通过 SkillTool 调用
|
|
||||||
const skillResult = await skillTool.handler({
|
|
||||||
skill: 'graph_memory',
|
|
||||||
args: 'recall - queryIntent: "用户 学习"'
|
|
||||||
}, context);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 可用 Skill 列表
|
#### 可用 Skill 列表
|
||||||
|
|
||||||
| Skill 名称 | 功能 | 使用场景 |
|
| Skill 名称 | 功能 | 使用场景 |
|
||||||
|------------|------|----------|
|
|------------|------|----------|
|
||||||
| `graph_memory` | 记忆 CRUD | 读取/写入/删除记忆 |
|
| `graph_memory` | 记忆 CRUD | 读取/写入/删除记忆 |
|
||||||
| `graph_memory_persona` | 人设管理 | 设置 AI 角色性格 |
|
| `persona` | 人设管理 | 设置 AI 角色性格 |
|
||||||
| `graph_memory_task` | 任务管理 | 创建/更新长期任务 |
|
| `task` | 任务管理 | 创建/更新长期任务 |
|
||||||
|
|
||||||
|
#### Skill 定义格式说明
|
||||||
|
|
||||||
|
WaterFlow 的 SkillLoader 会从 `SKILL.md` 中提取:
|
||||||
|
|
||||||
|
- **name**: 从目录名提取(如 `graph_memory`、`persona`、`task`)
|
||||||
|
- **description**: 从 Markdown 的第一个 `#` 标题提取
|
||||||
|
- **allowed-tools**: 转换为 `allowedTools` 字段
|
||||||
|
- **arguments**: 正确映射到 SkillDefinition.arguments
|
||||||
|
- **user-invocable**: 转换为 `userInvocable` 字段
|
||||||
|
|
||||||
|
**注意**: `when_to_use` 信息已整合到 Markdown body 中,通过 SkillRegistry.search() 可匹配。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -187,7 +160,7 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
|
|
||||||
| Action | 说明 | 参数 |
|
| Action | 说明 | 参数 |
|
||||||
|--------|------|------|
|
|--------|------|------|
|
||||||
| `recall` | 检索记忆 | `queryIntent`, `seedEntities`, `sessionFilter` |
|
| `recall` | 检索记忆 | `queryIntent`, `seedEntities`, `depth`, `sessionFilter` |
|
||||||
| `commit` | 写入记忆 | `triplets`, `sessionId`, `turnId` |
|
| `commit` | 写入记忆 | `triplets`, `sessionId`, `turnId` |
|
||||||
| `purge` | 删除记忆 | `criteria`, `mode` |
|
| `purge` | 删除记忆 | `criteria`, `mode` |
|
||||||
| `introspect` | 查看状态 | - |
|
| `introspect` | 查看状态 | - |
|
||||||
@ -196,6 +169,7 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
| `task_create` | 创建任务 | `task_id`, `description`, `info_nodes` |
|
| `task_create` | 创建任务 | `task_id`, `description`, `info_nodes` |
|
||||||
| `task_set_state` | 设置状态 | `task_id`, `state` |
|
| `task_set_state` | 设置状态 | `task_id`, `state` |
|
||||||
| `task_delete` | 删除任务 | `task_id` |
|
| `task_delete` | 删除任务 | `task_id` |
|
||||||
|
| `task_link_info` | 关联信息到任务 | `task_id`, `info_node` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -239,6 +213,35 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 关联信息到任务
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "task_link_info",
|
||||||
|
"params": {
|
||||||
|
"task_id": "Task_学习TypeScript",
|
||||||
|
"info_node": "用户喜欢 React"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 名称映射
|
||||||
|
|
||||||
|
OpenAI/DeepSeek API 要求工具名称符合 `^[a-zA-Z0-9_-]+$` 格式(不含冒号)。
|
||||||
|
内部工具 ID 使用 `builtin:xxx` 格式,需映射后发送给 API。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mapToolIdToApiName, mapApiNameToToolId } from 'trulymem/tools';
|
||||||
|
|
||||||
|
// 发送给 API
|
||||||
|
const apiName = mapToolIdToApiName('builtin:graph_memory'); // -> 'graph_memory'
|
||||||
|
|
||||||
|
// 收到 tool_use 后映射回
|
||||||
|
const internalId = mapApiNameToToolId('graph_memory'); // -> 'builtin:graph_memory'
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|||||||
187
README_EN.md
187
README_EN.md
@ -49,130 +49,103 @@ ts/
|
|||||||
|
|
||||||
## Usage in WaterFlow
|
## Usage in WaterFlow
|
||||||
|
|
||||||
This module supports two usage methods: **import as module** or **use as Skill**.
|
This module requires **zero changes** to WaterFlow source code. Just register it in your entry file.
|
||||||
|
|
||||||
### Method 1: Import as Module (for developer integration)
|
### Quick Start (Recommended)
|
||||||
|
|
||||||
#### Step 1: Copy source files
|
#### Step 1: Install
|
||||||
|
|
||||||
Copy the `ts/` directory to your WaterFlow project, for example:
|
|
||||||
|
|
||||||
```
|
|
||||||
your-waterflow-project/
|
|
||||||
├── src/
|
|
||||||
│ └── runtime/
|
|
||||||
│ └── core/
|
|
||||||
│ └── graph_memory/ # Copy from ts/src/runtime/core/
|
|
||||||
└── ts/ # Or place in project root
|
|
||||||
└── bundled-skills/ # Skill files
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: Build TypeScript
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ts/
|
npm install /path/to/TrulyMEM-TrueHumanMEM/ts
|
||||||
npm install
|
|
||||||
npm run build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Compiled files will be output to `ts/dist/`.
|
Or add to `package.json`:
|
||||||
|
|
||||||
#### Step 3: Import in your code
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"trulymem-waterflow": "file:../TrulyMEM-TrueHumanMEM/ts"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 2: Register in your entry file
|
||||||
|
|
||||||
|
Just two lines, zero changes to WaterFlow:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { createGraphMemoryTool } from './runtime/core/tools/builtin/graph_memory_tool';
|
import { getPlatform } from 'waterflow-ts/dist/platform/index.js';
|
||||||
|
import { installTrulyMEM } from 'trulymem/tools';
|
||||||
|
|
||||||
// Create tool instance, can pass sessionId to distinguish different sessions
|
// One-line install, returns configured ToolRegistry
|
||||||
const tool = createGraphMemoryTool('my-session-id');
|
const registry = await installTrulyMEM(getPlatform(), 'my-session-id');
|
||||||
|
|
||||||
// Prepare execution context
|
// Continue assembling WaterFlow...
|
||||||
const context = {
|
const toolExecutor = new ToolExecutor(registry);
|
||||||
toolCallId: 'call-123',
|
|
||||||
workingDirectory: '/project',
|
|
||||||
abortController: { signal: {} },
|
|
||||||
config: { timeout: 30000 },
|
|
||||||
logger: {
|
|
||||||
info: console.log,
|
|
||||||
warn: console.warn,
|
|
||||||
error: console.error,
|
|
||||||
debug: console.debug
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Commit memory example
|
|
||||||
const commitResult = await tool.handler({
|
|
||||||
action: 'commit',
|
|
||||||
params: {
|
|
||||||
triplets: [
|
|
||||||
{ subject: 'User', relation: 'likes', object: 'Programming' },
|
|
||||||
{ subject: 'User', relation: 'is learning', object: 'TypeScript' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}, context);
|
|
||||||
|
|
||||||
console.log(commitResult);
|
|
||||||
// Output: {"success":true,"data":{"createdEntities":4,"createdRelations":2}}
|
|
||||||
|
|
||||||
// Recall memory example
|
|
||||||
const recallResult = await tool.handler({
|
|
||||||
action: 'recall',
|
|
||||||
params: {
|
|
||||||
queryIntent: 'User Programming'
|
|
||||||
}
|
|
||||||
}, context);
|
|
||||||
|
|
||||||
console.log(recallResult);
|
|
||||||
// Output: {"success":true,"data":{"entities":[...],"relations":[...],"message":"Found X entities, Y relations"}}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Method 2: Use Skill (recommended for AI Agent)
|
### Manual Registration (More control)
|
||||||
|
|
||||||
|
If you want to control ToolRegistry creation yourself:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { getPlatform } from 'waterflow-ts/dist/platform/index.js';
|
||||||
|
import { initializeToolRegistry } from 'waterflow-ts/dist/runtime/core/tools/builtin/index.js';
|
||||||
|
import { registerGraphMemoryTool } from 'trulymem/tools';
|
||||||
|
|
||||||
|
const platform = getPlatform();
|
||||||
|
const registry = initializeToolRegistry(platform);
|
||||||
|
|
||||||
|
// Register graph memory tool
|
||||||
|
registerGraphMemoryTool(registry, 'my-session-id');
|
||||||
|
|
||||||
|
// Continue assembling...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Use Skill (AI Agent)
|
||||||
|
|
||||||
#### Step 1: Configure Skill source
|
#### Step 1: Configure Skill source
|
||||||
|
|
||||||
In your WaterFlow project, find the Skill configuration file and add bundled source pointing to this project's Skill directory:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// skill_interface.ts or config file
|
|
||||||
import { DEFAULT_SKILL_LOADER_CONFIG } from './skill_interface';
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
...DEFAULT_SKILL_LOADER_CONFIG,
|
...DEFAULT_SKILL_LOADER_CONFIG,
|
||||||
sources: {
|
sources: {
|
||||||
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
...DEFAULT_SKILL_LOADER_CONFIG.sources,
|
||||||
bundled: './ts/bundled-skills' // Point to this project's Skill directory
|
bundled: './node_modules/trulymem-waterflow/bundled-skills'
|
||||||
},
|
},
|
||||||
enabledSources: ['project', 'bundled']
|
enabledSources: ['project', 'bundled']
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Step 2: Call Skill via Agent
|
#### Step 2: Call via Agent
|
||||||
|
|
||||||
In your Agent or Workflow, call Skill via Tool:
|
AI Agent automatically reads SKILL.md and calls `builtin:graph_memory` tool.
|
||||||
|
|
||||||
```
|
|
||||||
Use skill:graph_memory for:
|
|
||||||
|
|
||||||
1. Commit memory: I like programming, learning TypeScript
|
|
||||||
2. Recall memory: Find memories related to me and programming
|
|
||||||
```
|
|
||||||
|
|
||||||
Or call via code:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// Call via SkillTool
|
|
||||||
const skillResult = await skillTool.handler({
|
|
||||||
skill: 'graph_memory',
|
|
||||||
args: 'recall - queryIntent: "User learning"'
|
|
||||||
}, context);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Available Skills
|
#### Available Skills
|
||||||
|
|
||||||
| Skill Name | Function | Use Case |
|
| Skill Name | Function | Use Case |
|
||||||
|------------|----------|----------|
|
|------------|----------|----------|
|
||||||
| `graph_memory` | Memory CRUD | Read/Write/Delete memories |
|
| `graph_memory` | Memory CRUD | Read/Write/Delete memories |
|
||||||
| `graph_memory_persona` | Persona management | Set AI role/personality |
|
| `persona` | Persona management | Set AI role/personality |
|
||||||
| `graph_memory_task` | Task management | Create/update long-term tasks |
|
| `task` | Task management | Create/update long-term tasks |
|
||||||
|
|
||||||
|
#### Skill Definition Format
|
||||||
|
|
||||||
|
WaterFlow's SkillLoader extracts from `SKILL.md`:
|
||||||
|
|
||||||
|
- **name**: Extracted from directory name (e.g., `graph_memory`, `persona`, `task`)
|
||||||
|
- **description**: Extracted from first Markdown `#` heading
|
||||||
|
- **allowed-tools**: Converted to `allowedTools` field
|
||||||
|
- **arguments**: Properly mapped to SkillDefinition.arguments
|
||||||
|
- **user-invocable**: Converted to `userInvocable` field
|
||||||
|
|
||||||
|
**Note**: `when_to_use` info is integrated into Markdown body, searchable via SkillRegistry.search().
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -188,7 +161,7 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
|
|
||||||
| Action | Description | Parameters |
|
| Action | Description | Parameters |
|
||||||
|--------|-------------|------------|
|
|--------|-------------|------------|
|
||||||
| `recall` | Retrieve memories | `queryIntent`, `seedEntities`, `sessionFilter` |
|
| `recall` | Retrieve memories | `queryIntent`, `seedEntities`, `depth`, `sessionFilter` |
|
||||||
| `commit` | Commit memories | `triplets`, `sessionId`, `turnId` |
|
| `commit` | Commit memories | `triplets`, `sessionId`, `turnId` |
|
||||||
| `purge` | Delete memories | `criteria`, `mode` |
|
| `purge` | Delete memories | `criteria`, `mode` |
|
||||||
| `introspect` | Inspect status | - |
|
| `introspect` | Inspect status | - |
|
||||||
@ -197,6 +170,7 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
| `task_create` | Create task | `task_id`, `description`, `info_nodes` |
|
| `task_create` | Create task | `task_id`, `description`, `info_nodes` |
|
||||||
| `task_set_state` | Set state | `task_id`, `state` |
|
| `task_set_state` | Set state | `task_id`, `state` |
|
||||||
| `task_delete` | Delete task | `task_id` |
|
| `task_delete` | Delete task | `task_id` |
|
||||||
|
| `task_link_info` | Link info to task | `task_id`, `info_node` |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -240,6 +214,35 @@ const tool = new GraphMemoryTool(sessionId?: string);
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Link Info to Task
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"action": "task_link_info",
|
||||||
|
"params": {
|
||||||
|
"task_id": "Task_LearnTypeScript",
|
||||||
|
"info_node": "User likes React"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API Name Mapping
|
||||||
|
|
||||||
|
OpenAI/DeepSeek API requires tool names to match `^[a-zA-Z0-9_-]+$` (no colons).
|
||||||
|
Internal tool IDs use `builtin:xxx` format and must be mapped before sending to API.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { mapToolIdToApiName, mapApiNameToToolId } from 'trulymem/tools';
|
||||||
|
|
||||||
|
// Send to API
|
||||||
|
const apiName = mapToolIdToApiName('builtin:graph_memory'); // -> 'graph_memory'
|
||||||
|
|
||||||
|
// Map back after receiving tool_use
|
||||||
|
const internalId = mapApiNameToToolId('graph_memory'); // -> 'builtin:graph_memory'
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@ -1,811 +0,0 @@
|
|||||||
# TrulyMEM → WaterFlow 迁移设计文档
|
|
||||||
|
|
||||||
**版本**: 1.0
|
|
||||||
**日期**: 2026-04-15
|
|
||||||
**目标**: 用 TypeScript 完全重写 TrulyMEM 的图记忆能力,集成到 WaterFlow
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 一、迁移策略
|
|
||||||
|
|
||||||
### 1.1 核心原则
|
|
||||||
|
|
||||||
- **完全重写**: 不保留 Python 代码,用 TypeScript 实现
|
|
||||||
- **架构一致**: 遵循 WaterFlow 的架构风格和设计模式
|
|
||||||
- **原生集成**: 作为 WaterFlow 的内置模块,而非外部依赖
|
|
||||||
|
|
||||||
### 1.2 迁移范围
|
|
||||||
|
|
||||||
| TrulyMEM (Python) | WaterFlow (TypeScript) | 说明 |
|
|
||||||
|-------------------|------------------------|------|
|
|
||||||
| `EmbeddedGraphDB` | `GraphDatabase` | SQLite 图数据库重写 |
|
|
||||||
| `GraphMemoryClient` | `MemoryService` | 记忆服务 |
|
|
||||||
| 12 个记忆工具 | `GraphMemoryTool` | WaterFlow Tool 接口 |
|
|
||||||
| System Prompt | 提示词模板 | 提示词管理 |
|
|
||||||
| TUI | ❌ 不迁移 | WaterFlow 无 TUI |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 二、架构设计
|
|
||||||
|
|
||||||
### 2.1 整体架构
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ WaterFlow Core │
|
|
||||||
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────────┐ │
|
|
||||||
│ │ Agent / │───>│ Query │───>│ ToolExecutor │ │
|
|
||||||
│ │ Workflow │ │ Engine │ │ │ │
|
|
||||||
│ └─────────────┘ └──────────────┘ └───────────┬────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────────────────────────┘ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
||||||
│ │ GraphMemory Module (NEW) │ │
|
|
||||||
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ │
|
|
||||||
│ │ │ GraphMemoryTool │ │ GraphDatabase │ │ MemoryService │ │ │
|
|
||||||
│ │ │ (Tool Interface)│ │ (SQLite Graph) │ │ (LLM Integration)│ │ │
|
|
||||||
│ │ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ │ │
|
|
||||||
│ │ │ │ │ │ │
|
|
||||||
│ │ └────────────────────┼────────────────────┘ │ │
|
|
||||||
│ │ ▼ │ │
|
|
||||||
│ │ ┌─────────────────────┐ │ │
|
|
||||||
│ │ │ GraphMemoryStore │ │ │
|
|
||||||
│ │ │ (In-Memory Cache) │ │ │
|
|
||||||
│ │ └─────────────────────┘ │ │
|
|
||||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 模块职责
|
|
||||||
|
|
||||||
| 模块 | 职责 | 位置 |
|
|
||||||
|------|------|------|
|
|
||||||
| `GraphMemoryTool` | WaterFlow Tool 接口,暴露记忆能力 | `runtime/core/tools/builtin/graph_memory/` |
|
|
||||||
| `GraphDatabase` | SQLite 图数据库实现 | `runtime/core/graph_memory/database/` |
|
|
||||||
| `MemoryService` | 封装业务逻辑 | `runtime/core/graph_memory/service/` |
|
|
||||||
| `GraphMemoryStore` | 内存缓存,加速查询 | `runtime/core/graph_memory/store/` |
|
|
||||||
| `SystemPrompt` | 提示词模板管理 | `runtime/core/graph_memory/prompts/` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 三、目录结构
|
|
||||||
|
|
||||||
### 3.1 新增目录
|
|
||||||
|
|
||||||
```
|
|
||||||
WaterFlow/ts/src/
|
|
||||||
├── runtime/core/
|
|
||||||
│ ├── graph_memory/ # 新增: 图记忆模块
|
|
||||||
│ │ ├── index.ts # 模块导出
|
|
||||||
│ │ ├── types.ts # 类型定义
|
|
||||||
│ │ ├── database/ # 图数据库实现
|
|
||||||
│ │ │ ├── index.ts
|
|
||||||
│ │ │ ├── graph_database.ts # 主类
|
|
||||||
│ │ │ ├── entity_store.ts # 实体存储
|
|
||||||
│ │ │ └── relation_store.ts # 关系存储
|
|
||||||
│ │ ├── service/ # 服务层
|
|
||||||
│ │ │ ├── index.ts
|
|
||||||
│ │ │ ├── memory_service.ts # 记忆服务
|
|
||||||
│ │ │ ├── recall_service.ts # 检索服务
|
|
||||||
│ │ │ └── task_service.ts # 任务服务
|
|
||||||
│ │ ├── store/ # 缓存层
|
|
||||||
│ │ │ ├── index.ts
|
|
||||||
│ │ │ └── memory_cache.ts
|
|
||||||
│ │ └── prompts/ # 提示词
|
|
||||||
│ │ └── system_prompt.ts
|
|
||||||
│ │
|
|
||||||
│ └── tools/builtin/
|
|
||||||
│ └── graph_memory/ # GraphMemory Tool
|
|
||||||
│ ├── index.ts
|
|
||||||
│ ├── graph_memory_tool.ts # Tool 实现
|
|
||||||
│ ├── types.ts # Tool 参数类型
|
|
||||||
│ └── tool_registry.ts # 自动注册
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 修改文件
|
|
||||||
|
|
||||||
| 文件 | 修改内容 |
|
|
||||||
|------|----------|
|
|
||||||
| `runtime/core/tools/builtin/index.ts` | 注册 GraphMemoryTool |
|
|
||||||
| `shared/types/index.ts` | 导出图记忆类型 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 四、核心类型定义
|
|
||||||
|
|
||||||
### 4.1 图数据库类型
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/runtime/core/graph_memory/types.ts
|
|
||||||
|
|
||||||
export interface Entity {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
mentionCount: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Relation {
|
|
||||||
id: string;
|
|
||||||
sourceId: string;
|
|
||||||
targetId: string;
|
|
||||||
relationType: string;
|
|
||||||
confidence: number;
|
|
||||||
status: RelationStatus;
|
|
||||||
sessionId: string;
|
|
||||||
turnId: number;
|
|
||||||
createdAt: Date;
|
|
||||||
updatedAt: Date;
|
|
||||||
dateBucket: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type RelationStatus = 'active' | 'deleted' | 'archived' | 'superseded';
|
|
||||||
|
|
||||||
export interface Triplet {
|
|
||||||
subject: string;
|
|
||||||
relation: string;
|
|
||||||
object: string;
|
|
||||||
confidence?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RecallParams {
|
|
||||||
queryIntent: string;
|
|
||||||
seedEntities?: string[];
|
|
||||||
depth?: number;
|
|
||||||
timeRange?: { days: number };
|
|
||||||
sessionFilter?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommitParams {
|
|
||||||
triplets: Triplet[];
|
|
||||||
entityTypes?: Record<string, string>;
|
|
||||||
temporalTag?: string;
|
|
||||||
sessionId?: string;
|
|
||||||
turnId?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PurgeParams {
|
|
||||||
criteria: {
|
|
||||||
subject?: string;
|
|
||||||
target?: string;
|
|
||||||
relation?: string;
|
|
||||||
sessionId?: string;
|
|
||||||
};
|
|
||||||
mode?: 'soft' | 'hard' | 'supersede';
|
|
||||||
newRelation?: { relation: string; target: string };
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TaskState = '进行中' | '已完成' | '已暂停' | '已取消';
|
|
||||||
|
|
||||||
export interface MemoryStats {
|
|
||||||
entityCount: number;
|
|
||||||
relationCount: number;
|
|
||||||
sessionId?: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Tool 参数类型
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/runtime/core/tools/builtin/graph_memory/types.ts
|
|
||||||
|
|
||||||
export type GraphMemoryAction =
|
|
||||||
| 'recall' | 'commit' | 'purge' | 'introspect' | 'archive' | 'cleanup'
|
|
||||||
| 'persona_update' | 'persona_clear'
|
|
||||||
| 'task_create' | 'task_set_state' | 'task_delete' | 'task_link_info';
|
|
||||||
|
|
||||||
export interface GraphMemoryToolInput {
|
|
||||||
action: GraphMemoryAction;
|
|
||||||
params: Record<string, unknown>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 五、核心实现
|
|
||||||
|
|
||||||
### 5.1 GraphDatabase 实现
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/runtime/core/graph_memory/database/graph_database.ts
|
|
||||||
|
|
||||||
export class GraphDatabase {
|
|
||||||
private db: Database;
|
|
||||||
|
|
||||||
constructor(dbPath: string) {
|
|
||||||
this.db = new Database(dbPath);
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
private initialize(): void {
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS entities (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
name TEXT UNIQUE NOT NULL,
|
|
||||||
type TEXT,
|
|
||||||
mention_count INTEGER DEFAULT 1,
|
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS relations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
source_id TEXT NOT NULL,
|
|
||||||
target_id TEXT NOT NULL,
|
|
||||||
relation_type TEXT NOT NULL,
|
|
||||||
confidence REAL DEFAULT 1.0,
|
|
||||||
status TEXT DEFAULT 'active',
|
|
||||||
session_id TEXT,
|
|
||||||
turn_id INTEGER,
|
|
||||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
date_bucket TEXT
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// 索引
|
|
||||||
this.db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_entity_name ON entities(name);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status);
|
|
||||||
`);
|
|
||||||
}
|
|
||||||
|
|
||||||
async recall(params: RecallParams): Promise<RecallResult> {
|
|
||||||
const { queryIntent, seedEntities, sessionFilter } = params;
|
|
||||||
const keywords = queryIntent.split(/[,\s]+/).filter(k => k.length > 0);
|
|
||||||
const entities: Entity[] = [];
|
|
||||||
const relations: Relation[] = [];
|
|
||||||
const entityIds = new Set<string>();
|
|
||||||
|
|
||||||
// 搜索实体
|
|
||||||
for (const keyword of keywords) {
|
|
||||||
const rows = this.db.exec(
|
|
||||||
`SELECT * FROM entities WHERE LOWER(name) LIKE ? LIMIT 50`,
|
|
||||||
[`%${keyword.toLowerCase()}%`]
|
|
||||||
);
|
|
||||||
for (const row of rows) {
|
|
||||||
if (!entityIds.has(row.id)) {
|
|
||||||
entityIds.add(row.id);
|
|
||||||
entities.push(this.rowToEntity(row));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索关系
|
|
||||||
if (entityIds.size > 0) {
|
|
||||||
const placeholders = Array.from(entityIds).map(() => '?').join(',');
|
|
||||||
let query = `
|
|
||||||
SELECT r.*, e1.name as source_name, e2.name as target_name
|
|
||||||
FROM relations r
|
|
||||||
JOIN entities e1 ON r.source_id = e1.id
|
|
||||||
JOIN entities e2 ON r.target_id = e2.id
|
|
||||||
WHERE (r.source_id IN (${placeholders}) OR r.target_id IN (${placeholders}))
|
|
||||||
AND r.status = 'active'
|
|
||||||
`;
|
|
||||||
const queryParams = [...entityIds, ...entityIds];
|
|
||||||
|
|
||||||
if (sessionFilter) {
|
|
||||||
query += ` AND r.session_id = ?`;
|
|
||||||
queryParams.push(sessionFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = this.db.exec(query, queryParams);
|
|
||||||
for (const row of rows) {
|
|
||||||
relations.push(this.rowToRelation(row));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { entities, relations, message: `找到 ${entities.length} 个实体, ${relations.length} 条关系` };
|
|
||||||
}
|
|
||||||
|
|
||||||
async commit(params: CommitParams): Promise<{ createdEntities: number; createdRelations: number }> {
|
|
||||||
const { triplets, sessionId, turnId } = params;
|
|
||||||
let createdEntities = 0;
|
|
||||||
let createdRelations = 0;
|
|
||||||
|
|
||||||
for (const triplet of triplets) {
|
|
||||||
const sourceId = this.upsertEntity(triplet.subject);
|
|
||||||
const targetId = this.upsertEntity(triplet.object);
|
|
||||||
|
|
||||||
this.db.exec(`
|
|
||||||
INSERT INTO relations (id, source_id, target_id, relation_type, confidence, session_id, turn_id, status)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'active')
|
|
||||||
`, [this.generateId(), sourceId, targetId, triplet.relation, triplet.confidence || 1.0, sessionId, turnId || 0]);
|
|
||||||
|
|
||||||
createdEntities += 2;
|
|
||||||
createdRelations++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { createdEntities, createdRelations };
|
|
||||||
}
|
|
||||||
|
|
||||||
async purge(params: PurgeParams): Promise<{ deleted: number; mode: string }> {
|
|
||||||
const { criteria, mode = 'soft' } = params;
|
|
||||||
const conditions: string[] = ['status = ?'];
|
|
||||||
const values: unknown[] = ['active'];
|
|
||||||
|
|
||||||
if (criteria.subject) {
|
|
||||||
conditions.push(`source_id IN (SELECT id FROM entities WHERE name = ?)`);
|
|
||||||
values.push(criteria.subject);
|
|
||||||
}
|
|
||||||
|
|
||||||
const whereClause = conditions.join(' AND ');
|
|
||||||
const result = this.db.exec(`UPDATE relations SET status = 'deleted' WHERE ${whereClause}`, values);
|
|
||||||
|
|
||||||
return { deleted: result.length, mode };
|
|
||||||
}
|
|
||||||
|
|
||||||
async introspect(): Promise<MemoryStats> {
|
|
||||||
const entityCount = this.db.exec(`SELECT COUNT(*) as c FROM entities`)[0]?.c || 0;
|
|
||||||
const relationCount = this.db.exec(`SELECT COUNT(*) as c FROM relations WHERE status = 'active'`)[0]?.c || 0;
|
|
||||||
return { entityCount, relationCount };
|
|
||||||
}
|
|
||||||
|
|
||||||
private upsertEntity(name: string): string {
|
|
||||||
const existing = this.db.exec(`SELECT id FROM entities WHERE name = ?`, [name]);
|
|
||||||
if (existing.length > 0) {
|
|
||||||
this.db.exec(`UPDATE entities SET mention_count = mention_count + 1 WHERE name = ?`, [name]);
|
|
||||||
return existing[0].id;
|
|
||||||
}
|
|
||||||
const id = this.generateId();
|
|
||||||
this.db.exec(`INSERT INTO entities (id, name, type) VALUES (?, ?, ?)`, [id, name, 'unknown']);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateId(): string {
|
|
||||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private rowToEntity(row: any): Entity {
|
|
||||||
return {
|
|
||||||
id: row.id, name: row.name, type: row.type || 'unknown',
|
|
||||||
mentionCount: row.mention_count || 1,
|
|
||||||
createdAt: new Date(row.created_at), updatedAt: new Date(row.updated_at)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private rowToRelation(row: any): Relation {
|
|
||||||
return {
|
|
||||||
id: row.id, sourceId: row.source_id, targetId: row.target_id,
|
|
||||||
relationType: row.relation_type, confidence: row.confidence || 1.0,
|
|
||||||
status: row.status || 'active', sessionId: row.session_id || '',
|
|
||||||
turnId: row.turn_id || 0,
|
|
||||||
createdAt: new Date(row.created_at), updatedAt: new Date(row.updated_at),
|
|
||||||
dateBucket: row.date_bucket || ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void { this.db.close(); }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 GraphMemoryTool 实现
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/runtime/core/tools/builtin/graph_memory/graph_memory_tool.ts
|
|
||||||
|
|
||||||
import type { Tool, ToolExecutionContext, ToolInputSchema } from '../tool_interface';
|
|
||||||
import { GraphDatabase } from '../../../graph_memory/database/graph_database';
|
|
||||||
import { MemoryService } from '../../../graph_memory/service/memory_service';
|
|
||||||
|
|
||||||
export class GraphMemoryTool implements Tool {
|
|
||||||
readonly id = 'builtin:graph_memory';
|
|
||||||
readonly name = 'GraphMemory';
|
|
||||||
readonly description = `图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
|
||||||
|
|
||||||
操作:
|
|
||||||
- recall: 检索记忆
|
|
||||||
- commit: 写入记忆
|
|
||||||
- purge: 删除记忆
|
|
||||||
- introspect: 查看状态
|
|
||||||
- persona_update/clear: 人设管理
|
|
||||||
- task_create/set_state/delete: 任务管理`;
|
|
||||||
|
|
||||||
readonly category = 'analysis';
|
|
||||||
readonly permissionLevel: 'safe' = 'safe';
|
|
||||||
readonly inputSchema: ToolInputSchema = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
action: {
|
|
||||||
type: 'string',
|
|
||||||
enum: ['recall', 'commit', 'purge', 'introspect', 'persona_update', 'persona_clear',
|
|
||||||
'task_create', 'task_set_state', 'task_delete', 'task_link_info'],
|
|
||||||
description: '记忆操作类型'
|
|
||||||
},
|
|
||||||
params: { type: 'object', description: '操作参数' }
|
|
||||||
},
|
|
||||||
required: ['action', 'params']
|
|
||||||
};
|
|
||||||
|
|
||||||
private db: GraphDatabase;
|
|
||||||
private service: MemoryService;
|
|
||||||
|
|
||||||
constructor(config: { dbPath: string; sessionId?: string }) {
|
|
||||||
this.db = new GraphDatabase(config.dbPath);
|
|
||||||
this.service = new MemoryService(this.db, config.sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
async handler(params: Record<string, unknown>, context: ToolExecutionContext): Promise<string> {
|
|
||||||
const action = params.action as string;
|
|
||||||
const actionParams = params.params as Record<string, unknown>;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await this.executeAction(action, actionParams);
|
|
||||||
return JSON.stringify({ success: true, data: result }, null, 2);
|
|
||||||
} catch (error) {
|
|
||||||
return JSON.stringify({
|
|
||||||
success: false,
|
|
||||||
error: { type: 'execution_error', message: error instanceof Error ? error.message : String(error) }
|
|
||||||
}, null, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async executeAction(action: string, params: Record<string, unknown>): Promise<unknown> {
|
|
||||||
switch (action) {
|
|
||||||
case 'recall': return this.service.recall(params as any);
|
|
||||||
case 'commit': return this.service.commit(params as any);
|
|
||||||
case 'purge': return this.service.purge(params as any);
|
|
||||||
case 'introspect': return this.service.introspect();
|
|
||||||
case 'persona_update': return this.service.updatePersona(params);
|
|
||||||
case 'persona_clear': return this.service.clearPersona(params);
|
|
||||||
case 'task_create': return this.service.createTask(params);
|
|
||||||
case 'task_set_state': return this.service.setTaskState(params);
|
|
||||||
case 'task_delete': return this.service.deleteTask(params);
|
|
||||||
default: throw new Error(`Unknown action: ${action}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close(): void { this.db.close(); }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.3 MemoryService 实现
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/runtime/core/graph_memory/service/memory_service.ts
|
|
||||||
|
|
||||||
import { GraphDatabase } from '../database/graph_database';
|
|
||||||
import type { RecallParams, CommitParams, PurgeParams } from '../types';
|
|
||||||
|
|
||||||
export class MemoryService {
|
|
||||||
private db: GraphDatabase;
|
|
||||||
private sessionId: string;
|
|
||||||
|
|
||||||
constructor(db: GraphDatabase, sessionId?: string) {
|
|
||||||
this.db = db;
|
|
||||||
this.sessionId = sessionId || `session-${Date.now()}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async recall(params: RecallParams) {
|
|
||||||
return this.db.recall({ ...params, sessionFilter: params.sessionFilter || this.sessionId });
|
|
||||||
}
|
|
||||||
|
|
||||||
async commit(params: CommitParams) {
|
|
||||||
return this.db.commit({ ...params, sessionId: params.sessionId || this.sessionId });
|
|
||||||
}
|
|
||||||
|
|
||||||
async purge(params: PurgeParams) {
|
|
||||||
return this.db.purge(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
async introspect() {
|
|
||||||
const stats = await this.db.introspect();
|
|
||||||
return { ...stats, sessionId: this.sessionId };
|
|
||||||
}
|
|
||||||
|
|
||||||
async updatePersona(params: Record<string, unknown>) {
|
|
||||||
const attributes = params.attributes as Array<{ attribute: string; value: string }>;
|
|
||||||
const mode = params.mode as string || 'merge';
|
|
||||||
|
|
||||||
if (mode === 'replace') {
|
|
||||||
await this.db.purge({ criteria: { subject: 'AI' }, mode: 'soft' });
|
|
||||||
}
|
|
||||||
|
|
||||||
const triplets = attributes.map(attr => ({
|
|
||||||
subject: 'AI', relation: attr.attribute, object: attr.value, confidence: 1.0
|
|
||||||
}));
|
|
||||||
|
|
||||||
await this.commit({ triplets });
|
|
||||||
return { status: 'success', updatedAttributes: attributes.length };
|
|
||||||
}
|
|
||||||
|
|
||||||
async clearPersona(params: Record<string, unknown>) {
|
|
||||||
if (params.confirm === false) return { status: 'cancelled', deletedCount: 0 };
|
|
||||||
const result = await this.purge({ criteria: { subject: 'AI' }, mode: 'soft' });
|
|
||||||
return { status: 'success', deletedCount: result.deleted };
|
|
||||||
}
|
|
||||||
|
|
||||||
async createTask(params: Record<string, unknown>) {
|
|
||||||
const taskId = params.task_id as string;
|
|
||||||
const description = params.description as string;
|
|
||||||
const infoNodes = (params.info_nodes as string[]) || [];
|
|
||||||
|
|
||||||
await this.commit({
|
|
||||||
triplets: [
|
|
||||||
{ subject: taskId, relation: 'is_type', object: 'TaskNode' },
|
|
||||||
{ subject: taskId, relation: 'has_description', object: description },
|
|
||||||
{ subject: taskId, relation: 'HAS_STATE', object: 'State_进行中' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (infoNodes.length > 0) {
|
|
||||||
await this.commit({
|
|
||||||
triplets: infoNodes.map(node => ({ subject: taskId, relation: 'CONTAINS_INFO', object: node }))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return { status: 'success', taskId };
|
|
||||||
}
|
|
||||||
|
|
||||||
async setTaskState(params: Record<string, unknown>) {
|
|
||||||
const taskId = params.task_id as string;
|
|
||||||
const state = params.state as string;
|
|
||||||
|
|
||||||
await this.purge({ criteria: { subject: taskId, relation: 'HAS_STATE' }, mode: 'soft' });
|
|
||||||
await this.commit({ triplets: [{ subject: taskId, relation: 'HAS_STATE', object: `State_${state}` }] });
|
|
||||||
|
|
||||||
return { status: 'success', newState: state };
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteTask(params: Record<string, unknown>) {
|
|
||||||
const taskId = params.task_id as string;
|
|
||||||
await this.purge({ criteria: { subject: taskId }, mode: 'soft' });
|
|
||||||
return { status: 'success', taskId };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 六、测试方案
|
|
||||||
|
|
||||||
### 6.1 测试文件结构
|
|
||||||
|
|
||||||
```
|
|
||||||
WaterFlow/ts/tests/
|
|
||||||
├── runtime/core/graph_memory/
|
|
||||||
│ ├── database/
|
|
||||||
│ │ └── graph_database.test.ts # 15+ 测试
|
|
||||||
│ └── service/
|
|
||||||
│ └── memory_service.test.ts # 12+ 测试
|
|
||||||
└── runtime/core/tools/builtin/
|
|
||||||
└── graph_memory/
|
|
||||||
└── graph_memory_tool.test.ts # 15+ 测试
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 数据库测试
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tests/runtime/core/graph_memory/database/graph_database.test.ts
|
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
||||||
import { GraphDatabase } from '../../../../../src/runtime/core/graph_memory/database/graph_database';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
describe('GraphDatabase', () => {
|
|
||||||
const testDbPath = '/tmp/test_graph_memory.db';
|
|
||||||
let db: GraphDatabase;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath);
|
|
||||||
db = new GraphDatabase(testDbPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
db.close();
|
|
||||||
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('commit', () => {
|
|
||||||
it('should create entities and relations', async () => {
|
|
||||||
const result = await db.commit({
|
|
||||||
triplets: [
|
|
||||||
{ subject: '用户', relation: '喜欢', object: 'Python' },
|
|
||||||
{ subject: '用户', relation: '正在学习', object: 'TypeScript' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
expect(result.createdEntities).toBe(3);
|
|
||||||
expect(result.createdRelations).toBe(2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('recall', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await db.commit({
|
|
||||||
triplets: [
|
|
||||||
{ subject: '用户', relation: '喜欢', object: 'Python' },
|
|
||||||
{ subject: 'Python', relation: '是', object: '编程语言' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should recall by keyword', async () => {
|
|
||||||
const result = await db.recall({ queryIntent: 'Python' });
|
|
||||||
expect(result.entities.some(e => e.name === 'Python')).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should recall relations', async () => {
|
|
||||||
const result = await db.recall({ queryIntent: '用户,Python' });
|
|
||||||
expect(result.relations.length).toBeGreaterThan(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('purge', () => {
|
|
||||||
beforeEach(async () => {
|
|
||||||
await db.commit({
|
|
||||||
triplets: [{ subject: '旧信息', relation: 'is', object: '垃圾' }]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should soft delete relations', async () => {
|
|
||||||
const result = await db.purge({ criteria: { subject: '旧信息' }, mode: 'soft' });
|
|
||||||
expect(result.deleted).toBeGreaterThan(0);
|
|
||||||
expect(result.mode).toBe('soft');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('introspect', () => {
|
|
||||||
it('should return statistics', async () => {
|
|
||||||
await db.commit({ triplets: [{ subject: 'A', relation: 'relates', object: 'B' }] });
|
|
||||||
const stats = await db.introspect();
|
|
||||||
expect(stats.entityCount).toBe(2);
|
|
||||||
expect(stats.relationCount).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.3 服务层测试
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tests/runtime/core/graph_memory/service/memory_service.test.ts
|
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { MemoryService } from '../../../../../src/runtime/core/graph_memory/service/memory_service';
|
|
||||||
import { GraphDatabase } from '../../../../../src/runtime/core/graph_memory/database/graph_database';
|
|
||||||
|
|
||||||
describe('MemoryService', () => {
|
|
||||||
const testDbPath = '/tmp/test_memory_service.db';
|
|
||||||
let db: GraphDatabase;
|
|
||||||
let service: MemoryService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
db = new GraphDatabase(testDbPath);
|
|
||||||
service = new MemoryService(db, 'test-session');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('persona management', () => {
|
|
||||||
it('should update persona', async () => {
|
|
||||||
const result = await service.updatePersona({
|
|
||||||
attributes: [{ attribute: '角色', value: '猫娘' }],
|
|
||||||
mode: 'replace'
|
|
||||||
});
|
|
||||||
expect(result.status).toBe('success');
|
|
||||||
expect(result.updatedAttributes).toBe(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear persona', async () => {
|
|
||||||
await service.updatePersona({ attributes: [{ attribute: '角色', value: '猫娘' }] });
|
|
||||||
const result = await service.clearPersona({ confirm: true });
|
|
||||||
expect(result.status).toBe('success');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('task management', () => {
|
|
||||||
it('should create task', async () => {
|
|
||||||
const result = await service.createTask({
|
|
||||||
task_id: 'Task_Test',
|
|
||||||
description: '测试任务',
|
|
||||||
info_nodes: ['info1']
|
|
||||||
});
|
|
||||||
expect(result.taskId).toBe('Task_Test');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set task state', async () => {
|
|
||||||
await service.createTask({ task_id: 'Task_State', description: '测试' });
|
|
||||||
const result = await service.setTaskState({ task_id: 'Task_State', state: '已完成' });
|
|
||||||
expect(result.newState).toBe('已完成');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.4 Tool 接口测试
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// tests/runtime/core/tools/builtin/graph_memory/graph_memory_tool.test.ts
|
|
||||||
|
|
||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
|
||||||
import { GraphMemoryTool } from '../../../../../src/runtime/core/tools/builtin/graph_memory/graph_memory_tool';
|
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
describe('GraphMemoryTool', () => {
|
|
||||||
const testDbPath = '/tmp/test_graph_memory_tool.db';
|
|
||||||
let tool: GraphMemoryTool;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
if (fs.existsSync(testDbPath)) fs.unlinkSync(testDbPath);
|
|
||||||
tool = new GraphMemoryTool({ dbPath: testDbPath, sessionId: 'test' });
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have correct metadata', () => {
|
|
||||||
expect(tool.id).toBe('builtin:graph_memory');
|
|
||||||
expect(tool.name).toBe('GraphMemory');
|
|
||||||
expect(tool.category).toBe('analysis');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('recall', () => {
|
|
||||||
it('should execute recall', async () => {
|
|
||||||
await tool.handler({ action: 'commit', params: { triplets: [{ subject: 'Test', relation: 't', object: 'D' }] } }, mockContext());
|
|
||||||
const result = await tool.handler({ action: 'recall', params: { query_intent: 'Test' } }, mockContext());
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed.success).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('commit', () => {
|
|
||||||
it('should execute commit', async () => {
|
|
||||||
const result = await tool.handler({
|
|
||||||
action: 'commit',
|
|
||||||
params: { triplets: [{ subject: '用户', relation: '喜欢', object: 'AI' }] }
|
|
||||||
}, mockContext());
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed.success).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('error handling', () => {
|
|
||||||
it('should return error for unknown action', async () => {
|
|
||||||
const result = await tool.handler({ action: 'unknown', params: {} }, mockContext());
|
|
||||||
const parsed = JSON.parse(result);
|
|
||||||
expect(parsed.success).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function mockContext() {
|
|
||||||
return {
|
|
||||||
toolCallId: 'test', workingDirectory: '/tmp', abortController: { signal: {} },
|
|
||||||
config: { timeout: 5000 }, logger: { info: () => {}, warn: () => {}, error: () => {}, debug: () => {} }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.5 验证检查清单
|
|
||||||
|
|
||||||
```
|
|
||||||
[ ] GraphDatabase.commit - 创建实体和关系
|
|
||||||
[ ] GraphDatabase.recall - 按关键词检索
|
|
||||||
[ ] GraphDatabase.purge - 软删除
|
|
||||||
[ ] GraphDatabase.introspect - 返回统计
|
|
||||||
|
|
||||||
[ ] MemoryService.updatePersona - 人设更新
|
|
||||||
[ ] MemoryService.clearPersona - 人设清除
|
|
||||||
[ ] MemoryService.createTask - 创建任务
|
|
||||||
[ ] MemoryService.setTaskState - 设置状态
|
|
||||||
[ ] MemoryService.deleteTask - 删除任务
|
|
||||||
|
|
||||||
[ ] GraphMemoryTool recall action
|
|
||||||
[ ] GraphMemoryTool commit action
|
|
||||||
[ ] GraphMemoryTool persona_update action
|
|
||||||
[ ] GraphMemoryTool task_create action
|
|
||||||
[ ] GraphMemoryTool error handling
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 七、实现计划
|
|
||||||
|
|
||||||
| Phase | 任务 | 周期 | 测试 |
|
|
||||||
|-------|------|------|------|
|
|
||||||
| 1 | GraphDatabase 实现 | 2-3 天 | 15+ |
|
|
||||||
| 2 | MemoryService 实现 | 1-2 天 | 12+ |
|
|
||||||
| 3 | GraphMemoryTool 实现 | 1-2 天 | 15+ |
|
|
||||||
| 4 | 集成测试 | 1 天 | 8+ |
|
|
||||||
|
|
||||||
**总计**: 5-8 天,50+ 测试用例
|
|
||||||
@ -1,24 +1,23 @@
|
|||||||
---
|
---
|
||||||
name: graph_memory
|
|
||||||
description: 图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
|
||||||
when_to_use: 需要 AI 记住或回忆信息时
|
|
||||||
context: inline
|
context: inline
|
||||||
allowed_tools:
|
allowed-tools:
|
||||||
- builtin:graph_memory
|
- builtin:graph_memory
|
||||||
arguments:
|
arguments:
|
||||||
- name: action
|
- name: action
|
||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
enum: [recall, commit, purge, introspect]
|
enum: [recall, commit, purge, introspect, persona_update, persona_clear, task_create, task_set_state, task_delete, task_link_info]
|
||||||
description: 记忆操作类型
|
description: 记忆操作类型
|
||||||
- name: params
|
- name: params
|
||||||
type: object
|
type: object
|
||||||
required: true
|
required: true
|
||||||
description: 操作参数
|
description: 操作参数
|
||||||
user_invocable: true
|
user-invocable: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# GraphMemory 图记忆操作
|
# 图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
||||||
|
|
||||||
|
**何时使用**: 需要 AI 记住、回忆、管理信息或任务时调用此技能。
|
||||||
|
|
||||||
你可以通过以下操作与图记忆系统交互。
|
你可以通过以下操作与图记忆系统交互。
|
||||||
|
|
||||||
@ -26,12 +25,12 @@ user_invocable: true
|
|||||||
|
|
||||||
### 1. recall - 检索记忆
|
### 1. recall - 检索记忆
|
||||||
|
|
||||||
从记忆图中检索相关信息。
|
从记忆图中检索相关信息。支持广度优先搜索(BFS),自动扩展关联实体。
|
||||||
|
|
||||||
**参数**:
|
**参数**:
|
||||||
- `queryIntent`: 搜索意图/关键词
|
- `queryIntent`: 搜索意图/关键词
|
||||||
- `seedEntities`: 可选的种子实体名
|
- `seedEntities`: 可选的种子实体名
|
||||||
- `depth`: 检索深度
|
- `depth`: 检索深度(默认 2,BFS 层数)
|
||||||
- `sessionFilter`: 可选的会话ID过滤
|
- `sessionFilter`: 可选的会话ID过滤
|
||||||
|
|
||||||
**示例**:
|
**示例**:
|
||||||
@ -40,14 +39,15 @@ action: recall
|
|||||||
params:
|
params:
|
||||||
queryIntent: "用户 喜欢 编程"
|
queryIntent: "用户 喜欢 编程"
|
||||||
seedEntities: ["用户"]
|
seedEntities: ["用户"]
|
||||||
|
depth: 2
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. commit - 写入记忆
|
### 2. commit - 写入记忆
|
||||||
|
|
||||||
将信息写入记忆图。
|
将信息写入记忆图。使用三元组(主体-关系-客体)格式。
|
||||||
|
|
||||||
**参数**:
|
**参数**:
|
||||||
- `triplets`: 三元组数组,每个包含 subject, relation, object
|
- `triplets`: 三元组数组,每个包含 subject, relation, object, confidence(可选)
|
||||||
- `sessionId`: 会话ID
|
- `sessionId`: 会话ID
|
||||||
- `turnId`: 轮次ID
|
- `turnId`: 轮次ID
|
||||||
|
|
||||||
@ -70,8 +70,7 @@ params:
|
|||||||
|
|
||||||
**参数**:
|
**参数**:
|
||||||
- `criteria`: 删除条件 (subject, target, relation, sessionId)
|
- `criteria`: 删除条件 (subject, target, relation, sessionId)
|
||||||
- `mode`: 删除模式 (soft/hard/supersede)
|
- `mode`: 删除模式 (soft=标记删除/hard=物理删除/supersede=替代)
|
||||||
- `newRelation`: 可选的替代关系
|
|
||||||
|
|
||||||
**示例**:
|
**示例**:
|
||||||
```
|
```
|
||||||
@ -84,7 +83,7 @@ params:
|
|||||||
|
|
||||||
### 4. introspect - 查看状态
|
### 4. introspect - 查看状态
|
||||||
|
|
||||||
查看当前记忆状态统计。
|
查看当前记忆状态统计(实体数、关系数)。
|
||||||
|
|
||||||
**参数**: 无
|
**参数**: 无
|
||||||
|
|
||||||
@ -94,9 +93,114 @@ action: introspect
|
|||||||
params: {}
|
params: {}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 人设管理
|
||||||
|
|
||||||
|
### 5. persona_update - 更新人设
|
||||||
|
|
||||||
|
更新 AI 的人设属性(性格、语气、角色等)。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `attributes`: 属性数组,每个包含 attribute 和 value
|
||||||
|
- `mode`: merge(合并) 或 replace(替换)
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: persona_update
|
||||||
|
params:
|
||||||
|
attributes:
|
||||||
|
- attribute: "性格"
|
||||||
|
value: "活泼可爱"
|
||||||
|
- attribute: "语气词"
|
||||||
|
value: "喵"
|
||||||
|
mode: "replace"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. persona_clear - 清除人设
|
||||||
|
|
||||||
|
清除所有人设,恢复默认身份。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `confirm`: 必须为 true 才执行
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: persona_clear
|
||||||
|
params:
|
||||||
|
confirm: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 任务管理
|
||||||
|
|
||||||
|
### 7. task_create - 创建任务
|
||||||
|
|
||||||
|
创建连续性任务节点,维持对话连贯性。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `task_id`: 任务唯一ID
|
||||||
|
- `description`: 任务描述
|
||||||
|
- `info_nodes`: 可选的关联信息节点列表
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: task_create
|
||||||
|
params:
|
||||||
|
task_id: "Task_成语接龙"
|
||||||
|
description: "成语接龙游戏,当前成语:为所欲为"
|
||||||
|
info_nodes: ["成语接龙_当前成语"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. task_set_state - 设置任务状态
|
||||||
|
|
||||||
|
更新任务状态(进行中/已完成/已暂停/已取消)。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `task_id`: 任务ID
|
||||||
|
- `state`: 新状态
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: task_set_state
|
||||||
|
params:
|
||||||
|
task_id: "Task_成语接龙"
|
||||||
|
state: "已暂停"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. task_delete - 删除任务
|
||||||
|
|
||||||
|
删除任务节点。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `task_id`: 任务ID
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: task_delete
|
||||||
|
params:
|
||||||
|
task_id: "Task_成语接龙"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. task_link_info - 关联信息到任务
|
||||||
|
|
||||||
|
将记忆节点关联到任务节点,实现"由一件事回忆起相关事情"。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
- `task_id`: 任务ID
|
||||||
|
- `info_node`: 信息节点名
|
||||||
|
|
||||||
|
**示例**:
|
||||||
|
```
|
||||||
|
action: task_link_info
|
||||||
|
params:
|
||||||
|
task_id: "Task_成语接龙"
|
||||||
|
info_node: "用户喜欢罗辑"
|
||||||
|
```
|
||||||
|
|
||||||
## 使用原则
|
## 使用原则
|
||||||
|
|
||||||
1. **选择性记忆**: 只记住重要和持久的信息
|
1. **选择性记忆**: 只记住重要和持久的信息
|
||||||
2. **结构化**: 使用三元组 (主体-关系-客体) 格式
|
2. **结构化**: 使用三元组 (主体-关系-客体) 格式
|
||||||
3. **关联**: 通过关系连接相关实体
|
3. **关联**: 通过关系连接相关实体
|
||||||
4. **定期清理**: 删除过时或错误的信息
|
4. **定期清理**: 删除过时或错误的信息
|
||||||
|
5. **BFS 搜索**: recall 支持广度优先搜索,depth 参数控制扩展层数
|
||||||
|
6. **工作记忆链**: 每轮对话必须查询和更新工作记忆链(TaskNode),这是维持对话连贯性的唯一机制
|
||||||
|
7. **人设优先**: 每轮对话必须先查询人设图,确保角色一致性
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: graph_memory_persona
|
|
||||||
description: 管理 AI 人设 - 更新或清除 AI 角色特征
|
|
||||||
when_to_use: 需要修改 AI 的角色设定或清除人设时
|
|
||||||
context: inline
|
context: inline
|
||||||
allowed_tools:
|
allowed-tools:
|
||||||
- builtin:graph_memory
|
- builtin:graph_memory
|
||||||
arguments:
|
arguments:
|
||||||
- name: action
|
- name: action
|
||||||
@ -22,10 +19,12 @@ arguments:
|
|||||||
- name: confirm
|
- name: confirm
|
||||||
type: boolean
|
type: boolean
|
||||||
description: 确认清除 (用于 clear)
|
description: 确认清除 (用于 clear)
|
||||||
user_invocable: true
|
user-invocable: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# GraphMemory Persona 人设管理
|
# 管理 AI 人设 - 更新或清除 AI 角色特征
|
||||||
|
|
||||||
|
**何时使用**: 需要修改 AI 的角色设定或清除人设时调用此技能。
|
||||||
|
|
||||||
管理 AI 的人设/角色特征。
|
管理 AI 的人设/角色特征。
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: graph_memory_task
|
|
||||||
description: 管理连续性任务 - 创建、更新、删除任务节点
|
|
||||||
when_to_use: 需要创建或管理长期任务时
|
|
||||||
context: inline
|
context: inline
|
||||||
allowed_tools:
|
allowed-tools:
|
||||||
- builtin:graph_memory
|
- builtin:graph_memory
|
||||||
arguments:
|
arguments:
|
||||||
- name: action
|
- name: action
|
||||||
@ -28,10 +25,12 @@ arguments:
|
|||||||
- name: info_node
|
- name: info_node
|
||||||
type: string
|
type: string
|
||||||
description: 信息节点 (用于 link_info)
|
description: 信息节点 (用于 link_info)
|
||||||
user_invocable: true
|
user-invocable: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# GraphMemory Task 任务管理
|
# 管理连续性任务 - 创建、更新、删除任务节点
|
||||||
|
|
||||||
|
**何时使用**: 需要创建或管理长期任务时调用此技能。
|
||||||
|
|
||||||
管理长期/连续性任务。
|
管理长期/连续性任务。
|
||||||
|
|
||||||
|
|||||||
1483
ts/package-lock.json
generated
1483
ts/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "trulymem-waterflow",
|
"name": "trulymem-waterflow",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"description": "TrulyMEM 图记忆系统 - WaterFlow Skill/Tool 实现",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
"main": "./dist/runtime/core/tools/builtin/index.js",
|
||||||
|
"exports": {
|
||||||
|
"./tools": "./dist/runtime/core/tools/builtin/index.js",
|
||||||
|
"./graph_memory": "./dist/runtime/core/graph_memory/index.js",
|
||||||
|
"./skills": "./bundled-skills"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"waterflow-ts": ">=0.1.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yaml": "^2.8.3"
|
"sql.js": "^1.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.5.2",
|
"@types/node": "^25.5.2",
|
||||||
"typescript": "^5.0.0",
|
"typescript": "^5.0.0",
|
||||||
"vitest": "^2.0.0"
|
"vitest": "^2.0.0",
|
||||||
|
"waterflow-ts": "file:../../WaterFlow/ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
ts/src/runtime/core/graph_memory/config.ts
Normal file
38
ts/src/runtime/core/graph_memory/config.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export interface TrulyMEMConfig {
|
||||||
|
dbPath: string;
|
||||||
|
autoSave: boolean;
|
||||||
|
debug: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_CONFIG: TrulyMEMConfig = {
|
||||||
|
dbPath: '.trulymem/graph_memory.db',
|
||||||
|
autoSave: true,
|
||||||
|
debug: false
|
||||||
|
};
|
||||||
|
|
||||||
|
let globalConfig: TrulyMEMConfig = { ...DEFAULT_CONFIG };
|
||||||
|
|
||||||
|
export function initConfig(config: Partial<TrulyMEMConfig> = {}): TrulyMEMConfig {
|
||||||
|
globalConfig = { ...DEFAULT_CONFIG, ...config };
|
||||||
|
return globalConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfig(): TrulyMEMConfig {
|
||||||
|
return globalConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setDbPath(dbPath: string): void {
|
||||||
|
globalConfig.dbPath = dbPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDbPath(): string {
|
||||||
|
return globalConfig.dbPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setAutoSave(autoSave: boolean): void {
|
||||||
|
globalConfig.autoSave = autoSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isAutoSave(): boolean {
|
||||||
|
return globalConfig.autoSave;
|
||||||
|
}
|
||||||
@ -1,53 +1,168 @@
|
|||||||
|
import initSqlJs, { type Database as SqlJsDatabase } from 'sql.js';
|
||||||
|
import type { Platform } from 'waterflow-ts/dist/platform/types.js';
|
||||||
import type { Entity, Relation, RecallParams, CommitParams, PurgeParams, RecallResult, CommitResult, PurgeResult, MemoryStats } from './types';
|
import type { Entity, Relation, RecallParams, CommitParams, PurgeParams, RecallResult, CommitResult, PurgeResult, MemoryStats } from './types';
|
||||||
|
import { getConfig } from './config';
|
||||||
|
|
||||||
export class GraphDatabase {
|
export class GraphDatabase {
|
||||||
private entities: Map<string, Entity> = new Map();
|
private db: SqlJsDatabase | null = null;
|
||||||
private relations: Map<string, Relation> = new Map();
|
|
||||||
private sessionId: string;
|
private sessionId: string;
|
||||||
|
private initPromise: Promise<void> | null = null;
|
||||||
|
private _platform: Platform | null = null;
|
||||||
|
|
||||||
constructor(sessionId?: string) {
|
constructor(sessionId?: string) {
|
||||||
this.sessionId = sessionId || `session-${Date.now()}`;
|
this.sessionId = sessionId || `session-${Date.now()}`;
|
||||||
|
this.initPromise = this.initDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initDatabase(): Promise<void> {
|
||||||
|
const SQL = await initSqlJs();
|
||||||
|
const { getPlatform } = await import('waterflow-ts/dist/platform/index.js');
|
||||||
|
this._platform = getPlatform();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const dbPath = this._platform.path.join(this._platform.getCwd(), getConfig().dbPath);
|
||||||
|
const fs = this._platform.fs;
|
||||||
|
if (fs) {
|
||||||
|
const exists = await fs.exists(dbPath);
|
||||||
|
if (exists) {
|
||||||
|
const data = await fs.readFile(dbPath, { encoding: 'binary' });
|
||||||
|
this.db = new SQL.Database(new Uint8Array(data as ArrayBuffer));
|
||||||
|
} else {
|
||||||
|
this.db = new SQL.Database();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.db = new SQL.Database();
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
this.db = new SQL.Database();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.createTables();
|
||||||
|
}
|
||||||
|
|
||||||
|
async save(): Promise<void> {
|
||||||
|
if (!this.db || !this._platform) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const platform = this._platform;
|
||||||
|
const fs = platform.fs;
|
||||||
|
if (!fs) return;
|
||||||
|
const dbPath = platform.path.join(platform.getCwd(), getConfig().dbPath);
|
||||||
|
const dir = platform.path.dirname(dbPath);
|
||||||
|
const dirExists = await fs.exists(dir);
|
||||||
|
if (!dirExists) {
|
||||||
|
await fs.mkdir(dir, true);
|
||||||
|
}
|
||||||
|
const data = this.db.export();
|
||||||
|
// sql.js returns Uint8Array, convert to Buffer for writeFile compatibility
|
||||||
|
await fs.writeFile(dbPath, Buffer.from(data), { encoding: 'binary' });
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`[GraphDatabase] Save failed: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createTables(): void {
|
||||||
|
if (!this.db) return;
|
||||||
|
this.db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS entities (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT DEFAULT 'unknown',
|
||||||
|
mention_count INTEGER DEFAULT 1,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
this.db.run(`
|
||||||
|
CREATE TABLE IF NOT EXISTS relations (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
source_id TEXT NOT NULL,
|
||||||
|
target_id TEXT NOT NULL,
|
||||||
|
relation_type TEXT NOT NULL,
|
||||||
|
confidence REAL DEFAULT 1.0,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
session_id TEXT NOT NULL,
|
||||||
|
turn_id INTEGER DEFAULT 0,
|
||||||
|
created_at TEXT NOT NULL,
|
||||||
|
updated_at TEXT NOT NULL,
|
||||||
|
date_bucket TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (source_id) REFERENCES entities(id),
|
||||||
|
FOREIGN KEY (target_id) REFERENCES entities(id)
|
||||||
|
)
|
||||||
|
`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_name ON entities(name)`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_entity_type ON entities(type)`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_source ON relations(source_id)`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_target ON relations(target_id)`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_type ON relations(relation_type)`);
|
||||||
|
this.db.run(`CREATE INDEX IF NOT EXISTS idx_relation_status ON relations(status)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureInit(): void {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error('Database not initialized');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async recall(params: RecallParams): Promise<RecallResult> {
|
async recall(params: RecallParams): Promise<RecallResult> {
|
||||||
const { queryIntent, seedEntities, sessionFilter } = params;
|
await this.initPromise;
|
||||||
|
this.ensureInit();
|
||||||
|
const { queryIntent, seedEntities, depth = 2, sessionFilter } = params;
|
||||||
const keywords = queryIntent.split(/[,\s]+/).filter(k => k.length > 0);
|
const keywords = queryIntent.split(/[,\s]+/).filter(k => k.length > 0);
|
||||||
const entities: Entity[] = [];
|
const entities: Entity[] = [];
|
||||||
const relations: Relation[] = [];
|
const relations: Relation[] = [];
|
||||||
const entityIds = new Set<string>();
|
const entityIds = new Set<string>();
|
||||||
|
|
||||||
for (const keyword of keywords) {
|
if (!this.db) return { entities, relations, message: 'Database not ready' };
|
||||||
const lowerKeyword = keyword.toLowerCase();
|
|
||||||
for (const [_, entity] of this.entities) {
|
if (!keywords.length && !seedEntities?.length) {
|
||||||
if (entity.name.toLowerCase().includes(lowerKeyword)) {
|
const rows = this.db.exec('SELECT * FROM entities ORDER BY mention_count DESC LIMIT 50');
|
||||||
if (!entityIds.has(entity.id)) {
|
if (rows.length > 0) {
|
||||||
entityIds.add(entity.id);
|
const columns = rows[0].columns;
|
||||||
entities.push(entity);
|
for (const row of rows[0].values) {
|
||||||
|
const obj = this.rowToObject(columns, row);
|
||||||
|
const id = obj.id as string;
|
||||||
|
entityIds.add(id);
|
||||||
|
entities.push(this.rowToEntity(obj, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (const keyword of keywords) {
|
||||||
|
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) LIKE ? LIMIT 100');
|
||||||
|
stmt.bind([`%${keyword.toLowerCase()}%`]);
|
||||||
|
while (stmt.step()) {
|
||||||
|
const row = stmt.getAsObject();
|
||||||
|
const id = row.id as string;
|
||||||
|
if (!entityIds.has(id)) {
|
||||||
|
entityIds.add(id);
|
||||||
|
entities.push(this.rowToEntity(row, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stmt.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seedEntities && seedEntities.length > 0) {
|
if (seedEntities?.length) {
|
||||||
for (const seedName of seedEntities) {
|
for (const seedName of seedEntities) {
|
||||||
for (const [_, entity] of this.entities) {
|
const stmt = this.db.prepare('SELECT * FROM entities WHERE LOWER(name) = ? LIMIT 1');
|
||||||
if (entity.name.toLowerCase() === seedName.toLowerCase()) {
|
stmt.bind([seedName.toLowerCase()]);
|
||||||
if (!entityIds.has(entity.id)) {
|
if (stmt.step()) {
|
||||||
entityIds.add(entity.id);
|
const row = stmt.getAsObject();
|
||||||
entities.push(entity);
|
const id = row.id as string;
|
||||||
}
|
if (!entityIds.has(id)) {
|
||||||
|
entityIds.add(id);
|
||||||
|
entities.push(this.rowToEntity(row, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stmt.free();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [_, relation] of this.relations) {
|
this.bfsExpand(entityIds, entities, relations, depth, sessionFilter);
|
||||||
if (entityIds.has(relation.sourceId) || entityIds.has(relation.targetId)) {
|
|
||||||
if (relation.status === 'active') {
|
for (const entity of entities) {
|
||||||
if (!sessionFilter || relation.sessionId === sessionFilter) {
|
if (entity.depth === undefined) {
|
||||||
relations.push(relation);
|
entity.depth = 0;
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,132 +173,268 @@ export class GraphDatabase {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bfsExpand(
|
||||||
|
seedIds: Set<string>,
|
||||||
|
entities: Entity[],
|
||||||
|
relations: Relation[],
|
||||||
|
maxDepth: number,
|
||||||
|
sessionFilter?: string
|
||||||
|
): void {
|
||||||
|
if (!this.db) return;
|
||||||
|
|
||||||
|
const visited = new Set(seedIds);
|
||||||
|
let currentLayer = new Set(seedIds);
|
||||||
|
const entityDepths: Record<string, number> = {};
|
||||||
|
for (const id of seedIds) {
|
||||||
|
entityDepths[id] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let layer = 0; layer < maxDepth; layer++) {
|
||||||
|
if (!currentLayer.size) break;
|
||||||
|
|
||||||
|
const placeholders = Array(currentLayer.size).fill('?').join(',');
|
||||||
|
let sql = `
|
||||||
|
SELECT r.id, r.source_id, r.target_id,
|
||||||
|
e1.name as source_name, e2.name as target_name,
|
||||||
|
r.relation_type, r.confidence, r.session_id,
|
||||||
|
r.turn_id, r.created_at, r.updated_at, r.status, r.date_bucket
|
||||||
|
FROM relations r
|
||||||
|
JOIN entities e1 ON r.source_id = e1.id
|
||||||
|
JOIN entities e2 ON r.target_id = e2.id
|
||||||
|
WHERE (r.source_id IN (${placeholders}) OR r.target_id IN (${placeholders}))
|
||||||
|
AND r.status = 'active'
|
||||||
|
`;
|
||||||
|
const params: (string | number)[] = [];
|
||||||
|
for (const id of currentLayer) { params.push(id); }
|
||||||
|
for (const id of currentLayer) { params.push(id); }
|
||||||
|
if (sessionFilter) {
|
||||||
|
sql += ` AND r.session_id = ?`;
|
||||||
|
params.push(sessionFilter);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = this.db.prepare(sql);
|
||||||
|
stmt.bind(params);
|
||||||
|
|
||||||
|
const nextLayer = new Set<string>();
|
||||||
|
const layerRelations: Relation[] = [];
|
||||||
|
|
||||||
|
while (stmt.step()) {
|
||||||
|
const row = stmt.getAsObject();
|
||||||
|
const sourceId = row.source_id as string;
|
||||||
|
const targetId = row.target_id as string;
|
||||||
|
const sourceDepth = entityDepths[sourceId] ?? layer;
|
||||||
|
const targetDepth = entityDepths[targetId] ?? layer;
|
||||||
|
const relationDepth = Math.max(sourceDepth, targetDepth) + 1;
|
||||||
|
|
||||||
|
layerRelations.push({
|
||||||
|
id: row.id as string,
|
||||||
|
sourceId,
|
||||||
|
targetId,
|
||||||
|
relationType: row.relation_type as string,
|
||||||
|
confidence: row.confidence as number,
|
||||||
|
status: row.status as Relation['status'],
|
||||||
|
sessionId: row.session_id as string,
|
||||||
|
turnId: row.turn_id as number,
|
||||||
|
createdAt: new Date(row.created_at as string),
|
||||||
|
updatedAt: new Date(row.updated_at as string),
|
||||||
|
dateBucket: row.date_bucket as string,
|
||||||
|
depth: relationDepth
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!visited.has(sourceId)) {
|
||||||
|
visited.add(sourceId);
|
||||||
|
nextLayer.add(sourceId);
|
||||||
|
entityDepths[sourceId] = layer + 1;
|
||||||
|
}
|
||||||
|
if (!visited.has(targetId)) {
|
||||||
|
visited.add(targetId);
|
||||||
|
nextLayer.add(targetId);
|
||||||
|
entityDepths[targetId] = layer + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stmt.free();
|
||||||
|
|
||||||
|
relations.push(...layerRelations);
|
||||||
|
|
||||||
|
if (nextLayer.size) {
|
||||||
|
const placeholders = Array(nextLayer.size).fill('?').join(',');
|
||||||
|
const entityStmt = this.db.prepare(
|
||||||
|
`SELECT * FROM entities WHERE id IN (${placeholders})`
|
||||||
|
);
|
||||||
|
entityStmt.bind(Array.from(nextLayer));
|
||||||
|
while (entityStmt.step()) {
|
||||||
|
const row = entityStmt.getAsObject();
|
||||||
|
const id = row.id as string;
|
||||||
|
entities.push(this.rowToEntity(row, entityDepths[id] ?? layer + 1));
|
||||||
|
}
|
||||||
|
entityStmt.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentLayer = nextLayer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async commit(params: CommitParams): Promise<CommitResult> {
|
async commit(params: CommitParams): Promise<CommitResult> {
|
||||||
|
await this.initPromise;
|
||||||
|
this.ensureInit();
|
||||||
const { triplets, sessionId, turnId } = params;
|
const { triplets, sessionId, turnId } = params;
|
||||||
let createdEntities = 0;
|
let createdEntities = 0;
|
||||||
let createdRelations = 0;
|
let createdRelations = 0;
|
||||||
|
|
||||||
|
if (!this.db) return { createdEntities: 0, createdRelations: 0 };
|
||||||
|
|
||||||
for (const triplet of triplets) {
|
for (const triplet of triplets) {
|
||||||
const sourceId = this.upsertEntity(triplet.subject);
|
const sourceId = this.upsertEntity(triplet.subject);
|
||||||
const targetId = this.upsertEntity(triplet.object);
|
const targetId = this.upsertEntity(triplet.object);
|
||||||
|
|
||||||
const relationId = this.generateId();
|
const relationId = this.generateId();
|
||||||
const now = new Date();
|
const now = new Date().toISOString();
|
||||||
const relation: Relation = {
|
this.db.run(
|
||||||
id: relationId,
|
`INSERT INTO relations (id, source_id, target_id, relation_type, confidence, status, session_id, turn_id, created_at, updated_at, date_bucket)
|
||||||
sourceId,
|
VALUES (?, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?)`,
|
||||||
targetId,
|
[relationId, sourceId, targetId, triplet.relation, triplet.confidence ?? 1.0, sessionId ?? this.sessionId, turnId ?? 0, now, now, new Date().toISOString().split('T')[0]]
|
||||||
relationType: triplet.relation,
|
);
|
||||||
confidence: triplet.confidence || 1.0,
|
|
||||||
status: 'active',
|
|
||||||
sessionId: sessionId || this.sessionId,
|
|
||||||
turnId: turnId || 0,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now,
|
|
||||||
dateBucket: this.getDateBucket(now)
|
|
||||||
};
|
|
||||||
|
|
||||||
this.relations.set(relationId, relation);
|
|
||||||
createdEntities += 2;
|
createdEntities += 2;
|
||||||
createdRelations++;
|
createdRelations++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getConfig().autoSave) {
|
||||||
|
await this.save();
|
||||||
|
}
|
||||||
|
|
||||||
return { createdEntities, createdRelations };
|
return { createdEntities, createdRelations };
|
||||||
}
|
}
|
||||||
|
|
||||||
async purge(params: PurgeParams): Promise<PurgeResult> {
|
async purge(params: PurgeParams): Promise<PurgeResult> {
|
||||||
|
await this.initPromise;
|
||||||
|
this.ensureInit();
|
||||||
const { criteria, mode = 'soft' } = params;
|
const { criteria, mode = 'soft' } = params;
|
||||||
let deleted = 0;
|
let deleted = 0;
|
||||||
|
|
||||||
for (const [id, relation] of this.relations) {
|
if (!this.db) return { deleted: 0, mode };
|
||||||
if (relation.status !== 'active') continue;
|
|
||||||
|
|
||||||
if (!criteria) {
|
const conditions: string[] = [];
|
||||||
continue;
|
const queryParams: (string | number)[] = [];
|
||||||
}
|
|
||||||
|
|
||||||
let matches = true;
|
if (criteria?.subject) {
|
||||||
if (criteria.subject) {
|
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
|
||||||
const sourceEntity = this.entities.get(relation.sourceId);
|
stmt.bind([criteria.subject.toLowerCase()]);
|
||||||
matches = sourceEntity?.name.toLowerCase() === criteria.subject.toLowerCase();
|
if (stmt.step()) {
|
||||||
}
|
const row = stmt.getAsObject();
|
||||||
if (matches && criteria.target) {
|
conditions.push(`source_id = ?`);
|
||||||
const targetEntity = this.entities.get(relation.targetId);
|
queryParams.push(row.id as string);
|
||||||
matches = targetEntity?.name.toLowerCase() === criteria.target.toLowerCase();
|
|
||||||
}
|
|
||||||
if (matches && criteria.relation) {
|
|
||||||
matches = relation.relationType.toLowerCase() === criteria.relation.toLowerCase();
|
|
||||||
}
|
|
||||||
if (matches && criteria.sessionId) {
|
|
||||||
matches = relation.sessionId === criteria.sessionId;
|
|
||||||
}
|
}
|
||||||
|
stmt.free();
|
||||||
|
}
|
||||||
|
|
||||||
if (matches) {
|
if (criteria?.target) {
|
||||||
if (mode === 'hard') {
|
const stmt = this.db.prepare('SELECT id FROM entities WHERE LOWER(name) = ?');
|
||||||
this.relations.delete(id);
|
stmt.bind([criteria.target.toLowerCase()]);
|
||||||
} else {
|
if (stmt.step()) {
|
||||||
relation.status = 'deleted';
|
const row = stmt.getAsObject();
|
||||||
relation.updatedAt = new Date();
|
conditions.push(`target_id = ?`);
|
||||||
}
|
queryParams.push(row.id as string);
|
||||||
deleted++;
|
|
||||||
}
|
}
|
||||||
|
stmt.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (criteria?.relation) {
|
||||||
|
conditions.push(`relation_type = ?`);
|
||||||
|
queryParams.push(criteria.relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!conditions.length) {
|
||||||
|
return { deleted: 0, mode, message: '无删除条件' };
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereClause = conditions.join(' AND ');
|
||||||
|
|
||||||
|
const countStmt = this.db.prepare(`SELECT COUNT(*) as cnt FROM relations WHERE ${whereClause} AND status = 'active'`);
|
||||||
|
countStmt.bind(queryParams);
|
||||||
|
if (countStmt.step()) {
|
||||||
|
const row = countStmt.getAsObject();
|
||||||
|
deleted = row.cnt as number;
|
||||||
|
}
|
||||||
|
countStmt.free();
|
||||||
|
|
||||||
|
if (mode === 'hard') {
|
||||||
|
this.db.run(`DELETE FROM relations WHERE ${whereClause} AND status = 'active'`, queryParams);
|
||||||
|
} else {
|
||||||
|
this.db.run(
|
||||||
|
`UPDATE relations SET status = 'deleted', updated_at = ? WHERE ${whereClause} AND status = 'active'`,
|
||||||
|
[new Date().toISOString(), ...queryParams]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getConfig().autoSave && deleted > 0) {
|
||||||
|
await this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { deleted, mode };
|
return { deleted, mode };
|
||||||
}
|
}
|
||||||
|
|
||||||
async introspect(): Promise<MemoryStats> {
|
async introspect(): Promise<MemoryStats> {
|
||||||
let entityCount = 0;
|
await this.initPromise;
|
||||||
for (const [_, entity] of this.entities) {
|
this.ensureInit();
|
||||||
if (!this.isEntityDeleted(entity.id)) entityCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
let relationCount = 0;
|
if (!this.db) return { entityCount: 0, relationCount: 0, sessionId: this.sessionId };
|
||||||
for (const [_, relation] of this.relations) {
|
|
||||||
if (relation.status === 'active') relationCount++;
|
const entityCount = (this.db.exec('SELECT COUNT(*) FROM entities')[0]?.values[0]?.[0] as number) ?? 0;
|
||||||
}
|
const relationCount = (this.db.exec("SELECT COUNT(*) FROM relations WHERE status = 'active'")[0]?.values[0]?.[0] as number) ?? 0;
|
||||||
|
|
||||||
return { entityCount, relationCount, sessionId: this.sessionId };
|
return { entityCount, relationCount, sessionId: this.sessionId };
|
||||||
}
|
}
|
||||||
|
|
||||||
private upsertEntity(name: string): string {
|
private upsertEntity(name: string): string {
|
||||||
for (const [id, entity] of this.entities) {
|
if (!this.db) return this.generateId();
|
||||||
if (entity.name === name && !this.isEntityDeleted(id)) {
|
|
||||||
entity.mentionCount++;
|
const existingStmt = this.db.prepare('SELECT id, mention_count FROM entities WHERE LOWER(name) = ?');
|
||||||
entity.updatedAt = new Date();
|
existingStmt.bind([name.toLowerCase()]);
|
||||||
return id;
|
if (existingStmt.step()) {
|
||||||
}
|
const row = existingStmt.getAsObject();
|
||||||
|
const id = row.id as string;
|
||||||
|
this.db.run('UPDATE entities SET mention_count = ?, updated_at = ? WHERE id = ?', [(row.mention_count as number) + 1, new Date().toISOString(), id]);
|
||||||
|
existingStmt.free();
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
existingStmt.free();
|
||||||
|
|
||||||
const id = this.generateId();
|
const id = this.generateId();
|
||||||
const now = new Date();
|
const now = new Date().toISOString();
|
||||||
const entity: Entity = {
|
this.db.run(
|
||||||
id,
|
'INSERT INTO entities (id, name, type, mention_count, created_at, updated_at) VALUES (?, ?, ?, 1, ?, ?)',
|
||||||
name,
|
[id, name, 'unknown', now, now]
|
||||||
type: 'unknown',
|
);
|
||||||
mentionCount: 1,
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now
|
|
||||||
};
|
|
||||||
this.entities.set(id, entity);
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEntityDeleted(entityId: string): boolean {
|
private rowToEntity(row: Record<string, unknown>, depth?: number): Entity {
|
||||||
for (const [_, relation] of this.relations) {
|
const entity: Entity = {
|
||||||
if ((relation.sourceId === entityId || relation.targetId === entityId) && relation.status === 'deleted') {
|
id: row.id as string,
|
||||||
return true;
|
name: row.name as string,
|
||||||
}
|
type: (row.type as string) ?? 'unknown',
|
||||||
|
mentionCount: row.mention_count as number,
|
||||||
|
createdAt: new Date(row.created_at as string),
|
||||||
|
updatedAt: new Date(row.updated_at as string)
|
||||||
|
};
|
||||||
|
if (depth !== undefined) {
|
||||||
|
entity.depth = depth;
|
||||||
}
|
}
|
||||||
return false;
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private rowToObject(columns: string[], values: unknown[]): Record<string, unknown> {
|
||||||
|
const obj: Record<string, unknown> = {};
|
||||||
|
columns.forEach((col, i) => { obj[col] = values[i]; });
|
||||||
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateId(): string {
|
private generateId(): string {
|
||||||
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
if (this._platform?.globals?.randomUUID) {
|
||||||
}
|
return this._platform.globals.randomUUID();
|
||||||
|
}
|
||||||
private getDateBucket(date: Date): string {
|
return `ent-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
return date.toISOString().split('T')[0] ?? '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSessionId(sessionId: string): void {
|
setSessionId(sessionId: string): void {
|
||||||
@ -193,4 +444,11 @@ export class GraphDatabase {
|
|||||||
getSessionId(): string {
|
getSessionId(): string {
|
||||||
return this.sessionId;
|
return this.sessionId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
close(): void {
|
||||||
|
if (this.db) {
|
||||||
|
this.db.close();
|
||||||
|
this.db = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
|
export * from './config';
|
||||||
export * from './graph_database';
|
export * from './graph_database';
|
||||||
export * from './memory_service';
|
export * from './memory_service';
|
||||||
@ -5,6 +5,7 @@ export interface Entity {
|
|||||||
mentionCount: number;
|
mentionCount: number;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
depth?: number; // BFS 搜索深度标注
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RelationStatus = 'active' | 'deleted' | 'archived' | 'superseded';
|
export type RelationStatus = 'active' | 'deleted' | 'archived' | 'superseded';
|
||||||
@ -21,6 +22,7 @@ export interface Relation {
|
|||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
dateBucket: string;
|
dateBucket: string;
|
||||||
|
depth?: number; // BFS 搜索深度标注
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Triplet {
|
export interface Triplet {
|
||||||
@ -71,6 +73,7 @@ export interface CommitResult {
|
|||||||
export interface PurgeResult {
|
export interface PurgeResult {
|
||||||
deleted: number;
|
deleted: number;
|
||||||
mode: string;
|
mode: string;
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TaskState = '进行中' | '已完成' | '已暂停' | '已取消';
|
export type TaskState = '进行中' | '已完成' | '已暂停' | '已取消';
|
||||||
|
|||||||
@ -1,25 +1,77 @@
|
|||||||
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolExecutionContext, ToolOutput } from '../tool_interface';
|
import type { Tool, ToolCategory, PermissionLevel, ToolInputSchema, ToolOutput, ToolInput, ToolExecutionContext } from 'waterflow-ts/dist/runtime/core/tools/tool_interface.js';
|
||||||
import { GraphDatabase } from '../../graph_memory/graph_database';
|
import { GraphDatabase } from '../../graph_memory/graph_database';
|
||||||
import { MemoryService } from '../../graph_memory/memory_service';
|
import { MemoryService } from '../../graph_memory/memory_service';
|
||||||
|
|
||||||
const GRAPH_MEMORY_TOOL_ID = 'builtin:graph_memory';
|
const GRAPH_MEMORY_TOOL_ID = 'builtin:graph_memory';
|
||||||
|
const GRAPH_MEMORY_TOOL_API_NAME = 'graph_memory'; // API 兼容名称(不含冒号,符合 ^[a-zA-Z0-9_-]+$ 要求)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工具名称映射工具 - 用于处理 API 对工具名称格式的限制
|
||||||
|
* OpenAI/DeepSeek API 要求工具名称符合 ^[a-zA-Z0-9_-]+$ 正则表达式
|
||||||
|
* 而 TrulyMEM 的内部 ID 使用 "builtin:xxx" 格式(含冒号)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将内部工具 ID 映射为 API 兼容名称
|
||||||
|
* @param toolId 内部工具 ID,如 "builtin:graph_memory"
|
||||||
|
* @returns API 兼容名称,如 "graph_memory"
|
||||||
|
*/
|
||||||
|
export function mapToolIdToApiName(toolId: string): string {
|
||||||
|
// 移除 "builtin:" 前缀
|
||||||
|
if (toolId.startsWith('builtin:')) {
|
||||||
|
return toolId.slice(8);
|
||||||
|
}
|
||||||
|
// 其他前缀也移除(如 "mcp:", "plugin:")
|
||||||
|
const colonIndex = toolId.indexOf(':');
|
||||||
|
if (colonIndex > 0) {
|
||||||
|
return toolId.slice(colonIndex + 1);
|
||||||
|
}
|
||||||
|
return toolId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 API 返回的工具名称映射回内部 ID
|
||||||
|
* @param apiName API 返回的工具名称,如 "graph_memory"
|
||||||
|
* @param prefix 内部 ID 前缀,默认 "builtin:"
|
||||||
|
* @returns 内部工具 ID,如 "builtin:graph_memory"
|
||||||
|
*/
|
||||||
|
export function mapApiNameToToolId(apiName: string, prefix = 'builtin:'): string {
|
||||||
|
// 如果已经是完整 ID 格式,直接返回
|
||||||
|
if (apiName.includes(':')) {
|
||||||
|
return apiName;
|
||||||
|
}
|
||||||
|
return `${prefix}${apiName}`;
|
||||||
|
}
|
||||||
|
|
||||||
const GRAPH_MEMORY_TOOL_DESCRIPTION = `图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
const GRAPH_MEMORY_TOOL_DESCRIPTION = `图记忆工具 - 让 AI 拥有真正的长期记忆能力
|
||||||
|
|
||||||
操作:
|
操作:
|
||||||
- recall: 检索记忆
|
- recall: 检索记忆 - 提供 queryIntent (搜索意图) 和可选的 seedEntities
|
||||||
- commit: 写入记忆
|
- commit: 写入记忆 - 必须使用 triplets 数组格式,每个三元组包含 subject, relation, object
|
||||||
- purge: 删除记忆
|
- purge: 删除记忆 - 提供 criteria 指定删除条件
|
||||||
- introspect: 查看状态
|
- introspect: 查看状态 - 无参数
|
||||||
- persona_update/clear: 人设管理
|
- persona_update/clear: 人设管理
|
||||||
- task_create/set_state/delete: 任务管理`;
|
- task_create/set_state/delete: 任务管理
|
||||||
|
|
||||||
|
【commit 操作的三元组格式】
|
||||||
|
triplets 必须是数组,每个元素是 {subject, relation, object, confidence} 格式:
|
||||||
|
示例: {"action":"commit","params":{"triplets":[{"subject":"Alice","relation":"is a","object":"engineer","confidence":0.95}]}}
|
||||||
|
- subject: 实体名称 (如用户名、技术名称)
|
||||||
|
- relation: 关系描述 (如 "is a", "likes", "knows")
|
||||||
|
- object: 目标实体 (如职业、爱好、技术)
|
||||||
|
- confidence: 置信度 0-1 (可选,默认0.9)
|
||||||
|
|
||||||
|
【recall 操作】
|
||||||
|
示例: {"action":"recall","params":{"queryIntent":"用户的学习偏好","seedEntities":["Alice"]}}`;
|
||||||
|
|
||||||
export class GraphMemoryTool implements Tool {
|
export class GraphMemoryTool implements Tool {
|
||||||
readonly id = GRAPH_MEMORY_TOOL_ID;
|
readonly id = GRAPH_MEMORY_TOOL_ID;
|
||||||
readonly name = 'GraphMemory';
|
readonly name = 'GraphMemory';
|
||||||
|
readonly apiName = GRAPH_MEMORY_TOOL_API_NAME;
|
||||||
readonly description = GRAPH_MEMORY_TOOL_DESCRIPTION;
|
readonly description = GRAPH_MEMORY_TOOL_DESCRIPTION;
|
||||||
readonly category: ToolCategory = 'analysis';
|
readonly category: ToolCategory = 'analysis';
|
||||||
readonly permissionLevel: PermissionLevel = 'safe';
|
readonly permissionLevel: PermissionLevel = 'safe';
|
||||||
|
readonly alwaysLoad = true;
|
||||||
|
|
||||||
readonly inputSchema: ToolInputSchema = {
|
readonly inputSchema: ToolInputSchema = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
@ -43,17 +95,17 @@ export class GraphMemoryTool implements Tool {
|
|||||||
sessionFilter: { type: 'string', description: '会话ID过滤' },
|
sessionFilter: { type: 'string', description: '会话ID过滤' },
|
||||||
triplets: {
|
triplets: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
|
description: '知识三元组数组。每个三元组描述 subject-relation-object 关系,用于存储记忆知识。',
|
||||||
items: {
|
items: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: '三元组',
|
description: '三元组: {subject, relation, object, confidence} - subject/relation/object 必填',
|
||||||
properties: {
|
properties: {
|
||||||
subject: { type: 'string', description: '主体' },
|
subject: { type: 'string', description: '主体实体,如人名、技术名称等' },
|
||||||
relation: { type: 'string', description: '关系' },
|
relation: { type: 'string', description: '关系描述,如 "is a", "likes", "knows", "uses" 等' },
|
||||||
object: { type: 'string', description: '客体' },
|
object: { type: 'string', description: '客体实体,如职业、爱好、技术名称等' },
|
||||||
confidence: { type: 'number', description: '置信度' }
|
confidence: { type: 'number', description: '置信度 (0-1),默认 0.9', default: 0.9 }
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
description: '三元组数组'
|
|
||||||
},
|
},
|
||||||
sessionId: { type: 'string', description: '会话ID' },
|
sessionId: { type: 'string', description: '会话ID' },
|
||||||
turnId: { type: 'number', description: '轮次ID' },
|
turnId: { type: 'number', description: '轮次ID' },
|
||||||
@ -100,14 +152,22 @@ export class GraphMemoryTool implements Tool {
|
|||||||
this.service = new MemoryService(this.db);
|
this.service = new MemoryService(this.db);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handler(params: Record<string, unknown>, _context: ToolExecutionContext): Promise<ToolOutput> {
|
async handler(params: ToolInput, context: ToolExecutionContext): Promise<ToolOutput> {
|
||||||
|
if (context.abortController?.signal?.aborted) {
|
||||||
|
throw new Error('Operation aborted');
|
||||||
|
}
|
||||||
|
|
||||||
const action = params.action as string;
|
const action = params.action as string;
|
||||||
const actionParams = params.params as Record<string, unknown>;
|
const actionParams = params.params as Record<string, unknown>;
|
||||||
|
const logger = context?.logger;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
logger?.info(`[GraphMemoryTool] Executing action: ${action}`);
|
||||||
const result = await this.executeAction(action, actionParams);
|
const result = await this.executeAction(action, actionParams);
|
||||||
|
logger?.info(`[GraphMemoryTool] Action ${action} completed successfully`);
|
||||||
return JSON.stringify({ success: true, data: result }, null, 2);
|
return JSON.stringify({ success: true, data: result }, null, 2);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger?.error(`[GraphMemoryTool] Action ${action} failed:`, error);
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
error: {
|
error: {
|
||||||
|
|||||||
20
ts/src/runtime/core/tools/builtin/index.ts
Normal file
20
ts/src/runtime/core/tools/builtin/index.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import type { Tool } from 'waterflow-ts/dist/runtime/core/tools/tool_interface.js';
|
||||||
|
import type { Platform } from 'waterflow-ts/dist/platform/types.js';
|
||||||
|
import type { ToolRegistry } from 'waterflow-ts/dist/runtime/core/tools/tool_registry.js';
|
||||||
|
import { GraphMemoryTool, createGraphMemoryTool, mapToolIdToApiName, mapApiNameToToolId } from './graph_memory_tool';
|
||||||
|
|
||||||
|
export { GraphMemoryTool, createGraphMemoryTool, mapToolIdToApiName, mapApiNameToToolId };
|
||||||
|
|
||||||
|
export function registerGraphMemoryTool(
|
||||||
|
registry: { register: (tool: Tool) => void },
|
||||||
|
sessionId?: string
|
||||||
|
): void {
|
||||||
|
registry.register(createGraphMemoryTool(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function installTrulyMEM(platform: Platform, sessionId?: string): Promise<ToolRegistry> {
|
||||||
|
const { initializeToolRegistry } = await import('waterflow-ts/dist/runtime/core/tools/builtin/index.js');
|
||||||
|
const registry = initializeToolRegistry(platform);
|
||||||
|
registerGraphMemoryTool(registry, sessionId);
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
@ -1,59 +0,0 @@
|
|||||||
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'mcp' | 'custom';
|
|
||||||
|
|
||||||
export type PermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
|
|
||||||
|
|
||||||
export type SchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
|
||||||
|
|
||||||
export interface SchemaProperty {
|
|
||||||
type: SchemaType;
|
|
||||||
description: string;
|
|
||||||
enum?: string[];
|
|
||||||
minimum?: number;
|
|
||||||
maximum?: number;
|
|
||||||
minLength?: number;
|
|
||||||
maxLength?: number;
|
|
||||||
pattern?: string;
|
|
||||||
default?: unknown;
|
|
||||||
examples?: unknown[];
|
|
||||||
items?: SchemaProperty;
|
|
||||||
properties?: Record<string, SchemaProperty>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolInputSchema {
|
|
||||||
type: 'object';
|
|
||||||
properties: Record<string, SchemaProperty>;
|
|
||||||
required?: string[];
|
|
||||||
additionalProperties?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ToolOutputSchema {
|
|
||||||
type: 'object';
|
|
||||||
properties: Record<string, SchemaProperty>;
|
|
||||||
format?: 'json' | 'text' | 'markdown' | 'binary';
|
|
||||||
maxSize?: number;
|
|
||||||
maxLines?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ToolInput = Record<string, unknown>;
|
|
||||||
export type ToolOutput = string | Record<string, unknown> | void;
|
|
||||||
|
|
||||||
export interface ToolExecutionContext {
|
|
||||||
toolCallId: string;
|
|
||||||
workingDirectory: string;
|
|
||||||
abortController: { signal: AbortSignal };
|
|
||||||
config: { timeout?: number };
|
|
||||||
logger: { info: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; debug: (...args: unknown[]) => void };
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ToolHandler = (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
|
|
||||||
|
|
||||||
export interface Tool {
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
readonly description: string;
|
|
||||||
readonly category: ToolCategory;
|
|
||||||
readonly inputSchema: ToolInputSchema;
|
|
||||||
readonly outputSchema?: ToolOutputSchema;
|
|
||||||
readonly handler: ToolHandler;
|
|
||||||
readonly permissionLevel: PermissionLevel;
|
|
||||||
}
|
|
||||||
29
ts/src/types/sql.js.d.ts
vendored
Normal file
29
ts/src/types/sql.js.d.ts
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
declare module 'sql.js' {
|
||||||
|
export interface Database {
|
||||||
|
run(sql: string, params?: (string | number | null | Uint8Array)[]): void;
|
||||||
|
exec(sql: string): QueryExecResult[];
|
||||||
|
prepare(sql: string): Statement;
|
||||||
|
export(): Uint8Array;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Statement {
|
||||||
|
bind(params?: (string | number | null | Uint8Array)[]): boolean;
|
||||||
|
step(): boolean;
|
||||||
|
getAsObject(): Record<string, unknown>;
|
||||||
|
free(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryExecResult {
|
||||||
|
columns: string[];
|
||||||
|
values: (string | number | null | Uint8Array)[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SqlJsStatic {
|
||||||
|
Database: new (data?: ArrayLike<number>) => Database;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function initSqlJs(config?: {
|
||||||
|
locateFile?: (file: string) => string;
|
||||||
|
}): Promise<SqlJsStatic>;
|
||||||
|
}
|
||||||
637
ts/src/types/waterflow.d.ts
vendored
Normal file
637
ts/src/types/waterflow.d.ts
vendored
Normal file
@ -0,0 +1,637 @@
|
|||||||
|
declare module 'waterflow/platform' {
|
||||||
|
import type { Platform, CreatePlatformOptions, PlatformCapabilities } from 'waterflow-ts/platform/types';
|
||||||
|
export function getPlatform(): Platform;
|
||||||
|
export function hasCapability(capability: keyof PlatformCapabilities): boolean;
|
||||||
|
export function initPlatform(options?: CreatePlatformOptions): Platform;
|
||||||
|
export function resetPlatform(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'waterflow/platform/types' {
|
||||||
|
export type BufferSource = ArrayBuffer | ArrayBufferView;
|
||||||
|
export type RuntimeType = 'node' | 'web' | 'harmony' | 'unknown';
|
||||||
|
export type OSType = 'windows' | 'macos' | 'linux' | 'android' | 'ios' | 'harmony' | 'unknown';
|
||||||
|
|
||||||
|
export interface PlatformCapabilities {
|
||||||
|
fileSystem: boolean;
|
||||||
|
processExecution: boolean;
|
||||||
|
network: boolean;
|
||||||
|
storage: boolean;
|
||||||
|
webSocket: boolean;
|
||||||
|
workers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformAbortSignal {
|
||||||
|
readonly aborted: boolean;
|
||||||
|
readonly reason?: unknown;
|
||||||
|
addEventListener(type: 'abort', listener: () => void): void;
|
||||||
|
removeEventListener(type: 'abort', listener: () => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformAbortController {
|
||||||
|
readonly signal: PlatformAbortSignal;
|
||||||
|
abort(reason?: unknown): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformTextEncoder {
|
||||||
|
encode(input?: string): Uint8Array;
|
||||||
|
encodeInto(src: string, dest: Uint8Array): { read: number; written: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformTextDecoder {
|
||||||
|
decode(input?: BufferSource): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformURL {
|
||||||
|
href: string;
|
||||||
|
origin: string;
|
||||||
|
protocol: string;
|
||||||
|
host: string;
|
||||||
|
hostname: string;
|
||||||
|
port: string;
|
||||||
|
pathname: string;
|
||||||
|
search: string;
|
||||||
|
hash: string;
|
||||||
|
toString(): string;
|
||||||
|
toJSON(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformGlobals {
|
||||||
|
TextEncoder: new (encoding?: string) => PlatformTextEncoder;
|
||||||
|
TextDecoder: new (encoding?: string) => PlatformTextDecoder;
|
||||||
|
URL: new (url: string) => { href: string; pathname: string; toString(): string };
|
||||||
|
randomUUID(): string;
|
||||||
|
now(): number;
|
||||||
|
btoa(data: string): string;
|
||||||
|
atob(data: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobOptions {
|
||||||
|
cwd?: string;
|
||||||
|
ignore?: string[];
|
||||||
|
absolute?: boolean;
|
||||||
|
dot?: boolean;
|
||||||
|
onlyFiles?: boolean;
|
||||||
|
onlyDirectories?: boolean;
|
||||||
|
deep?: number;
|
||||||
|
ignoreCase?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobResult {
|
||||||
|
path: string;
|
||||||
|
isFile: boolean;
|
||||||
|
isDirectory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobTool {
|
||||||
|
glob(pattern: string, options?: GlobOptions): Promise<string[]>;
|
||||||
|
globWithInfo(pattern: string, options?: GlobOptions): Promise<GlobResult[]>;
|
||||||
|
isMatch(path: string, pattern: string): boolean;
|
||||||
|
search(pattern: string, basePath?: string): Promise<string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrepOptions {
|
||||||
|
cwd?: string;
|
||||||
|
ignoreCase?: boolean;
|
||||||
|
multiline?: boolean;
|
||||||
|
glob?: string | string[];
|
||||||
|
include?: string[];
|
||||||
|
exclude?: string[];
|
||||||
|
context?: number;
|
||||||
|
beforeContext?: number;
|
||||||
|
afterContext?: number;
|
||||||
|
headLimit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrepMatch {
|
||||||
|
path: string;
|
||||||
|
line: number;
|
||||||
|
column?: number;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrepSearchOptions {
|
||||||
|
pattern: string;
|
||||||
|
path?: string;
|
||||||
|
glob?: string | string[];
|
||||||
|
ignoreCase?: boolean;
|
||||||
|
context?: number;
|
||||||
|
outputMode?: 'content' | 'files_with_matches' | 'count';
|
||||||
|
maxResults?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrepSearchResult {
|
||||||
|
lines: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrepTool {
|
||||||
|
grep(pattern: string | RegExp, options?: GrepOptions): Promise<GrepMatch[]>;
|
||||||
|
grepFiles(pattern: string | RegExp, options?: GrepOptions): Promise<string[]>;
|
||||||
|
grepCount(pattern: string | RegExp, options?: GrepOptions): Promise<number>;
|
||||||
|
search(options: GrepSearchOptions): Promise<GrepSearchResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformTools {
|
||||||
|
glob: GlobTool | null;
|
||||||
|
grep: GrepTool | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileInfo {
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
isFile: boolean;
|
||||||
|
isDirectory: boolean;
|
||||||
|
size: number;
|
||||||
|
modifiedTime?: number;
|
||||||
|
createdTime?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileReadOptions {
|
||||||
|
encoding?: 'utf-8' | 'binary' | 'base64';
|
||||||
|
start?: number;
|
||||||
|
end?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileWriteOptions {
|
||||||
|
encoding?: 'utf-8' | 'binary' | 'base64';
|
||||||
|
append?: boolean;
|
||||||
|
createDir?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FileSystemOperations {
|
||||||
|
readFile(path: string, options?: FileReadOptions): Promise<string | ArrayBuffer>;
|
||||||
|
writeFile(path: string, data: string | ArrayBuffer, options?: FileWriteOptions): Promise<void>;
|
||||||
|
appendFile(path: string, data: string, options?: FileWriteOptions): Promise<void>;
|
||||||
|
deleteFile(path: string): Promise<void>;
|
||||||
|
exists(path: string): Promise<boolean>;
|
||||||
|
stat(path: string): Promise<FileInfo>;
|
||||||
|
readdir(path: string): Promise<FileInfo[]>;
|
||||||
|
mkdir(path: string, recursive?: boolean): Promise<void>;
|
||||||
|
rmdir(path: string, recursive?: boolean): Promise<void>;
|
||||||
|
copy(src: string, dest: string): Promise<void>;
|
||||||
|
move(src: string, dest: string): Promise<void>;
|
||||||
|
watch?(path: string, callback: (event: string, filename: string) => void): () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessResult {
|
||||||
|
exitCode: number;
|
||||||
|
stdout: string;
|
||||||
|
stderr: string;
|
||||||
|
signal?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessOptions {
|
||||||
|
cwd?: string;
|
||||||
|
env?: Record<string, string>;
|
||||||
|
timeout?: number;
|
||||||
|
input?: string;
|
||||||
|
shell?: boolean;
|
||||||
|
maxBuffer?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessOperations {
|
||||||
|
exec(command: string, options?: ProcessOptions): Promise<ProcessResult>;
|
||||||
|
execFile(file: string, args: string[], options?: ProcessOptions): Promise<ProcessResult>;
|
||||||
|
spawn?(command: string, args: string[], options?: ProcessOptions): AsyncIterable<string>;
|
||||||
|
which?(command: string): Promise<string | null>;
|
||||||
|
kill?(pid: number, signal?: string): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageOperations {
|
||||||
|
get(key: string): Promise<string | null>;
|
||||||
|
set(key: string, value: string, ttl?: number): Promise<void>;
|
||||||
|
delete(key: string): Promise<void>;
|
||||||
|
clear(): Promise<void>;
|
||||||
|
keys(): Promise<string[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PathOperations {
|
||||||
|
join(...paths: string[]): string;
|
||||||
|
dirname(path: string): string;
|
||||||
|
basename(path: string, ext?: string): string;
|
||||||
|
extname(path: string): string;
|
||||||
|
normalize(path: string): string;
|
||||||
|
isAbsolute(path: string): boolean;
|
||||||
|
resolve(...paths: string[]): string;
|
||||||
|
relative(from: string, to: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformResponse {
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
ok: boolean;
|
||||||
|
text(): Promise<string>;
|
||||||
|
json(): Promise<any>;
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformFetchRequestInit {
|
||||||
|
method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
|
||||||
|
headers?: Record<string, string>;
|
||||||
|
body?: string | ArrayBuffer | Record<string, string | ArrayBuffer>;
|
||||||
|
timeout?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformWebSocket {
|
||||||
|
readonly readyState: number;
|
||||||
|
readonly url: string;
|
||||||
|
send(data: string | ArrayBuffer): void;
|
||||||
|
close(code?: number, reason?: string): void;
|
||||||
|
addEventListener(type: string, listener: (event: any) => void): void;
|
||||||
|
removeEventListener(type: string, listener: (event: any) => void): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NetworkOperations {
|
||||||
|
fetch(url: string, options?: PlatformFetchRequestInit): Promise<PlatformResponse>;
|
||||||
|
fetchStream?(url: string, options?: PlatformFetchRequestInit): AsyncGenerator<ArrayBuffer, PlatformResponse, unknown>;
|
||||||
|
connectWebSocket?(url: string, protocols?: string[]): Promise<PlatformWebSocket>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlatformInfo {
|
||||||
|
runtime: RuntimeType;
|
||||||
|
os: OSType;
|
||||||
|
version?: string;
|
||||||
|
arch?: string;
|
||||||
|
hostname?: string;
|
||||||
|
capabilities: PlatformCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly apiName?: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly category: ToolCategory;
|
||||||
|
readonly inputSchema: ToolInputSchema;
|
||||||
|
readonly outputSchema?: ToolOutputSchema;
|
||||||
|
readonly handler: (params: ToolInput, context: ToolExecutionContext) => Promise<ToolOutput>;
|
||||||
|
readonly permissionLevel: ToolPermissionLevel;
|
||||||
|
readonly requiredPermissions?: string[];
|
||||||
|
readonly features?: ToolFeatures;
|
||||||
|
readonly metadata?: ToolMetadata;
|
||||||
|
readonly isMcp?: boolean;
|
||||||
|
readonly shouldDefer?: boolean;
|
||||||
|
readonly alwaysLoad?: boolean;
|
||||||
|
readonly searchHint?: string;
|
||||||
|
prompt?(options: ToolPromptOptions): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Platform {
|
||||||
|
getInfo(): PlatformInfo;
|
||||||
|
createAbortController(): PlatformAbortController;
|
||||||
|
readonly path: PathOperations;
|
||||||
|
readonly fs: FileSystemOperations | null;
|
||||||
|
readonly process: ProcessOperations | null;
|
||||||
|
readonly storage: StorageOperations;
|
||||||
|
readonly network: NetworkOperations;
|
||||||
|
readonly globals: PlatformGlobals;
|
||||||
|
readonly tools: PlatformTools;
|
||||||
|
readonly builtinTools: Tool[];
|
||||||
|
getEnv(key: string): string | undefined;
|
||||||
|
getAllEnv?(): Record<string, string>;
|
||||||
|
setEnv?(key: string, value: string): void;
|
||||||
|
getCwd(): string;
|
||||||
|
setCwd?(path: string): void;
|
||||||
|
exit?(code: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreatePlatformOptions {
|
||||||
|
storagePath?: string;
|
||||||
|
storageType?: 'localStorage' | 'indexedDB';
|
||||||
|
dbName?: string;
|
||||||
|
context?: any;
|
||||||
|
storageName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolInput = Record<string, unknown>;
|
||||||
|
export type ToolOutput = string | Record<string, unknown> | void;
|
||||||
|
|
||||||
|
export type ToolPermissionLevel = 'safe' | 'moderate' | 'dangerous' | 'restricted';
|
||||||
|
export type ToolCategory = 'file' | 'code' | 'search' | 'execute' | 'network' | 'analysis' | 'generation' | 'communication' | 'custom';
|
||||||
|
|
||||||
|
export interface ToolSchemaProperty {
|
||||||
|
type: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object';
|
||||||
|
description: string;
|
||||||
|
enum?: string[];
|
||||||
|
default?: unknown;
|
||||||
|
examples?: unknown[];
|
||||||
|
items?: ToolSchemaProperty;
|
||||||
|
properties?: Record<string, ToolSchemaProperty>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolInputSchema {
|
||||||
|
type: 'object';
|
||||||
|
properties: Record<string, ToolSchemaProperty>;
|
||||||
|
required?: string[];
|
||||||
|
additionalProperties?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolExecutionContext {
|
||||||
|
toolCallId: string;
|
||||||
|
workingDirectory: string;
|
||||||
|
additionalWorkingDirectories?: string[];
|
||||||
|
abortController: PlatformAbortController;
|
||||||
|
config: {
|
||||||
|
timeout?: number;
|
||||||
|
maxOutputSize?: number;
|
||||||
|
allowedDirectories?: string[];
|
||||||
|
};
|
||||||
|
logger: {
|
||||||
|
info(message: string, ...args: unknown[]): void;
|
||||||
|
warn(message: string, ...args: unknown[]): void;
|
||||||
|
error(message: string, ...args: unknown[]): void;
|
||||||
|
debug(message: string, ...args: unknown[]): void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BuiltinTool {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly apiName?: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly category: ToolCategory;
|
||||||
|
readonly inputSchema: ToolInputSchema;
|
||||||
|
readonly permissionLevel: ToolPermissionLevel;
|
||||||
|
handler(params: ToolInput, context: ToolExecutionContext): Promise<ToolOutput>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'waterflow/runtime/core/tools/tool_interface' {
|
||||||
|
import type { PlatformAbortController } from 'waterflow-ts/platform/types';
|
||||||
|
import type { AgentId } from 'waterflow-ts/shared/types/agent';
|
||||||
|
import type { WorkflowRunner } from 'waterflow-ts/runtime/core/workflow/types';
|
||||||
|
import type { WorkflowRegistryImpl } from 'waterflow-ts/runtime/core/workflow/workflow_registry';
|
||||||
|
import type { AgentRegistryImpl } from 'waterflow-ts/runtime/core/workflow/agent_registry';
|
||||||
|
import type { AgentExecutor } from 'waterflow-ts/runtime/core/agent/agent_executor';
|
||||||
|
import type { MCPClient } from 'waterflow-ts/runtime/core/tools/mcp/mcp_client';
|
||||||
|
|
||||||
|
export type ToolCategory =
|
||||||
|
| 'file'
|
||||||
|
| 'code'
|
||||||
|
| 'search'
|
||||||
|
| 'execute'
|
||||||
|
| 'network'
|
||||||
|
| 'analysis'
|
||||||
|
| 'generation'
|
||||||
|
| 'communication'
|
||||||
|
| 'mcp'
|
||||||
|
| 'custom';
|
||||||
|
|
||||||
|
export type PermissionLevel =
|
||||||
|
| 'safe'
|
||||||
|
| 'moderate'
|
||||||
|
| 'dangerous'
|
||||||
|
| 'restricted';
|
||||||
|
|
||||||
|
export type SchemaType =
|
||||||
|
| 'string'
|
||||||
|
| 'number'
|
||||||
|
| 'integer'
|
||||||
|
| 'boolean'
|
||||||
|
| 'array'
|
||||||
|
| 'object';
|
||||||
|
|
||||||
|
export interface SchemaProperty {
|
||||||
|
type: SchemaType;
|
||||||
|
description: string;
|
||||||
|
enum?: string[];
|
||||||
|
minimum?: number;
|
||||||
|
maximum?: number;
|
||||||
|
minLength?: number;
|
||||||
|
maxLength?: number;
|
||||||
|
pattern?: string;
|
||||||
|
default?: any;
|
||||||
|
examples?: any[];
|
||||||
|
suggestedSource?: 'context' | 'literal' | 'file';
|
||||||
|
items?: SchemaProperty;
|
||||||
|
properties?: Record<string, SchemaProperty>;
|
||||||
|
required?: string[];
|
||||||
|
additionalProperties?: boolean | SchemaProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolInputSchema {
|
||||||
|
type: 'object';
|
||||||
|
properties: Record<string, SchemaProperty>;
|
||||||
|
required?: string[];
|
||||||
|
additionalProperties?: boolean;
|
||||||
|
semanticHints?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolOutputSchema {
|
||||||
|
type: 'object';
|
||||||
|
properties: Record<string, SchemaProperty>;
|
||||||
|
format?: 'json' | 'text' | 'markdown' | 'binary';
|
||||||
|
maxSize?: number;
|
||||||
|
maxLines?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolInput = Record<string, unknown>;
|
||||||
|
export type ToolOutput = string | Record<string, unknown> | void;
|
||||||
|
|
||||||
|
export interface Logger {
|
||||||
|
info(message: string, ...args: any[]): void;
|
||||||
|
warn(message: string, ...args: any[]): void;
|
||||||
|
error(message: string, ...args: any[]): void;
|
||||||
|
debug(message: string, ...args: any[]): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolConfig {
|
||||||
|
timeout?: number;
|
||||||
|
maxOutputSize?: number;
|
||||||
|
allowedDirectories?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolExecutionContext {
|
||||||
|
toolCallId: string;
|
||||||
|
agentId?: AgentId;
|
||||||
|
workingDirectory: string;
|
||||||
|
additionalWorkingDirectories?: string[] | undefined;
|
||||||
|
abortController: PlatformAbortController;
|
||||||
|
config: ToolConfig;
|
||||||
|
logger: Logger;
|
||||||
|
workflowRunner?: WorkflowRunner | undefined;
|
||||||
|
workflowRegistry?: WorkflowRegistryImpl | undefined;
|
||||||
|
agentExecutor?: AgentExecutor | undefined;
|
||||||
|
agentRegistry?: AgentRegistryImpl | undefined;
|
||||||
|
allowedAgentTypes?: string[] | undefined;
|
||||||
|
mcpClients?: Map<string, MCPClient> | undefined;
|
||||||
|
tools?: Tool[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolHandler = (
|
||||||
|
params: ToolInput,
|
||||||
|
context: ToolExecutionContext
|
||||||
|
) => Promise<ToolOutput>;
|
||||||
|
|
||||||
|
export interface ToolFeatures {
|
||||||
|
isAsync?: boolean;
|
||||||
|
isStreamable?: boolean;
|
||||||
|
isCacheable?: boolean;
|
||||||
|
requiresConfirmation?: boolean;
|
||||||
|
supportsProgress?: boolean;
|
||||||
|
supportsCancellation?: boolean;
|
||||||
|
producesFiles?: boolean;
|
||||||
|
producesImages?: boolean;
|
||||||
|
producesStructuredOutput?: boolean;
|
||||||
|
requiresMcp?: boolean;
|
||||||
|
requiresNetwork?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolExample {
|
||||||
|
description: string;
|
||||||
|
input: ToolInput;
|
||||||
|
output: ToolOutput;
|
||||||
|
explanation?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolMetadata {
|
||||||
|
source: 'builtin' | 'mcp' | 'plugin' | 'external';
|
||||||
|
version?: string;
|
||||||
|
author?: string;
|
||||||
|
documentationUrl?: string;
|
||||||
|
examples?: ToolExample[];
|
||||||
|
estimatedDuration?: number;
|
||||||
|
estimatedTokens?: number;
|
||||||
|
compatibleModels?: string[];
|
||||||
|
incompatibleModels?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolPromptOptions {
|
||||||
|
tools: Tool[];
|
||||||
|
agentRegistry?: AgentRegistryImpl | undefined;
|
||||||
|
workflowRegistry?: WorkflowRegistryImpl | undefined;
|
||||||
|
allowedAgentTypes?: string[] | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly apiName?: string;
|
||||||
|
readonly description: string;
|
||||||
|
readonly category: ToolCategory;
|
||||||
|
readonly inputSchema: ToolInputSchema;
|
||||||
|
readonly outputSchema?: ToolOutputSchema;
|
||||||
|
readonly handler: ToolHandler;
|
||||||
|
readonly permissionLevel: PermissionLevel;
|
||||||
|
readonly requiredPermissions?: string[];
|
||||||
|
readonly features?: ToolFeatures;
|
||||||
|
readonly metadata?: ToolMetadata;
|
||||||
|
readonly isMcp?: boolean;
|
||||||
|
readonly shouldDefer?: boolean;
|
||||||
|
readonly alwaysLoad?: boolean;
|
||||||
|
readonly searchHint?: string;
|
||||||
|
prompt?(options: ToolPromptOptions): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolExecutionError {
|
||||||
|
type: ToolErrorType;
|
||||||
|
message: string;
|
||||||
|
code?: string;
|
||||||
|
details?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ToolErrorType =
|
||||||
|
| 'validation_error'
|
||||||
|
| 'permission_denied'
|
||||||
|
| 'timeout'
|
||||||
|
| 'execution_error'
|
||||||
|
| 'network_error'
|
||||||
|
| 'mcp_error'
|
||||||
|
| 'unknown_error';
|
||||||
|
|
||||||
|
export interface ToolExecutionResult {
|
||||||
|
success: boolean;
|
||||||
|
toolId: string;
|
||||||
|
toolCallId: string;
|
||||||
|
output: ToolOutput;
|
||||||
|
error?: ToolExecutionError;
|
||||||
|
metadata: {
|
||||||
|
duration: number;
|
||||||
|
tokensUsed?: number;
|
||||||
|
cached?: boolean;
|
||||||
|
retryCount?: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolCall {
|
||||||
|
id: string;
|
||||||
|
toolId: string;
|
||||||
|
toolName: string;
|
||||||
|
input: ToolInput;
|
||||||
|
callerId: AgentId | string;
|
||||||
|
callerType: 'agent' | 'workflow' | 'main';
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTextOutput(text: string): ToolOutput;
|
||||||
|
export function createJSONOutput(data: Record<string, unknown>): ToolOutput;
|
||||||
|
export function createErrorOutput(message: string, code?: string, details?: Record<string, unknown>): ToolOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'waterflow/runtime/core/tools/builtin' {
|
||||||
|
import type { Platform } from 'waterflow-ts/platform/types';
|
||||||
|
import type { Tool } from 'waterflow-ts/runtime/core/tools/tool_interface';
|
||||||
|
import { ToolRegistry } from 'waterflow-ts/runtime/core/tools/tool_registry';
|
||||||
|
|
||||||
|
export const FRAMEWORK_TOOLS: Tool[];
|
||||||
|
export function initializeToolRegistry(platform: Platform): ToolRegistry;
|
||||||
|
export function getFrameworkTools(): Tool[];
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'waterflow/runtime/core/tools/tool_registry' {
|
||||||
|
import type { Tool, ToolCategory, ToolInput } from 'waterflow-ts/runtime/core/tools/tool_interface';
|
||||||
|
|
||||||
|
export interface ValidationResult {
|
||||||
|
valid: boolean;
|
||||||
|
errors?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolSearchResult {
|
||||||
|
tool: Tool;
|
||||||
|
relevanceScore: number;
|
||||||
|
matchReason: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchOptions {
|
||||||
|
category?: ToolCategory;
|
||||||
|
permissionLevel?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegistryStatistics {
|
||||||
|
totalTools: number;
|
||||||
|
byCategory: Record<string, number>;
|
||||||
|
bySource: Record<string, number>;
|
||||||
|
byPermissionLevel: Record<string, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolDefinitionExtended {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
parameters: {
|
||||||
|
type: 'object';
|
||||||
|
properties: Record<string, any>;
|
||||||
|
required?: string[];
|
||||||
|
additionalProperties?: boolean;
|
||||||
|
};
|
||||||
|
execute: (params: Record<string, any>) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ToolRegistry {
|
||||||
|
register(tool: Tool | ToolDefinitionExtended): void;
|
||||||
|
registerAll(tools: (Tool | ToolDefinitionExtended)[]): void;
|
||||||
|
unregister(toolId: string): void;
|
||||||
|
get(toolId: string): Tool | ToolDefinitionExtended | undefined;
|
||||||
|
getByName(name: string): Tool | ToolDefinitionExtended | undefined;
|
||||||
|
has(toolId: string): boolean;
|
||||||
|
size(): number;
|
||||||
|
listAll(): (Tool | ToolDefinitionExtended)[];
|
||||||
|
listByCategory(category: ToolCategory): (Tool | ToolDefinitionExtended)[];
|
||||||
|
listByPermissionLevel(level: string): (Tool | ToolDefinitionExtended)[];
|
||||||
|
search(query: string, options?: SearchOptions): ToolSearchResult[];
|
||||||
|
isAvailable(toolId: string, context?: any): boolean;
|
||||||
|
validateInput(toolId: string, input: ToolInput): ValidationResult;
|
||||||
|
clear(): void;
|
||||||
|
getStatistics(): RegistryStatistics;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,7 +16,8 @@
|
|||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"typeRoots": ["./src/types", "./node_modules/@types"]
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
|||||||
Reference in New Issue
Block a user