jjf123 / src /utils /thoughtSignatureCache.js
jjf1999's picture
Upload 138 files
ddce7e8 verified
Raw
History Blame Contribute Delete
8.75 kB
// 签名缓存(文件存储版本):
// - 按 model 维度缓存"最近 N 个"签名(环形队列)
// - 签名和思考内容绑定存储:{ signature, content }
// - 自动先进先出:超过容量自动挤掉最旧的
// - 存储在 data/signature-cache/ 目录下
import fs from 'fs';
import path from 'path';
import config from '../config/config.js';
import log from './logger.js';
// 缓存目录路径
const CACHE_DIR = path.join(process.cwd(), 'data', 'signature-cache');
// 上限:每个模型保留的签名数量
const MAX_SIGNATURES_PER_MODEL = 3;
/**
* 确保缓存目录存在
*/
function ensureCacheDir() {
if (!fs.existsSync(CACHE_DIR)) {
fs.mkdirSync(CACHE_DIR, { recursive: true });
}
}
/**
* 生成模型的缓存文件名(处理特殊字符)
* @param {string} model - 模型名称
* @returns {string} 安全的文件名
*/
function makeModelKey(model) {
if (!model) return null;
const raw = String(model);
// 生图模型会带分辨率后缀(例如 `-4K` / `-2K`),但实际请求时会被剥离为基础模型名。
// 为避免缓存 miss,这里统一按"基础模型名"缓存。
const baseModel = raw.replace(/-(?:1k|2k|4k|8k)$/i, '');
// 将不安全的文件名字符替换为下划线
return baseModel.replace(/[<>:"/\\|?*]/g, '_');
}
/**
* 获取模型缓存文件路径
* @param {string} modelKey - 模型 key
* @returns {string} 文件路径
*/
function getCacheFilePath(modelKey) {
return path.join(CACHE_DIR, `${modelKey}.json`);
}
/**
* 从文件读取模型的签名缓存
* @param {string} modelKey - 模型 key
* @returns {Array} 签名数组 [{ signature, content }, ...]
*/
function readModelCache(modelKey) {
if (!modelKey) return [];
try {
const filePath = getCacheFilePath(modelKey);
if (!fs.existsSync(filePath)) return [];
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
return Array.isArray(data.signatures) ? data.signatures : [];
} catch (e) {
log.warn(`读取签名缓存失败 (${modelKey}):`, e?.message || e);
return [];
}
}
/**
* 将签名缓存写入文件
* @param {string} modelKey - 模型 key
* @param {Array} signatures - 签名数组
*/
function writeModelCache(modelKey, signatures) {
if (!modelKey) return;
try {
ensureCacheDir();
const filePath = getCacheFilePath(modelKey);
const data = {
model: modelKey,
signatures: signatures,
lastModified: Date.now()
};
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
} catch (e) {
log.warn(`写入签名缓存失败 (${modelKey}):`, e?.message || e);
}
}
/**
* 获取最新的签名条目
* @param {string} modelKey - 模型 key
* @returns {{ signature: string, content: string } | null}
*/
function getLatestEntry(modelKey) {
if (!modelKey) return null;
const signatures = readModelCache(modelKey);
if (signatures.length === 0) return null;
return signatures[signatures.length - 1] || null;
}
/**
* 添加新签名条目(FIFO 环形队列)
* @param {string} modelKey - 模型 key
* @param {Object} entry - 签名条目 { signature, content }
*/
function pushEntry(modelKey, entry) {
if (!modelKey || !entry || !entry.signature) return;
const signatures = readModelCache(modelKey);
// 去重:避免同一个签名重复入队
if (signatures.length > 0 && signatures[signatures.length - 1]?.signature === entry.signature) {
return;
}
// 添加新条目
signatures.push(entry);
// 超过容量时移除最旧的
while (signatures.length > MAX_SIGNATURES_PER_MODEL) {
signatures.shift();
}
writeModelCache(modelKey, signatures);
}
/**
* 判断是否应该缓存签名
* @param {Object} options - 选项
* @param {boolean} options.hasTools - 是否使用了工具
* @param {boolean} options.isImageModel - 是否是图像模型
* @returns {boolean}
*/
export function shouldCacheSignature({ hasTools = false, isImageModel = false } = {}) {
// 全部缓存签名开启时,任何时候都缓存
if (config.cacheAllSignatures) return true;
// 工具签名开启且使用了工具
if (config.cacheToolSignatures && hasTools) return true;
// 图像签名开启且是图像模型
if (config.cacheImageSignatures && isImageModel) return true;
return false;
}
/**
* 判断是否是图像模型
* @param {string} model - 模型名称
* @returns {boolean}
*/
export function isImageModel(model) {
if (!model) return false;
const lowerModel = model.toLowerCase();
// 图像模型通常包含 'image' 关键字
return lowerModel.includes('image');
}
/**
* 处理思考内容(根据 cacheThinking 配置)
* @param {string} content - 原始思考内容
* @returns {string} 处理后的内容
*/
function processThinkingContent(content) {
if (!config.cacheThinking) {
return ' '; // 不缓存思考内容时用空格替代
}
return content || ' ';
}
/**
* 设置签名和内容(通用接口)
* @param {string} sessionId - 会话 ID(保留兼容,不参与缓存 key)
* @param {string} model - 模型名称
* @param {string} signature - 签名
* @param {string} content - 思考内容(可选)
* @param {Object} options - 选项
* @param {boolean} options.hasTools - 是否使用了工具
* @param {boolean} options.isImageModel - 是否是图像模型
*/
export function setSignature(sessionId, model, signature, content = ' ', options = {}) {
if (!signature || !model) return;
// 判断是否应该缓存
const isImage = options.isImageModel ?? isImageModel(model);
const hasTools = options.hasTools ?? false;
if (!shouldCacheSignature({ hasTools, isImageModel: isImage })) {
return; // 不符合缓存条件
}
const processedContent = processThinkingContent(content);
pushEntry(makeModelKey(model), { signature, content: processedContent });
}
/**
* 获取签名和内容
* @param {string} sessionId - 会话 ID
* @param {string} model - 模型名称
* @param {Object} options - 选项
* @param {boolean} options.hasTools - 是否使用了工具
* @returns {{ signature: string, content: string } | null}
*/
export function getSignature(sessionId, model, options = {}) {
if (!model) return null;
const entry = getLatestEntry(makeModelKey(model));
if (!entry) return null;
// 根据 cacheThinking 配置处理返回的内容
return {
signature: entry.signature,
content: config.cacheThinking ? entry.content : ' '
};
}
// ========== 兼容旧 API ==========
/**
* 设置思维链签名和内容(兼容旧 API)
* @param {string} sessionId - 会话 ID
* @param {string} model - 模型名称
* @param {string} signature - 签名
* @param {string} content - 思考内容
* @param {Object} options - 选项
*/
export function setReasoningSignature(sessionId, model, signature, content = ' ', options = {}) {
setSignature(sessionId, model, signature, content, options);
}
/**
* 获取思维链签名和内容(兼容旧 API)
* @param {string} sessionId - 会话 ID
* @param {string} model - 模型名称
* @param {Object} options - 选项
* @returns {{ signature: string, content: string } | null}
*/
export function getReasoningSignature(sessionId, model, options = {}) {
return getSignature(sessionId, model, options);
}
/**
* 设置工具签名和内容(兼容旧 API,实际上现在统一存储)
* @param {string} sessionId - 会话 ID
* @param {string} model - 模型名称
* @param {string} signature - 签名
* @param {string} content - 思考内容
* @param {Object} options - 选项
*/
export function setToolSignature(sessionId, model, signature, content = ' ', options = {}) {
// 工具签名默认 hasTools = true
setSignature(sessionId, model, signature, content, { ...options, hasTools: true });
}
/**
* 获取工具签名和内容(兼容旧 API)
* @param {string} sessionId - 会话 ID
* @param {string} model - 模型名称
* @param {Object} options - 选项
* @returns {{ signature: string, content: string } | null}
*/
export function getToolSignature(sessionId, model, options = {}) {
return getSignature(sessionId, model, options);
}
/**
* 清理所有签名缓存(删除缓存目录下的所有文件)
*/
export function clearThoughtSignatureCaches() {
try {
if (fs.existsSync(CACHE_DIR)) {
const files = fs.readdirSync(CACHE_DIR);
for (const file of files) {
if (file.endsWith('.json')) {
fs.unlinkSync(path.join(CACHE_DIR, file));
}
}
}
log.info('签名缓存已清除');
} catch (e) {
log.warn('清除签名缓存失败:', e?.message || e);
}
}
// 初始化时确保目录存在
ensureCacheDir();