@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.
@@ -146,7 +146,7 @@ exports.appConfig = {
146
146
  * 必定压缩,与内容多少无关 —— 这是「没聊几句就压缩」最直接的原因。
147
147
  * 条数只作为体积估算失真时的兜底,阈值应远高于正常对话轮数。
148
148
  */
149
- summaryThreshold: parseInt(process.env.SUMMARY_THRESHOLD || '60', 10),
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 = 200000;
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
- const apiKey = index_js_1.persistStore.getModelApiKey(model.id);
396
- if (!apiKey) {
397
- throw new Error('No API Key');
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
- throw Error('exceed max message tokens');
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: false,
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, [], 600000);
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, [], 600000);
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, [], 600000);
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, [], 600000);
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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myassis/gateway",
3
- "version": "1.0.108",
3
+ "version": "1.0.110",
4
4
  "description": "我的助手 Gateway Service - 本地 AI 网关服务,支持认证、WebSocket 实时通信和任务调度",
5
5
  "main": "dist/index.js",
6
6
  "bin": {