@myassis/gateway 1.0.108 → 1.0.110
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/dist/config/index.js
CHANGED
|
@@ -146,7 +146,7 @@ exports.appConfig = {
|
|
|
146
146
|
* 必定压缩,与内容多少无关 —— 这是「没聊几句就压缩」最直接的原因。
|
|
147
147
|
* 条数只作为体积估算失真时的兜底,阈值应远高于正常对话轮数。
|
|
148
148
|
*/
|
|
149
|
-
summaryThreshold: parseInt(process.env.SUMMARY_THRESHOLD || '
|
|
149
|
+
summaryThreshold: parseInt(process.env.SUMMARY_THRESHOLD || '10', 10),
|
|
150
150
|
appName: '我的助手'
|
|
151
151
|
};
|
|
152
152
|
/**
|
|
@@ -12,7 +12,7 @@ const logger = (0, shared_1.getLogger)('LLMClient');
|
|
|
12
12
|
/** 历史工具输出在上下文中的最大保留长度 */
|
|
13
13
|
const HISTORY_TOOL_OUTPUT_LIMIT = index_js_2.appConfig.oldToolContentLimit;
|
|
14
14
|
/** 请求体字符硬上限,超过则视为上下文超限 */
|
|
15
|
-
const HARD_MAX_CHARS =
|
|
15
|
+
const HARD_MAX_CHARS = index_js_2.appConfig.contextMaxChars;
|
|
16
16
|
/**
|
|
17
17
|
* 规范化工具调用参数。
|
|
18
18
|
*
|
|
@@ -371,30 +371,16 @@ class LLMClient {
|
|
|
371
371
|
*/
|
|
372
372
|
async buildRequest(stream, model) {
|
|
373
373
|
// 系统模式:转发到 Server LLM 代理
|
|
374
|
-
if (this.systemMode) {
|
|
375
|
-
const { mapped } = this.mapMessages(this.messages);
|
|
376
|
-
return {
|
|
377
|
-
url: `${this.serverUrl}/api/v1/llm/${stream ? 'chat' : 'invoke'}`,
|
|
378
|
-
headers: {
|
|
379
|
-
'Content-Type': 'application/json',
|
|
380
|
-
'Authorization': `Bearer ${this.authToken}`,
|
|
381
|
-
},
|
|
382
|
-
body: {
|
|
383
|
-
modelId: model.modelId,
|
|
384
|
-
messages: mapped,
|
|
385
|
-
temperature: 0.7,
|
|
386
|
-
tools: this.tools,
|
|
387
|
-
},
|
|
388
|
-
isSystemMode: true,
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
374
|
// 自定义模式:直接调用 LLM API
|
|
392
375
|
if (!model) {
|
|
393
376
|
throw new Error('No available model');
|
|
394
377
|
}
|
|
395
|
-
|
|
396
|
-
if (!
|
|
397
|
-
|
|
378
|
+
var apiKey;
|
|
379
|
+
if (!this.systemMode) {
|
|
380
|
+
apiKey = index_js_1.persistStore.getModelApiKey(model.id);
|
|
381
|
+
if (!apiKey) {
|
|
382
|
+
throw new Error('No API Key');
|
|
383
|
+
}
|
|
398
384
|
}
|
|
399
385
|
// 使用统一的消息映射
|
|
400
386
|
const { mapped: mappedMessages, existAttachments, existScreenShot } = this.mapMessages(this.messages);
|
|
@@ -410,17 +396,17 @@ class LLMClient {
|
|
|
410
396
|
const bodyChars = JSON.stringify(body).length;
|
|
411
397
|
logger.debug('context chars', bodyChars);
|
|
412
398
|
// 附件/截图场景 base64 体积大但不可裁剪,跳过硬上限检查
|
|
413
|
-
if (bodyChars > HARD_MAX_CHARS && !existAttachments && !existScreenShot) {
|
|
414
|
-
|
|
415
|
-
}
|
|
399
|
+
// if (bodyChars > HARD_MAX_CHARS && !existAttachments && !existScreenShot) {
|
|
400
|
+
// throw Error('exceed max message tokens')
|
|
401
|
+
// }
|
|
416
402
|
return {
|
|
417
|
-
url: `${model.baseUrl}/chat/completions`,
|
|
403
|
+
url: this.systemMode ? `${this.serverUrl}/api/v1/llm/${stream ? 'chat' : 'invoke'}` : `${model.baseUrl}/chat/completions`,
|
|
418
404
|
headers: {
|
|
419
405
|
'Content-Type': 'application/json',
|
|
420
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
406
|
+
'Authorization': `Bearer ${this.systemMode ? this.authToken : apiKey}`,
|
|
421
407
|
},
|
|
422
408
|
body,
|
|
423
|
-
isSystemMode:
|
|
409
|
+
isSystemMode: this.systemMode,
|
|
424
410
|
};
|
|
425
411
|
}
|
|
426
412
|
/**
|
|
@@ -37,6 +37,10 @@ const DEFAULT_CONFIG = {
|
|
|
37
37
|
summaryTriggerChars: index_js_2.appConfig.summaryTriggerChars,
|
|
38
38
|
enabled: true,
|
|
39
39
|
};
|
|
40
|
+
/** 单次摘要模型请求超时(毫秒) */
|
|
41
|
+
const SUMMARY_REQUEST_TIMEOUT = 45000;
|
|
42
|
+
/** 整个压缩流程的硬超时(毫秒),超过后放弃压缩、沿用原始历史 */
|
|
43
|
+
const SUMMARY_OVERALL_TIMEOUT = 90000;
|
|
40
44
|
/**
|
|
41
45
|
* 会话级压缩任务表:sessionId -> 进行中的压缩任务。
|
|
42
46
|
*
|
|
@@ -298,6 +302,7 @@ class MemoryManager {
|
|
|
298
302
|
compressionTasks.delete(this.session.id);
|
|
299
303
|
}
|
|
300
304
|
if (!summary) {
|
|
305
|
+
this.emitSSE({ type: 'context_compress_done' });
|
|
301
306
|
// 摘要失败/被中断:沿用旧摘要或退回原始历史,不写入残缺摘要
|
|
302
307
|
if (plan.action === 'incremental') {
|
|
303
308
|
return [
|
|
@@ -307,6 +312,7 @@ class MemoryManager {
|
|
|
307
312
|
}
|
|
308
313
|
return plan.prevRetained;
|
|
309
314
|
}
|
|
315
|
+
this.emitSSE({ type: 'context_compress_done' });
|
|
310
316
|
return [
|
|
311
317
|
this.toSummaryMessage(summary, plan.newBoundaryAt, '', plan.ledger),
|
|
312
318
|
...plan.retained,
|
|
@@ -351,7 +357,7 @@ class MemoryManager {
|
|
|
351
357
|
const prompt = TURN_COMPACT_PROMPT_HEAD + transcript;
|
|
352
358
|
try {
|
|
353
359
|
const models = await this.getSummaryModels();
|
|
354
|
-
const llmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: prompt }], this.signal, [],
|
|
360
|
+
const llmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: prompt }], this.signal, [], SUMMARY_REQUEST_TIMEOUT);
|
|
355
361
|
if (this.session.useSystemMode) {
|
|
356
362
|
const { getRequestToken, getServerBaseUrl } = await Promise.resolve().then(() => __importStar(require('../../api/index.js')));
|
|
357
363
|
llmClient.setSystemMode(true, this.session.selectModelId, getServerBaseUrl(), getRequestToken());
|
|
@@ -554,6 +560,26 @@ ${conversation}
|
|
|
554
560
|
if (!this.childAgent) {
|
|
555
561
|
this.emitSSE({ type: 'context_compressing' });
|
|
556
562
|
}
|
|
563
|
+
try {
|
|
564
|
+
return await Promise.race([
|
|
565
|
+
this.doGenerateSummary(messages, lastSummary),
|
|
566
|
+
this.summaryTimeout(),
|
|
567
|
+
]);
|
|
568
|
+
}
|
|
569
|
+
catch (error) {
|
|
570
|
+
logger.warn(`压缩流程异常,保留原始历史: ${error?.message}`);
|
|
571
|
+
return lastSummary || null;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
summaryTimeout() {
|
|
575
|
+
return new Promise((resolve) => {
|
|
576
|
+
setTimeout(() => {
|
|
577
|
+
logger.warn(`压缩流程超时(${SUMMARY_OVERALL_TIMEOUT}ms),放弃压缩`);
|
|
578
|
+
resolve(null);
|
|
579
|
+
}, SUMMARY_OVERALL_TIMEOUT);
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
async doGenerateSummary(messages, lastSummary) {
|
|
557
583
|
const MAX_INPUT_CHARS = 30000; // 单次摘要最大输入字符数
|
|
558
584
|
// 格式化所有消息用于估算长度
|
|
559
585
|
const formattedMessages = messages
|
|
@@ -565,7 +591,7 @@ ${conversation}
|
|
|
565
591
|
return await this.hierarchicalSummary(formattedMessages, lastSummary);
|
|
566
592
|
}
|
|
567
593
|
const summaryPrompt = this.buildSummaryPrompt(messages, lastSummary);
|
|
568
|
-
const llmClient = new LLMClient_js_1.LLMClient(await this.getSummaryModels(), [{ role: 'user', content: summaryPrompt }], this.signal, [],
|
|
594
|
+
const llmClient = new LLMClient_js_1.LLMClient(await this.getSummaryModels(), [{ role: 'user', content: summaryPrompt }], this.signal, [], SUMMARY_REQUEST_TIMEOUT);
|
|
569
595
|
if (this.session.useSystemMode) {
|
|
570
596
|
const { getRequestToken, getServerBaseUrl } = await Promise.resolve().then(() => __importStar(require('../../api/index.js')));
|
|
571
597
|
llmClient.setSystemMode(true, this.session.selectModelId, getServerBaseUrl(), getRequestToken());
|
|
@@ -598,7 +624,7 @@ ${conversation}
|
|
|
598
624
|
const models = await this.getSummaryModels();
|
|
599
625
|
const summarizeBatch = async (batch) => {
|
|
600
626
|
const batchPrompt = `请简洁总结以下对话片段的关键信息,特别关注:项目结构、关键决策、错误及修复。\n\n${batch.join('\n\n')}`;
|
|
601
|
-
const llmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: batchPrompt }], this.signal, [],
|
|
627
|
+
const llmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: batchPrompt }], this.signal, [], SUMMARY_REQUEST_TIMEOUT);
|
|
602
628
|
if (this.session.useSystemMode) {
|
|
603
629
|
const { getRequestToken, getServerBaseUrl } = await Promise.resolve().then(() => __importStar(require('../../api/index.js')));
|
|
604
630
|
llmClient.setSystemMode(true, this.session.selectModelId, getServerBaseUrl(), getRequestToken());
|
|
@@ -609,6 +635,7 @@ ${conversation}
|
|
|
609
635
|
// 保持批次顺序,同时限制并发
|
|
610
636
|
const subSummaries = new Array(batches.length).fill('');
|
|
611
637
|
let aborted = false;
|
|
638
|
+
let consecutiveFailures = 0;
|
|
612
639
|
for (let i = 0; i < batches.length; i += CONCURRENCY) {
|
|
613
640
|
if (this.signal.aborted) {
|
|
614
641
|
aborted = true;
|
|
@@ -616,14 +643,26 @@ ${conversation}
|
|
|
616
643
|
}
|
|
617
644
|
const slice = batches.slice(i, i + CONCURRENCY);
|
|
618
645
|
const results = await Promise.allSettled(slice.map((batch) => summarizeBatch(batch)));
|
|
646
|
+
let groupSuccess = 0;
|
|
619
647
|
results.forEach((result, offset) => {
|
|
620
648
|
if (result.status === 'fulfilled' && result.value) {
|
|
621
649
|
subSummaries[i + offset] = result.value;
|
|
650
|
+
groupSuccess++;
|
|
622
651
|
}
|
|
623
652
|
else if (result.status === 'rejected') {
|
|
624
653
|
logger.warn(`子摘要生成失败: ${result.reason?.message}`);
|
|
625
654
|
}
|
|
626
655
|
});
|
|
656
|
+
if (groupSuccess === 0) {
|
|
657
|
+
consecutiveFailures++;
|
|
658
|
+
if (consecutiveFailures >= 2) {
|
|
659
|
+
logger.warn(`分层摘要连续失败,提前终止`);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
consecutiveFailures = 0;
|
|
665
|
+
}
|
|
627
666
|
}
|
|
628
667
|
// 被中断时不写入残缺摘要,交由调用方回退到原始历史
|
|
629
668
|
if (aborted || this.signal.aborted) {
|
|
@@ -638,7 +677,7 @@ ${conversation}
|
|
|
638
677
|
}
|
|
639
678
|
// 合并子摘要为最终摘要
|
|
640
679
|
const mergePrompt = this.buildMergePrompt(valid, lastSummary);
|
|
641
|
-
const mergeLlmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: mergePrompt }], this.signal, [],
|
|
680
|
+
const mergeLlmClient = new LLMClient_js_1.LLMClient(models, [{ role: 'user', content: mergePrompt }], this.signal, [], SUMMARY_REQUEST_TIMEOUT);
|
|
642
681
|
if (this.session.useSystemMode) {
|
|
643
682
|
const { getRequestToken, getServerBaseUrl } = await Promise.resolve().then(() => __importStar(require('../../api/index.js')));
|
|
644
683
|
mergeLlmClient.setSystemMode(true, this.session.selectModelId, getServerBaseUrl(), getRequestToken());
|
|
@@ -1031,6 +1031,7 @@ class Session {
|
|
|
1031
1031
|
if (built.trimmed) {
|
|
1032
1032
|
logger.debug(`上下文裁剪: level=${built.level} chars=${built.chars}`);
|
|
1033
1033
|
}
|
|
1034
|
+
logger.debug(`调用模型${this.selectModelId},messages:`, built.messages);
|
|
1034
1035
|
// 收尾阶段不再下发工具定义:只要工具还在,模型大概率继续调用而不收尾
|
|
1035
1036
|
const llmClient = new LLMClient_js_1.LLMClient(models, built.messages, this.abortController.signal, forceFinalize ? [] : tools);
|
|
1036
1037
|
llmClient.setPreferredModel(this.selectModelId);
|
|
@@ -1370,12 +1371,6 @@ class Session {
|
|
|
1370
1371
|
};
|
|
1371
1372
|
await Promise.all(llmResult.toolCalls.map(x => startToolCall(x)));
|
|
1372
1373
|
toolCalls.push(toolCall);
|
|
1373
|
-
// 重置 toolCall:下一轮工具调用必须开启新的分组。
|
|
1374
|
-
// 否则模型连续多轮只返回工具调用(无正文)时,所有轮次的结果会
|
|
1375
|
-
// 累积进同一个 toolCall 对象,并被重复 push 到 toolCalls 数组,
|
|
1376
|
-
// 导致持久化消息出现重复的工具调用组,历史回放时模型看到重复
|
|
1377
|
-
// 的 tool_calls/tool 结果对,进而重复执行工具。
|
|
1378
|
-
toolCall = null;
|
|
1379
1374
|
// 本轮工具已执行完,增量落库一次。工具轮次可能很多且每轮都耗时,
|
|
1380
1375
|
// 这里保存能让崩溃后仍保留已完成的工具调用记录。
|
|
1381
1376
|
if (!childAgent) {
|