实时语音模拟面试
Published in:2025-11-30 |
Words: 1.4k | Reading time: 5min | reading:

GitHub 仓库: https://github.com/caozhaoqi/jd_agent

摘要:在实现了基于 JD 生成面试指南(v1.0)后,我们并未止步。本文记录了 jd_agent 项目的重大升级:引入 模拟面试(Mock Interview) 模式,并集成了 ASR(语音识别)TTS(语音合成) 技术。我们通过 FastAPI 的流式响应、Next.js 的音频队列管理以及 双模型配置策略,打造了一个能够“听懂你、并开口提问”的 AI 面试官。


1. 架构升级:从单向生成到双向交互

v1.0 版本主要解决的是“信息提取”问题(JD -> 报告)。
v2.0 版本则致力于解决“实战演练”问题(用户 <-> AI 面试官)。为此,我们在架构中引入了 音频层 (Audio Layer)会话状态管理

1.1 系统架构图 (v2.0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
graph TD
User((用户))

subgraph Frontend [Next.js Client]
Microphone -->|Audio Blob| ASR_Handler
Chat_UI -->|Text Stream| SSE_Reader
SSE_Reader -->|Sentence Buffer| Audio_Queue
Audio_Queue -->|Text Segments| TTS_Player
TTS_Player --> Speaker
end

subgraph Backend [FastAPI Server]
ASR_Handler -->|POST /audio/transcribe| Whisper_Service
Chat_UI -->|POST /chat/stream| Chat_Service
TTS_Player -->|POST /audio/tts| TTS_Service

subgraph Services
Chat_Service[LangChain Chat Flow]
Whisper_Service[ASR Adapter]
TTS_Service[TTS Adapter]
end

subgraph Model_Layer [Model Factory]
Chat_Service -->|Text Generation| DeepSeek_V3
Whisper_Service & TTS_Service -->|Audio Processing| SiliconFlow/OpenAI
end
end

2. 核心功能实现细节

2.1 模拟面试与流式对话 (Streaming Chat)

为了模拟真实的面试场景,我们摒弃了“生成完毕再一次性返回”的模式,全面拥抱 SSE (Server-Sent Events)

  • 后端实现:使用 LangChain.astream() 方法配合 FastAPI 的 StreamingResponse
  • Prompt 设计:根据 Session 标题动态切换 System Prompt。如果检测到是面试模式,AI 变身为“严厉的面试官”,每次只问一个问题,并根据候选人的回答进行追问。
1
2
3
4
5
6
7
8
9
10
11
12
# app/api/endpoints.py
@router.post("/chat/stream")
async def stream_chat(req: ChatRequest):
# 动态注入人设
system_prompt = "你是一名专业的面试官。请根据求职者的回答进行追问..."

async def generate():
async for chunk in chain.astream(messages):
yield f"data: {chunk}\n\n"
yield "data: [DONE]\n\n"

return StreamingResponse(generate(), media_type="text/event-stream")

2.2 语音交互闭环 (The Voice Loop)

这是本次升级的技术高地。我们实现了一个完整的语音交互循环:录音 -> ASR -> LLM 思考 -> TTS 朗读

A. ASR (听)

前端使用 react-media-recorder 捕获音频 Blob,后端对接 OpenAI 兼容接口(如 SiliconFlow 的 SenseVoiceSmall,识别速度极快)。

B. TTS (说) - 关键优化:音频队列 (Audio Queue)

为了解决“AI 说话卡顿”或“多重语音重叠”的问题,我们在前端实现了一个音频播放队列

痛点:AI 生成速度很快,如果每收到一段文字就请求播放,声音会重叠。如果等全部生成完再播放,用户等待时间太长。
方案

  1. 分句检测:前端监听 SSE 流,利用标点符号(。?!)切分句子。
  2. 入队:切分好的句子推入 audioQueue
  3. 串行播放processAudioQueue 递归函数确保上一句播完才请求下一句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// hooks/useAudioQueue.ts
const processAudioQueue = async () => {
if (isPlayingRef.current || queue.length === 0) return;

isPlayingRef.current = true;
const text = queue.shift();

// 请求 TTS API 并播放
const audio = new Audio(tts_url);
audio.onended = () => {
isPlayingRef.current = false;
processAudioQueue(); // 递归处理下一句
};
await audio.play();
};

3. 基础设施与配置策略 (Infrastructure)

在引入语音功能时,我们遇到了一个典型的工程问题:DeepSeek 很强,但它不支持音频

为了兼顾智能(文本)功能(语音),我们设计了双模型配置策略

3.1 分离配置 (.env & config.py)

我们将 LLM 的配置与 Audio 的配置完全解耦,实现了“大脑”用 DeepSeek,“嘴巴和耳朵”用 OpenAI/SiliconFlow。

1
2
3
4
5
6
7
8
9
10
# app/core/config.py
class Settings(BaseSettings):
# 大脑:DeepSeek V3 (高智商,低成本)
OPENAI_API_KEY: str
OPENAI_API_BASE: str = "https://api.deepseek.com"

# 五官:SiliconFlow / OpenAI (处理语音)
AUDIO_API_KEY: Optional[str]
AUDIO_API_BASE: Optional[str] = "https://api.siliconflow.cn/v1"
ASR_MODEL: str = "FunAudioLLM/SenseVoiceSmall"

这种设计使得系统极具灵活性,可以在不修改代码的情况下,随意更换底层的语音服务商。


4. 前端工程化踩坑记录 (Troubleshooting)

在 Next.js + React 的开发过程中,我们解决了几个棘手的 Bug。

坑位 1:Worker is not defined

  • 现象:引入 react-media-recorder 后,页面刷新直接报错。
  • 原因:Next.js 默认进行服务端渲染 (SSR),而录音库依赖浏览器特有的 Worker API,Node.js 环境里没有。
  • 解决:使用 next/dynamic 进行动态导入,并禁用 SSR。
    1
    const ChatInput = dynamic(() => import("@/components/ChatInput"), { ssr: false });

坑位 2:布局遮挡与滚动失效

  • 现象:输入框使用 absolute 定位,导致聊天记录过长时被输入框遮挡,无法看到最后一条消息。
  • 解决:重构 CSS 布局,放弃绝对定位,改用 Flexbox
    • 容器:flex flex-col h-screen
    • 消息区:flex-1 overflow-y-auto
    • 输入区:flex-shrink-0 (固定在底部,挤压消息区高度)

坑位 3:Markdown 样式丢失

  • 现象:AI 回复的 Markdown 没有格式(无加粗、无列表)。
  • 解决:引入 @tailwindcss/typography 插件,并正确包裹 prose 类名。
    1
    2
    3
    <div className="prose prose-sm ...">
    <ReactMarkdown>{content}</ReactMarkdown>
    </div>

5. 总结

通过本次 v2.0 的迭代,jd_agent 已经不仅仅是一个简单的文本分析工具,而是一个具备多模态交互能力的智能助手。

  • 技术栈: FastAPI, LangChain, Next.js, Tailwind, Web Audio API.
  • 核心突破: 解决了 LLM 生成与 TTS 播放的时序同步问题,实现了流畅的语音对话体验。

希望这篇实战记录能为正在构建 AI Agent 的开发者提供参考!

Prev:
打造 L5 级人机协同与全双工语音的 AI 面试官
Next:
从零构建全栈 AI 简历助手:复刻 DeepSeek 交互与长期记忆实现