foliko 1.0.86 → 1.0.87
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/.agent/data/default.json +25 -4227
- package/.agent/plugins/marknative/index.js +2 -7
- package/.claude/settings.local.json +179 -171
- package/examples/test-chat-debug.js +102 -0
- package/examples/test-chat-result.js +76 -0
- package/examples/test-chat-stream-diff.js +63 -0
- package/examples/test-concurrent-chat.js +60 -0
- package/examples/test-long-chat.js +77 -0
- package/examples/test-session-chat.js +93 -0
- package/package.json +1 -1
- package/plugins/session-plugin.js +21 -0
- package/src/core/agent-chat.js +102 -55
- package/src/core/agent.js +3 -61
- package/src/utils/index.js +1 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 测试 chat 和 chatStream 的差异
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Framework } = require('../src');
|
|
6
|
+
const AIPlugin = require('../plugins/ai-plugin');
|
|
7
|
+
const SessionPlugin = require('../plugins/session-plugin');
|
|
8
|
+
require('dotenv').config();
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('=== 测试 chat vs chatStream 差异 ===\n');
|
|
12
|
+
|
|
13
|
+
const framework = new Framework({ debug: false });
|
|
14
|
+
|
|
15
|
+
await framework.loadPlugin(
|
|
16
|
+
new AIPlugin({
|
|
17
|
+
provider: 'minimax',
|
|
18
|
+
model: 'MiniMax-M2.7',
|
|
19
|
+
apiKey: process.env.MINIMAX_API_KEY || 'your-api-key',
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
await framework.loadPlugin(new SessionPlugin());
|
|
23
|
+
|
|
24
|
+
const systemPrompt = `你是一个有帮助的助手。`;
|
|
25
|
+
|
|
26
|
+
const agent = framework.createAgent({
|
|
27
|
+
name: 'TestAgent',
|
|
28
|
+
systemPrompt: systemPrompt,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const sessionId = 'test_session';
|
|
32
|
+
const sessionPlugin = framework.pluginManager.get('session');
|
|
33
|
+
sessionPlugin.getOrCreateSession(sessionId, { metadata: {} });
|
|
34
|
+
|
|
35
|
+
// 测试 chat
|
|
36
|
+
console.log('--- Test 1: chat() ---');
|
|
37
|
+
const result1 = await agent.chat('你好,介绍一下自己', { sessionId });
|
|
38
|
+
console.log('chat result.message length:', result1.message?.length);
|
|
39
|
+
console.log('chat result.message:', result1.message?.substring(0, 100));
|
|
40
|
+
|
|
41
|
+
// 测试 chatStream
|
|
42
|
+
console.log('\n--- Test 2: chatStream() ---');
|
|
43
|
+
let streamText = '';
|
|
44
|
+
for await (const chunk of agent.chatStream('今天天气怎么样?', { sessionId })) {
|
|
45
|
+
if (chunk.type === 'text') {
|
|
46
|
+
streamText += chunk.text;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log('chatStream full text length:', streamText.length);
|
|
50
|
+
console.log('chatStream full text:', streamText.substring(0, 100));
|
|
51
|
+
|
|
52
|
+
// 测试更多轮对话后的 chat
|
|
53
|
+
console.log('\n--- Test 3: 多轮后 chat() ---');
|
|
54
|
+
await agent.chat('再说一次你的名字', { sessionId });
|
|
55
|
+
const result3 = await agent.chat('我叫什么?', { sessionId });
|
|
56
|
+
console.log('chat result.message length:', result3.message?.length);
|
|
57
|
+
console.log('chat result.message:', result3.message?.substring(0, 100));
|
|
58
|
+
|
|
59
|
+
await framework.destroy();
|
|
60
|
+
console.log('\n[Done]');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 测试并发 chat 调用
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Framework } = require('../src');
|
|
6
|
+
const AIPlugin = require('../plugins/ai-plugin');
|
|
7
|
+
const SessionPlugin = require('../plugins/session-plugin');
|
|
8
|
+
require('dotenv').config();
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('=== 测试并发 chat 调用 ===\n');
|
|
12
|
+
|
|
13
|
+
const framework = new Framework({ debug: false });
|
|
14
|
+
|
|
15
|
+
await framework.loadPlugin(
|
|
16
|
+
new AIPlugin({
|
|
17
|
+
provider: 'minimax',
|
|
18
|
+
model: 'MiniMax-M2.7',
|
|
19
|
+
apiKey: process.env.MINIMAX_API_KEY || 'your-api-key',
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
await framework.loadPlugin(new SessionPlugin());
|
|
23
|
+
|
|
24
|
+
const systemPrompt = `你是一个微信助手。`;
|
|
25
|
+
|
|
26
|
+
// 创建一个 agent(模拟单个用户)
|
|
27
|
+
const agent = framework.createAgent({
|
|
28
|
+
name: 'WeixinAgent',
|
|
29
|
+
systemPrompt: systemPrompt,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const sessionId = 'weixin_test_user';
|
|
33
|
+
const sessionPlugin = framework.pluginManager.get('session');
|
|
34
|
+
sessionPlugin.getOrCreateSession(sessionId, { metadata: { platform: 'weixin' } });
|
|
35
|
+
|
|
36
|
+
// 模拟 3 个并发 chat 调用
|
|
37
|
+
console.log('\n--- 并发发送 3 条消息 ---');
|
|
38
|
+
|
|
39
|
+
const results = await Promise.all([
|
|
40
|
+
agent.chat('你好', { sessionId }),
|
|
41
|
+
agent.chat('今天天气怎么样?', { sessionId }),
|
|
42
|
+
agent.chat('帮我查一下天气', { sessionId }),
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
console.log('\n--- 结果 ---');
|
|
46
|
+
for (let i = 0; i < results.length; i++) {
|
|
47
|
+
const r = results[i];
|
|
48
|
+
console.log(`[${i}] message length: ${r.message?.length || 'EMPTY'}`);
|
|
49
|
+
console.log(`[${i}] message: ${r.message?.substring(0, 50) || 'EMPTY'}...`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 检查 _messages 状态
|
|
53
|
+
const chatHandler = agent._chatHandler;
|
|
54
|
+
console.log(`\n[DEBUG] _messages count: ${chatHandler?._messages.length}`);
|
|
55
|
+
|
|
56
|
+
await framework.destroy();
|
|
57
|
+
console.log('\n[Done]');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 测试长对话压缩场景
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const { Framework } = require('../src');
|
|
6
|
+
const AIPlugin = require('../plugins/ai-plugin');
|
|
7
|
+
const SessionPlugin = require('../plugins/session-plugin');
|
|
8
|
+
require('dotenv').config();
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('=== 测试长对话压缩场景 ===\n');
|
|
12
|
+
|
|
13
|
+
const framework = new Framework({ debug: false });
|
|
14
|
+
|
|
15
|
+
await framework.loadPlugin(
|
|
16
|
+
new AIPlugin({
|
|
17
|
+
provider: 'minimax',
|
|
18
|
+
model: 'MiniMax-M2.7',
|
|
19
|
+
apiKey: process.env.MINIMAX_API_KEY || 'your-api-key',
|
|
20
|
+
})
|
|
21
|
+
);
|
|
22
|
+
await framework.loadPlugin(new SessionPlugin());
|
|
23
|
+
|
|
24
|
+
const systemPrompt = `你是一个有帮助的助手。`;
|
|
25
|
+
|
|
26
|
+
const agent = framework.createAgent({
|
|
27
|
+
name: 'TestAgent',
|
|
28
|
+
systemPrompt: systemPrompt,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const sessionId = 'test_session';
|
|
32
|
+
const sessionPlugin = framework.pluginManager.get('session');
|
|
33
|
+
sessionPlugin.getOrCreateSession(sessionId, { metadata: {} });
|
|
34
|
+
|
|
35
|
+
// 发送多条长消息来触发压缩
|
|
36
|
+
const longContent = `请详细描述以下内容:人工智能的发展历史、现状和未来趋势。包括机器学习、深度学习、自然语言处理等核心技术的发展历程,以及在各个行业中的应用场景。同时分析当前面临的挑战,如数据隐私、算法偏见、能源消耗等问题。`;
|
|
37
|
+
|
|
38
|
+
console.log('--- 发送 15 条长消息 ---');
|
|
39
|
+
for (let i = 1; i <= 15; i++) {
|
|
40
|
+
try {
|
|
41
|
+
const result = await agent.chat(`${longContent}\n\n[消息 ${i}]`, { sessionId });
|
|
42
|
+
const msgLen = result.message?.length || 0;
|
|
43
|
+
console.log(`[${i}] message length: ${msgLen}, success: ${result.success}`);
|
|
44
|
+
|
|
45
|
+
if (!result.message) {
|
|
46
|
+
console.log(`[ERROR] message is empty!`);
|
|
47
|
+
console.log(`[DEBUG] result:`, JSON.stringify(result, null, 2));
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(`[${i}] Error:`, err.message);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 检查压缩统计
|
|
56
|
+
const chatHandler = agent._chatHandler;
|
|
57
|
+
console.log(`\n[DEBUG] compression count: ${chatHandler?._compressionCount}`);
|
|
58
|
+
console.log(`[DEBUG] _messages count: ${chatHandler?._messages.length}`);
|
|
59
|
+
|
|
60
|
+
// 压缩后再发一条
|
|
61
|
+
console.log('\n--- 压缩后发送测试 ---');
|
|
62
|
+
try {
|
|
63
|
+
const result = await agent.chat('你好,压缩后你还认识我吗?', { sessionId });
|
|
64
|
+
console.log(`message length: ${result.message?.length || 0}`);
|
|
65
|
+
console.log(`message: ${result.message?.substring(0, 100)}`);
|
|
66
|
+
if (!result.message) {
|
|
67
|
+
console.log(`[ERROR] message is empty after compression!`);
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`Error:`, err.message);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await framework.destroy();
|
|
74
|
+
console.log('\n[Done]');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 模拟微信持续聊天场景
|
|
3
|
+
* 测试多轮对话后 result.message 是否为空
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { Framework } = require('../src');
|
|
7
|
+
const AIPlugin = require('../plugins/ai-plugin');
|
|
8
|
+
const SessionPlugin = require('../plugins/session-plugin');
|
|
9
|
+
require('dotenv').config();
|
|
10
|
+
|
|
11
|
+
async function main() {
|
|
12
|
+
console.log('=== 模拟微信持续聊天场景 ===\n');
|
|
13
|
+
|
|
14
|
+
const framework = new Framework({ debug: false });
|
|
15
|
+
|
|
16
|
+
await framework.loadPlugin(
|
|
17
|
+
new AIPlugin({
|
|
18
|
+
provider: 'minimax',
|
|
19
|
+
model: 'MiniMax-M2.7',
|
|
20
|
+
apiKey: process.env.MINIMAX_API_KEY || 'your-api-key',
|
|
21
|
+
})
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
// 加载 session 插件
|
|
25
|
+
await framework.loadPlugin(new SessionPlugin());
|
|
26
|
+
|
|
27
|
+
// 模拟微信的 system prompt
|
|
28
|
+
const systemPrompt = `你是一个微信助手。`;
|
|
29
|
+
|
|
30
|
+
// 创建 Agent(模拟 _getSessionAgent)
|
|
31
|
+
const agent = framework.createAgent({
|
|
32
|
+
name: 'WeixinAgent',
|
|
33
|
+
systemPrompt: systemPrompt,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 模拟 session
|
|
37
|
+
const sessionPlugin = framework.pluginManager.get('session');
|
|
38
|
+
const sessionId = 'weixin_test_user';
|
|
39
|
+
|
|
40
|
+
// 初始化 session
|
|
41
|
+
sessionPlugin.getOrCreateSession(sessionId, {
|
|
42
|
+
metadata: { platform: 'weixin', userId: 'test_user' },
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 模拟微信的 _processChat
|
|
46
|
+
const processChat = async (userId, text) => {
|
|
47
|
+
console.log(`\n[User] ${text}`);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const result = await agent.chat(text, { sessionId: `weixin_${userId}` });
|
|
51
|
+
const message = result.message || '';
|
|
52
|
+
|
|
53
|
+
if (message) {
|
|
54
|
+
console.log(`[Agent] ${message.substring(0, 100)}${message.length > 100 ? '...' : ''}`);
|
|
55
|
+
console.log(`[OK] message length: ${message.length}`);
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`[WARNING] result.message is empty!`);
|
|
58
|
+
console.log(`[DEBUG] result:`, JSON.stringify(result, null, 2).substring(0, 500));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 检查 _messages 状态
|
|
62
|
+
const chatHandler = agent._chatHandler;
|
|
63
|
+
if (chatHandler) {
|
|
64
|
+
console.log(`[DEBUG] _messages count: ${chatHandler._messages.length}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return message;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
console.error(`[Error]`, err.message);
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// 模拟多轮对话
|
|
75
|
+
await processChat('user1', '你好');
|
|
76
|
+
await processChat('user1', '今天天气怎么样?');
|
|
77
|
+
await processChat('user1', '帮我查一下北京的人口');
|
|
78
|
+
await processChat('user1', '再说一次北京的人口');
|
|
79
|
+
|
|
80
|
+
console.log('\n--- 测试压缩场景 ---');
|
|
81
|
+
// 发送更多消息触发压缩
|
|
82
|
+
for (let i = 0; i < 5; i++) {
|
|
83
|
+
await processChat('user1', `这是第 ${i + 1} 条测试消息`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 压缩后再发送一条
|
|
87
|
+
await processChat('user1', '你好,你还记得我吗?');
|
|
88
|
+
|
|
89
|
+
console.log('\n[Done]');
|
|
90
|
+
await framework.destroy();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch(console.error);
|
package/package.json
CHANGED
|
@@ -335,6 +335,11 @@ class SessionPlugin extends Plugin {
|
|
|
335
335
|
addMessage(sessionId, message) {
|
|
336
336
|
const session = this.getSession(sessionId)
|
|
337
337
|
if (session) {
|
|
338
|
+
// 基础消息验证
|
|
339
|
+
if (!message || !message.role || !message.content) {
|
|
340
|
+
log.warn('Invalid message format, skipping:', { sessionId, message });
|
|
341
|
+
return session;
|
|
342
|
+
}
|
|
338
343
|
session.messages.push(message)
|
|
339
344
|
session.lastActive = new Date()
|
|
340
345
|
|
|
@@ -349,6 +354,22 @@ class SessionPlugin extends Plugin {
|
|
|
349
354
|
return session
|
|
350
355
|
}
|
|
351
356
|
|
|
357
|
+
/**
|
|
358
|
+
* 整体替换会话消息(用于压缩后同步)
|
|
359
|
+
* @param {string} sessionId - 会话 ID
|
|
360
|
+
* @param {Array} messages - 新的消息数组
|
|
361
|
+
*/
|
|
362
|
+
replaceMessages(sessionId, messages) {
|
|
363
|
+
const session = this.getSession(sessionId)
|
|
364
|
+
if (session) {
|
|
365
|
+
session.messages = messages || []
|
|
366
|
+
session.lastActive = new Date()
|
|
367
|
+
// 持久化到 storage
|
|
368
|
+
this._saveToStorage(session)
|
|
369
|
+
}
|
|
370
|
+
return session
|
|
371
|
+
}
|
|
372
|
+
|
|
352
373
|
/**
|
|
353
374
|
* 获取会话历史
|
|
354
375
|
*/
|
package/src/core/agent-chat.js
CHANGED
|
@@ -123,6 +123,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
123
123
|
|
|
124
124
|
// Session 历史存储配置
|
|
125
125
|
this._sessionHistoryKey = config.sessionHistoryKey || 'chat_history';
|
|
126
|
+
|
|
127
|
+
// Per-Session 队列管理
|
|
128
|
+
this._sessionQueues = new Map(); // sessionId -> Queue of {message, options, resolve, reject}
|
|
129
|
+
this._processingSessions = new Set(); // sessionId -> processing flag
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
/**
|
|
@@ -139,6 +143,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
139
143
|
if (!sessionPlugin) return [];
|
|
140
144
|
|
|
141
145
|
const messages = sessionPlugin.getHistory(sessionId);
|
|
146
|
+
// 直接返回,保存时已清理,数据应该是干净的
|
|
142
147
|
return messages || [];
|
|
143
148
|
} catch (err) {
|
|
144
149
|
// 忽略加载错误
|
|
@@ -153,28 +158,78 @@ class AgentChatHandler extends EventEmitter {
|
|
|
153
158
|
* @private
|
|
154
159
|
*/
|
|
155
160
|
_saveHistoryToSession(sessionId, messages) {
|
|
156
|
-
if (!sessionId || !this.agent?.framework || !messages
|
|
161
|
+
if (!sessionId || !this.agent?.framework || !messages) return;
|
|
157
162
|
|
|
158
163
|
try {
|
|
159
164
|
const sessionPlugin = this.agent.framework.pluginManager.get('session');
|
|
160
165
|
if (!sessionPlugin) return;
|
|
161
166
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// 只添加新消息(避免重复添加已加载的历史)
|
|
167
|
-
const simpleMessages = messages;
|
|
168
|
-
|
|
169
|
-
const newMessages = simpleMessages.slice(existingCount);
|
|
170
|
-
for (const msg of newMessages) {
|
|
171
|
-
sessionPlugin.addMessage(sessionId, msg);
|
|
172
|
-
}
|
|
167
|
+
// 清理消息格式后整体替换
|
|
168
|
+
const cleanedMessages = prepareMessagesForAPI(messages);
|
|
169
|
+
sessionPlugin.replaceMessages(sessionId, cleanedMessages);
|
|
173
170
|
} catch (err) {
|
|
174
171
|
// 忽略保存错误
|
|
175
172
|
}
|
|
176
173
|
}
|
|
177
174
|
|
|
175
|
+
/**
|
|
176
|
+
* 将消息加入队列(Per-Session)
|
|
177
|
+
* @param {string} sessionId - 会话 ID
|
|
178
|
+
* @param {string|Object} message - 消息
|
|
179
|
+
* @param {Object} options - 选项
|
|
180
|
+
* @returns {Promise}
|
|
181
|
+
*/
|
|
182
|
+
enqueue(sessionId, message, options = {}) {
|
|
183
|
+
if (!this._sessionQueues.has(sessionId)) {
|
|
184
|
+
this._sessionQueues.set(sessionId, []);
|
|
185
|
+
}
|
|
186
|
+
const queue = this._sessionQueues.get(sessionId);
|
|
187
|
+
|
|
188
|
+
if (this._processingSessions.has(sessionId)) {
|
|
189
|
+
// 已在处理中,加入队列
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
queue.push({ message, options, resolve, reject });
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// 直接处理
|
|
196
|
+
return this._processWithSession(sessionId, message, options);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 处理单个 session 的消息
|
|
201
|
+
* @param {string} sessionId - 会话 ID
|
|
202
|
+
* @param {string|Object} message - 消息
|
|
203
|
+
* @param {Object} options - 选项
|
|
204
|
+
* @returns {Promise}
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
async _processWithSession(sessionId, message, options) {
|
|
208
|
+
this._processingSessions.add(sessionId);
|
|
209
|
+
try {
|
|
210
|
+
return await this._doChat(message, options);
|
|
211
|
+
} finally {
|
|
212
|
+
this._processingSessions.delete(sessionId);
|
|
213
|
+
// 处理队列中的下一条
|
|
214
|
+
setImmediate(() => this._drainQueue(sessionId));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 消费队列中的下一条消息
|
|
220
|
+
* @param {string} sessionId - 会话 ID
|
|
221
|
+
* @private
|
|
222
|
+
*/
|
|
223
|
+
_drainQueue(sessionId) {
|
|
224
|
+
const queue = this._sessionQueues.get(sessionId);
|
|
225
|
+
if (!queue || queue.length === 0) return;
|
|
226
|
+
|
|
227
|
+
const item = queue.shift();
|
|
228
|
+
this._processWithSession(sessionId, item.message, item.options)
|
|
229
|
+
.then(item.resolve)
|
|
230
|
+
.catch(item.reject);
|
|
231
|
+
}
|
|
232
|
+
|
|
178
233
|
/**
|
|
179
234
|
* 静态方法:保留接口兼容性(无实际作用)
|
|
180
235
|
*/
|
|
@@ -287,9 +342,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
287
342
|
* 1. 如果启用了智能摘要且有 AI 客户端,对早期消息进行 AI 总结
|
|
288
343
|
* 2. 否则使用简单裁剪 + 标记
|
|
289
344
|
* 3. 支持超时控制,防止压缩阻塞太久
|
|
345
|
+
* @param {string} sessionId - 会话 ID(用于压缩后同步)
|
|
290
346
|
* @private
|
|
291
347
|
*/
|
|
292
|
-
async _compressContext() {
|
|
348
|
+
async _compressContext(sessionId) {
|
|
293
349
|
// 如果已经有压缩在进行,返回现有的 Promise
|
|
294
350
|
if (this._compressionInProgress && this._compressionPromise) {
|
|
295
351
|
logger.debug('Compression already in progress, waiting for it...');
|
|
@@ -303,7 +359,7 @@ class AgentChatHandler extends EventEmitter {
|
|
|
303
359
|
this._compressionInProgress = true;
|
|
304
360
|
|
|
305
361
|
// 创建压缩 Promise,带超时控制
|
|
306
|
-
this._compressionPromise = this._executeCompressionWithTimeout().finally(() => {
|
|
362
|
+
this._compressionPromise = this._executeCompressionWithTimeout(sessionId).finally(() => {
|
|
307
363
|
this._compressionInProgress = false;
|
|
308
364
|
this._compressionPromise = null;
|
|
309
365
|
});
|
|
@@ -313,15 +369,16 @@ class AgentChatHandler extends EventEmitter {
|
|
|
313
369
|
|
|
314
370
|
/**
|
|
315
371
|
* 执行压缩(带超时)
|
|
372
|
+
* @param {string} sessionId - 会话 ID
|
|
316
373
|
* @private
|
|
317
374
|
*/
|
|
318
|
-
async _executeCompressionWithTimeout() {
|
|
375
|
+
async _executeCompressionWithTimeout(sessionId) {
|
|
319
376
|
try {
|
|
320
|
-
return await Promise.race([this._doCompress(), this._createTimeoutPromise()]);
|
|
377
|
+
return await Promise.race([this._doCompress(sessionId), this._createTimeoutPromise()]);
|
|
321
378
|
} catch (err) {
|
|
322
379
|
logger.warn('Compression failed:', err.message);
|
|
323
380
|
// 压缩失败时使用简单的截断策略
|
|
324
|
-
this._simpleCompress();
|
|
381
|
+
this._simpleCompress(sessionId);
|
|
325
382
|
}
|
|
326
383
|
}
|
|
327
384
|
|
|
@@ -358,9 +415,10 @@ class AgentChatHandler extends EventEmitter {
|
|
|
358
415
|
|
|
359
416
|
/**
|
|
360
417
|
* 执行实际压缩逻辑
|
|
418
|
+
* @param {string} sessionId - 会话 ID
|
|
361
419
|
* @private
|
|
362
420
|
*/
|
|
363
|
-
async _doCompress() {
|
|
421
|
+
async _doCompress(sessionId) {
|
|
364
422
|
const systemMessages = this._messages.filter((m) => m.role === 'system');
|
|
365
423
|
const otherMessages = this._messages.filter((m) => m.role !== 'system');
|
|
366
424
|
|
|
@@ -398,13 +456,19 @@ class AgentChatHandler extends EventEmitter {
|
|
|
398
456
|
logger.info(
|
|
399
457
|
`Context compressed (${this._compressionCount} times). Messages: ${this._messages.length}, Est. tokens: ${totalTokens}`
|
|
400
458
|
);
|
|
459
|
+
|
|
460
|
+
// 压缩后立即同步到 session
|
|
461
|
+
if (sessionId) {
|
|
462
|
+
this._saveHistoryToSession(sessionId, this._messages);
|
|
463
|
+
}
|
|
401
464
|
}
|
|
402
465
|
|
|
403
466
|
/**
|
|
404
467
|
* 简单压缩(当 AI 压缩失败时使用)
|
|
468
|
+
* @param {string} sessionId - 会话 ID
|
|
405
469
|
* @private
|
|
406
470
|
*/
|
|
407
|
-
_simpleCompress() {
|
|
471
|
+
_simpleCompress(sessionId) {
|
|
408
472
|
const systemMessages = this._messages.filter((m) => m.role === 'system');
|
|
409
473
|
const otherMessages = this._messages.filter((m) => m.role !== 'system');
|
|
410
474
|
const recentMessages = otherMessages.slice(-this._keepRecentMessages);
|
|
@@ -425,6 +489,11 @@ class AgentChatHandler extends EventEmitter {
|
|
|
425
489
|
logger.info(
|
|
426
490
|
`Context simple compressed (${this._compressionCount} times). Messages: ${this._messages.length}`
|
|
427
491
|
);
|
|
492
|
+
|
|
493
|
+
// 压缩后立即同步到 session
|
|
494
|
+
if (sessionId) {
|
|
495
|
+
this._saveHistoryToSession(sessionId, this._messages);
|
|
496
|
+
}
|
|
428
497
|
}
|
|
429
498
|
|
|
430
499
|
/**
|
|
@@ -653,15 +722,25 @@ ${truncatedContent}${truncatedNote}
|
|
|
653
722
|
}
|
|
654
723
|
|
|
655
724
|
/**
|
|
656
|
-
*
|
|
725
|
+
* 发送消息(非流式)- 使用队列机制
|
|
657
726
|
* @param {string|Object} message - 消息
|
|
658
727
|
* @param {Object} options - 选项
|
|
659
728
|
*/
|
|
660
729
|
async chat(message, options = {}) {
|
|
730
|
+
const sessionId = options.sessionId || null;
|
|
731
|
+
return this.enqueue(sessionId, message, options);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* 执行实际聊天逻辑
|
|
736
|
+
* @param {string|Object} message - 消息
|
|
737
|
+
* @param {Object} options - 选项
|
|
738
|
+
* @private
|
|
739
|
+
*/
|
|
740
|
+
async _doChat(message, options = {}) {
|
|
661
741
|
const sessionId = options.sessionId || null;
|
|
662
742
|
const context = { sessionId, isStream: false };
|
|
663
743
|
const framework = this.agent.framework;
|
|
664
|
-
const self = this; // 保存引用用于回调
|
|
665
744
|
|
|
666
745
|
try {
|
|
667
746
|
// 从 session 加载聊天历史
|
|
@@ -696,7 +775,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
696
775
|
`Context large (${totalTokens}/${this._maxContextTokens} tokens = msgs:${messagesTokens} + tools:${toolsTokens} + sys:${systemPromptTokens}), compressing...`
|
|
697
776
|
);
|
|
698
777
|
// 使用带超时控制的压缩
|
|
699
|
-
await this._compressContext();
|
|
778
|
+
await this._compressContext(sessionId);
|
|
700
779
|
}
|
|
701
780
|
|
|
702
781
|
const maxSteps = options.maxSteps || this._maxSteps;
|
|
@@ -791,7 +870,7 @@ ${truncatedContent}${truncatedNote}
|
|
|
791
870
|
`Context large (${totalTokens}/${this._maxContextTokens} tokens), compressing...`
|
|
792
871
|
);
|
|
793
872
|
// 流式调用时等待压缩完成(使用带超时控制的压缩)
|
|
794
|
-
await this._compressContext();
|
|
873
|
+
await this._compressContext(sessionId);
|
|
795
874
|
}
|
|
796
875
|
|
|
797
876
|
const maxSteps = options.maxSteps || this._maxSteps;
|
|
@@ -1001,38 +1080,6 @@ ${truncatedContent}${truncatedNote}
|
|
|
1001
1080
|
return cleaned;
|
|
1002
1081
|
}
|
|
1003
1082
|
|
|
1004
|
-
/**
|
|
1005
|
-
* 清理消息格式
|
|
1006
|
-
* @private
|
|
1007
|
-
*/
|
|
1008
|
-
_cleanMessages(messages) {
|
|
1009
|
-
return messages.map((msg) => {
|
|
1010
|
-
if (!msg || typeof msg !== 'object') {
|
|
1011
|
-
return { role: 'user', content: '' };
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
const cleaned = {
|
|
1015
|
-
role: msg.role || 'user',
|
|
1016
|
-
};
|
|
1017
|
-
|
|
1018
|
-
if (Array.isArray(msg.content)) {
|
|
1019
|
-
cleaned.content = msg.content;
|
|
1020
|
-
} else if (typeof msg.content === 'string') {
|
|
1021
|
-
cleaned.content = msg.content;
|
|
1022
|
-
} else if (typeof msg.content === 'object' && msg.content !== null) {
|
|
1023
|
-
// 对象类型的 content(如 tool result),转为字符串
|
|
1024
|
-
cleaned.content =
|
|
1025
|
-
typeof msg.content === 'object' && msg.content.content !== undefined
|
|
1026
|
-
? String(msg.content.content)
|
|
1027
|
-
: JSON.stringify(msg.content);
|
|
1028
|
-
} else {
|
|
1029
|
-
cleaned.content = msg.text || msg.input || '';
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
return cleaned;
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
1083
|
/**
|
|
1037
1084
|
* 销毁
|
|
1038
1085
|
*/
|