79 lines
2.3 KiB
JavaScript
79 lines
2.3 KiB
JavaScript
// src/brain/context-trimmer.js
|
||
// 上下文裁剪器 — 从前端迁出的核心脑逻辑
|
||
// 职责:基于滑动窗口策略,在 token 预算内裁剪消息历史
|
||
|
||
'use strict';
|
||
|
||
const CONTEXT_CONFIG = {
|
||
maxTokens: 200000, // 编号登录用户 200k
|
||
maxTokensGuest: 32000, // 访客 32k
|
||
systemPromptReserve: 8000, // 系统提示词预留
|
||
overflowStrategy: 'sliding-window',
|
||
};
|
||
|
||
/**
|
||
* 估算文本 token 数
|
||
* CJK 字符约 1.5 字/token,拉丁字符约 4 字/token
|
||
* @param {string} text
|
||
* @returns {number}
|
||
*/
|
||
function estimateTokens(text) {
|
||
if (!text) return 0;
|
||
const s = String(text);
|
||
let cjk = 0, lat = 0;
|
||
for (let i = 0; i < s.length; i++) {
|
||
const c = s.charCodeAt(i);
|
||
if ((c >= 0x4E00 && c <= 0x9FFF) || (c >= 0x3000 && c <= 0x30FF) || (c >= 0xAC00 && c <= 0xD7AF)) {
|
||
cjk++;
|
||
} else {
|
||
lat++;
|
||
}
|
||
}
|
||
return Math.ceil(cjk / 1.5 + lat / 4);
|
||
}
|
||
|
||
/**
|
||
* 滑动窗口裁剪消息
|
||
* @param {Array} systemMessages - 系统消息数组 [{role:'system', content:'...'}]
|
||
* @param {Array} messages - 用户/助手消息历史
|
||
* @param {Object} opts
|
||
* @param {boolean} opts.isGuest - 是否为访客
|
||
* @param {number} opts.contextBudget - 模型路由建议的上下文窗口(可覆盖默认值)
|
||
* @returns {{ messages: Array, trimmed: number, totalTokens: number }}
|
||
*/
|
||
function trimMessages(systemMessages, messages, opts = {}) {
|
||
const { isGuest = false, contextBudget = 0 } = opts;
|
||
|
||
// 确定 token 上限
|
||
let limit;
|
||
if (contextBudget > 0) {
|
||
limit = contextBudget;
|
||
} else {
|
||
limit = isGuest ? CONTEXT_CONFIG.maxTokensGuest : CONTEXT_CONFIG.maxTokens;
|
||
}
|
||
|
||
const sysTokens = systemMessages.reduce((n, m) => n + estimateTokens(m.content), 0);
|
||
const reserve = Math.max(sysTokens, CONTEXT_CONFIG.systemPromptReserve);
|
||
const budget = limit - reserve;
|
||
|
||
// 从最新消息向前累加,保留预算内的消息
|
||
const kept = [];
|
||
let used = 0;
|
||
for (let i = messages.length - 1; i >= 0; i--) {
|
||
const t = estimateTokens(messages[i].content);
|
||
if (used + t > budget && kept.length > 0) break;
|
||
kept.unshift(messages[i]);
|
||
used += t;
|
||
}
|
||
|
||
const trimmed = messages.length - kept.length;
|
||
|
||
return {
|
||
messages: [...systemMessages, ...kept],
|
||
trimmed,
|
||
totalTokens: reserve + used,
|
||
};
|
||
}
|
||
|
||
module.exports = { trimMessages, estimateTokens, CONTEXT_CONFIG };
|