欢迎光临
我们一直在努力

我用 Agora 从零搭了一个能打断、会追问的 AI 语音面试官,Python 后端 + Web 前端,10 分钟跑通!

为什么想做这个?

最近有些读者在准备跳槽,毕竟金九银十快来了嘛,他们刷面经的时候就发现一个痛点:背八股文容易,但没人陪你模拟面试。你对着模拟屏幕自问自答,和真人对线完全是两码事——真实的面试官会追问、会打断、会根据你的回答临时调整方向。

刚好,我前阵子刷到 OpenAI 搞了 Realtime API,Agora 是他们首批官方合作伙伴。我就想:能不能用这个东西搞一个语音面试官呢?能听我说话,能实时回复,还能在我说跑题的时候把我拉回来。

研究了一圈,发现 Agora 把语音 Agent 需要的东西(传输、ASR、LLM、TTS、打断逻辑)打包成了一个叫 Conversational AI Engine 的东西。简单说就是:你不用自己拼 ASR + LLM + TTS + WebSocket 打断逻辑,它帮你搞好了。

那我试试。

准备工作

注册账号

先去官网https://console.agora.io注册账号。建议:如果大家有GitHub账号的,直接可用GitHub账号免注册直接登录。

我全文内容是基于选择基于Agora官方所提供的Voice Agent Builder来进行演示&操作。

接着新建项目,新项目默认就开了 Conversational AI Engine,不用额外申请。你需要拿到两样东西:

  • App ID:项目的唯一标识

  • App Certificate:用于生成 token,在项目设置里找

具体如下截图演示,都已经提供出来了。

装 CLI 和 SDK

Agora 提供了一个 CLI 工具,能帮你管理项目、生成配置。我是Windows环境,一行命令装好:

curl -fsSL https://dl.agora.io/cli/install.sh | sh
agora –help

终端输出:

Agora CLI – Manage Agora projects and Conversational AI agents

Usage:
agora [command]

Available Commands:
login Login to your Agora account
init Clone a starter project and bind it to your Agora project
project Manage Agora projects
help Help about any command

Flags:
-h, –help help for agora

然后登录:

agora login

会弹浏览器让你授权,回来就登好了。

Python SDK 也装上:

pip install agora-agents

这个 agora-agents 包就是 Conversational AI 的 Python SDK,里面有 Agent、Agora client、各家 STT/TTS/LLM 的 builder。

跑官方 Quickstart 确认环境没问题

在写自己的东西之前,我先跑了下官方的 Python 模板,确认环境通:

agora init my-python-demo –template python
cd my-python-demo
bun run setup
bun run dev

打开 http://localhost:3000,点 Start conversation,浏览器弹了个麦克风授权。然后一个声音跟我说:“Hello, how can I help you today?”

通了。延迟体感大概不到 1 秒,能打断,体验还行。

然后,我们也先在agora平台,新建一个AI Agent,为了下一步产出正式产品做铺垫,我们先来模拟体验下我们创建的Agent。

可以模拟跟ai实时对话,真真发现我的idea可以通过它来实现了。

开始搭自己的东西

Quickstart 跑通后,我开始改成自己想要的面试官。整体思路是:Python FastAPI 做后端,负责生成 token 和控制 Agent 的生命周期;前端用原生 HTML + Agora Web SDK,负责采集麦克风音频和播放 Agent 语音。

Agent 配置:核心在这

整个项目最核心的文件就是 Agent 的配置。Agora 的 Agent 是一个 builder 模式,链式调用,把 STT、LLM、TTS 串起来:

from agora_agent import (
Agent, Agora, Area,
DeepgramSTT, OpenAI, MiniMaxTTS,
TurnDetectionConfig, InterruptionConfig,
)

client = Agora(
app_id=APP_ID,
app_certificate=APP_CERTIFICATE,
area=Area.US,
)

INTERVIEWER_PROMPT = """你是一位资深的技术面试官,正在对一名中高级后端工程师候选人进行模拟面试。

规则:
1. 每次只问一个问题,等候选人完整回答后再继续。
2. 按顺序覆盖以下四个方向:自我介绍、一道编程概念题、一道系统设计题、一道行为面试题。
3. 如果候选人回答得不够完整或比较模糊,请追问细节。
4. 每个问题回答后,给出 1-2 句话的简要反馈,再进入下一题。
5. 保持自然、口语化的交流风格——这是语音面试,不是笔试。不要用列表或代码块。
6. 如果候选人要求跳过某道题,允许跳过并继续下一题。
7. 四个方向都聊完后,用一段话总结候选人的优势和可以改进的地方。

记住:你在说话,不是在写字。每次回复控制在 2-4 句话,语气像真人面试官一样自然。"""

agent = (
Agent(client)
.with_stt(DeepgramSTT(
model="nova-3",
language="zh-CN",
))
.with_llm(OpenAI(
model="gpt-4o-mini",
system_messages=[{"role": "system", "content": INTERVIEWER_PROMPT}],
greeting_message="你好!我是今天的面试官。我们先从自我介绍开始吧——请简单介绍一下你的背景和技术栈。",
failure_message="抱歉,刚才没听清,可以再说一遍吗?",
max_history=20,
))
.with_tts(MiniMaxTTS(
model="speech-2.6-turbo",
voice_id="Chinese_emotional_female1",
))
.with_turn_detection(TurnDetectionConfig(
type="semantic_vad",
interrupt_mode="interrupt",
silence_duration_ms=500,
prefix_padding_ms=300,
))
.with_interruption(InterruptionConfig(
mode="start_of_speech",
))
)

几个关键点解释下:

STT 用 Deepgram Nova-3,language 设 ****zh-CN。我一开始用的 en-US,但面试官和候选人都是中国人,说中文更自然。切到 zh-CN 后,识别准确率明显提升,特别是技术术语(“Redis”、“布隆过滤器”、“分库分表”)都能正确识别。

LLM 用 GPT-4o-mini。本来想用 GPT-4o,但考虑到语音场景里 LLM 的响应速度比智商更重要,4o-mini 够用了。而且 managed mode 下 Agora 帮你管 key,不用自己搞 OpenAI 账号。

TTS 用 MiniMax,voice_id 选 ****Chinese_emotional_female1。这个声音是中文女声,语调比较自然,不像某些 TTS 那样机械感重。MiniMax 的 speech-2.6-turbo 延迟也不高,中文发音准确。

Turn Detection 用 semantic_vad 模式。这是重点。语音 Agent 最难的不是接模型,是判断"用户说完了没"。有几种模式:

  • server_vad:纯靠静音检测,停顿超过阈值就认为说完了

  • semantic_vad:用 LLM 判断语义,理解你说的是不是一个完整的意思

面试场景下 semantic_vad 明显更合适。因为面试者经常会停顿思考,纯 VAD 会把"嗯…让我想想…"误判为说完。

另外还有个 InterruptionConfig,配 mode="start_of_speech",意思是用户一开口说话就触发打断。配合上面的 interrupt_mode="interrupt",Agent 会在检测到用户声音时立即停止 TTS 输出。这两个配置组合起来就是全双工对话的核心。

后端:FastAPI 三条路由

后端就三件事:发 token、启动 Agent、停止 Agent。

@app.get("/api/get_config")
async def get_config(channel: str | None = None, uid: int | None = None):
config = issue_config(channel, uid)
return {"code": 0, "msg": "success", "data": config}

@app.post("/api/startAgent")
async def start_agent(req: StartAgentRequest):
agent_id = start_interview(req.channelName, req.userUid)
return {"code": 0, "msg": "success", "data": {"agent_id": agent_id}}

@app.post("/api/stopAgent")
async def stop_agent(req: StopAgentRequest):
stop_interview(req.agentId)
return {"code": 0, "msg": "success"}

get_config 负责生成 RTC token。RTC 管音频传输——Agent 和用户加入同一个频道,音频走 Agora 的 SDRTN 网络。

startAgent 调用 agent.create_session().start(),Agent 就会以 UID 0 加入你的频道。stopAgent 调 session.stop() 让它退出。

Token 生成用的是 SDK 自带的方法:

from agora_agent import generate_rtc_token, generate_convo_ai_token

rtc_token = generate_rtc_token(
app_id, app_certificate, channel, uid, role=1, expiry_seconds=86400
)

前端:浏览器端的音频采集和播放

前端的逻辑也不复杂。核心是 Agora Web SDK:

// RTC — 采集麦克风、播放 Agent 语音
rtcClient = AgoraRTC.createClient({ mode: "rtc", codec: "vp8" });

rtcClient.on("user-published", async (user, mediaType) => {
if (mediaType === "audio") {
await rtcClient.subscribe(user, mediaType);
user.audioTrack.play();
}
});

await rtcClient.join(config.app_id, config.channel_name, config.rtc_token, config.uid);

// 发布本地麦克风音频
localAudioTrack = await AgoraRTC.createMicrophoneAudioTrack();
await rtcClient.publish([localAudioTrack]);

这里有个坑:codec 参数不能填 "opus"。虽然我们只传音频不传视频,但 Web SDK 要求 codec 必须是视频编解码器之一(vp8、vp9、h264 等),填 opus 会直接报错。用 vp8 就行,实际不影响音频质量。

用户和 Agent 加入同一个 Agora 频道,音频走 RTC 双向传输,就这样。

跑起来:实际对话实录

启动服务:

python -m server.server

终端输出:

浏览器打开 localhost:8080,看到通话前界面:

点"开始面试",后端开始走流程:生成 token → 加入频道 → 启动 Agent。Agent 加入频道后,界面切换到通话状态,Agent 先开口了:

面试官:你好!我是今天的面试官。我们先从自我介绍开始吧——请简单介绍一下你的背景和技术栈。

我说:

我:你好,我叫李明,有 5 年后端开发经验,主要用 Java 和 Go,做过高并发电商系统和支付网关。

终端日志显示状态切换:

[INFO] Agent state: listening → thinking → speaking

面试官:不错,支付网关这块经验很扎实。那我们来聊一道概念题:Redis 的缓存雪崩、击穿和穿透,你能分别讲一下区别和应对方案吗?

这里我故意停顿了大概 3 秒在思考,Agent 没有打断我,也没有急着说话。这就是 semantic turn detection 的作用——它判断我只是在思考,还没说完。

我:缓存雪崩是大面积 key 同时过期或 Redis 挂了;击穿是热点 key 过期瞬间大量请求打到 DB;穿透是查询不存在的 key 绕过缓存。应对的话,雪崩可以用随机过期时间加集群高可用,击穿用互斥锁或逻辑过期,穿透用布隆过滤器和空值缓存。

说到一半我突然想到忘了说一个点,就停了。Agent 等了大约 500ms,确认我说完了,然后回复:

面试官:总结得很到位,特别是布隆过滤器的使用场景抓得很准。那下一个问题,如果你要设计一个支持 10 万 QPS 的短链服务,核心架构会怎么考虑?

注意到没,它做了两件事:第一,肯定了我的回答(“总结得很到位”);第二,平滑过渡到下一个问题。这比很多真人面试官做得都好。

我:我会用分片存储加本地缓存,发号器用雪花算法,跳转用 302 重定向,再用 CDN 和边缘节点做热点加速。

面试官:思路清晰,本地缓存和边缘节点都是加分项。最后一个问题,你在之前项目里遇到过的最大技术挑战是什么,怎么解决的?

然后我试了下打断功能。Agent 正在说最后一个问题的时候,我直接开口:

我:之前做过一次数据库迁移,从 MySQL 分库分表到 TiDB,主要挑战是数据一致性校验和灰度切换,我们用了双写加比对工具,花了两周完成零停机上云。

Agent 立刻停了,然后说:

面试官:非常好的案例,能体现出你对风险控制和工程落地的重视。

打断响应非常快,几乎是话音刚落 Agent 就停了。这就是全双工的好处——不需要等 Agent 说完才能开口。

最后 Agent 给出了总结:

面试官:今天的面试就到这里,整体来说你的技术深度和架构思维都不错,建议在分布式事务的细节上再加强一下。祝你后续顺利!

silence_duration 参值调整

也不是所有时候都顺畅。在测试过程中,有一次我停了大概 4 秒在组织语言。这次 Agent 没等,直接说了"没关系,你可以慢慢想"。这说明 4 秒的静音还是触发了 turn detection,即使 semantic 模式也判断我说完了。后面我查了下官方文档,silence_duration 默认是 500ms,对于需要长时间思考的场景可能偏短。可以调大到 1000-1500ms,但太大会导致正常对话的响应变慢。这是个 trade-off。

注新免费送时长

注册后 Conversational AI 有 300 分钟免费额度,已包含默认的 STT / LLM / TTS。我们从开发到测试再到最终成品,我们也只不过耗了44min,官方送的免费时长300min,太充裕了。

拆一下语音智能体的四层架构

跑通之后我回头看了下 Agora 的架构设计,其实语音智能体就四层,每层解决一个核心问题:

┌──────────────────────────────────────────┐
│ 4. Endpoint (端上体验) │
│ 浏览器 / 手机 / 硬件设备 │
│ 采集音频 · 播放音频 · 显示转写 │
├──────────────────────────────────────────┤
│ 3. Models (AI 模型) │
│ ASR (语音→文本) · LLM (推理) · TTS (文本→语音) │
├──────────────────────────────────────────┤
│ 2. Agent Runtime (运行时) │
│ 会话管理 · 轮次检测 · 打断 · 记忆 · 工具 │
├──────────────────────────────────────────┤
│ 1. Real-time Transport (实时传输) │
│ 低延迟音频传输 · 全球节点 · SDRTN │
└──────────────────────────────────────────┘

第一层:实时传输

这一层解决的是"音频怎么从用户传到 Agent,又怎么传回来"。

你可能会想:这不是 WebSocket 就能搞?确实,OpenAI 最早发布 GPT-4o Realtime API 时用的就是 WebSocket + 插网线。但实际场景里,WebSocket 走的是普通公网路由,跨地域延迟不稳定,丢包了也没有好的重传策略。

Agora 用的是自己的 SDRTN(Software-Defined Real-Time Network),本质是一个全球覆盖的 UDP 网络,做了针对实时音频的 QoS 优化。官方数据是端到端 650ms,我实测体感也差不多——从我说完到 Agent 开始回复,大概 600-800ms。这个延迟在语音对话里基本可以用"自然"来形容了。

第二层:Agent Runtime

这层是整个系统最有技术含量的部分。它管的事情包括:

轮次检测(Turn Detection):判断用户说完了没有。前面提到了,有 VAD 和 semantic 两种模式。semantic 模式会用一个小模型来理解语义,代价是多一点延迟,换来的是更自然的对话节奏。

打断处理(Interruption):用户说话时 Agent 正在说话,需要立即停止 TTS 输出。这个在代码里你不用自己写,Agora 的 runtime 帮你处理了。从体验上看,打断几乎是瞬时的。

会话记忆:max_history 参数控制保留多少轮对话上下文。面试场景设 20 轮差不多,太多会拖慢 LLM 响应。

工具调用(Tool Calling):Agent 可以调用外部工具,比如查数据库、调 API。这个我的面试官没用,但官方文档里有 MCP Tools 的示例,可以让 Agent 调用 MCP server。

第三层:AI 模型

这是 ASR + LLM + TTS 三件套。Agora 的 managed mode 帮你管 key,支持的组合是:

层默认 Provider模型
ASR Deepgram nova-3
LLM OpenAI gpt-4o-mini / gpt-4.1-mini
TTS MiniMax speech-2.6-turbo

如果你不想用默认的,BYOK(Bring Your Own Key)模式可以换成自己的 provider。比如 LLM 可以换成 Claude、DeepSeek,TTS 换成 Cartesia、ElevenLabs。只需要在 builder 里传 api_key 就行。

我的建议是先用 managed mode 跑通,再根据需求换。managed mode 每月有 300 分钟免费额度,够测试了。

第四层:端上体验

这层就是用户实际看到的东西。我用了浏览器方案:Web SDK 采集麦克风音频走 RTC,Agent 的语音也通过 RTC 传回来播放。

官方文档里覆盖了 7 种客户端方案:Python / Next.js / Go / Flutter / Android Kotlin / SwiftUI / React Native。如果做移动端可以直接用对应的方案改。

需要注意的点

1. App Certificate 默认不显示

新建项目后 App ID 直接能看到,但 App Certificate 需要在项目设置里点一下才显示。第一次用容易找不到。

2. Token 是有有效期的

RTC token 有有效期,默认 1 小时。如果你做长时间对话场景,需要在前端做 token 续期逻辑,不然到时间了连接会断。

3. 根据业务场景,调整沉默阈值

默认 500ms 在需要思考的场景偏短。但调大了又会让正常对话感觉迟钝。面试场景我调到 800ms 比较舒服。如果你的场景是快问快答(比如客服),500ms 甚至更短都行。

4. TurnDetectionConfig 的 interrupt_mode 参数值

这个坑踩了好久。interrupt_mode 的可选值是 'interrupt'、'append'、'ignore'、'keyword'、'adaptive',而 InterruptionConfig 的 mode 参数可选值是 'start_of_speech'、'keywords'、'custom'。俩参数名差不多,取值范围完全不同。我一开始把 interrupt_mode 也填了 "start_of_speech",结果 Agora API 直接返回 400,报 InvalidFieldValue。查了官方文档才搞清楚这俩是不同的东西。

5. Web SDK 的 codec 参数

AgoraRTC.createClient({ mode: "rtc", codec: "opus" }) 会报错,因为 opus 是音频编解码器,SDK 要求必须填视频编解码器。改成 codec: "vp8" 就好了。虽然我们只传音频,但这个参数是必填的,且只接受视频编解码器。

6. managed mode 的模型选择

managed mode 下 LLM 默认是 gpt-4o-mini。如果你需要更强的推理能力,可以换成 gpt-4.1-mini。但注意 gpt-4.1-mini 的延迟会高一些,在语音场景里这个 trade-off 需要权衡。

7. 中文场景的 STT 和 TTS 配置

把 STT 的 language 从 "en-US" 切到 "zh-CN" 后,技术术语识别准确率提升明显。TTS 方面,MiniMax 的 Chinese_emotional_female1 声音中文发音自然,没有机械感。如果你用英文 voice_id 说中文,听起来会很怪。

延迟拆解

我测了下各环节的延迟,大致分布:

环节延迟说明
语音采集 + 编码 + 传输 ~80ms 浏览器到 Agora 节点
Deepgram ASR ~150ms 首字识别,流式
OpenAI LLM 首 token ~200ms gpt-4o-mini
MiniMax TTS 首音节 ~150ms 流式合成
传输回浏览器 + 解码 ~70ms Agora 节点到浏览器
合计 ~650ms 和官方数据基本吻合

650ms 在语音对话里是什么概念?人类正常对话的响应间隔大概 200-500ms,所以 650ms 会感觉"略微有点想了一下",但不至于觉得卡。如果换成 gpt-4o(非 mini),LLM 首 token 延迟可能到 400ms+,一般延迟 850ms ,人的感觉还行,不会有明显的延迟和不舒服。

一些思考

做完这个项目,我对语音智能体有几个判断:

1. 传输层与编排是被低估的壁垒。 很多人觉得语音 Agent 就是 ASR + LLM + TTS 串起来。但你真正做的时候会发现,延迟的 60% 花在网络上。模型推理再快,网络不行也白搭。这也是 OpenAI 为什么选 Agora 当合作伙伴——Realtime API 的体验瓶颈不在模型,相对而言,OpenAI 同时官宣合作的 LiveKit,在亚洲节点非常少,延迟就会明显高很多。。

2. Turn Detection 是体验的分水岭。 做过语音对话的人都知道,判断"用户说完没"是最恶心的部分。纯 VAD 方案在安静环境下还行,一到有背景噪声或者用户习惯停顿思考的场景就崩。semantic 模式用 LLM 做语义判断,虽然多了一点延迟,但对话自然度完全不在一个量级。

3. Managed mode 降低了门槛但锁定了供应商。 不用自己搞 Deepgram / OpenAI / MiniMax 的 key 确实方便,300 分钟免费也够玩。但如果你要上生产,成本和可控性是需要算的。BYOK 模式可以接自托管模型,做到延迟和价格的平衡,这是更灵活的选择。

4. 300 行代码能跑通一个 voice agent,这件事本身就有意义。 我之前估算过自己从零拼 STT + LLM + TTS + WebSocket + 打断逻辑,怎么也得几千行代码,还得处理音频编码、网络重连、回声消除这些破事。Agora 把这些打包好了,你只需要关注 Agent 的 prompt 和业务逻辑。

项目代码

完整代码目录结构如下所示:

agora-voice-interviewer/
├── server/
│ ├── server.py # FastAPI 后端(token + agent 生命周期管理)
│ ├── agent.py # Agent 配置(STT/LLM/TTS + turn detection)
│ └── token.py # RTC token 生成
├── web/
│ ├── index.html # 浏览器 UI
│ ├── app.js # RTC 音频采集与播放
│ └── style.css # 样式
├── requirements.txt
└── .env.example

有需要源码的同学,直接评论区告知,完整项目中只需要填你的 App ID 和 App Certificate,然后小黑框中python -m server.server 就能跑。

总结

这次实践整体体验还行。从注册到跑通自己的 voice agent,大概花了两个小时,其中一半时间在调 prompt 和 turn detection 参数。

如果你也想试试语音 Agent,建议路径是:先跑官方 quickstart 确认环境,再改 prompt 做自己的场景,最后按需调 turn detection 和 VAD 参数。managed mode 够你测试用,上线再考虑 BYOK。

语音交互这条路才刚起步,现在做的东西还很粗糙。但 300 行代码就能搭出一个能对话、能打断、有上下文记忆的语音 Agent——放在两年前这是不敢想的。

-End-

赞(0)
未经允许不得转载:171主机测评 » 我用 Agora 从零搭了一个能打断、会追问的 AI 语音面试官,Python 后端 + Web 前端,10 分钟跑通!
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址