尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

实时辩论AI裁判开发实战:WebSocket语音链路与LLM评分设计

实时辩论AI裁判开发实战:WebSocket语音链路与LLM评分设计 之前在做实时语音类项目时经常要在“低延迟交互”和“大模型判断质量”之间做取舍WebSocket 推流容易做但到了 AI 裁判这一层既要听懂口语化表达又要判断辩论逻辑稍不注意延迟就飙到不可用。本文从零拆解一个“实时辩论对战 AI 裁判”项目的完整实现思路包含信令服务、语音链路、裁判提示词设计、前端交互与常见坑点。适合正在做 AI 实时交互应用、AI Agent 或语音产品的开发者参考。1. 项目背景与核心概念1.1 “AI 辩论对战游戏”是什么先看一个直观场景两名玩家进入一个在线房间围绕同一个辩题例如“远程办公是否优于坐班办公”进行限时辩论。系统实时采集双方语音转写成文字后交给大模型裁判。裁判根据论点质量、逻辑强度、回应针对性、事实依据等维度进行评分并在倒计时结束时输出胜负结论和理由。这就是“Real-time argument duello game”的核心体验用 AI 替代传统真人评委让辩论对战可以随时开局、自动裁决。从产品形态上看它结合了三块成熟技术实时通信玩家语音或文字通过 WebSocket 等通道实时传输。语音识别ASR把口语转为可分析的文字。大模型判断LLM 扮演裁判角色对辩论内容进行结构化评分。这种玩法并不只是“聊天机器人套壳”。它的难点在于语音片段是连续到达的AI 裁判需要在限定时间内读取完整辩论记录并给出有说服力的裁决理由。1.2 AI 裁判解决什么问题传统辩论赛需要真人评委成本高、排期慢且评委主观性较强。AI 裁判能带来的价值主要有三点即时性辩论结束即可出结果不需要等待人工评审。可解释性AI 可以输出“正方第 2 轮回应了反方论点但缺乏数据支撑”这类结构化反馈。可扩展性同一套裁判逻辑可以复用到演讲练习、销售话术训练、面试模拟等场景。从技术视角看AI 裁判本质上是一个“带评分规则的文本评估器”。它接收辩论记录输出结构化的裁决结果。因此如何设计裁判的输入格式和评分标准决定了整个游戏的上限。1.3 三类典型读者本文内容适合以下读者正在做 AI 实时交互类产品的开发者想了解 WebSocket LLM 的完整链路。对 AI Agent 或大模型应用感兴趣想找一个有实战价值的练手项目。语音产品、在线教育、游戏化学习方向的工程师希望把 AI 裁判能力集成到自有业务。无论你是前端、后端还是全栈工程师本文都会尽量避免塞入过多无关概念重点讲清“代码该写在哪个文件、为什么要这样写”。2. 系统整体设计与技术选型2.1 功能模块拆分先把项目拆成可独立开发的模块方便后续逐步实现模块职责关键技术房间管理创建房间、加入房间、分配正反方WebSocket、房间状态管理实时信令转发语音/文字消息、同步开局/结束状态WebSocket、消息协议语音采集与识别采集玩家语音转写为文字MediaRecorder、ASR API辩论记录存储保存每轮发言与时间戳内存存储或 Redis/数据库AI 裁判基于辩论记录生成评分与结论LLM API / 本地大模型结果展示展示评分维度、胜负理由、精彩回合前端渲染对于 MVP 版本不建议一开始就接数据库。先用内存对象保存房间和辩论记录等核心流程跑通后再考虑持久化。2.2 技术栈选择技术选型需要兼顾开发效率和实时性。下面是一套比较稳妥的组合后端Node.js Express wsWebSocket 库也可以使用 Socket.IO本文以 ws 为例演示核心逻辑。前端Vue 3 或 React配合浏览器的 MediaRecorder 采集麦克风音频。语音识别可以使用浏览器端 Web Speech API 做原型也可以接入成熟的 ASR 服务。AI 裁判OpenAI 兼容接口或本地部署的大模型通过服务端调用避免在前端暴露密钥。部署云主机 Nginx 反向代理WebSocket 需要单独配置升级头。需要说明的是不同 ASR 服务和 LLM 服务的接入方式差异较大本文重点演示“接入思路”具体 API 参数需要按你实际使用的服务版本调整。2.3 核心流程一次完整的对战流程可以拆成如下步骤玩家 A 创建房间玩家 B 通过房间号加入。系统随机或手动分配正反方。倒计时开始双方交替发言也可以自由发言。浏览器采集麦克风音频实时或分段发送到服务端。服务端调用 ASR 转写文本保存到辩论记录。辩论时间结束服务端将完整记录发给 LLM 裁判。裁判输出评分、胜负、理由前端展示。这里需要注意实时辩论的“回合”边界并不总是清晰的。为了让 LLM 裁判更好判断可以在转写文本中标注“正方xxx”“反方xxx”或者按时间段切分。3. 环境准备与项目结构3.1 开发环境本项目不依赖特定操作系统Windows / macOS / Linux 均可。建议准备以下环境Node.js 18 或更高版本自带 npm。Python 3.10用于编写 AI 裁判服务也可以直接用 Node.js 调用 LLM API。一个可用的 LLM API Key或本地部署的大模型服务。Chrome / Edge 浏览器用于前端页面调试。如果你不打算接真实 ASR 服务可以先在浏览器里用 Web Speech API 完成语音转写优点是零成本、无需后端额外调用缺点是识别准确率和语言支持会受浏览器限制。3.2 示例项目结构建议采用前后端分离的结构argument-duello/ ├── server/ │ ├── package.json │ ├── index.js # HTTP 服务入口 │ ├── ws-handler.js # WebSocket 消息处理 │ ├── room-manager.js # 房间与玩家状态 │ ├── judge.js # AI 裁判逻辑 │ └── prompt-templates.js # LLM 提示词模板 ├── client/ │ ├── index.html │ ├── style.css │ └── app.js # 前端逻辑采集、连接、渲染 └── README.md如果只有你自己开发也可以把前端页面直接放到 server 的静态目录下减少跨域配置成本。4. 核心模块实现4.1 房间管理与实时信令先实现最基础的房间管理。用Map保存房间对象房间内保存玩家连接、正反方分配、辩论状态。// 文件路径server/room-manager.js class Room { constructor(id) { this.id id; this.players []; // 玩家连接对象 this.sides {}; // 玩家ID - pro | con this.status waiting; // waiting | debating | finished this.log []; // 辩论记录 [{ side: pro, text: ..., ts: Date.now() }] } } const rooms new Map(); function createRoom() { const id Math.random().toString(36).slice(2, 8).toUpperCase(); const room new Room(id); rooms.set(id, room); return room; } function getRoom(roomId) { return rooms.get(roomId); }这里用随机短码作为房间号方便玩家输入。生产环境可以考虑加过期清理逻辑避免内存泄漏。WebSocket 消息协议可以统一设计成 JSON 格式{ type: join_room, roomId: AB12CD, playerName: Alice }服务端根据type分发到不同处理函数。4.2 WebSocket 服务端使用ws库创建 WebSocket 服务并在连接建立后绑定消息处理。// 文件路径server/index.js const express require(express); const http require(http); const { WebSocketServer } require(ws); const { createRoom, getRoom } require(./room-manager); const app express(); const server http.createServer(app); const wss new WebSocketServer({ server }); wss.on(connection, (ws) { ws.on(message, (raw) { const msg JSON.parse(raw.toString()); switch (msg.type) { case create_room: handleCreateRoom(ws); break; case join_room: handleJoinRoom(ws, msg.roomId, msg.playerName); break; case speech_text: handleSpeechText(ws, msg.text); break; default: break; } }); }); server.listen(3000, () { console.log(Server is running on http://localhost:3000); });这里只是入口代码。实际开发时handleCreateRoom、handleJoinRoom等函数需要更新房间状态并给房间内所有玩家广播消息。广播可以使用ws.send()但要判断连接是否处于可写状态。4.3 语音采集与转写前端前端使用MediaRecorder采集麦克风音频。为了让延迟可控可以采用分段录制每 3 秒生成一段音频发送到服务端识别。// 文件路径client/app.js核心片段 let mediaRecorder; let audioChunks []; async function startCapture() { const stream await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder new MediaRecorder(stream); mediaRecorder.ondataavailable (event) { if (event.data.size 0) { audioChunks.push(event.data); } }; mediaRecorder.onstop async () { const blob new Blob(audioChunks, { type: audio/webm }); audioChunks []; await sendAudioToServer(blob); }; mediaRecorder.start(); setInterval(() { if (mediaRecorder.state recording) { mediaRecorder.stop(); mediaRecorder.start(); } }, 3000); }这种分段发送的方式比较适合后端 ASR 服务。若想让两端发言不互相打断可以设计成“按住说话”模式按住按钮开始录音松开后自动发送。如果你只想快速验证不接入 ASR 服务可以让玩家用键盘输入文字。不过真实辩论场景中语音输入的沉浸感会强很多。4.4 AI 裁判逻辑AI 裁判是整个项目的灵魂。它的主要工作是分析辩论记录输出结构化裁决结果。关键点在于提示词设计。一个简单的裁判提示词可以这样写// 文件路径server/prompt-templates.js function buildJudgePrompt(topic, log) { const transcript log .map((item) ${item.side pro ? 正方 : 反方}${item.text}) .join(\n); return 你是一名专业的辩论赛裁判。请根据以下辩论记录进行评分并给出裁决结果。 辩题${topic} 辩论记录 ${transcript} 评分规则 1. 论点质量观点是否清晰、有逻辑。 2. 论据支撑是否使用事实、数据或可靠依据。 3. 回应能力是否有效回应对手观点而非各说各话。 4. 表达说服力整体表达是否连贯、有说服力。 请输出 JSON 格式结果 { pro_score: 0, con_score: 0, winner: pro 或 con 或 draw, reason: 简要说明胜负理由, dimensions: { pro_argument: 正方论点质量评价, con_argument: 反方论点质量评价, pro_evidence: 正方论据评价, con_evidence: 反方论据评价 } }; }在裁判服务中调用 LLM API 时建议开启response_format: { type: json_object }如果你的模型服务支持并要求模型只输出 JSON方便程序解析。// 文件路径server/judge.js核心片段 async function judgeDebate(topic, log) { const prompt buildJudgePrompt(topic, log); const response await callLLM(prompt); // 伪代码实际请接入你的模型服务 return JSON.parse(response); }需要特别注意辩论记录越长LLM 上下文占用越大。MVP 阶段可以限制辩题为 3~5 分钟或在提示词中截取最近 N 条记录。4.5 结果展示前端收到裁判结果后可以渲染成评分卡片。展示内容建议包含正反方得分。胜者标识。裁判理由。各维度评价论点质量、论据支撑、回应能力、表达说服力。为了让结果更直观可以用简单的表格或进度条展示。下面是一个评分结果的数据结构示例{ winner: pro, pro_score: 82, con_score: 76, reason: 正方在回应反方关于效率的质疑时补充了具体案例和时间数据逻辑链条更完整。, dimensions: { pro_argument: 论点清晰层次分明。, con_argument: 论点合理但部分表述略显模糊。 } }5. 完整实战从零搭建一个最小可玩版本5.1 初始化后端项目先创建server目录并初始化 npm 项目mkdir argument-duello cd argument-duello mkdir server cd server npm init -y npm install express ws cors dotenv创建.env文件保存模型服务地址和密钥不要提交到仓库LLM_API_KEYyour_api_key LLM_BASE_URLhttps://your-llm-service.example.com PORT30005.2 实现房间管理与消息处理为了控制篇幅这里提供一份简化版的index.js包含创建房间、加入房间、广播消息三个基础功能// 文件路径server/index.js const express require(express); const http require(http); const cors require(cors); const { WebSocketServer } require(ws); const { createRoom, getRoom } require(./room-manager); const app express(); app.use(cors()); app.use(express.json()); const server http.createServer(app); const wss new WebSocketServer({ server }); function broadcastToRoom(room, message) { const data JSON.stringify(message); room.players.forEach((player) { if (player.readyState 1) { player.send(data); } }); } wss.on(connection, (ws) { ws.on(message, (raw) { const msg JSON.parse(raw.toString()); if (msg.type create_room) { const room createRoom(); room.players.push(ws); ws.send(JSON.stringify({ type: room_created, roomId: room.id })); } else if (msg.type join_room) { const room getRoom(msg.roomId); if (!room) { ws.send(JSON.stringify({ type: error, message: 房间不存在 })); return; } room.players.push(ws); broadcastToRoom(room, { type: player_joined, playerName: msg.playerName }); } }); }); server.listen(process.env.PORT || 3000, () { console.log(Server running on port ${process.env.PORT || 3000}); });这个版本的目的是先让玩家能创建房间、加入房间并收到状态广播。后续可以再加“分配正反方”“开始辩论”等事件。5.3 实现前端页面前端client/index.html只需要一个房间号输入框、创建房间按钮、加入房间按钮以及一个显示实时状态和最终结果的面板。!-- 文件路径client/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleAI 辩论对战/title link relstylesheet hrefstyle.css / /head body div idapp h1AI 辩论对战/h1 div button idcreateBtn创建房间/button input idroomIdInput placeholder输入房间号 / button idjoinBtn加入房间/button /div div idstatus/div div idresult/div /div script srcapp.js/script /body /html前端client/app.js负责建立 WebSocket 连接并把服务端推送的消息渲染到页面上// 文件路径client/app.js const socket new WebSocket(ws://localhost:3000); socket.onmessage (event) { const msg JSON.parse(event.data); if (msg.type room_created) { document.getElementById(status).innerText 房间已创建房间号 msg.roomId; } else if (msg.type player_joined) { document.getElementById(status).innerText msg.playerName 加入了房间; } else if (msg.type judge_result) { document.getElementById(result).innerText JSON.stringify(msg.result, null, 2); } }; document.getElementById(createBtn).onclick () { socket.send(JSON.stringify({ type: create_room })); }; document.getElementById(joinBtn).onclick () { const roomId document.getElementById(roomIdInput).value.trim(); socket.send(JSON.stringify({ type: join_room, roomId })); };5.4 接入 AI 裁判结果当辩论倒计时结束后服务端应调用judgeDebate()并把结果广播给双方。// 文件路径server/index.js追加处理 const { judgeDebate } require(./judge); // 假设这是辩论结束后的处理函数 async function finishDebate(room, topic) { const result await judgeDebate(topic, room.log); broadcastToRoom(room, { type: judge_result, result }); }这部分逻辑可以挂在一个倒计时定时器里。比如开局后 180 秒触发finishDebate。5.5 运行验证启动服务cd server node index.js浏览器打开client/index.html点击“创建房间”会自动连接 WebSocket 并获得房间号。再开一个浏览器窗口输入房间号加入。此时两个页面处于同一房间可以继续扩展语音交互和辩论流程。预期输出[页面1] 房间已创建房间号AB12CD [页面2] 玩家 2 加入了房间 [页面1] 玩家 2 加入了房间如果页面控制台没有报错说明 WebSocket 链路已经打通。6. 常见问题与排查思路在实现实时辩论 AI 裁判项目时最容易踩的坑集中在“实时链路”和“大模型输出解析”两部分。问题现象常见原因解决思路WebSocket 连接失败前端连接地址写成了http://或端口错误使用ws://localhost:3000检查服务端端口房间加入后收不到消息广播时没有判断连接状态调用send()前检查readyState 1语音识别结果为空麦克风权限未授权或分段音频太短检查浏览器权限设置适当延长录音分段大模型返回非 JSON提示词未约束输出格式在提示词中明确“只输出 JSON”并启用 response_format 校验裁判结果延迟过高辩论记录过长模型推理时间增加限制总轮数或截取最近 N 条记录再送裁判内存占用持续增长房间对象未清理增加房间过期清理逻辑定期删除无玩家房间6.1 WebSocket 连不上的排查顺序如果前端报错WebSocket connection to ws://localhost:3000 failed建议按以下顺序排查确认服务端是否启动成功控制台是否打印监听端口。确认前端地址协议是否为ws://端口是否匹配。确认没有浏览器插件阻断 WebSocket。查看服务端是否有异常日志比如房间相关代码抛错。6.2 大模型输出解析失败的兜底方案大模型偶尔会输出多余文字导致JSON.parse报错。稳妥做法是先做一次“提取 JSON 块”的预处理function extractJSON(text) { const start text.indexOf({); const end text.lastIndexOf(}); if (start -1 || end -1) { throw new Error(No JSON block found); } return JSON.parse(text.slice(start, end 1)); }另外建议启用模型服务的 JSON 模式并要求 API 返回时固定temperature参数减少输出随机性。7. 最佳实践与工程建议7.1 安全与合规不要在浏览器端直接暴露 LLM API Key。所有大模型调用都应放在服务端。对用户输入做长度限制。辩论单条发言建议限制在 500 字以内防止恶意超长文本导致上下文溢出。对参与者昵称做过滤避免注入特殊字符影响前后端渲染。如果要部署到公网务必启用 HTTPS/WSS避免语音和文本内容被中间人截获。7.2 延迟优化实时辩论对延迟比较敏感。主要优化方向有三个降低 ASR 分段延迟不要等整句话说完再识别可采用流式识别接口。裁判时机前置在辩论进行中每轮发言结束后就让模型增量评估而不是攒到最后一次性评分。前端体验兜底在模型推理期间先展示“裁判正在思考”的动画避免用户误以为卡死。7.3 日志与可观测性AI 裁判的可解释性关系到产品信任度。建议记录以下日志每轮辩论的原始转写文本。送入裁判服务的最终 prompt。裁判返回的原始响应。解析失败时的原始输出。这些日志不仅能帮你排查问题还能在裁判结果争议较大时做复盘。7.4 扩展方向MVP 跑通后可以从以下方向继续迭代增加“观众”角色观众可以围观实时辩论并投票AI 裁判结果与观众投票对比。增加多维度排行榜累计胜场、最佳论点、最佳回应等。接入 TTSAI 裁判在给出结论时用语音播报结果强化游戏感。支持自定义辩题让玩家输入任意辩题并自动生成“立论提示”。7.5 技术栈演进建议如果你不满足于 Node.js 单机方案可以参考以下演进路线将房间状态迁移到 Redis支持多实例横向扩展。将辩论记录持久化到数据库支持历史回看。将 ASR 换成流式识别服务降低延迟。将 LLM 调用封装成独立微服务方便团队内复用。这套架构的模块边界比较清晰按上述方向演进时不需要推倒重写。8. 总结与后续学习建议通过本文的拆解你应该已经掌握了实时辩论对战游戏的核心链路WebSocket 房间通信、前端语音采集与分段发送、服务端 ASR 转写接入、大模型裁判的提示词设计与结果解析。相比普通聊天机器人AI 裁判项目最需要下功夫的是“输入结构的稳定性”和“输出结构的可靠性”——只要把辩论记录格式和评分 JSON 格式定义清楚后续做功能迭代都会顺很多。建议你按照最小可玩版本先把 WebSocket 房间跑通再逐步加入语音识别和 AI 裁判。不要一开始就把所有模块堆上去否则排错成本会很高。下一步可以继续研究流式语音识别服务如何接入 WebSocket 协议。大模型输出如何做更稳定的结构化校验。如何用本地大模型替代外部 API降低单次对局成本。如何设计 AI 评分规则让裁判结果更贴合真实辩论标准。如果本文对你有帮助可以先收藏备用。动手搭一个最小版本跑通一次完整的“创建房间 → 双方发言 → 裁判出结果”你对这套实时 AI 应用架构的理解会立刻上一个台阶。
返回列表