Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions modules/tool/packages/memmachine/DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# MemMachine 系统工具

## 参考信息

本工具集用于处理 MemMachine 相关操作。

### 参考文档

- [MemMachine 文档](https://docs.memmachine.ai/)
- [MemMachine API 文档](https://api.memmachine.ai/docs)

### 测试密钥环境变量名

无需环境变量配置

密钥获取链接:https://console.memmachine.ai/

### 工具集/子工具列表

一个MemMachine工具集,包含以下工具:

#### 1. 存储记忆(store)
- **功能**:将用户的记忆内容通过 MemMachine API 存储到 MemMachine 服务端。
- **API**:`POST {baseUrl}/memories`
- **输入**:组织 ID, 项目 ID, 记忆类型, 记忆消息, 消息发送者, 消息接收者, 消息创建时间, 消息角色, 附加属性
- **输出**:新的记忆 ID

#### 2. 搜索记忆(search)
- **功能**:根据关键词,通过 MemMachine API 检索已存储的记忆内容。
- **API**:`POST {baseUrl}/memories/search`
- **输入**:组织 ID, 项目 ID, 记忆类型, 搜索内容,最大返回数量,过滤条件,上下文模板
- **输出**:记忆上下文
89 changes: 89 additions & 0 deletions modules/tool/packages/memmachine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# MemMachine 工具集

MemMachine 是一个面向高级 AI 智能体的开源记忆层,使 AI 应用能够学习、存储和回溯历史会话中的数据与偏好,提升后续交互的智能性与个性化体验。

## 密钥获取

1. 访问 [MemMachine 在线平台](https://console.memmachine.ai/) 并注册账号。
2. 登录平台后,前往 [API Keys 页面](https://console.memmachine.ai/api-keys) 获取 API 密钥。
3. 密钥格式为:`mm-xxxxxxxxxxxxx`(以 `mm-` 开头)。

## 功能

### 记忆存储

将关于用户或对话的重要新信息存入记忆。

**主要特性:**

- 存储用户各类信息内容
- 基于已存信息智能总结用户偏好等特征

**参数配置**

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| orgId | string | universal | 组织的唯一标识符 |
| projectId | string | universal | 项目的唯一标识符 |
| types | multipleSelect | ['episodic', 'semantic'] | 记忆类型,留空则添加到全部类型 |
| content | string | - | **必填** 要存储的记忆内容 |
| producer | string | user | 记忆内容的发送者 |
| producedFor | string | - | 记忆内容的接收者 |
| timestamp | string | - | 记忆内容的创建时间(ISO 8601格式) |
| role | string | - | 记忆内容在对话中的角色 |
| metadata | string | - | 附加的记忆内容属性(JSON格式) |

**输出格式**
```json
{
"memoryId": "新的记忆 ID"
}
```

### 记忆搜索

检索用户的相关上下文、记忆或画像信息。

**主要特性:**

- 支持自然语言查询,查找相关记忆内容
- 可跨会话回溯,获取全面的历史信息

**参数配置**

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| orgId | string | universal | 组织的唯一标识符 |
| projectId | string | universal | 项目的唯一标识符 |
| types | multipleSelect | ['episodic', 'semantic'] | 记忆类型,留空则搜索全部类型 |
| query | string | - | **必填** 记忆检索的自然语言查询 |
| limit | number | 10 | **必填** 搜索结果数量上限 |
| filter | string | - | 过滤记忆的条件 |
| contextTemplate | string | 默认模板 | 构建记忆上下文的模板 |

**输出格式**
```json
{
"memoryContext": "记忆上下文"
}
```

## 开发和测试

```bash
# 安装依赖
bun install

# 运行测试
bun run test

# 构建项目
bun run build:runtime
```

## 支持与反馈

如有问题或建议,请通过以下方式联系:

- [GitHub Issues](https://github.com/MemMachine/MemMachine/issues)
- [MemMachine 文档](https://docs.memmachine.ai/)
93 changes: 93 additions & 0 deletions modules/tool/packages/memmachine/children/search/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { defineTool } from '@tool/type';
import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';
import { contextTemplate } from './src/contextTemplate';

export default defineTool({
name: {
'zh-CN': '搜索记忆',
en: 'Search Memory'
},
description: {
'zh-CN': '用于通过 MemMachine API 搜索记忆。',
en: 'Used to search memory via the MemMachine API.'
},
toolDescription: 'Searches memory efficiently via the MemMachine API.',
versionList: [
{
value: '0.1.0',
description: 'Default version',
inputs: [
{
key: 'orgId',
label: '组织 ID',
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
selectedTypeIndex: 1,
valueType: WorkflowIOValueTypeEnum.string,
description: '可选,指定搜索记忆的组织 ID'
},
{
key: 'projectId',
label: '项目 ID',
renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference],
selectedTypeIndex: 1,
valueType: WorkflowIOValueTypeEnum.string,
description: '可选,指定搜索记忆的项目 ID'
},
{
key: 'types',
label: '记忆类型',
renderTypeList: [FlowNodeInputTypeEnum.multipleSelect, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.arrayString,
list: [
{ label: '情节记忆', value: 'episodic' },
{ label: '语义记忆', value: 'semantic' }
],
defaultValue: ['episodic', 'semantic'],
description: '指定记忆的类型。未指定时将搜索所有类型的记忆。'
},
{
key: 'query',
label: '搜索内容',
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
required: true,
description: '用于语义记忆检索的自然语言查询。应为对所需信息的描述性字符串。'
},
{
key: 'limit',
label: '最大返回数量',
renderTypeList: [FlowNodeInputTypeEnum.numberInput, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.number,
defaultValue: 10,
required: true,
description: '指定搜索结果中要返回的最大记忆条目数量。'
},
{
key: 'filter',
label: '过滤条件',
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
placeholder: '例如:metadata.session_id=session_123 AND metadata.user_id=user_456',
description:
"可选,指定用于过滤记忆的条件。应用于记忆的附加属性,支持简单查询语法(如 'metadata.user_id=123')来实现精确匹配,多个条件可通过 AND 组合。"
},
{
key: 'contextTemplate',
label: '上下文模板',
renderTypeList: [FlowNodeInputTypeEnum.textarea, FlowNodeInputTypeEnum.reference],
valueType: WorkflowIOValueTypeEnum.string,
defaultValue: contextTemplate,
description: '用于构建记忆上下文的模板,支持占位符替换。'
}
],
outputs: [
{
valueType: WorkflowIOValueTypeEnum.string,
key: 'memoryContext',
label: '记忆上下文',
description: '记忆上下文'
}
]
}
]
});
10 changes: 10 additions & 0 deletions modules/tool/packages/memmachine/children/search/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import config from './config';
import { InputType, OutputType, tool as toolCb } from './src';
import { exportTool } from '@tool/utils/tool';

export default exportTool({
toolCb,
InputType,
OutputType,
config
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export const contextTemplate = `# 记忆上下文

**使用说明**:
以语义记忆为用户的核心事实依据。
结合短期记忆和事件摘要,获取近期上下文与交互概览。
查阅长期记忆,洞察历史模式与深层信息。

## 用户画像(语义记忆)
{{semanticMemory}}

## 当前上下文(短期记忆)
{{shortTermMemory}}

## 事件摘要
{{episodeSummary}}

## 历史上下文(长期记忆)
{{longTermMemory}}
`;
61 changes: 61 additions & 0 deletions modules/tool/packages/memmachine/children/search/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { z } from 'zod';
import { renderTemplate } from './renderTemplate';

export const InputType = z.object({
baseUrl: z.preprocess(
(v) => (typeof v === 'string' && v.trim() === '' ? undefined : v),
z.string().default('https://api.memmachine.ai/v2')
),
apiKey: z.string().nonempty(),
orgId: z.string().optional(),
projectId: z.string().optional(),
types: z.array(z.string()).default(['episodic', 'semantic']),
query: z.string().nonempty(),
limit: z.number().default(10),
filter: z.string().default(''),
contextTemplate: z.string().default('')
});

export const OutputType = z.object({
memoryContext: z.string()
});

export async function tool({
baseUrl,
apiKey,
orgId,
projectId,
types,
query,
limit,
filter,
contextTemplate
}: z.infer<typeof InputType>): Promise<z.infer<typeof OutputType>> {
// 请求数据
const response = await fetch(`${baseUrl}/memories/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
authorization: `Bearer ${apiKey}`
},
body: JSON.stringify({
org_id: orgId,
project_id: projectId,
types,
top_k: limit,
query,
filter
})
});

if (!response.ok) {
return Promise.reject({
error: `MemMachine API Error: ${response.status} ${response.statusText}`
});
}

const data = await response.json();
return {
memoryContext: renderTemplate(contextTemplate, data?.content || {})
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
interface EpisodeItem {
content?: string;
producer_id?: string;
produced_for_id?: string;
}

interface SemanticFeature {
tag?: string;
feature_name?: string;
value?: string;
}

export function renderTemplate(template: string, content: Record<string, any>): string {
// semantic memory
const semanticMemory = content.semantic_memory || [];

// episodic memory
const episodicMemory = content.episodic_memory || {};
const shortTermMemory = episodicMemory.short_term_memory?.episodes || [];
const episodeSummary = episodicMemory.short_term_memory?.episode_summary || [];
const longTermMemory = episodicMemory.long_term_memory?.episodes || [];

let result = template;
result = result.replace('{{semanticMemory}}', _formatSemanticMemory(semanticMemory));
result = result.replace('{{shortTermMemory}}', _formatEpisodicMemory(shortTermMemory));
result = result.replace('{{episodeSummary}}', _formatEpisodeSummary(episodeSummary));
result = result.replace('{{longTermMemory}}', _formatEpisodicMemory(longTermMemory));

return result;
}

function _formatSemanticMemory(semanticMemory?: SemanticFeature[]): string {
if (!Array.isArray(semanticMemory) || semanticMemory.length === 0) {
return '*No semantic features available*';
}

return semanticMemory
.map((feature) => {
const tag = feature?.tag?.trim() || 'General';
const featureName = feature?.feature_name?.trim() || 'property';
const value = feature?.value?.trim() || '';
return value ? `- **${tag}** / ${featureName}: ${value}` : `- **${tag}** / ${featureName}`;
})
.join('\n');
}

function _formatEpisodicMemory(episodes?: EpisodeItem[]): string {
if (!Array.isArray(episodes) || episodes.length === 0) {
return '*No memories available*';
}

return episodes
.map((episode) => {
const content = episode?.content?.trim() || '';
const producer = episode?.producer_id?.trim() || 'Unknown';
const producedFor = episode?.produced_for_id?.trim() || 'Unknown';
return content
? `- **${producer}** → ${producedFor}: ${content}`
: `- **${producer}** → ${producedFor}`;
})
.join('\n');
}

function _formatEpisodeSummary(episodeSummary?: string[]): string {
if (!Array.isArray(episodeSummary) || !episodeSummary.some((s) => s?.trim())) {
return '*No episode summaries available*';
}

return episodeSummary
.map((summary) => summary?.trim())
.filter((summary) => !!summary)
.map((summary) => `> ${summary}`)
.join('\n\n');
}
Loading