webaiapi 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +184 -2
- package/bin/webapi.mjs +45 -0
- package/dist/browser/auth-capture.d.ts +110 -0
- package/dist/browser/auth-capture.d.ts.map +1 -0
- package/dist/browser/auth-capture.js +487 -0
- package/dist/browser/auth-capture.js.map +1 -0
- package/dist/browser/credentials.d.ts +102 -0
- package/dist/browser/credentials.d.ts.map +1 -0
- package/dist/browser/credentials.js +209 -0
- package/dist/browser/credentials.js.map +1 -0
- package/dist/browser/js-eval.d.ts +44 -0
- package/dist/browser/js-eval.d.ts.map +1 -0
- package/dist/browser/js-eval.js +20 -0
- package/dist/browser/js-eval.js.map +1 -0
- package/dist/browser/login-flow.d.ts +171 -0
- package/dist/browser/login-flow.d.ts.map +1 -0
- package/dist/browser/login-flow.js +606 -0
- package/dist/browser/login-flow.js.map +1 -0
- package/dist/browser/manager.d.ts +144 -0
- package/dist/browser/manager.d.ts.map +1 -0
- package/dist/browser/manager.js +586 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser/platforms.d.ts +96 -0
- package/dist/browser/platforms.d.ts.map +1 -0
- package/dist/browser/platforms.js +288 -0
- package/dist/browser/platforms.js.map +1 -0
- package/dist/browser/sse-capture.d.ts +23 -0
- package/dist/browser/sse-capture.d.ts.map +1 -0
- package/dist/browser/sse-capture.js +199 -0
- package/dist/browser/sse-capture.js.map +1 -0
- package/dist/browser/stealth.d.ts +12 -0
- package/dist/browser/stealth.d.ts.map +1 -0
- package/dist/browser/stealth.js +12 -0
- package/dist/browser/stealth.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +441 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +90 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +246 -0
- package/dist/config.js.map +1 -0
- package/dist/controllers/history-db.d.ts +56 -0
- package/dist/controllers/history-db.d.ts.map +1 -0
- package/dist/controllers/history-db.js +260 -0
- package/dist/controllers/history-db.js.map +1 -0
- package/dist/controllers/request-pool.d.ts +78 -0
- package/dist/controllers/request-pool.d.ts.map +1 -0
- package/dist/controllers/request-pool.js +222 -0
- package/dist/controllers/request-pool.js.map +1 -0
- package/dist/controllers/router.d.ts +172 -0
- package/dist/controllers/router.d.ts.map +1 -0
- package/dist/controllers/router.js +569 -0
- package/dist/controllers/router.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/log/api-logger.d.ts +55 -0
- package/dist/log/api-logger.d.ts.map +1 -0
- package/dist/log/api-logger.js +264 -0
- package/dist/log/api-logger.js.map +1 -0
- package/dist/log/debug-logger.d.ts +49 -0
- package/dist/log/debug-logger.d.ts.map +1 -0
- package/dist/log/debug-logger.js +285 -0
- package/dist/log/debug-logger.js.map +1 -0
- package/dist/log/logger.d.ts +16 -0
- package/dist/log/logger.d.ts.map +1 -0
- package/dist/log/logger.js +35 -0
- package/dist/log/logger.js.map +1 -0
- package/dist/postinstall.d.ts +3 -0
- package/dist/postinstall.d.ts.map +1 -0
- package/dist/postinstall.js +90 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/providers/base.d.ts +83 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +51 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/chatglm-dom-extract.d.ts +24 -0
- package/dist/providers/chatglm-dom-extract.d.ts.map +1 -0
- package/dist/providers/chatglm-dom-extract.js +175 -0
- package/dist/providers/chatglm-dom-extract.js.map +1 -0
- package/dist/providers/chatglm-dom.d.ts +110 -0
- package/dist/providers/chatglm-dom.d.ts.map +1 -0
- package/dist/providers/chatglm-dom.js +384 -0
- package/dist/providers/chatglm-dom.js.map +1 -0
- package/dist/providers/chatglm-sse.d.ts +119 -0
- package/dist/providers/chatglm-sse.d.ts.map +1 -0
- package/dist/providers/chatglm-sse.js +241 -0
- package/dist/providers/chatglm-sse.js.map +1 -0
- package/dist/providers/chatglm.d.ts +288 -0
- package/dist/providers/chatglm.d.ts.map +1 -0
- package/dist/providers/chatglm.js +1631 -0
- package/dist/providers/chatglm.js.map +1 -0
- package/dist/routes/chat.d.ts +117 -0
- package/dist/routes/chat.d.ts.map +1 -0
- package/dist/routes/chat.js +1346 -0
- package/dist/routes/chat.js.map +1 -0
- package/dist/routes/compress.d.ts +142 -0
- package/dist/routes/compress.d.ts.map +1 -0
- package/dist/routes/compress.js +551 -0
- package/dist/routes/compress.js.map +1 -0
- package/dist/routes/failover.d.ts +34 -0
- package/dist/routes/failover.d.ts.map +1 -0
- package/dist/routes/failover.js +221 -0
- package/dist/routes/failover.js.map +1 -0
- package/dist/routes/helpers.d.ts +174 -0
- package/dist/routes/helpers.d.ts.map +1 -0
- package/dist/routes/helpers.js +525 -0
- package/dist/routes/helpers.js.map +1 -0
- package/dist/routes/meta.d.ts +31 -0
- package/dist/routes/meta.d.ts.map +1 -0
- package/dist/routes/meta.js +184 -0
- package/dist/routes/meta.js.map +1 -0
- package/dist/routes/sse.d.ts +82 -0
- package/dist/routes/sse.d.ts.map +1 -0
- package/dist/routes/sse.js +77 -0
- package/dist/routes/sse.js.map +1 -0
- package/dist/server/app.d.ts +24 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +292 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/middleware/api-key.d.ts +61 -0
- package/dist/server/middleware/api-key.d.ts.map +1 -0
- package/dist/server/middleware/api-key.js +95 -0
- package/dist/server/middleware/api-key.js.map +1 -0
- package/dist/server/middleware/rate-limit.d.ts +67 -0
- package/dist/server/middleware/rate-limit.d.ts.map +1 -0
- package/dist/server/middleware/rate-limit.js +134 -0
- package/dist/server/middleware/rate-limit.js.map +1 -0
- package/dist/server/middleware/timeout.d.ts +34 -0
- package/dist/server/middleware/timeout.d.ts.map +1 -0
- package/dist/server/middleware/timeout.js +64 -0
- package/dist/server/middleware/timeout.js.map +1 -0
- package/dist/server/types.d.ts +20 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +2 -0
- package/dist/server/types.js.map +1 -0
- package/dist/tools/build.d.ts +14 -0
- package/dist/tools/build.d.ts.map +1 -0
- package/dist/tools/build.js +105 -0
- package/dist/tools/build.js.map +1 -0
- package/dist/tools/clean.d.ts +32 -0
- package/dist/tools/clean.d.ts.map +1 -0
- package/dist/tools/clean.js +65 -0
- package/dist/tools/clean.js.map +1 -0
- package/dist/tools/convert.d.ts +50 -0
- package/dist/tools/convert.d.ts.map +1 -0
- package/dist/tools/convert.js +289 -0
- package/dist/tools/convert.js.map +1 -0
- package/dist/tools/names.d.ts +138 -0
- package/dist/tools/names.d.ts.map +1 -0
- package/dist/tools/names.js +578 -0
- package/dist/tools/names.js.map +1 -0
- package/dist/tools/parse.d.ts +31 -0
- package/dist/tools/parse.d.ts.map +1 -0
- package/dist/tools/parse.js +273 -0
- package/dist/tools/parse.js.map +1 -0
- package/dist/tools/placeholder.d.ts +21 -0
- package/dist/tools/placeholder.d.ts.map +1 -0
- package/dist/tools/placeholder.js +135 -0
- package/dist/tools/placeholder.js.map +1 -0
- package/dist/tools/prompts.d.ts +42 -0
- package/dist/tools/prompts.d.ts.map +1 -0
- package/dist/tools/prompts.js +69 -0
- package/dist/tools/prompts.js.map +1 -0
- package/dist/tools/protocol.d.ts +58 -0
- package/dist/tools/protocol.d.ts.map +1 -0
- package/dist/tools/protocol.js +287 -0
- package/dist/tools/protocol.js.map +1 -0
- package/dist/tools/repair.d.ts +83 -0
- package/dist/tools/repair.d.ts.map +1 -0
- package/dist/tools/repair.js +510 -0
- package/dist/tools/repair.js.map +1 -0
- package/dist/tools/workspace.d.ts +40 -0
- package/dist/tools/workspace.d.ts.map +1 -0
- package/dist/tools/workspace.js +224 -0
- package/dist/tools/workspace.js.map +1 -0
- package/dist/types/message.d.ts +87 -0
- package/dist/types/message.d.ts.map +1 -0
- package/dist/types/message.js +85 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/openai.d.ts +100 -0
- package/dist/types/openai.d.ts.map +1 -0
- package/dist/types/openai.js +66 -0
- package/dist/types/openai.js.map +1 -0
- package/dist/utils/errors.d.ts +82 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +115 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/mutex.d.ts +31 -0
- package/dist/utils/mutex.d.ts.map +1 -0
- package/dist/utils/mutex.js +61 -0
- package/dist/utils/mutex.js.map +1 -0
- package/dist/utils/prompt.d.ts +150 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +514 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/text.d.ts +30 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +70 -0
- package/dist/utils/text.js.map +1 -0
- package/package.json +67 -4
- package/postinstall.mjs +34 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 历史压缩 —— `legacy-python/src/api/routes.py` 第 499~1037 行的移植。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么要有这个模块
|
|
5
|
+
* 提示词超过网页输入框上限(64K)时,原做法是「保留头尾、丢弃中间」
|
|
6
|
+
* (实测 90375 → 64000,中间 26375 字符直接丢掉)。这会**随机丢失上下文**,
|
|
7
|
+
* 模型拿不到完整信息 → 表现为「只描述不调用工具」。
|
|
8
|
+
*
|
|
9
|
+
* 改进:额外发起一次对话,让网页端把**旧历史**压缩成要点摘要,
|
|
10
|
+
* 再用「摘要 + 最近原文」拼出最终提示词 —— 关键信息(文件路径、已完成的修改、
|
|
11
|
+
* 待办事项)被保留,提示词体积大幅下降。
|
|
12
|
+
*
|
|
13
|
+
* ## 这个模块里全是「省时间」的踩坑结论
|
|
14
|
+
* 压缩不是免费的:每一次压缩 = **一整轮完整对话**(30~60s)。
|
|
15
|
+
* 所以每个常量都在权衡「多压一块 vs 少一轮预算」。
|
|
16
|
+
*/
|
|
17
|
+
import { logger } from '../log/logger.js';
|
|
18
|
+
import { streamWithFailover } from './failover.js';
|
|
19
|
+
import { BROWSER_PROMPT_MAX_LEN, COMPRESS_CHUNK_MAX_SECONDS, COMPRESS_MIN_BUDGET, truncatePromptForBrowser, } from './helpers.js';
|
|
20
|
+
/* ==================================================================== */
|
|
21
|
+
/* 常量 */
|
|
22
|
+
/* ==================================================================== */
|
|
23
|
+
/** 最多缓存的会话数(按 `conversation_id`)。 */
|
|
24
|
+
export const SUMMARY_CACHE_MAX = 8;
|
|
25
|
+
/** 单次压缩请求的输入上限(需 < 输入框上限)。 */
|
|
26
|
+
export const COMPRESS_CHUNK_MAX = 50000;
|
|
27
|
+
/** 新增历史小于此值时直接追加,无需再压缩。 */
|
|
28
|
+
export const COMPRESS_APPEND_THRESHOLD = 6000;
|
|
29
|
+
/**
|
|
30
|
+
* 原样保留的最近上下文长度。
|
|
31
|
+
*
|
|
32
|
+
* ⚠️ 这个值直接决定「要压几块」= 要额外跑几轮对话(每块一轮):
|
|
33
|
+
* - 提示词 8 万字符时,保留 2 万 → 旧历史 5.5~6.7 万 → **2 块 = 2 轮 = 30~117s**;
|
|
34
|
+
* - 保留 4 万 → 旧历史约 4 万 → **1 块 = 1 轮**,省掉约一半压缩耗时,
|
|
35
|
+
* 而且对当前工作更重要的近期原文保留得更多(信息更保真)。
|
|
36
|
+
* 最终提示词 = 4 万原文 + 约 6000 摘要 ≈ 4.7 万,仍远低于 64K 上限。
|
|
37
|
+
*/
|
|
38
|
+
export const RECENT_KEEP = 40000;
|
|
39
|
+
/** 最近原文的**上限**:再多就装不下摘要了(64K 上限 − 摘要与包装文本约 10K)。 */
|
|
40
|
+
export const RECENT_KEEP_MAX = 54000;
|
|
41
|
+
/**
|
|
42
|
+
* 单块最多压 3 次(1 次不理想就再来)。
|
|
43
|
+
*
|
|
44
|
+
* 实测压缩比本身很好(8.5~10.4x),所以**不需要无脑压 2/3 次** ——
|
|
45
|
+
* 每多压一次就多花 30~60s 预算。改为「结果不理想才重试」。
|
|
46
|
+
*/
|
|
47
|
+
export const COMPRESS_MAX_ATTEMPTS = 3;
|
|
48
|
+
/** 每次重试前至少要有的剩余预算(秒)。 */
|
|
49
|
+
export const COMPRESS_RETRY_MIN_BUDGET = 45.0;
|
|
50
|
+
/** 摘要短于此值视为「没压出东西」。 */
|
|
51
|
+
export const COMPRESS_MIN_SUMMARY_CHARS = 200;
|
|
52
|
+
/** 明显拒答 / 无法处理的措辞。 */
|
|
53
|
+
export const COMPRESS_BAD_MARKERS = [
|
|
54
|
+
'无法压缩', '不能压缩', '无法完成', '无法处理', '很抱歉', '抱歉,我',
|
|
55
|
+
'内容过长无法', '请提供完整',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* 命中这些说明**平台侧有硬限制**(限频/繁忙),重试只会更糟 → 立刻放弃、不重试。
|
|
59
|
+
*/
|
|
60
|
+
export const COMPRESS_ABORT_MARKERS = [
|
|
61
|
+
'请求过于频繁', '平台限制', '请稍后再试', '平台繁忙', '稍后再试',
|
|
62
|
+
'今天的对话次数', '已达到上限',
|
|
63
|
+
];
|
|
64
|
+
/**
|
|
65
|
+
* 某块重试后仍然失败时用它兜底。
|
|
66
|
+
*
|
|
67
|
+
* ⚠️ 绝不能静默丢弃这段历史(旧实现 `if s:` 直接跳过,那段历史永久丢失,
|
|
68
|
+
* 模型下一轮再也看不到它 → 表现为「忘了已经读过/改过什么」)。
|
|
69
|
+
*/
|
|
70
|
+
export const COMPRESS_FAIL_FALLBACK_CHARS = 4000;
|
|
71
|
+
export const HISTORY_SUMMARY_CACHE = new Map();
|
|
72
|
+
/** 压缩提示词(`{content}` 占位符)。 */
|
|
73
|
+
export const COMPRESS_PROMPT = `请把下面这段对话历史压缩成要点摘要,供后续继续开发时作为上下文。
|
|
74
|
+
|
|
75
|
+
必须保留(不得丢失):
|
|
76
|
+
1. 涉及到的**文件绝对路径**
|
|
77
|
+
2. 已经完成的修改、以及工具执行结果里的关键结论
|
|
78
|
+
3. 尚未完成的待办事项,以及用户明确提出过的要求
|
|
79
|
+
|
|
80
|
+
输出要求:不要解释、不要客套,直接输出摘要;用简洁的要点列表;控制在 1500 字以内。
|
|
81
|
+
|
|
82
|
+
# 对话历史
|
|
83
|
+
{content}
|
|
84
|
+
`;
|
|
85
|
+
/* ==================================================================== */
|
|
86
|
+
/* 切分 */
|
|
87
|
+
/* ==================================================================== */
|
|
88
|
+
/**
|
|
89
|
+
* 把切分点对齐到消息边界(`\n\n[Role]`),避免把一条消息切成两半。
|
|
90
|
+
*
|
|
91
|
+
* ⚠️ 两个坑(实测踩出来的):
|
|
92
|
+
* - **只取「前一个」边界**:若那条消息很长,边界离目标位置很远,实际保留的
|
|
93
|
+
* 最近原文会远超预期(想留 4 万,结果留了 5.87 万,最终提示词 59,692 字符,
|
|
94
|
+
* 离 64K 上限只剩 4 千余,随时会被强制中间截断)。
|
|
95
|
+
* - **只取「最近」边界**(前后都比):可能选到**更晚**的边界,要压缩的旧历史
|
|
96
|
+
* 反而变大(实测想压成 1 块,变成 2 块,58s vs 21s)。
|
|
97
|
+
*
|
|
98
|
+
* 做法:优先对齐到目标位置之前的消息边界;边界离目标超过 `maxSkip`
|
|
99
|
+
* 就退化为按**行**切(宁可切在消息内部,也不能让压缩块数/提示词体积失控)。
|
|
100
|
+
*/
|
|
101
|
+
export function snapToMessageBoundary(text, pos, maxSkip = 8000) {
|
|
102
|
+
if (pos <= 0)
|
|
103
|
+
return 0;
|
|
104
|
+
const prev = text.lastIndexOf('\n\n[', pos);
|
|
105
|
+
if (prev > 0 && pos - prev <= maxSkip)
|
|
106
|
+
return prev;
|
|
107
|
+
const line = text.lastIndexOf('\n', pos);
|
|
108
|
+
return line > 0 ? line : pos;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 把提示词切成 `[旧历史, 最近原文, 切点]`。
|
|
112
|
+
*
|
|
113
|
+
* 切分点:在「多保留近期原文(省时间、更保真)」与「让压缩尽量只压 1 块」之间取平衡。
|
|
114
|
+
* - `keepMin`:想压成 1 块,最近原文至少要留 `len - 单块上限`;
|
|
115
|
+
* - `RECENT_KEEP_MAX`:留太多会挤爆 64K 上限(还要装摘要),设个天花板。
|
|
116
|
+
*
|
|
117
|
+
* 抽成公共函数,是为了让「请求内的压缩」与「后台预压缩」用**完全一致**的切分口径 ——
|
|
118
|
+
* 口径不一致时后台预压缩存下的缓存会前缀对不上,白跑一轮。
|
|
119
|
+
*/
|
|
120
|
+
export function splitForCompression(messageText) {
|
|
121
|
+
if (messageText.length <= 500) {
|
|
122
|
+
return { oldHistory: messageText, recent: '', cut: messageText.length };
|
|
123
|
+
}
|
|
124
|
+
const keep = Math.min(RECENT_KEEP_MAX, Math.max(RECENT_KEEP, messageText.length - COMPRESS_CHUNK_MAX));
|
|
125
|
+
const cut = snapToMessageBoundary(messageText, Math.max(0, messageText.length - keep));
|
|
126
|
+
return {
|
|
127
|
+
oldHistory: messageText.slice(0, cut),
|
|
128
|
+
recent: messageText.slice(cut),
|
|
129
|
+
cut,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/* ==================================================================== */
|
|
133
|
+
/* 压缩质量判定 */
|
|
134
|
+
/* ==================================================================== */
|
|
135
|
+
/** 是否是「平台侧硬限制」类回复(限频/繁忙)—— 重试只会更糟,立刻放弃。 */
|
|
136
|
+
export function summaryIsAbortable(summary) {
|
|
137
|
+
const s = (summary ?? '').trim();
|
|
138
|
+
if (!s || s.length > 200)
|
|
139
|
+
return false;
|
|
140
|
+
return COMPRESS_ABORT_MARKERS.some((m) => s.includes(m));
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 压缩结果是否「不理想」(值得再压一次)。
|
|
144
|
+
*
|
|
145
|
+
* 判据(保守,避免白白多花一轮时间):
|
|
146
|
+
* 1. 空 / 极短 —— 模型没答或只回了一句话,等于这段历史丢了;
|
|
147
|
+
* 2. 明显拒答/无法处理("无法压缩""很抱歉…");
|
|
148
|
+
* 3. 大段输入却基本没压(输出 ≥ 输入)—— 说明它把原文抄回来了。
|
|
149
|
+
*/
|
|
150
|
+
export function summaryIsBad(summary, source) {
|
|
151
|
+
const s = (summary ?? '').trim();
|
|
152
|
+
if (!s)
|
|
153
|
+
return true;
|
|
154
|
+
const minChars = Math.min(COMPRESS_MIN_SUMMARY_CHARS, Math.max(50, Math.trunc(source.length / 50)));
|
|
155
|
+
if (s.length < minChars)
|
|
156
|
+
return true;
|
|
157
|
+
const head = s.slice(0, 200);
|
|
158
|
+
if (COMPRESS_BAD_MARKERS.some((m) => head.includes(m)))
|
|
159
|
+
return true;
|
|
160
|
+
if (source.length > 20000 && s.length >= source.length)
|
|
161
|
+
return true;
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 调用网页端把 `text` 压缩成摘要;`text` 过长时先分块压缩再合并。
|
|
166
|
+
*
|
|
167
|
+
* @returns `allOk=false` 表示有分块走了截断兜底 —— 这份内容只能当上下文用。
|
|
168
|
+
*/
|
|
169
|
+
export async function compressTextWithPage(text, modelRouter, model, historyDb, requestId, deadlineTs) {
|
|
170
|
+
if (!text.trim())
|
|
171
|
+
return { summary: '', allOk: true };
|
|
172
|
+
const chunks = [];
|
|
173
|
+
for (let i = 0; i < text.length; i += COMPRESS_CHUNK_MAX) {
|
|
174
|
+
chunks.push(text.slice(i, i + COMPRESS_CHUNK_MAX));
|
|
175
|
+
}
|
|
176
|
+
/** 跑一轮对话拿结果。 */
|
|
177
|
+
const run = async (prompt, tag) => {
|
|
178
|
+
/* ⚠️ 压缩本身也是**一整轮对话**:一份 8 万字符的提示词要压 2 次,
|
|
179
|
+
* 实测烧掉 117s,把预算吃得所剩无几,导致后面的「两轮策略 / 自驱动续跑」
|
|
180
|
+
* 全部因剩余时间不足被跳过。
|
|
181
|
+
* 因此:每块压缩有自己的上限,且**绝不越过**整个请求的截止时间。 */
|
|
182
|
+
let dl = Date.now() + COMPRESS_CHUNK_MAX_SECONDS * 1000;
|
|
183
|
+
if (deadlineTs)
|
|
184
|
+
dl = Math.min(dl, deadlineTs);
|
|
185
|
+
const req = {
|
|
186
|
+
message: prompt,
|
|
187
|
+
model,
|
|
188
|
+
stream: false,
|
|
189
|
+
deadlineTs: dl,
|
|
190
|
+
// ⚠️ 必须用**辅助页面**:压缩是往网页里再发一轮对话,
|
|
191
|
+
// 若跑在主页面就会把主会话切走 —— 主会话的上下文丢了、
|
|
192
|
+
// 「只补发增量」的会话复用随之失效;
|
|
193
|
+
// 而后台预压缩更严重:它正是在等客户端下一轮请求,
|
|
194
|
+
// 跑在主页面等于直接把主会话顶掉。
|
|
195
|
+
context: { use_aux_page: true },
|
|
196
|
+
};
|
|
197
|
+
let out = '';
|
|
198
|
+
for await (const [resp] of streamWithFailover(modelRouter, model, req, historyDb, `${requestId}#${tag}`)) {
|
|
199
|
+
out += resp.content ?? '';
|
|
200
|
+
}
|
|
201
|
+
return out.trim();
|
|
202
|
+
};
|
|
203
|
+
/**
|
|
204
|
+
* 压一块:结果不理想才重试(最多 `COMPRESS_MAX_ATTEMPTS` 次)。
|
|
205
|
+
*
|
|
206
|
+
* 每次重试都要花一整轮对话(30~60s),所以重试必须同时满足:
|
|
207
|
+
* 结果确实不理想 **且** 剩余预算够。返回历次里最长的那份,避免越试越差。
|
|
208
|
+
*/
|
|
209
|
+
const compressOne = async (content, tag) => {
|
|
210
|
+
let best = '';
|
|
211
|
+
for (let attempt = 1; attempt <= COMPRESS_MAX_ATTEMPTS; attempt += 1) {
|
|
212
|
+
if (attempt > 1) {
|
|
213
|
+
const remain = deadlineTs ? deadlineTs - Date.now() : Number.POSITIVE_INFINITY;
|
|
214
|
+
if (deadlineTs && remain / 1000 < COMPRESS_RETRY_MIN_BUDGET) {
|
|
215
|
+
logger.warn(`[prompt] ${tag} 放弃第 ${attempt} 次重试:剩余预算不足(${(remain / 1000).toFixed(0)}s)`);
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
logger.warn(`[prompt] ${tag} 上次压缩结果不理想(${best.length} 字符),` +
|
|
219
|
+
`重试第 ${attempt}/${COMPRESS_MAX_ATTEMPTS} 次`);
|
|
220
|
+
}
|
|
221
|
+
const t0 = Date.now();
|
|
222
|
+
const s = await run(COMPRESS_PROMPT.replace('{content}', content), `${tag}t${attempt}`);
|
|
223
|
+
const bad = summaryIsBad(s, content);
|
|
224
|
+
const abort = summaryIsAbortable(s);
|
|
225
|
+
logger.info(`[prompt] ${tag} 第 ${attempt} 次压缩:输入 ${content.length} → ` +
|
|
226
|
+
`输出 ${s.length} 字符,耗时 ${((Date.now() - t0) / 1000).toFixed(0)}s,` +
|
|
227
|
+
`${abort ? '平台限制(放弃)' : bad ? '不理想(将重试)' : 'OK'}`);
|
|
228
|
+
if (abort) {
|
|
229
|
+
// 限频/繁忙:重试只会让情况更糟(实测连试 3 次全被限)
|
|
230
|
+
logger.warn(`[prompt] ${tag} 命中平台限制,放弃压缩重试`);
|
|
231
|
+
return '';
|
|
232
|
+
}
|
|
233
|
+
if (s && s.length > best.length)
|
|
234
|
+
best = s;
|
|
235
|
+
if (!bad && s)
|
|
236
|
+
return s;
|
|
237
|
+
}
|
|
238
|
+
return best;
|
|
239
|
+
};
|
|
240
|
+
const summaries = [];
|
|
241
|
+
let allOk = true;
|
|
242
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
243
|
+
const chunk = chunks[i];
|
|
244
|
+
logger.info(`[prompt] 压缩历史分块 ${i + 1}/${chunks.length}(${chunk.length} 字符)`);
|
|
245
|
+
const s = await compressOne(chunk, `compress${i}`);
|
|
246
|
+
if (s) {
|
|
247
|
+
summaries.push(s);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
/* ⚠️ 旧实现是 `if s:` 直接跳过 —— 压缩失败的这一段历史会**永久丢失**,
|
|
251
|
+
* 模型下一轮就再也看不到它(表现为「忘了已经读过/改过什么」)。
|
|
252
|
+
* 这里退化为截断保底,至少把开头部分留下。
|
|
253
|
+
*
|
|
254
|
+
* 但**必须标记「不完整」**:这只是兜底产物,不能当作摘要写进缓存
|
|
255
|
+
* (否则缓存会谎报"已覆盖到 N 字符",下一轮直接复用这份残缺摘要
|
|
256
|
+
* → 上下文真的丢了)。 */
|
|
257
|
+
allOk = false;
|
|
258
|
+
const fallback = chunk.slice(0, COMPRESS_FAIL_FALLBACK_CHARS);
|
|
259
|
+
logger.warn(`[prompt] 分块 ${i + 1} 压缩失败(已尝试 ${COMPRESS_MAX_ATTEMPTS} 次),` +
|
|
260
|
+
`改用截断兜底保留前 ${fallback.length} 字符`);
|
|
261
|
+
summaries.push(`(该段历史压缩失败,以下为原文开头截断)\n${fallback}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
let merged = summaries.join('\n\n');
|
|
265
|
+
// 合并后仍过长则再压一次(同样带质量重试)
|
|
266
|
+
if (merged.length > COMPRESS_CHUNK_MAX) {
|
|
267
|
+
merged = await compressOne(merged.slice(0, COMPRESS_CHUNK_MAX), 'compress_merge');
|
|
268
|
+
}
|
|
269
|
+
return { summary: merged, allOk };
|
|
270
|
+
}
|
|
271
|
+
/* ==================================================================== */
|
|
272
|
+
/* 提示词压缩入口(超长时替代中间截断) */
|
|
273
|
+
/* ==================================================================== */
|
|
274
|
+
/**
|
|
275
|
+
* 提示词超长时,用「压缩历史」替代「中间截断」。失败时回退到中间截断,
|
|
276
|
+
* 保证请求始终发得出去。
|
|
277
|
+
*/
|
|
278
|
+
export async function shrinkPromptViaCompression(messageText, modelRouter, model, historyDb, requestId, conversationId, deadlineTs) {
|
|
279
|
+
if (messageText.length <= BROWSER_PROMPT_MAX_LEN)
|
|
280
|
+
return messageText;
|
|
281
|
+
// 压缩要额外跑 1~2 轮完整对话(实测 117s),预算不够就别压:
|
|
282
|
+
// 直接中间截断更快,把剩余时间留给「两轮策略 / 自驱动续跑」。
|
|
283
|
+
// 实测代价:压缩把预算吃光 → reparse 被跳过 → 模型只交了计划书就结束回合。
|
|
284
|
+
if (deadlineTs && (deadlineTs - Date.now()) / 1000 < COMPRESS_MIN_BUDGET) {
|
|
285
|
+
logger.warn(`[prompt] 剩余预算 ${((deadlineTs - Date.now()) / 1000).toFixed(0)}s < ` +
|
|
286
|
+
`${COMPRESS_MIN_BUDGET.toFixed(0)}s,跳过历史压缩,直接中间截断`);
|
|
287
|
+
return truncatePromptForBrowser(messageText);
|
|
288
|
+
}
|
|
289
|
+
const { oldHistory, recent } = splitForCompression(messageText);
|
|
290
|
+
if (oldHistory.length < 500) {
|
|
291
|
+
// 没有可压缩的旧历史(超长的是最近部分本身),只能截断
|
|
292
|
+
return truncatePromptForBrowser(messageText);
|
|
293
|
+
}
|
|
294
|
+
logger.info(`[prompt] 提示词 ${messageText.length} 字符超限,` +
|
|
295
|
+
`改用「压缩历史」策略(旧历史 ${oldHistory.length} + 最近 ${recent.length})`);
|
|
296
|
+
const cached = HISTORY_SUMMARY_CACHE.get(conversationId);
|
|
297
|
+
let summary = '';
|
|
298
|
+
let covered = 0;
|
|
299
|
+
try {
|
|
300
|
+
if (cached && cached.prefix && oldHistory.startsWith(cached.prefix)) {
|
|
301
|
+
// 命中缓存:只处理新增部分,避免每轮都重复压缩
|
|
302
|
+
const prevPrefix = cached.prefix;
|
|
303
|
+
const prevSummary = cached.summary;
|
|
304
|
+
const prevCovered = cached.coveredLen ?? prevPrefix.length;
|
|
305
|
+
const newPart = oldHistory.slice(prevPrefix.length);
|
|
306
|
+
if (prevCovered >= oldHistory.length) {
|
|
307
|
+
// 后台预压缩已经把这段历史(甚至更多)折进摘要了 → **零压缩成本**
|
|
308
|
+
summary = prevSummary;
|
|
309
|
+
covered = prevCovered;
|
|
310
|
+
logger.info(`[prompt] 命中后台预压缩摘要(已覆盖 ${prevCovered} 字符历史、` +
|
|
311
|
+
`本次只需 ${oldHistory.length}),零压缩成本`);
|
|
312
|
+
}
|
|
313
|
+
else if (newPart.length <= COMPRESS_APPEND_THRESHOLD) {
|
|
314
|
+
summary = `${prevSummary}\n${newPart}`.trim();
|
|
315
|
+
covered = oldHistory.length;
|
|
316
|
+
logger.info('[prompt] 命中历史摘要缓存(新增历史直接追加)');
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
const r = await compressTextWithPage(`${prevSummary}\n\n${newPart}`, modelRouter, model, historyDb, requestId, deadlineTs);
|
|
320
|
+
summary = r.summary;
|
|
321
|
+
covered = r.allOk ? oldHistory.length : 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
const r = await compressTextWithPage(oldHistory, modelRouter, model, historyDb, requestId, deadlineTs);
|
|
326
|
+
summary = r.summary;
|
|
327
|
+
covered = r.allOk ? oldHistory.length : 0;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (error) {
|
|
331
|
+
logger.warn(`[prompt] 历史压缩失败: ${String(error)}`);
|
|
332
|
+
summary = '';
|
|
333
|
+
}
|
|
334
|
+
if (!summary) {
|
|
335
|
+
logger.warn('[prompt] 历史压缩无输出,回退为中间截断');
|
|
336
|
+
return truncatePromptForBrowser(messageText);
|
|
337
|
+
}
|
|
338
|
+
// 更新缓存(LRU:超出上限时丢弃最早的一条)。
|
|
339
|
+
// ⚠️ 只有**真实摘要**(covered>0)才能进缓存:截断兜底产物若被当摘要缓存,
|
|
340
|
+
// 下一轮会「零成本复用它」→ 那段历史等于被静默丢弃。
|
|
341
|
+
if (covered > 0) {
|
|
342
|
+
HISTORY_SUMMARY_CACHE.set(conversationId, {
|
|
343
|
+
prefix: oldHistory,
|
|
344
|
+
summary,
|
|
345
|
+
coveredLen: Math.max(covered, oldHistory.length),
|
|
346
|
+
});
|
|
347
|
+
while (HISTORY_SUMMARY_CACHE.size > SUMMARY_CACHE_MAX) {
|
|
348
|
+
const oldest = HISTORY_SUMMARY_CACHE.keys().next();
|
|
349
|
+
if (oldest.done)
|
|
350
|
+
break;
|
|
351
|
+
HISTORY_SUMMARY_CACHE.delete(oldest.value);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
logger.warn('[prompt] 本次只有截断兜底产物,不写入摘要缓存');
|
|
356
|
+
}
|
|
357
|
+
let newText = '[以下是之前对话历史的压缩摘要,请基于它继续]\n' +
|
|
358
|
+
`${summary}\n\n` +
|
|
359
|
+
'[以下是最近的对话原文]\n' +
|
|
360
|
+
`${recent}`;
|
|
361
|
+
logger.info(`[prompt] 历史压缩完成:${oldHistory.length} 字符旧历史 → ` +
|
|
362
|
+
`${summary.length} 字符摘要,最终提示词 ${newText.length} 字符`);
|
|
363
|
+
if (newText.length > BROWSER_PROMPT_MAX_LEN) {
|
|
364
|
+
newText = truncatePromptForBrowser(newText);
|
|
365
|
+
}
|
|
366
|
+
return newText;
|
|
367
|
+
}
|
|
368
|
+
/* ==================================================================== */
|
|
369
|
+
/* 后台预压缩:把「压缩历史」从用户请求的预算里挪出去 */
|
|
370
|
+
/* ==================================================================== */
|
|
371
|
+
//
|
|
372
|
+
// 为什么值得做:上下文是**逐轮增长**的(每轮都会带回工具结果,尤其 read_file
|
|
373
|
+
// 结果不截断)。轮次越多提示词越大,每一轮都要现场压一遍 ——
|
|
374
|
+
// 实测 89k 字符的提示词压缩要 25~117s,直接吃掉用户请求的 1/10~1/2 预算,
|
|
375
|
+
// 而这个预算本该用来干活(reparse / 自驱动续跑)。
|
|
376
|
+
//
|
|
377
|
+
// 做法:本轮响应结束、客户端还在执行工具的那段空档里,后台把「本轮已发的历史 +
|
|
378
|
+
// 本轮新增内容」折叠进摘要缓存;下一轮进来时命中缓存且新增部分很小 →
|
|
379
|
+
// 走**纯追加**分支,压缩耗时降为 0。
|
|
380
|
+
/** 同时最多几个后台压缩(多了会抢账号/页面)。 */
|
|
381
|
+
const PRECOMPRESS_MAX_CONCURRENT = 2;
|
|
382
|
+
/** 单次后台压缩的时间上限(秒)。 */
|
|
383
|
+
const PRECOMPRESS_BUDGET = 100.0;
|
|
384
|
+
/**
|
|
385
|
+
* 预压缩要「往前多看一段」才有效:只对齐本次切点等于白跑(请求内压缩已盖到那里)。
|
|
386
|
+
* 20000 大致能顶 1~2 轮上下文增长,代价是摘要略微变长 + 与最近原文有少量重复。
|
|
387
|
+
*/
|
|
388
|
+
const PRECOMPRESS_LOOKAHEAD = 20000;
|
|
389
|
+
/**
|
|
390
|
+
* 让位机制:浏览器通道是**串行**的(provider 有全局锁),后台压缩若和用户的
|
|
391
|
+
* 下一个请求撞上,会把它堵在锁上、白吃它的时间预算。所以:
|
|
392
|
+
* ① 先睡 `PRECOMPRESS_DELAY`,等客户端去执行工具(读文件等);
|
|
393
|
+
* ② 醒来后若发现「最近有请求进来」(安静期不足),直接放弃这一轮。
|
|
394
|
+
*
|
|
395
|
+
* ⚠️ 安静期要够长:实测工具调用型任务里,客户端读完文件 1~5s 就会发下一轮请求,
|
|
396
|
+
* 这时根本没有空档 —— 抢锁只会拖慢它。只有「执行命令/跑测试」这类
|
|
397
|
+
* 长时间工具才会出现真正的空档,那才是预压缩该出手的时候。
|
|
398
|
+
*/
|
|
399
|
+
const PRECOMPRESS_DELAY = 10.0;
|
|
400
|
+
const PRECOMPRESS_QUIET_GAP = 12.0;
|
|
401
|
+
/** 最近一次收到用户请求的时间戳(由 `chatCompletions` 通过 {@link markRequestTs} 更新)。 */
|
|
402
|
+
let lastRequestTs = 0;
|
|
403
|
+
/** 记录「刚收到用户请求」(供预压缩判断安静期)。 */
|
|
404
|
+
export function markRequestTs(now = Date.now()) {
|
|
405
|
+
lastRequestTs = now;
|
|
406
|
+
}
|
|
407
|
+
/** 在跑的后台任务(用于并发上限控制)。 */
|
|
408
|
+
const BG_TASKS = new Set();
|
|
409
|
+
/** 毫秒级等待。 */
|
|
410
|
+
function sleep(ms) {
|
|
411
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 后台把本轮历史折叠进摘要缓存(下一轮即可 0 成本命中)。
|
|
415
|
+
*
|
|
416
|
+
* @param rawText **压缩前**的完整提示词:切分口径与下一轮完全一致,
|
|
417
|
+
* 这样下一轮算出的 `oldHistory` 才会以本次缓存的前缀开头(走纯追加分支)。
|
|
418
|
+
*/
|
|
419
|
+
export async function precompressHistory(rawText, conversationId, modelRouter, model, historyDb, requestId) {
|
|
420
|
+
try {
|
|
421
|
+
// ① 先让客户端把工具跑起来(读文件/改文件都要时间),再抢浏览器通道
|
|
422
|
+
await sleep(PRECOMPRESS_DELAY * 1000);
|
|
423
|
+
/* ② 有人在用就放弃:**仅在没有独立辅助通道时**才需要让位。
|
|
424
|
+
*
|
|
425
|
+
* 浏览器通道原本是串行的(provider 一把全局锁),后台压缩与用户下一个请求
|
|
426
|
+
* 撞上就会把它堵在锁上、白吃它的时间预算。
|
|
427
|
+
* 现在辅助任务可以走「另一个账号 + 独立 context 页面的辅助通道」:
|
|
428
|
+
* 不同账号 → 不同并发额度,不同页面/锁 → 真正并行,不再堵住用户请求,
|
|
429
|
+
* 因此这种情况下**不必让位**(下一轮压缩成本能真正降为 0)。 */
|
|
430
|
+
let auxAvailable = false;
|
|
431
|
+
try {
|
|
432
|
+
auxAvailable = modelRouter.getAuxCandidates().length > 0;
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
auxAvailable = false;
|
|
436
|
+
}
|
|
437
|
+
const since = (Date.now() - lastRequestTs) / 1000;
|
|
438
|
+
if (since < PRECOMPRESS_QUIET_GAP) {
|
|
439
|
+
if (!auxAvailable) {
|
|
440
|
+
logger.info(`[precompress] 检测到 ${since.toFixed(1)}s 前有新请求进来,` +
|
|
441
|
+
'让位给用户请求,跳过本轮预压缩(无独立辅助通道)');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
logger.info(`[precompress] 独立辅助通道可用 → 与主对话并行执行,` +
|
|
445
|
+
`不再让位(${since.toFixed(1)}s 前有新请求)`);
|
|
446
|
+
}
|
|
447
|
+
const { oldHistory, cut } = splitForCompression(rawText);
|
|
448
|
+
if (oldHistory.length < 500)
|
|
449
|
+
return;
|
|
450
|
+
const deadline = Date.now() + PRECOMPRESS_BUDGET * 1000;
|
|
451
|
+
const cached = HISTORY_SUMMARY_CACHE.get(conversationId);
|
|
452
|
+
const t0 = Date.now();
|
|
453
|
+
// 缓存前缀用本次切点:切点随提示词长度**单调不减**,
|
|
454
|
+
// 因此 `raw.slice(0, cut)` 一定是下一轮 `oldHistory` 的前缀(必命中)。
|
|
455
|
+
let prefix = oldHistory;
|
|
456
|
+
let baseSummary = '';
|
|
457
|
+
let covered = 0;
|
|
458
|
+
if (cached && cached.prefix && oldHistory.startsWith(cached.prefix)) {
|
|
459
|
+
prefix = cached.prefix;
|
|
460
|
+
baseSummary = cached.summary;
|
|
461
|
+
covered = cached.coveredLen ?? cached.prefix.length;
|
|
462
|
+
}
|
|
463
|
+
/* 关键:往前多看一段「最近原文区」的开头。
|
|
464
|
+
* 只对齐本次切点等于白跑(请求内压缩已经覆盖到那里了);
|
|
465
|
+
* 把 cut ~ cut+LOOKAHEAD 这段也折进摘要,下一轮(甚至下两轮)
|
|
466
|
+
* 的 oldHistory 就整段落在已覆盖范围内 → 压缩成本 0。 */
|
|
467
|
+
const targetCover = Math.min(rawText.length, cut + PRECOMPRESS_LOOKAHEAD);
|
|
468
|
+
if (covered >= targetCover) {
|
|
469
|
+
logger.info(`[precompress] 摘要已覆盖 ${covered} 字符(目标 ${targetCover}),无需预压缩`);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
const material = rawText.slice(covered, targetCover);
|
|
473
|
+
if (material.length < 1000) {
|
|
474
|
+
logger.info(`[precompress] 待折入的历史只有 ${material.length} 字符,跳过`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const prompt = baseSummary ? `${baseSummary}\n\n${material}` : material;
|
|
478
|
+
logger.info(`[precompress] 预压缩:摘要 ${baseSummary.length} + ` +
|
|
479
|
+
`历史 ${covered}~${targetCover}(${material.length} 字符)`);
|
|
480
|
+
const { summary, allOk } = await compressTextWithPage(prompt, modelRouter, model, historyDb, `${requestId}#precompress`, deadline);
|
|
481
|
+
if (!summary || !allOk) {
|
|
482
|
+
/* ⚠️ 失败/截断兜底产物**绝不能写缓存**:它会谎报「已覆盖到 N 字符」,
|
|
483
|
+
* 下一轮零成本复用它 → 那段历史真的丢了(实测被限频时就是这样)。 */
|
|
484
|
+
logger.warn(`[precompress] 预压缩未产出可信摘要` +
|
|
485
|
+
`(output=${summary.length}, ok=${allOk}),保持原缓存不变`);
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
HISTORY_SUMMARY_CACHE.set(conversationId, {
|
|
489
|
+
prefix,
|
|
490
|
+
summary,
|
|
491
|
+
coveredLen: targetCover,
|
|
492
|
+
});
|
|
493
|
+
while (HISTORY_SUMMARY_CACHE.size > SUMMARY_CACHE_MAX) {
|
|
494
|
+
const oldest = HISTORY_SUMMARY_CACHE.keys().next();
|
|
495
|
+
if (oldest.done)
|
|
496
|
+
break;
|
|
497
|
+
HISTORY_SUMMARY_CACHE.delete(oldest.value);
|
|
498
|
+
}
|
|
499
|
+
logger.info(`[precompress] 预压缩完成:已覆盖到 ${targetCover} 字符(切点 ${cut}),` +
|
|
500
|
+
`摘要 ${summary.length} 字符,耗时 ${((Date.now() - t0) / 1000).toFixed(0)}s` +
|
|
501
|
+
'(下一轮压缩成本降为 0)');
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
logger.warn(`[precompress] 预压缩失败: ${String(error)}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* 在响应结束后排一个后台预压缩任务(不阻塞客户端)。
|
|
509
|
+
*
|
|
510
|
+
* 只在「本轮**压缩前**的提示词已经超限」时启动:这说明下一轮十有八九也要压缩,
|
|
511
|
+
* 预压缩才有意义;提示词还短时压了也白压(切分口径不同,缓存对不上)。
|
|
512
|
+
*
|
|
513
|
+
* ⚠️ 与原实现的差异:Python 用 `asyncio.create_task` 并维护 `_BG_TASKS` 集合。
|
|
514
|
+
* TS 里 `void promise` 就是「发射后不管」,用 `BG_TASKS` 集合只为控制并发数
|
|
515
|
+
* 与在关服时等待它们结束(见 {@link waitForBackgroundTasks})。
|
|
516
|
+
*/
|
|
517
|
+
export function schedulePrecompress(rawText, conversationId, modelRouter, model, historyDb, requestId) {
|
|
518
|
+
if (!conversationId || !rawText)
|
|
519
|
+
return;
|
|
520
|
+
if (rawText.length <= BROWSER_PROMPT_MAX_LEN)
|
|
521
|
+
return;
|
|
522
|
+
if (BG_TASKS.size >= PRECOMPRESS_MAX_CONCURRENT) {
|
|
523
|
+
logger.info(`[precompress] 已有 ${BG_TASKS.size} 个后台压缩在跑,跳过本轮预压缩`);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const task = precompressHistory(rawText, conversationId, modelRouter, model, historyDb, requestId)
|
|
527
|
+
.catch((error) => {
|
|
528
|
+
logger.warn(`[precompress] 后台任务异常: ${String(error)}`);
|
|
529
|
+
})
|
|
530
|
+
.finally(() => {
|
|
531
|
+
BG_TASKS.delete(task);
|
|
532
|
+
});
|
|
533
|
+
BG_TASKS.add(task);
|
|
534
|
+
logger.info(`[precompress] 已排入后台预压缩(会话 ${conversationId},` +
|
|
535
|
+
`压缩前提示词 ${rawText.length} 字符)`);
|
|
536
|
+
}
|
|
537
|
+
/** 等待所有后台任务结束(优雅关服用)。 */
|
|
538
|
+
export async function waitForBackgroundTasks() {
|
|
539
|
+
const pending = [...BG_TASKS];
|
|
540
|
+
if (pending.length > 0) {
|
|
541
|
+
logger.info(`[precompress] 等待 ${pending.length} 个后台压缩任务结束...`);
|
|
542
|
+
await Promise.allSettled(pending);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/** 清空预压缩相关状态(测试用)。 */
|
|
546
|
+
export function __resetPrecompressState() {
|
|
547
|
+
BG_TASKS.clear();
|
|
548
|
+
HISTORY_SUMMARY_CACHE.clear();
|
|
549
|
+
lastRequestTs = 0;
|
|
550
|
+
}
|
|
551
|
+
//# sourceMappingURL=compress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compress.js","sourceRoot":"","sources":["../../src/routes/compress.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ChatRequest, ChatResponse } from '../providers/base.js';
|
|
2
|
+
import type { HistoryDB } from '../controllers/history-db.js';
|
|
3
|
+
import type { ModelRouter } from '../controllers/router.js';
|
|
4
|
+
import { extractPlatformName } from './helpers.js';
|
|
5
|
+
/**
|
|
6
|
+
* 构造「两轮对话」策略的输入文本。
|
|
7
|
+
*
|
|
8
|
+
* 与展示给客户端的文本**目的不同**:
|
|
9
|
+
* - 展示:只能用 SSE 的最终回答(`type=text`),不能把思考过程泄漏出去;
|
|
10
|
+
* - 恢复工具调用:**信号越多越好**。
|
|
11
|
+
*
|
|
12
|
+
* 实测失败案例:模型把完整工具意图写在**思考**里(`type=think` 有 3339 字符,
|
|
13
|
+
* 例如「我需要:1. 用 list_dir 查看项目结构 并读取关键文件:core/backtest_engine.py…」),
|
|
14
|
+
* 而最终回答只有 77 个字符(没有 `<tool_call>`)。
|
|
15
|
+
* 此时若只把 77 字符的回答交给转换器,必然恢复不出任何工具调用。
|
|
16
|
+
*
|
|
17
|
+
* 因此这里把 SSE 回答、SSE 思考、DOM 拼接文本合并去重后一起交给转换器。
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildReparseInput(parseSource: string, fullResponse: string): string;
|
|
20
|
+
/** 故障切换流的一步:`[响应分片, 平台名]`。 */
|
|
21
|
+
export type FailoverChunk = [ChatResponse, string];
|
|
22
|
+
/**
|
|
23
|
+
* 带跨平台故障切换的流式响应生成器。
|
|
24
|
+
*
|
|
25
|
+
* 策略:
|
|
26
|
+
* - `mix` 模型:按优先级依次尝试各平台,当前平台失败后切换到下一个候选平台。
|
|
27
|
+
* 跨平台切换时从 `history_db` 注入最近 N 条历史作为上下文。
|
|
28
|
+
* - 平台名模型(如 `chatglm`):直接调用对应平台的 Browser Provider。
|
|
29
|
+
*
|
|
30
|
+
* @throws {PlatformUnavailableError} 所有候选平台都失败
|
|
31
|
+
*/
|
|
32
|
+
export declare function streamWithFailover(modelRouter: ModelRouter, model: string, chatRequest: ChatRequest, historyDb: HistoryDB, requestId?: string): AsyncGenerator<FailoverChunk>;
|
|
33
|
+
export { extractPlatformName };
|
|
34
|
+
//# sourceMappingURL=failover.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failover.d.ts","sourceRoot":"","sources":["../../src/routes/failover.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAI5D,OAAO,EACL,mBAAmB,EAIpB,MAAM,cAAc,CAAC;AAOtB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,CAsBnF;AAED,8BAA8B;AAC9B,MAAM,MAAM,aAAa,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AAEnD;;;;;;;;;GASG;AACH,wBAAuB,kBAAkB,CACvC,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,SAAS,EACpB,SAAS,SAAY,GACpB,cAAc,CAAC,aAAa,CAAC,CAoL/B;AAED,OAAO,EAAE,mBAAmB,EAAE,CAAC"}
|