Files
bigwo/test2/server/services/redisClient.js
User 56940676f6 feat(kb): VikingDB纯检索+重排+Redis上下文+全库搜索+别名扩展+KB保护窗口+RAG语气引导
- 新增 kbRetriever.js: VikingDB search_knowledge 纯检索替代 Ark chat/completions, doubao-seed-rerank 重排, RAG payload 语气引导缓解音色差异

- 新增 redisClient.js: Redis 连接管理 + 5轮对话历史 + KB缓存双写

- toolExecutor.js: 产品别名扩展25条, 全库检索topK=25, 检索阈值0.01, 精简 buildDeterministicKnowledgeQuery

- nativeVoiceGateway.js: isPureChitchat扩展, KB保护窗口60s, prequery参数调优

- realtimeDialogRouting.js: resolveReply感知KB保护窗口, fast-path适配raw模式

- app.js: 健康检查新增 redis/reranker/kbRetrievalMode

- 新增测试: alias A/B测试, KB retriever测试, Redis客户端测试, raw模式集成测试
2026-03-26 14:30:32 +08:00

185 lines
4.6 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.

const Redis = require('ioredis');
// ============ 配置 ============
const REDIS_URL = process.env.REDIS_URL || 'redis://127.0.0.1:6379';
const REDIS_PASSWORD = process.env.REDIS_PASSWORD || '';
const REDIS_DB = parseInt(process.env.REDIS_DB) || 0;
const KEY_PREFIX = process.env.REDIS_KEY_PREFIX || 'bigwo:';
const HISTORY_MAX_LEN = 10; // 5轮 × 2条/轮
const HISTORY_TTL_S = 1800; // 30分钟
const KB_CACHE_HIT_TTL_S = 300; // 5分钟
const KB_CACHE_NOHIT_TTL_S = 120; // 2分钟
// ============ 连接管理 ============
let client = null;
let isReady = false;
function createClient() {
if (client) return client;
const opts = {
db: REDIS_DB,
keyPrefix: KEY_PREFIX,
retryStrategy(times) {
if (times > 10) {
console.error('[Redis] max retries reached, giving up');
return null;
}
return Math.min(times * 200, 3000);
},
reconnectOnError(err) {
const targetErrors = ['READONLY', 'ECONNRESET'];
return targetErrors.some(e => err.message.includes(e));
},
lazyConnect: true,
maxRetriesPerRequest: 2,
connectTimeout: 5000,
commandTimeout: 3000,
};
if (REDIS_PASSWORD) {
opts.password = REDIS_PASSWORD;
}
client = new Redis(REDIS_URL, opts);
client.on('ready', () => {
isReady = true;
console.log('[Redis] connected and ready');
});
client.on('error', (err) => {
console.warn('[Redis] error:', err.message);
});
client.on('close', () => {
isReady = false;
console.warn('[Redis] connection closed');
});
client.on('reconnecting', () => {
console.log('[Redis] reconnecting...');
});
client.connect().catch((err) => {
console.warn('[Redis] initial connect failed:', err.message);
});
return client;
}
function getClient() {
if (!client) createClient();
return client;
}
function isAvailable() {
return isReady && client && client.status === 'ready';
}
// ============ 对话历史 ============
const historyKey = (sessionId) => `voice:history:${sessionId}`;
async function pushMessage(sessionId, msg) {
if (!isAvailable()) return false;
try {
const key = historyKey(sessionId);
const value = JSON.stringify({
role: msg.role,
content: msg.content,
source: msg.source || '',
ts: Date.now(),
});
await client.lpush(key, value);
await client.ltrim(key, 0, HISTORY_MAX_LEN - 1);
await client.expire(key, HISTORY_TTL_S);
return true;
} catch (err) {
console.warn('[Redis] pushMessage failed:', err.message);
return false;
}
}
async function getRecentHistory(sessionId, maxRounds = 5) {
if (!isAvailable()) return null;
try {
const key = historyKey(sessionId);
const items = await client.lrange(key, 0, maxRounds * 2 - 1);
if (!items || items.length === 0) return [];
// lpush 是倒序插入lrange 取出的也是最新在前,需要 reverse 恢复时间顺序
return items
.map((item) => {
try { return JSON.parse(item); } catch { return null; }
})
.filter(Boolean)
.reverse();
} catch (err) {
console.warn('[Redis] getRecentHistory failed:', err.message);
return null;
}
}
async function clearSession(sessionId) {
if (!isAvailable()) return false;
try {
await client.del(historyKey(sessionId));
return true;
} catch (err) {
console.warn('[Redis] clearSession failed:', err.message);
return false;
}
}
// ============ KB 缓存 ============
const kbCacheKey = (cacheKey) => `kb_cache:${cacheKey}`;
async function setKbCache(cacheKey, result) {
// 只缓存 hit 结果no-hit 不写入 Redis避免阻止后续 retry
if (!isAvailable() || !result.hit) return false;
try {
const key = kbCacheKey(cacheKey);
await client.set(key, JSON.stringify(result), 'EX', KB_CACHE_HIT_TTL_S);
return true;
} catch (err) {
console.warn('[Redis] setKbCache failed:', err.message);
return false;
}
}
async function getKbCache(cacheKey) {
if (!isAvailable()) return null;
try {
const key = kbCacheKey(cacheKey);
const data = await client.get(key);
if (!data) return null;
return JSON.parse(data);
} catch (err) {
console.warn('[Redis] getKbCache failed:', err.message);
return null;
}
}
// ============ 优雅关闭 ============
async function disconnect() {
if (client) {
try {
await client.quit();
} catch {
client.disconnect();
}
client = null;
isReady = false;
console.log('[Redis] disconnected');
}
}
module.exports = {
createClient,
getClient,
isAvailable,
pushMessage,
getRecentHistory,
clearSession,
setKbCache,
getKbCache,
disconnect,
};