Files
bigwo/test2/server/services/assistantProfileService.js
User 9567eb7358 feat(server): KB prompt优化、字幕修复、S2S重连、助手配置API
- assistantProfileConfig: KB answer prompt改为分层策略(严格产品信息+灵活常识补充)
- nativeVoiceGateway: S2S upstream自动重连(最多50次)、event 351字幕debounce(800ms取最长文本)
- toolExecutor: 确定性query改写增强、KB查询传递session上下文
- contextKeywordTracker: 支持KB话题记忆优先enrichment
- contentSafeGuard: 新增品牌安全内容过滤服务
- assistantProfileService: 新增助手配置CRUD服务
- routes/assistantProfile: 新增助手配置API路由
- knowledgeKeywords: 扩展KB关键词词典
- fastAsrCorrector: ASR纠错规则更新
- tests/: KB prompt测试、保护窗口测试、Viking性能测试
- docs/: 助手配置API文档、系统提示词目录
2026-03-24 17:19:36 +08:00

179 lines
5.3 KiB
JavaScript

const axios = require('axios');
const {
DEFAULT_VOICE_ASSISTANT_PROFILE,
resolveAssistantProfile,
} = require('./assistantProfileConfig');
const assistantProfileCache = new Map();
function getAssistantProfileApiUrl() {
const value = String(process.env.ASSISTANT_PROFILE_API_URL || '').trim();
return value && !value.startsWith('your_') ? value : '';
}
function getAssistantProfileApiMethod() {
const method = String(process.env.ASSISTANT_PROFILE_API_METHOD || 'GET').trim().toUpperCase();
return method === 'POST' ? 'POST' : 'GET';
}
function getAssistantProfileTimeoutMs() {
const value = Number(process.env.ASSISTANT_PROFILE_API_TIMEOUT_MS || 5000);
return Number.isFinite(value) && value > 0 ? value : 5000;
}
function getAssistantProfileCacheTtlMs() {
const value = Number(process.env.ASSISTANT_PROFILE_CACHE_TTL_MS || 60000);
return Number.isFinite(value) && value >= 0 ? value : 60000;
}
function getAssistantProfileCacheKey(userId = null) {
return String(userId || 'global').trim() || 'global';
}
function parseAssistantProfileHeaders() {
const raw = String(process.env.ASSISTANT_PROFILE_API_HEADERS || '').trim();
if (!raw) return {};
try {
const parsed = JSON.parse(raw);
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
return {};
}
return Object.fromEntries(
Object.entries(parsed)
.map(([key, value]) => [String(key || '').trim(), String(value || '').trim()])
.filter(([key, value]) => key && value)
);
} catch (error) {
console.warn('[AssistantProfile] parse headers failed:', error.message);
return {};
}
}
function pickAssistantProfilePayload(payload) {
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
return {};
}
const candidates = [
payload.assistantProfile,
payload.profile,
payload.data?.assistantProfile,
payload.data?.profile,
payload.data,
payload,
];
for (const candidate of candidates) {
if (candidate && typeof candidate === 'object' && !Array.isArray(candidate)) {
return candidate;
}
}
return {};
}
function sanitizeAssistantProfilePayload(payload) {
const source = pickAssistantProfilePayload(payload);
return {
documents: source.documents,
email: source.email,
nickname: source.nickname,
wxl: source.wxl,
mobile: source.mobile,
wx_code: source.wx_code,
intro: source.intro,
sign: source.sign,
story: source.story,
};
}
async function fetchRemoteAssistantProfile(userId = null) {
const url = getAssistantProfileApiUrl();
if (!url) {
return {
profile: resolveAssistantProfile(),
source: 'default',
cached: false,
fetchedAt: Date.now(),
configured: false,
error: null,
};
}
const headers = {
Accept: 'application/json',
...parseAssistantProfileHeaders(),
};
const token = String(process.env.ASSISTANT_PROFILE_API_TOKEN || '').trim();
if (token) {
headers.Authorization = `Bearer ${token}`;
}
const method = getAssistantProfileApiMethod();
const timeout = getAssistantProfileTimeoutMs();
const params = userId ? { userId } : undefined;
const response = method === 'POST'
? await axios.post(url, userId ? { userId } : {}, { headers, timeout })
: await axios.get(url, { headers, timeout, params });
const profile = resolveAssistantProfile(sanitizeAssistantProfilePayload(response.data));
return {
profile,
source: 'remote_api',
cached: false,
fetchedAt: Date.now(),
configured: true,
error: null,
};
}
async function getAssistantProfile(options = {}) {
const userId = String(options.userId || '').trim() || null;
const forceRefresh = !!options.forceRefresh;
const overrides = options.overrides && typeof options.overrides === 'object' ? options.overrides : null;
const cacheKey = getAssistantProfileCacheKey(userId);
const ttlMs = getAssistantProfileCacheTtlMs();
const cached = assistantProfileCache.get(cacheKey);
if (!forceRefresh && cached && (Date.now() - cached.fetchedAt) <= ttlMs) {
return {
profile: resolveAssistantProfile({ ...cached.profile, ...(overrides || {}) }),
source: cached.source,
cached: true,
fetchedAt: cached.fetchedAt,
configured: cached.configured,
error: null,
};
}
try {
const fetched = await fetchRemoteAssistantProfile(userId);
assistantProfileCache.set(cacheKey, fetched);
return {
profile: resolveAssistantProfile({ ...fetched.profile, ...(overrides || {}) }),
source: fetched.source,
cached: false,
fetchedAt: fetched.fetchedAt,
configured: fetched.configured,
error: null,
};
} catch (error) {
const fallback = cached?.profile || DEFAULT_VOICE_ASSISTANT_PROFILE;
return {
profile: resolveAssistantProfile({ ...fallback, ...(overrides || {}) }),
source: cached ? 'cache_fallback' : 'default_fallback',
cached: !!cached,
fetchedAt: cached?.fetchedAt || null,
configured: !!getAssistantProfileApiUrl(),
error: error.message,
};
}
}
function clearAssistantProfileCache(userId = null) {
if (userId == null || String(userId).trim() === '') {
assistantProfileCache.clear();
return;
}
assistantProfileCache.delete(getAssistantProfileCacheKey(userId));
}
module.exports = {
getAssistantProfile,
clearAssistantProfileCache,
};