guanghulab/src/brain/context-trimmer.js
2026-05-10 13:12:44 +08:00

79 lines
2.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 };