diff --git a/backend/app/config.py b/backend/app/config.py index 5c49d92..f353cc6 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -82,6 +82,13 @@ class Settings: S2S_BUILD_RETRIES = int(_env("DPM_S2S_BUILD_RETRIES", "4")) S2S_BUILD_RETRY_DELAY = float(_env("DPM_S2S_BUILD_RETRY_DELAY", "10")) S2S_LOG_LEVEL = _env("DPM_S2S_LOG_LEVEL", "INFO").upper() + # 语音助手基础提示词(.env 设 DPM_S2S_INSTRUCTIONS 可覆盖;知识库内容由 rag 自动拼接) + S2S_INSTRUCTIONS = _env( + "DPM_S2S_INSTRUCTIONS", + "你是 PineSound 园区智能语音助手。可用工具:get_park_overview(园区实时数据)、" + "query_companies(企业名录)、control_display(大屏控制)、get_time(时间)。" + "涉及园区数据/企业/大屏控制时务必调用工具获取准确信息。请用简洁专业的中文回答,不超过三句话。", + ) diff --git a/backend/app/rag.py b/backend/app/rag.py index 815920b..bb1e661 100644 --- a/backend/app/rag.py +++ b/backend/app/rag.py @@ -100,3 +100,16 @@ def brief(max_chars: int = 1200) -> str: out.append(c) total += len(c) return "\n\n".join(out) + + +def build_instructions() -> str: + """语音助手的完整基础提示词 = 配置提示词 + 园区知识库摘要(服务端统一组装)""" + from .config import settings + base = settings.S2S_INSTRUCTIONS.strip() + try: + kb = brief() + if kb: + base += f"\n\n【园区知识库】\n{kb}" + except Exception as e: # noqa: BLE001 + log.warning("rag: 知识库拼接失败(仅用基础提示词): %s", e) + return base diff --git a/backend/app/routers.py b/backend/app/routers.py index c34e33e..e839428 100644 --- a/backend/app/routers.py +++ b/backend/app/routers.py @@ -465,3 +465,13 @@ def kb_retrieve(q: str = "", top_k: int = 3): except Exception as e: # noqa: BLE001 log.warning("kb retrieve 失败: %s", e) return {"ok": False, "error": str(e)} + +@router.get("/api/s2s/instructions") +def s2s_instructions(): + """语音助手基础提示词(服务端统一组装:配置提示词 + 园区知识库)""" + try: + from .rag import build_instructions + return {"ok": True, "instructions": build_instructions()} + except Exception as e: # noqa: BLE001 + log.warning("s2s instructions 构建失败: %s", e) + return {"ok": False, "error": str(e)} diff --git a/src/pages/VoiceAssistant.jsx b/src/pages/VoiceAssistant.jsx index 679ae83..9a4c305 100644 --- a/src/pages/VoiceAssistant.jsx +++ b/src/pages/VoiceAssistant.jsx @@ -16,7 +16,9 @@ import './voice-original.css'; import './voice-overrides.css'; const VOICE_URL = window.__DPM_VOICE_WS__ || `ws://${window.location.hostname}:8765/v1/realtime`; -const DEFAULT_INSTRUCTIONS = '你是 PineSound 园区智能语音助手。可用工具:get_park_overview(园区实时数据)、query_companies(企业名录)、control_display(大屏控制)、get_time(时间)。涉及园区数据/企业/大屏控制时务必调用工具获取准确信息。请用简洁专业的中文回答,不超过三句话。'; +// 基础提示词由后端统一管理(GET /api/s2s/instructions:配置提示词 + 园区知识库), +// 前端不硬编码;此处仅为后端不可用时的兜底 +const FALLBACK_INSTRUCTIONS = '你是 PineSound 园区智能语音助手,请用简洁专业的中文回答,不超过三句话。'; const DEFAULT_VOICE = 'Cherry'; const DEFAULT_GATE_DB = -50; @@ -316,15 +318,13 @@ export default function VoiceAssistant() { if (!audioCtxRef.current) audioCtxRef.current = new AudioContext(); if (audioCtxRef.current.state === 'suspended') await audioCtxRef.current.resume(); - // 知识库:拉取园区知识摘要拼进 instructions(静态知识一次注入;失败不影响对话) - let instructions = DEFAULT_INSTRUCTIONS; + // 基础提示词:从后端拉取(配置 + 园区知识库已由服务端组装);失败用极简兜底 + let instructions = FALLBACK_INSTRUCTIONS; try { - const kbRes = await fetch(`${API_BASE}/api/kb/brief`, { cache: 'no-store' }); - const kbData = await kbRes.json(); - if (kbData?.ok && kbData.brief) { - instructions = `${DEFAULT_INSTRUCTIONS}\n\n【园区知识库】\n${kbData.brief}`; - } - } catch { /* 知识库不可用不影响对话 */ } + const insRes = await fetch(`${API_BASE}/api/s2s/instructions`, { cache: 'no-store' }); + const insData = await insRes.json(); + if (insData?.ok && insData.instructions) instructions = insData.instructions; + } catch { /* 后端不可用不影响对话 */ } const client = new S2sWsRealtimeClient({ directUrl: VOICE_URL,