173 lines
6.2 KiB
JavaScript
173 lines
6.2 KiB
JavaScript
const { v4: uuidv4 } = require('uuid');
|
||
|
||
class VoiceChatConfigBuilder {
|
||
/**
|
||
* 构建 StartVoiceChat 的完整配置(S2S 端到端语音大模型 + LLM 混合编排)
|
||
* OutputMode=1: 混合模式,S2S 处理普通对话,LLM 处理工具调用
|
||
*/
|
||
static build(options) {
|
||
const {
|
||
roomId,
|
||
taskId,
|
||
userId,
|
||
botName = '小智',
|
||
systemRole = '你是一个友善的智能助手。',
|
||
speakingStyle = '请使用温和、清晰的口吻。',
|
||
modelVersion = '1.2.1.0',
|
||
speaker = 'zh_female_vv_jupiter_bigtts',
|
||
tools = [],
|
||
llmSystemPrompt = '',
|
||
enableWebSearch = false,
|
||
vadEndMs = 1200,
|
||
chatHistory = [],
|
||
} = options;
|
||
|
||
const botUserId = `bot_${uuidv4().slice(0, 8)}`;
|
||
|
||
const providerParams = {
|
||
app: {
|
||
appid: process.env.VOLC_S2S_APP_ID,
|
||
token: process.env.VOLC_S2S_TOKEN,
|
||
},
|
||
dialog: this._buildDialogConfig(modelVersion, botName, systemRole, speakingStyle, enableWebSearch, chatHistory),
|
||
tts: {
|
||
speaker: speaker,
|
||
},
|
||
asr: {
|
||
extra: {
|
||
enable_custom_vad: true,
|
||
end_smooth_window_ms: vadEndMs,
|
||
},
|
||
},
|
||
};
|
||
|
||
// === 调试模式:纯 S2S(OutputMode=0),排除 LLM 干扰 ===
|
||
// ARK 端点已配置正确,启用混合编排模式
|
||
const DEBUG_PURE_S2S = false;
|
||
|
||
const llmConfig = {
|
||
Mode: 'ArkV3',
|
||
EndPointId: process.env.VOLC_ARK_ENDPOINT_ID,
|
||
MaxTokens: 1024,
|
||
Temperature: 0.1,
|
||
TopP: 0.3,
|
||
SystemMessages: [llmSystemPrompt || this._buildDefaultLLMPrompt(tools)],
|
||
HistoryLength: 10,
|
||
ThinkingType: 'disabled',
|
||
};
|
||
if (tools.length > 0) {
|
||
llmConfig.Tools = tools;
|
||
}
|
||
|
||
// 混合模式:通过 UserPrompts 传入聊天历史作为上下文(官方推荐方式)
|
||
if (chatHistory && chatHistory.length > 0 && !DEBUG_PURE_S2S) {
|
||
const userPrompts = chatHistory.slice(-10).map(m => ({
|
||
Role: m.role === 'user' ? 'user' : 'assistant',
|
||
Content: m.content,
|
||
}));
|
||
llmConfig.UserPrompts = userPrompts;
|
||
console.log(`[VoiceChatConfig] Injected ${userPrompts.length} UserPrompts into LLMConfig`);
|
||
}
|
||
|
||
const config = {
|
||
AppId: process.env.VOLC_RTC_APP_ID,
|
||
RoomId: roomId,
|
||
TaskId: taskId,
|
||
AgentConfig: {
|
||
TargetUserId: [userId],
|
||
WelcomeMessage: `你好,我是${botName},有什么需要帮忙的吗?`,
|
||
UserId: botUserId,
|
||
EnableConversationStateCallback: true,
|
||
},
|
||
Config: {
|
||
S2SConfig: {
|
||
Provider: 'volcano',
|
||
OutputMode: DEBUG_PURE_S2S ? 0 : 1,
|
||
ProviderParams: providerParams,
|
||
},
|
||
// 注意:S2S 端到端模式下不需要独立 TTSConfig
|
||
// ExternalTextToSpeech 在 S2S 模式下不产生音频,只用 Command:function
|
||
SubtitleConfig: {
|
||
SubtitleMode: 1,
|
||
},
|
||
InterruptMode: 0,
|
||
},
|
||
};
|
||
|
||
// 混合模式才需要 LLMConfig
|
||
if (!DEBUG_PURE_S2S) {
|
||
config.Config.LLMConfig = llmConfig;
|
||
|
||
// Function Calling 回调配置:RTC 服务通过此 URL 发送 tool call 请求
|
||
if (tools.length > 0) {
|
||
const serverUrl = process.env.FC_SERVER_URL || 'https://demo.tensorgrove.com.cn/api/voice/fc_callback';
|
||
config.Config.FunctionCallingConfig = {
|
||
ServerMessageUrl: serverUrl,
|
||
ServerMessageSignature: process.env.FC_SIGNATURE || 'default_signature',
|
||
};
|
||
console.log(`[VoiceChatConfig] FunctionCallingConfig enabled, URL: ${serverUrl}`);
|
||
}
|
||
}
|
||
|
||
console.log('[VoiceChatConfig] DEBUG_PURE_S2S:', DEBUG_PURE_S2S);
|
||
console.log('[VoiceChatConfig] OutputMode:', config.Config.S2SConfig.OutputMode);
|
||
console.log('[VoiceChatConfig] ProviderParams type:', typeof config.Config.S2SConfig.ProviderParams);
|
||
console.log('[VoiceChatConfig] S2S AppId:', providerParams.app.appid);
|
||
console.log('[VoiceChatConfig] S2S Token:', providerParams.app.token ? '***set***' : '***MISSING***');
|
||
|
||
return { config, botUserId };
|
||
}
|
||
|
||
static _buildDialogConfig(modelVersion, botName, systemRole, speakingStyle, enableWebSearch, chatHistory = []) {
|
||
const isOSeries = modelVersion === 'O' || modelVersion.startsWith('1.');
|
||
const dialog = {
|
||
extra: { model: modelVersion },
|
||
};
|
||
|
||
// 如果有文字聊天历史,将其追加到 system_role 作为上下文
|
||
let fullSystemRole = systemRole;
|
||
if (chatHistory && chatHistory.length > 0) {
|
||
const historyText = chatHistory
|
||
.slice(-10)
|
||
.map(m => `${m.role === 'user' ? '用户' : '助手'}:${m.content}`)
|
||
.join('\n');
|
||
fullSystemRole += `\n\n## 之前的对话记录(请延续此上下文)\n${historyText}`;
|
||
console.log(`[VoiceChatConfig] Injected ${chatHistory.length} chat history messages into system_role`);
|
||
}
|
||
|
||
if (isOSeries) {
|
||
dialog.bot_name = botName;
|
||
dialog.system_role = fullSystemRole;
|
||
dialog.speaking_style = speakingStyle;
|
||
} else {
|
||
dialog.character_manifest = `${fullSystemRole}\n你的名字是${botName}。${speakingStyle}`;
|
||
}
|
||
|
||
if (enableWebSearch && process.env.VOLC_WEBSEARCH_API_KEY) {
|
||
dialog.extra.enable_volc_websearch = true;
|
||
dialog.extra.volc_websearch_api_key = process.env.VOLC_WEBSEARCH_API_KEY;
|
||
dialog.extra.volc_websearch_type = 'web_summary';
|
||
dialog.extra.volc_websearch_no_result_message = '抱歉,我没有查到相关信息。';
|
||
}
|
||
|
||
return dialog;
|
||
}
|
||
|
||
static _buildDefaultLLMPrompt(tools) {
|
||
const toolNames = tools.map((t) => t.function?.name).filter(Boolean);
|
||
if (toolNames.length === 0) {
|
||
return '你是一个智能助手。对于所有问题直接回答即可。';
|
||
}
|
||
return `你是一个企业智能客服助手。你可以使用以下工具:${toolNames.join('、')}。
|
||
|
||
## 最高优先级规则
|
||
1. 每次用户提问,你**必须**先调用 search_knowledge 工具查询知识库
|
||
2. 收到工具返回的知识库内容后,你**必须完整、详细地朗读**知识库返回的内容给用户
|
||
3. 不要省略、总结或缩写知识库的内容,要逐字朗读
|
||
4. 如果知识库没有相关内容,再用你自己的知识简洁回答
|
||
5. 如果知识库返回"未找到相关信息",直接告诉用户并提供建议`;
|
||
}
|
||
}
|
||
|
||
module.exports = VoiceChatConfigBuilder;
|