@cicctencent/agent-midway 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -0
- package/dist/adapters/express.d.ts +8 -0
- package/dist/adapters/express.js +91 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +21 -0
- package/dist/adapters/koa.d.ts +3 -0
- package/dist/adapters/koa.js +75 -0
- package/dist/adapters/midway.d.ts +5 -0
- package/dist/adapters/midway.js +11 -0
- package/dist/adapters/next.d.ts +12 -0
- package/dist/adapters/next.js +89 -0
- package/dist/adapters/shared.d.ts +4 -0
- package/dist/adapters/shared.js +31 -0
- package/dist/channel/dingtalk.d.ts +18 -0
- package/dist/channel/dingtalk.js +68 -0
- package/dist/channel/feishu.d.ts +20 -0
- package/dist/channel/feishu.js +96 -0
- package/dist/channel/index.d.ts +46 -0
- package/dist/channel/index.js +311 -0
- package/dist/channel/types.d.ts +77 -0
- package/dist/channel/types.js +7 -0
- package/dist/channel/wecom.d.ts +22 -0
- package/dist/channel/wecom.js +106 -0
- package/dist/component.d.ts +49 -0
- package/dist/component.js +129 -0
- package/dist/connector/calendar-adapter.d.ts +19 -0
- package/dist/connector/calendar-adapter.js +236 -0
- package/dist/connector/db-adapter.d.ts +28 -0
- package/dist/connector/db-adapter.js +193 -0
- package/dist/connector/email-adapter.d.ts +23 -0
- package/dist/connector/email-adapter.js +192 -0
- package/dist/connector/fs-adapter.d.ts +15 -0
- package/dist/connector/fs-adapter.js +199 -0
- package/dist/connector/http-adapter.d.ts +29 -0
- package/dist/connector/http-adapter.js +181 -0
- package/dist/connector/index.d.ts +24 -0
- package/dist/connector/index.js +454 -0
- package/dist/connector/mcp-adapter.d.ts +27 -0
- package/dist/connector/mcp-adapter.js +156 -0
- package/dist/connector/mq-adapter.d.ts +25 -0
- package/dist/connector/mq-adapter.js +181 -0
- package/dist/connector/types.d.ts +205 -0
- package/dist/connector/types.js +9 -0
- package/dist/controller/a2a.controller.d.ts +41 -0
- package/dist/controller/a2a.controller.js +150 -0
- package/dist/controller/agent-profile.controller.d.ts +97 -0
- package/dist/controller/agent-profile.controller.js +200 -0
- package/dist/controller/agent.controller.d.ts +199 -0
- package/dist/controller/agent.controller.js +414 -0
- package/dist/controller/application.controller.d.ts +113 -0
- package/dist/controller/application.controller.js +217 -0
- package/dist/controller/automation.controller.d.ts +113 -0
- package/dist/controller/automation.controller.js +246 -0
- package/dist/controller/channel.controller.d.ts +73 -0
- package/dist/controller/channel.controller.js +183 -0
- package/dist/controller/chat.controller.d.ts +188 -0
- package/dist/controller/chat.controller.js +375 -0
- package/dist/controller/connector.controller.d.ts +134 -0
- package/dist/controller/connector.controller.js +257 -0
- package/dist/controller/knowledge-base.controller.d.ts +157 -0
- package/dist/controller/knowledge-base.controller.js +278 -0
- package/dist/controller/mcp-server.controller.d.ts +115 -0
- package/dist/controller/mcp-server.controller.js +236 -0
- package/dist/controller/model-config.controller.d.ts +139 -0
- package/dist/controller/model-config.controller.js +274 -0
- package/dist/controller/observability.controller.d.ts +124 -0
- package/dist/controller/observability.controller.js +142 -0
- package/dist/controller/security.controller.d.ts +91 -0
- package/dist/controller/security.controller.js +172 -0
- package/dist/controller/settings.controller.d.ts +83 -0
- package/dist/controller/settings.controller.js +280 -0
- package/dist/core/ai-workstation.d.ts +17 -0
- package/dist/core/ai-workstation.js +129 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.js +20 -0
- package/dist/core/service-container.d.ts +12 -0
- package/dist/core/service-container.js +54 -0
- package/dist/core/sse.d.ts +6 -0
- package/dist/core/sse.js +56 -0
- package/dist/core/types.d.ts +72 -0
- package/dist/core/types.js +2 -0
- package/dist/dto/agent.dto.d.ts +21 -0
- package/dist/dto/agent.dto.js +79 -0
- package/dist/dto/ai-config.dto.d.ts +67 -0
- package/dist/dto/ai-config.dto.js +249 -0
- package/dist/dto/chat.dto.d.ts +40 -0
- package/dist/dto/chat.dto.js +122 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +195 -0
- package/dist/memory/db-store.d.ts +33 -0
- package/dist/memory/db-store.js +143 -0
- package/dist/memory/index.d.ts +187 -0
- package/dist/memory/index.js +443 -0
- package/dist/model/ai-agent-profile.entity.d.ts +32 -0
- package/dist/model/ai-agent-profile.entity.js +289 -0
- package/dist/model/ai-application.entity.d.ts +20 -0
- package/dist/model/ai-application.entity.js +166 -0
- package/dist/model/ai-chat-memory.entity.d.ts +16 -0
- package/dist/model/ai-chat-memory.entity.js +123 -0
- package/dist/model/ai-chat-message.entity.d.ts +16 -0
- package/dist/model/ai-chat-message.entity.js +122 -0
- package/dist/model/ai-chat-skill.entity.d.ts +19 -0
- package/dist/model/ai-chat-skill.entity.js +155 -0
- package/dist/model/ai-chat-thread.entity.d.ts +15 -0
- package/dist/model/ai-chat-thread.entity.js +113 -0
- package/dist/model/ai-chat-workspace.entity.d.ts +17 -0
- package/dist/model/ai-chat-workspace.entity.js +136 -0
- package/dist/model/ai-kb-document.entity.d.ts +16 -0
- package/dist/model/ai-kb-document.entity.js +122 -0
- package/dist/model/ai-knowledge-base.entity.d.ts +22 -0
- package/dist/model/ai-knowledge-base.entity.js +185 -0
- package/dist/model/ai-mcp-server.entity.d.ts +23 -0
- package/dist/model/ai-mcp-server.entity.js +198 -0
- package/dist/model/ai-model-config.entity.d.ts +24 -0
- package/dist/model/ai-model-config.entity.js +200 -0
- package/dist/service/a2a.service.d.ts +142 -0
- package/dist/service/a2a.service.js +537 -0
- package/dist/service/agent-profile.service.d.ts +34 -0
- package/dist/service/agent-profile.service.js +110 -0
- package/dist/service/agent-server.service.d.ts +91 -0
- package/dist/service/agent-server.service.js +634 -0
- package/dist/service/agent-task-queue.service.d.ts +98 -0
- package/dist/service/agent-task-queue.service.js +283 -0
- package/dist/service/ai-chat.service.d.ts +103 -0
- package/dist/service/ai-chat.service.js +431 -0
- package/dist/service/ai-skill.service.d.ts +116 -0
- package/dist/service/ai-skill.service.js +457 -0
- package/dist/service/application.service.d.ts +42 -0
- package/dist/service/application.service.js +139 -0
- package/dist/service/automation.service.d.ts +37 -0
- package/dist/service/automation.service.js +196 -0
- package/dist/service/connector.service.d.ts +136 -0
- package/dist/service/connector.service.js +524 -0
- package/dist/service/knowledge-base.service.d.ts +138 -0
- package/dist/service/knowledge-base.service.js +528 -0
- package/dist/service/mcp-server.service.d.ts +39 -0
- package/dist/service/mcp-server.service.js +143 -0
- package/dist/service/model-config.service.d.ts +57 -0
- package/dist/service/model-config.service.js +168 -0
- package/dist/service/observability.service.d.ts +145 -0
- package/dist/service/observability.service.js +281 -0
- package/dist/service/openai.service.d.ts +88 -0
- package/dist/service/openai.service.js +406 -0
- package/dist/service/prompt-builder.service.d.ts +50 -0
- package/dist/service/prompt-builder.service.js +246 -0
- package/dist/tools/code-exec.tool.d.ts +37 -0
- package/dist/tools/code-exec.tool.js +162 -0
- package/dist/tools/datetime.tool.d.ts +21 -0
- package/dist/tools/datetime.tool.js +379 -0
- package/dist/tools/http-request.tool.d.ts +43 -0
- package/dist/tools/http-request.tool.js +455 -0
- package/dist/tools/registry.d.ts +71 -0
- package/dist/tools/registry.js +77 -0
- package/dist/tools/text-process.tool.d.ts +7 -0
- package/dist/tools/text-process.tool.js +366 -0
- package/dist/tools/web-search.tool.d.ts +28 -0
- package/dist/tools/web-search.tool.js +304 -0
- package/dist/types.d.ts +70 -0
- package/dist/types.js +7 -0
- package/package.json +69 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent 记忆系统
|
|
3
|
+
*
|
|
4
|
+
* 参考 Mastra Memory(https://mastra.ai/docs/memory/overview),
|
|
5
|
+
* 提供工作记忆(会话上下文)和长期记忆(跨会话知识)。
|
|
6
|
+
*
|
|
7
|
+
* 架构分层:
|
|
8
|
+
* ┌─────────────────────────────────┐
|
|
9
|
+
* │ AgentMemory │ ← 统一接口层
|
|
10
|
+
* │ (buildMemoryContext / recall) │
|
|
11
|
+
* ├─────────────────────────────────┤
|
|
12
|
+
* │ Working Memory │ ← 当前会话线程的近期记忆
|
|
13
|
+
* │ (getByThread, save) │
|
|
14
|
+
* ├─────────────────────────────────┤
|
|
15
|
+
* │ Long-term Memory │ ← 跨会话的持久化知识
|
|
16
|
+
* │ (search, saveLongTerm) │
|
|
17
|
+
* ├─────────────────────────────────┤
|
|
18
|
+
* │ MemoryStore │ ← 存储抽象层(可插拔)
|
|
19
|
+
* │ (InMemoryStore / DB Store) │
|
|
20
|
+
* └─────────────────────────────────┘
|
|
21
|
+
*
|
|
22
|
+
* 记忆提取策略:
|
|
23
|
+
* - 用户消息中包含“喜欢/偏好/习惯”等关键词 → 提取为 preference 记忆
|
|
24
|
+
* - 用户消息中包含事实性表述且长度 > 10 字 → 提取为 fact 记忆
|
|
25
|
+
* - 每轮对话结束后保存最后一条消息到工作记忆
|
|
26
|
+
*
|
|
27
|
+
* 生产环境升级路径:
|
|
28
|
+
* - 将 InMemoryStore 替换为数据库实现(如 PostgreSQL + pgvector)
|
|
29
|
+
* - 将关键词匹配搜索升级为向量相似度搜索
|
|
30
|
+
* - 将规则提取升级为 LLM 提取(调用 LLM 从对话中提取结构化记忆)
|
|
31
|
+
*/
|
|
32
|
+
import type { MemoryEntry, MemoryStore } from '../types';
|
|
33
|
+
import { DataSource } from 'typeorm';
|
|
34
|
+
export { DBMemoryStore } from './db-store';
|
|
35
|
+
/**
|
|
36
|
+
* 内存记忆存储实现
|
|
37
|
+
*
|
|
38
|
+
* 适用于单实例 / 开发环境,数据存储在内存中,重启后丢失。
|
|
39
|
+
* 生产环境应替换为数据库实现(实现 MemoryStore 接口即可)。
|
|
40
|
+
*
|
|
41
|
+
* 搜索算法:
|
|
42
|
+
* 当前使用简单的关键词匹配 + 分类加权:
|
|
43
|
+
* - 每个匹配关键词 +1 分
|
|
44
|
+
* - fact 分类额外 +0.5 分
|
|
45
|
+
* - preference 分类额外 +0.3 分
|
|
46
|
+
* - 按总分降序排序,返回 topK 结果
|
|
47
|
+
*/
|
|
48
|
+
export declare class InMemoryStore implements MemoryStore {
|
|
49
|
+
private entries;
|
|
50
|
+
save(entry: Omit<MemoryEntry, 'id' | 'createdAt'>): Promise<MemoryEntry>;
|
|
51
|
+
load(userId: string, options?: {
|
|
52
|
+
threadId?: number | string;
|
|
53
|
+
category?: string;
|
|
54
|
+
limit?: number;
|
|
55
|
+
}): Promise<MemoryEntry[]>;
|
|
56
|
+
getByThread(threadId: string | number, limit?: number): Promise<MemoryEntry[]>;
|
|
57
|
+
getByUser(userId: string, category?: string, limit?: number): Promise<MemoryEntry[]>;
|
|
58
|
+
/**
|
|
59
|
+
* 搜索相关记忆
|
|
60
|
+
* 使用关键词匹配 + 分类加权算法,过滤已过期记忆。
|
|
61
|
+
*
|
|
62
|
+
* @param userId 用户 ID
|
|
63
|
+
* @param query 搜索查询(自然语言)
|
|
64
|
+
* @param topK 返回结果数量,默认 10
|
|
65
|
+
* @returns 按相关度降序排列的记忆列表
|
|
66
|
+
*/
|
|
67
|
+
search(userId: string, query: string, topK?: number): Promise<MemoryEntry[]>;
|
|
68
|
+
delete(id: string | number): Promise<boolean>;
|
|
69
|
+
clearThread(threadId: string | number): Promise<void>;
|
|
70
|
+
clear(userId: string): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 创建 MemoryStore 实例
|
|
74
|
+
* 根据环境变量 AGENT_MEMORY_STORE 决定使用内存还是数据库存储。
|
|
75
|
+
*
|
|
76
|
+
* @param dataSource TypeORM DataSource(使用 db 存储时必需)
|
|
77
|
+
* @returns MemoryStore 实例
|
|
78
|
+
*/
|
|
79
|
+
export declare function createMemoryStore(dataSource?: DataSource): MemoryStore;
|
|
80
|
+
/**
|
|
81
|
+
* Agent 记忆管理器
|
|
82
|
+
*
|
|
83
|
+
* 封装工作记忆(当前会话)和长期记忆(跨会话)的统一访问接口。
|
|
84
|
+
* Agent 在每轮对话前后通过此类注入 / 提取记忆。
|
|
85
|
+
*
|
|
86
|
+
* 典型调用流程:
|
|
87
|
+
* 1. Agent 构建 system prompt 时调用 buildMemoryContext() 注入相关记忆
|
|
88
|
+
* 2. Agent 对话完成后调用 extractAndSave() 从对话中提取新记忆
|
|
89
|
+
*/
|
|
90
|
+
export declare class AgentMemory {
|
|
91
|
+
private store;
|
|
92
|
+
constructor(store?: MemoryStore);
|
|
93
|
+
/** 获取存储实例 */
|
|
94
|
+
getStore(): MemoryStore;
|
|
95
|
+
/**
|
|
96
|
+
* 保存会话消息到工作记忆
|
|
97
|
+
* 用于当前会话的上下文保持
|
|
98
|
+
*/
|
|
99
|
+
saveWorkingMemory(threadId: string, userId: string, content: string, meta?: Record<string, any>): Promise<MemoryEntry>;
|
|
100
|
+
/**
|
|
101
|
+
* 获取工作记忆(当前会话的近期记忆)
|
|
102
|
+
*/
|
|
103
|
+
getWorkingMemory(threadId: string | number, limit?: number): Promise<MemoryEntry[]>;
|
|
104
|
+
/**
|
|
105
|
+
* 保存长期记忆
|
|
106
|
+
* 从对话中提取的关键信息,跨会话持久化
|
|
107
|
+
*/
|
|
108
|
+
saveLongTermMemory(userId: string, content: string, category?: MemoryEntry['category'], meta?: Record<string, any>): Promise<MemoryEntry>;
|
|
109
|
+
/**
|
|
110
|
+
* 检索相关记忆
|
|
111
|
+
* 综合工作记忆(当前线程近期)和长期记忆(语义搜索),去重后按相关度排序。
|
|
112
|
+
*
|
|
113
|
+
* @param userId 用户 ID
|
|
114
|
+
* @param query 搜索查询
|
|
115
|
+
* @param options.threadId 当前线程 ID(用于加载工作记忆)
|
|
116
|
+
* @param options.topK 返回数量,默认 10
|
|
117
|
+
* @param options.includeWorking 是否包含工作记忆,默认 true
|
|
118
|
+
*/
|
|
119
|
+
recall(userId: string, query: string, options?: {
|
|
120
|
+
threadId?: string | number;
|
|
121
|
+
topK?: number;
|
|
122
|
+
includeWorking?: boolean;
|
|
123
|
+
}): Promise<MemoryEntry[]>;
|
|
124
|
+
/**
|
|
125
|
+
* 从对话内容中提取记忆
|
|
126
|
+
*
|
|
127
|
+
* 当前使用简单规则提取(生产环境可升级为 LLM 提取):
|
|
128
|
+
* - 包含“喜欢/偏好/习惯/通常/一般/总是” → preference 记忆
|
|
129
|
+
* - 包含事实性表述且长度 > 10 字 → fact 记忆
|
|
130
|
+
* - 每轮对话的最后一条消息保存为工作记忆摘要
|
|
131
|
+
*
|
|
132
|
+
* @param userId 用户 ID
|
|
133
|
+
* @param threadId 线程 ID
|
|
134
|
+
* @param messages 本轮对话的消息列表
|
|
135
|
+
* @returns 新提取的记忆条目
|
|
136
|
+
*/
|
|
137
|
+
extractAndSave(userId: string, threadId: string, messages: Array<{
|
|
138
|
+
role: string;
|
|
139
|
+
content: string;
|
|
140
|
+
}>): Promise<MemoryEntry[]>;
|
|
141
|
+
/**
|
|
142
|
+
* 构建记忆上下文(注入到系统提示词)
|
|
143
|
+
*
|
|
144
|
+
* 检索与当前查询相关的记忆,格式化为 markdown 列表,
|
|
145
|
+
* Agent 会将此内容拼接到 system prompt 中,使 LLM “记住”之前的对话。
|
|
146
|
+
*
|
|
147
|
+
* @param userId 用户 ID
|
|
148
|
+
* @param query 当前用户输入(用于搜索相关记忆)
|
|
149
|
+
* @param threadId 当前线程 ID
|
|
150
|
+
* @returns 格式化后的记忆上下文字符串,无相关记忆时返回空字符串
|
|
151
|
+
*/
|
|
152
|
+
buildMemoryContext(userId: string, query: string, threadId?: string): Promise<string>;
|
|
153
|
+
/** 清除线程记忆 */
|
|
154
|
+
clearThread(threadId: string | number): Promise<void>;
|
|
155
|
+
/** 删除指定记忆 */
|
|
156
|
+
delete(id: string | number): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* 构建对话历史消息(注入到 Agent messages 中)
|
|
159
|
+
*
|
|
160
|
+
* 从工作记忆中加载近期对话摘要,格式化为 user/assistant 交替的消息对,
|
|
161
|
+
* 让 Agent 能“记住”当前会话之前的对话内容。
|
|
162
|
+
*
|
|
163
|
+
* 策略:
|
|
164
|
+
* - 工作记忆中的 summary 类型条目代表每轮对话的摘要
|
|
165
|
+
* - 按时间倒序取最近 N 条,反转为正序
|
|
166
|
+
* - 如果历史轮次过多,将早期轮次压缩为单个摘要
|
|
167
|
+
*
|
|
168
|
+
* @param threadId 线程 ID
|
|
169
|
+
* @param maxTurns 最大历史轮次,默认 8
|
|
170
|
+
* @returns 对话历史消息数组(role + content)
|
|
171
|
+
*/
|
|
172
|
+
buildConversationHistory(threadId: string | number, maxTurns?: number): Promise<Array<{
|
|
173
|
+
role: 'user' | 'assistant';
|
|
174
|
+
content: string;
|
|
175
|
+
}>>;
|
|
176
|
+
/**
|
|
177
|
+
* 提取话题标签并存入线程元数据
|
|
178
|
+
*
|
|
179
|
+
* 从用户消息中提取关键词作为话题标签,
|
|
180
|
+
* 跨会话持久化,新会话时可注入到 system prompt。
|
|
181
|
+
*/
|
|
182
|
+
extractTopicTags(userId: string, content: string): Promise<string[]>;
|
|
183
|
+
/**
|
|
184
|
+
* 获取用户的历史话题标签
|
|
185
|
+
*/
|
|
186
|
+
getTopicTags(userId: string, limit?: number): Promise<string[]>;
|
|
187
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentMemory = exports.InMemoryStore = exports.DBMemoryStore = void 0;
|
|
4
|
+
exports.createMemoryStore = createMemoryStore;
|
|
5
|
+
const db_store_1 = require("./db-store");
|
|
6
|
+
var db_store_2 = require("./db-store");
|
|
7
|
+
Object.defineProperty(exports, "DBMemoryStore", { enumerable: true, get: function () { return db_store_2.DBMemoryStore; } });
|
|
8
|
+
/** 生成唯一 ID */
|
|
9
|
+
function generateId() {
|
|
10
|
+
return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 内存记忆存储实现
|
|
14
|
+
*
|
|
15
|
+
* 适用于单实例 / 开发环境,数据存储在内存中,重启后丢失。
|
|
16
|
+
* 生产环境应替换为数据库实现(实现 MemoryStore 接口即可)。
|
|
17
|
+
*
|
|
18
|
+
* 搜索算法:
|
|
19
|
+
* 当前使用简单的关键词匹配 + 分类加权:
|
|
20
|
+
* - 每个匹配关键词 +1 分
|
|
21
|
+
* - fact 分类额外 +0.5 分
|
|
22
|
+
* - preference 分类额外 +0.3 分
|
|
23
|
+
* - 按总分降序排序,返回 topK 结果
|
|
24
|
+
*/
|
|
25
|
+
class InMemoryStore {
|
|
26
|
+
constructor() {
|
|
27
|
+
this.entries = [];
|
|
28
|
+
}
|
|
29
|
+
async save(entry) {
|
|
30
|
+
const full = {
|
|
31
|
+
...entry,
|
|
32
|
+
id: generateId(),
|
|
33
|
+
createdAt: Date.now(),
|
|
34
|
+
};
|
|
35
|
+
this.entries.push(full);
|
|
36
|
+
return full;
|
|
37
|
+
}
|
|
38
|
+
async load(userId, options) {
|
|
39
|
+
const limit = options?.limit || 50;
|
|
40
|
+
return this.entries
|
|
41
|
+
.filter(e => e.userId === userId &&
|
|
42
|
+
(!options?.threadId || String(e.threadId) === String(options.threadId)) &&
|
|
43
|
+
(!options?.category || e.category === options.category) &&
|
|
44
|
+
(!e.expiresAt || Number(e.expiresAt) > Date.now()))
|
|
45
|
+
.sort((a, b) => Number(b.createdAt) - Number(a.createdAt))
|
|
46
|
+
.slice(0, limit);
|
|
47
|
+
}
|
|
48
|
+
async getByThread(threadId, limit = 50) {
|
|
49
|
+
return this.entries
|
|
50
|
+
.filter(e => String(e.threadId) === String(threadId) &&
|
|
51
|
+
(!e.expiresAt || Number(e.expiresAt) > Date.now()))
|
|
52
|
+
.sort((a, b) => Number(b.createdAt) - Number(a.createdAt))
|
|
53
|
+
.slice(0, limit);
|
|
54
|
+
}
|
|
55
|
+
async getByUser(userId, category, limit = 50) {
|
|
56
|
+
return this.entries
|
|
57
|
+
.filter(e => e.userId === userId &&
|
|
58
|
+
(!category || e.category === category) &&
|
|
59
|
+
(!e.expiresAt || Number(e.expiresAt) > Date.now()))
|
|
60
|
+
.sort((a, b) => Number(b.createdAt) - Number(a.createdAt))
|
|
61
|
+
.slice(0, limit);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 搜索相关记忆
|
|
65
|
+
* 使用关键词匹配 + 分类加权算法,过滤已过期记忆。
|
|
66
|
+
*
|
|
67
|
+
* @param userId 用户 ID
|
|
68
|
+
* @param query 搜索查询(自然语言)
|
|
69
|
+
* @param topK 返回结果数量,默认 10
|
|
70
|
+
* @returns 按相关度降序排列的记忆列表
|
|
71
|
+
*/
|
|
72
|
+
async search(userId, query, topK = 10) {
|
|
73
|
+
const queryLower = query.toLowerCase();
|
|
74
|
+
const scored = this.entries
|
|
75
|
+
.filter(e => e.userId === userId &&
|
|
76
|
+
(!e.expiresAt || Number(e.expiresAt) > Date.now()))
|
|
77
|
+
.map(e => {
|
|
78
|
+
const contentLower = e.content.toLowerCase();
|
|
79
|
+
// 简单的关键词匹配评分
|
|
80
|
+
const words = queryLower.split(/\s+/).filter(w => w.length > 1);
|
|
81
|
+
let score = 0;
|
|
82
|
+
for (const word of words) {
|
|
83
|
+
if (contentLower.includes(word))
|
|
84
|
+
score += 1;
|
|
85
|
+
}
|
|
86
|
+
// 分类加权
|
|
87
|
+
if (e.category === 'fact')
|
|
88
|
+
score += 0.5;
|
|
89
|
+
if (e.category === 'preference')
|
|
90
|
+
score += 0.3;
|
|
91
|
+
return { entry: e, score };
|
|
92
|
+
})
|
|
93
|
+
.filter(s => s.score > 0)
|
|
94
|
+
.sort((a, b) => b.score - a.score)
|
|
95
|
+
.slice(0, topK);
|
|
96
|
+
return scored.map(s => ({ ...s.entry, relevance: s.score }));
|
|
97
|
+
}
|
|
98
|
+
async delete(id) {
|
|
99
|
+
const idStr = String(id);
|
|
100
|
+
const before = this.entries.length;
|
|
101
|
+
this.entries = this.entries.filter(e => String(e.id) !== idStr);
|
|
102
|
+
return this.entries.length < before;
|
|
103
|
+
}
|
|
104
|
+
async clearThread(threadId) {
|
|
105
|
+
this.entries = this.entries.filter(e => String(e.threadId) !== String(threadId));
|
|
106
|
+
}
|
|
107
|
+
async clear(userId) {
|
|
108
|
+
this.entries = this.entries.filter(e => e.userId !== userId);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.InMemoryStore = InMemoryStore;
|
|
112
|
+
/**
|
|
113
|
+
* 创建 MemoryStore 实例
|
|
114
|
+
* 根据环境变量 AGENT_MEMORY_STORE 决定使用内存还是数据库存储。
|
|
115
|
+
*
|
|
116
|
+
* @param dataSource TypeORM DataSource(使用 db 存储时必需)
|
|
117
|
+
* @returns MemoryStore 实例
|
|
118
|
+
*/
|
|
119
|
+
function createMemoryStore(dataSource) {
|
|
120
|
+
const storeType = process.env.AGENT_MEMORY_STORE || 'memory';
|
|
121
|
+
if (storeType === 'db') {
|
|
122
|
+
if (!dataSource) {
|
|
123
|
+
throw new Error('[MemoryStore] AGENT_MEMORY_STORE=db 时需要传入 DataSource。请通过 InjectDataSource 注入后传入。');
|
|
124
|
+
}
|
|
125
|
+
console.log('[MemoryStore] 使用 DBMemoryStore(数据库存储)');
|
|
126
|
+
return new db_store_1.DBMemoryStore(dataSource);
|
|
127
|
+
}
|
|
128
|
+
console.log('[MemoryStore] 使用 InMemoryStore(内存存储)');
|
|
129
|
+
return new InMemoryStore();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Agent 记忆管理器
|
|
133
|
+
*
|
|
134
|
+
* 封装工作记忆(当前会话)和长期记忆(跨会话)的统一访问接口。
|
|
135
|
+
* Agent 在每轮对话前后通过此类注入 / 提取记忆。
|
|
136
|
+
*
|
|
137
|
+
* 典型调用流程:
|
|
138
|
+
* 1. Agent 构建 system prompt 时调用 buildMemoryContext() 注入相关记忆
|
|
139
|
+
* 2. Agent 对话完成后调用 extractAndSave() 从对话中提取新记忆
|
|
140
|
+
*/
|
|
141
|
+
class AgentMemory {
|
|
142
|
+
constructor(store) {
|
|
143
|
+
this.store = store || new InMemoryStore();
|
|
144
|
+
}
|
|
145
|
+
/** 获取存储实例 */
|
|
146
|
+
getStore() {
|
|
147
|
+
return this.store;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* 保存会话消息到工作记忆
|
|
151
|
+
* 用于当前会话的上下文保持
|
|
152
|
+
*/
|
|
153
|
+
async saveWorkingMemory(threadId, userId, content, meta) {
|
|
154
|
+
return this.store.save({
|
|
155
|
+
threadId,
|
|
156
|
+
userId,
|
|
157
|
+
content,
|
|
158
|
+
category: 'summary',
|
|
159
|
+
meta,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 获取工作记忆(当前会话的近期记忆)
|
|
164
|
+
*/
|
|
165
|
+
async getWorkingMemory(threadId, limit = 20) {
|
|
166
|
+
return this.store.getByThread?.(threadId, limit) ?? [];
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 保存长期记忆
|
|
170
|
+
* 从对话中提取的关键信息,跨会话持久化
|
|
171
|
+
*/
|
|
172
|
+
async saveLongTermMemory(userId, content, category = 'fact', meta) {
|
|
173
|
+
return this.store.save({
|
|
174
|
+
threadId: '__long_term__',
|
|
175
|
+
userId,
|
|
176
|
+
content,
|
|
177
|
+
category,
|
|
178
|
+
meta,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* 检索相关记忆
|
|
183
|
+
* 综合工作记忆(当前线程近期)和长期记忆(语义搜索),去重后按相关度排序。
|
|
184
|
+
*
|
|
185
|
+
* @param userId 用户 ID
|
|
186
|
+
* @param query 搜索查询
|
|
187
|
+
* @param options.threadId 当前线程 ID(用于加载工作记忆)
|
|
188
|
+
* @param options.topK 返回数量,默认 10
|
|
189
|
+
* @param options.includeWorking 是否包含工作记忆,默认 true
|
|
190
|
+
*/
|
|
191
|
+
async recall(userId, query, options) {
|
|
192
|
+
const topK = options?.topK || 10;
|
|
193
|
+
const results = [];
|
|
194
|
+
// 长期记忆搜索
|
|
195
|
+
const longTerm = await this.store.search?.(userId, query, topK) ?? [];
|
|
196
|
+
results.push(...longTerm);
|
|
197
|
+
// 工作记忆(当前线程近期消息)
|
|
198
|
+
if (options?.includeWorking !== false && options?.threadId) {
|
|
199
|
+
const working = await this.store.getByThread?.(options.threadId, Math.min(topK, 10)) ?? [];
|
|
200
|
+
results.push(...working);
|
|
201
|
+
}
|
|
202
|
+
// 去重并按相关度排序
|
|
203
|
+
const seen = new Set();
|
|
204
|
+
return results
|
|
205
|
+
.filter(e => {
|
|
206
|
+
if (seen.has(String(e.id)))
|
|
207
|
+
return false;
|
|
208
|
+
seen.add(String(e.id));
|
|
209
|
+
return true;
|
|
210
|
+
})
|
|
211
|
+
.sort((a, b) => (b.relevance || 0) - (a.relevance || 0))
|
|
212
|
+
.slice(0, topK);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* 从对话内容中提取记忆
|
|
216
|
+
*
|
|
217
|
+
* 当前使用简单规则提取(生产环境可升级为 LLM 提取):
|
|
218
|
+
* - 包含“喜欢/偏好/习惯/通常/一般/总是” → preference 记忆
|
|
219
|
+
* - 包含事实性表述且长度 > 10 字 → fact 记忆
|
|
220
|
+
* - 每轮对话的最后一条消息保存为工作记忆摘要
|
|
221
|
+
*
|
|
222
|
+
* @param userId 用户 ID
|
|
223
|
+
* @param threadId 线程 ID
|
|
224
|
+
* @param messages 本轮对话的消息列表
|
|
225
|
+
* @returns 新提取的记忆条目
|
|
226
|
+
*/
|
|
227
|
+
async extractAndSave(userId, threadId, messages) {
|
|
228
|
+
const saved = [];
|
|
229
|
+
for (const msg of messages) {
|
|
230
|
+
if (msg.role !== 'user')
|
|
231
|
+
continue;
|
|
232
|
+
const content = msg.content.trim();
|
|
233
|
+
if (!content || content.length < 5)
|
|
234
|
+
continue;
|
|
235
|
+
// 提取偏好信息
|
|
236
|
+
if (/喜欢|偏好|习惯|通常|一般|总是/i.test(content)) {
|
|
237
|
+
const entry = await this.saveLongTermMemory(userId, content, 'preference', { source: 'auto_extract' });
|
|
238
|
+
saved.push(entry);
|
|
239
|
+
}
|
|
240
|
+
// 提取事实信息
|
|
241
|
+
else if (/是|有|在|了|的/.test(content) && content.length > 10) {
|
|
242
|
+
const entry = await this.saveLongTermMemory(userId, content, 'fact', { source: 'auto_extract' });
|
|
243
|
+
saved.push(entry);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// 保存会话摘要到工作记忆(包含原始 user + assistant 对,以便重建对话历史)
|
|
247
|
+
if (messages.length > 0) {
|
|
248
|
+
const userMsgs = messages.filter(m => m.role === 'user');
|
|
249
|
+
const assistantMsgs = messages.filter(m => m.role === 'assistant');
|
|
250
|
+
const lastUser = userMsgs[userMsgs.length - 1];
|
|
251
|
+
const lastAssistant = assistantMsgs[assistantMsgs.length - 1];
|
|
252
|
+
await this.saveWorkingMemory(threadId, userId, lastAssistant?.content || lastUser?.content || '', {
|
|
253
|
+
messageCount: messages.length,
|
|
254
|
+
userContent: lastUser?.content || '',
|
|
255
|
+
assistantContent: lastAssistant?.content || '',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
return saved;
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 构建记忆上下文(注入到系统提示词)
|
|
262
|
+
*
|
|
263
|
+
* 检索与当前查询相关的记忆,格式化为 markdown 列表,
|
|
264
|
+
* Agent 会将此内容拼接到 system prompt 中,使 LLM “记住”之前的对话。
|
|
265
|
+
*
|
|
266
|
+
* @param userId 用户 ID
|
|
267
|
+
* @param query 当前用户输入(用于搜索相关记忆)
|
|
268
|
+
* @param threadId 当前线程 ID
|
|
269
|
+
* @returns 格式化后的记忆上下文字符串,无相关记忆时返回空字符串
|
|
270
|
+
*/
|
|
271
|
+
async buildMemoryContext(userId, query, threadId) {
|
|
272
|
+
const memories = await this.recall(userId, query, {
|
|
273
|
+
threadId,
|
|
274
|
+
topK: 5,
|
|
275
|
+
});
|
|
276
|
+
if (!memories.length)
|
|
277
|
+
return '';
|
|
278
|
+
const lines = ['## 相关记忆'];
|
|
279
|
+
for (const mem of memories) {
|
|
280
|
+
const categoryLabel = {
|
|
281
|
+
fact: '事实',
|
|
282
|
+
preference: '偏好',
|
|
283
|
+
summary: '摘要',
|
|
284
|
+
entity: '实体',
|
|
285
|
+
task: '任务',
|
|
286
|
+
}[mem.category] || mem.category;
|
|
287
|
+
lines.push(`- [${categoryLabel}] ${mem.content}`);
|
|
288
|
+
}
|
|
289
|
+
return lines.join('\n');
|
|
290
|
+
}
|
|
291
|
+
/** 清除线程记忆 */
|
|
292
|
+
async clearThread(threadId) {
|
|
293
|
+
return this.store.clearThread?.(threadId);
|
|
294
|
+
}
|
|
295
|
+
/** 删除指定记忆 */
|
|
296
|
+
async delete(id) {
|
|
297
|
+
await this.store.delete(id);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 构建对话历史消息(注入到 Agent messages 中)
|
|
301
|
+
*
|
|
302
|
+
* 从工作记忆中加载近期对话摘要,格式化为 user/assistant 交替的消息对,
|
|
303
|
+
* 让 Agent 能“记住”当前会话之前的对话内容。
|
|
304
|
+
*
|
|
305
|
+
* 策略:
|
|
306
|
+
* - 工作记忆中的 summary 类型条目代表每轮对话的摘要
|
|
307
|
+
* - 按时间倒序取最近 N 条,反转为正序
|
|
308
|
+
* - 如果历史轮次过多,将早期轮次压缩为单个摘要
|
|
309
|
+
*
|
|
310
|
+
* @param threadId 线程 ID
|
|
311
|
+
* @param maxTurns 最大历史轮次,默认 8
|
|
312
|
+
* @returns 对话历史消息数组(role + content)
|
|
313
|
+
*/
|
|
314
|
+
async buildConversationHistory(threadId, maxTurns = 8) {
|
|
315
|
+
const entries = await this.store.getByThread?.(threadId, maxTurns * 2) ?? [];
|
|
316
|
+
if (!entries.length)
|
|
317
|
+
return [];
|
|
318
|
+
// 筛选 summary 类型的摘要条目(每轮对话保存一条)
|
|
319
|
+
const summaries = entries
|
|
320
|
+
.filter(e => e.category === 'summary')
|
|
321
|
+
.sort((a, b) => Number(a.createdAt) - Number(b.createdAt));
|
|
322
|
+
if (!summaries.length)
|
|
323
|
+
return [];
|
|
324
|
+
// 如果轮次超过上限,将早期轮次压缩为单个摘要
|
|
325
|
+
const MAX_SUMMARIES = maxTurns;
|
|
326
|
+
const history = [];
|
|
327
|
+
if (summaries.length > MAX_SUMMARIES) {
|
|
328
|
+
// 早期摘要拼接为单条系统消息
|
|
329
|
+
const earlySummaries = summaries.slice(0, summaries.length - MAX_SUMMARIES);
|
|
330
|
+
const compressed = earlySummaries.map(s => s.content).join('\n');
|
|
331
|
+
history.push({
|
|
332
|
+
role: 'user',
|
|
333
|
+
content: `[早期对话摘要]\n${compressed}`,
|
|
334
|
+
});
|
|
335
|
+
history.push({
|
|
336
|
+
role: 'assistant',
|
|
337
|
+
content: '好的,我已了解早期对话的内容。',
|
|
338
|
+
});
|
|
339
|
+
// 保留最近的轮次
|
|
340
|
+
const recentSummaries = summaries.slice(summaries.length - MAX_SUMMARIES);
|
|
341
|
+
for (const s of recentSummaries) {
|
|
342
|
+
const content = s.content;
|
|
343
|
+
const meta = s.meta;
|
|
344
|
+
// meta 中存储了原始的 user + assistant 对
|
|
345
|
+
if (meta?.userContent) {
|
|
346
|
+
history.push({
|
|
347
|
+
role: 'user',
|
|
348
|
+
content: String(meta.userContent).slice(0, 500),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (meta?.assistantContent) {
|
|
352
|
+
history.push({
|
|
353
|
+
role: 'assistant',
|
|
354
|
+
content: String(meta.assistantContent).slice(0, 1000),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
history.push({
|
|
359
|
+
role: 'assistant',
|
|
360
|
+
content: content.slice(0, 500),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
// 轮次不多,直接展开
|
|
367
|
+
for (const s of summaries) {
|
|
368
|
+
const meta = s.meta;
|
|
369
|
+
if (meta?.userContent) {
|
|
370
|
+
history.push({
|
|
371
|
+
role: 'user',
|
|
372
|
+
content: String(meta.userContent).slice(0, 500),
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
if (meta?.assistantContent) {
|
|
376
|
+
history.push({
|
|
377
|
+
role: 'assistant',
|
|
378
|
+
content: String(meta.assistantContent).slice(0, 1000),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
// 仅摘要,拼接为单条
|
|
383
|
+
history.push({
|
|
384
|
+
role: 'user',
|
|
385
|
+
content: s.content.slice(0, 500),
|
|
386
|
+
});
|
|
387
|
+
history.push({
|
|
388
|
+
role: 'assistant',
|
|
389
|
+
content: '已处理该请求。',
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return history;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* 提取话题标签并存入线程元数据
|
|
398
|
+
*
|
|
399
|
+
* 从用户消息中提取关键词作为话题标签,
|
|
400
|
+
* 跨会话持久化,新会话时可注入到 system prompt。
|
|
401
|
+
*/
|
|
402
|
+
async extractTopicTags(userId, content) {
|
|
403
|
+
const tags = [];
|
|
404
|
+
// 简单的关键词提取规则(生产环境可升级为 LLM 提取)
|
|
405
|
+
const patterns = [
|
|
406
|
+
/(?:分析|查看|对比|统计)(.{2,8})/g,
|
|
407
|
+
/(?:营收|利润|成本|收入|支出|毛利|净利)/g,
|
|
408
|
+
/(?:趋势|变化|走势|增长|下降)/g,
|
|
409
|
+
/(?:产品|客户|地区|部门|渠道)/g,
|
|
410
|
+
];
|
|
411
|
+
for (const p of patterns) {
|
|
412
|
+
const matches = content.matchAll(p);
|
|
413
|
+
for (const m of matches) {
|
|
414
|
+
const tag = (m[1] || m[0]).trim();
|
|
415
|
+
if (tag.length >= 2 && tag.length <= 8 && !tags.includes(tag)) {
|
|
416
|
+
tags.push(tag);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// 保存为长期记忆(category: entity)
|
|
421
|
+
for (const tag of tags.slice(0, 5)) {
|
|
422
|
+
await this.store.save({
|
|
423
|
+
threadId: '__topics__',
|
|
424
|
+
userId,
|
|
425
|
+
content: tag,
|
|
426
|
+
category: 'entity',
|
|
427
|
+
meta: { source: 'topic_extract' },
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return tags;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* 获取用户的历史话题标签
|
|
434
|
+
*/
|
|
435
|
+
async getTopicTags(userId, limit = 20) {
|
|
436
|
+
const entries = await this.store.getByUser(userId, 'entity', limit);
|
|
437
|
+
return entries
|
|
438
|
+
.filter(e => e.threadId === '__topics__')
|
|
439
|
+
.map(e => e.content)
|
|
440
|
+
.slice(0, limit);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
exports.AgentMemory = AgentMemory;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import BaseEntity from '@fefeding/common/dist/models/base/baseORM';
|
|
2
|
+
/**
|
|
3
|
+
* Agent Profile 实体
|
|
4
|
+
* @table t_agent_profile
|
|
5
|
+
*/
|
|
6
|
+
export default class AIAgentProfileEntity extends BaseEntity {
|
|
7
|
+
id: number;
|
|
8
|
+
applicationId: number;
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
icon: string;
|
|
12
|
+
color: string;
|
|
13
|
+
basePrompt: string;
|
|
14
|
+
model: string;
|
|
15
|
+
modelConfigId: string;
|
|
16
|
+
maxIterations: number;
|
|
17
|
+
maxMessages: number;
|
|
18
|
+
toolTimeout: number;
|
|
19
|
+
disabledTools: string[] | null;
|
|
20
|
+
mcpServers: (number | string)[] | null;
|
|
21
|
+
selectedSkills: string[] | null;
|
|
22
|
+
alwaysInjectSkills: string[] | null;
|
|
23
|
+
connectorIds: string[] | null;
|
|
24
|
+
isDefault: number;
|
|
25
|
+
agentType: string;
|
|
26
|
+
domain: string;
|
|
27
|
+
remoteUrl: string;
|
|
28
|
+
remoteCard: any | null;
|
|
29
|
+
remoteAuth: any | null;
|
|
30
|
+
a2aProtocol: string;
|
|
31
|
+
restA2AConfig: any | null;
|
|
32
|
+
}
|