[一键开通华为云码道 CodeArts 代码智能体](https://developer.huaweicloud.com/codeartsco.html?source=dmzntgwatomgit1&sourcead=dmzntgwatomgithd)
参赛投稿 · 记录一次完整的 AI 辅助开发实践:架构设计、代码移植、流式调试、安全加固与落地验证。
0. 为什么做这个项目
许多高校辩手备赛最痛苦的三件事:拿到辩题无从破题、立论稿来回憋三天、没有队友陪练质询。市面上的通用大模型能聊,但缺少辩论场景的专业约束和训练流程。
我决定用华为云 CodeArts 代码智能体做一个“大学辩论赛 AI 辩手助手”——一个把"破题 → 立论 → 质询 → 对辩 → 评审"五个环节串成一条备赛链路的纯前端网站。方案定为:
- 技术形态:零依赖静态网站(HTML + CSS + 原生 JS),克隆即用、无需构建,方便同学们在任何电脑上双击运行;
- AI 能力:通过大模型 Chat Completions 接口,把辩论教练、对手、评委三份"人格"封装进不同模式;
- 工程要求:一次开发要同时满足**架构(30%)/代码质量(30%)/安全合规(20%)/UI-UX(15%)/博文(5%)**五维评审,因此每个环节都要有正式工程的做法。
1. AI 辅助实操流程:先立"骨架",再填"血肉"
我的工作流不是"让 AI 一口气生成整个站",而是分四步推进:
- 需求拆解:把辩论备赛拆成五个有明确输入/输出的功能模块,写进模式配置表;
- 架构先行:和 CodeArts 智能体对齐分层设计(展示层 / 服务层 / 配置层),先确定模块边界,再写代码;
- 核心难点优先:先攻克"流式 SSE 调用"这个最不确定的技术点(见第 2 节),打通后再铺 UI;
- 安全与测试收尾:渲染层防 XSS、输入校验、单元测试 + 浏览器端到端(E2E)验证。
1.1 产品功能:一张模式配置表驱动整个应用
五个模式共享同一套"发送 → 流式渲染 → 追加历史"管道,差异全部收敛到一张配置表里:
constMODES=[{id:"topic",label:"辩题解剖",icon:"🔍",needsTopic:true,buildMessages(input){/* 拆分歧、画论证地图 */}},{id:"construct",label:"立论生成",icon:"📜",needsTopic:true,needsSide:true,/* 正/反方生成一辩稿 */},{id:"cross",label:"质询攻防",icon:"⚔️",needsTopic:true,needsSide:true,keepHistory:true},{id:"debate",label:"自由辩论",icon:"🗣️",needsTopic:true,needsSide:true,keepHistory:true},{id:"judge",label:"陈词评分",icon:"🏛️",needsSpeech:true}];关键理解:模式是配置,不是分支。后续想加"赛前心理训练""评委复盘"等模式,只需往表里加一行,渲染、请求、历史管理全部复用——这是"可扩展架构"最廉价的实现方式。
2. 核心代码:把 Python 流式调用"翻译"成 JavaScript
平台给了参考实现的 Python 片段(requests+iter_lines消费 SSE)。我的目标是在浏览器里等价实现,且要做到可测试、可中止:
# Python 参考实现forlineinresponse.iter_lines():ifnotline.startswith(b"data:"):continueifline.strip()in(b"data:[DONE]",b"data: [DONE]"):returnyieldjson.loads(line.decode("utf-8").lstrip("data:"))2.1 JavaScript 版本:ReadableStream 逐 token 消费
浏览器没有iter_lines,我用fetch+ReadableStream.getReader()做等价实现,并把最关键的一步——SSE 行解析——抽成不依赖任何浏览器 API 的纯函数:
functionparseSSELine(line){consttrimmed=String(line).trim();if(!trimmed.startsWith("data:"))return{type:"idle"};if(trimmed==="data:[DONE]"||trimmed==="data: [DONE]"){return{type:"done"};}constpayload=trimmed.slice(5).trim();try{constjson=JSON.parse(payload);consttext=json.choices?.[0]?.["delta"]?.content||"";returntext===""?{type:"idle"}:{type:"delta",text};}catch(_){returnnull;// 半行被截断,等缓冲拼接后重试}}调试要点(踩坑记录):流式数据经常在行中间被网络分片切断,直接逐行 JSON.parse 会偶发报错。解法是维护一个buffer,按\n切分后把最后一段未换行的残块留到下一轮再解析:
buffer+=decoder.decode(value,{stream:true});constlines=buffer.split("\n");buffer=lines.pop();// 残块继续攒着for(constlineoflines)onLine(line);// 完整行逐个解析这个"分帧重组"逻辑正是浏览器端 SSE 最容易被忽视的坑,也是我反复让 CodeArts 智能体解释、最终单测覆盖的点。
2.2 把参数映射、错误归一、取消机制补齐
Python 版直接把采样参数塞进 payload;工程化版本要额外做三件事:参数 clamp 到合法区间、HTTP 错误体解析出可读 message、支持AbortController中止。比如温度限定在[0,2]、max_tokens限定在[1,32768],防止手滑传参导致 400。
为了让 SDK 在 Node 和浏览器都能跑(便于后续做服务端转发、做单元测试),出口用了一个轻量 UMD:
(function(global,factory){if(typeofmodule==="object"&&module.exports){module.exports=factory(global);// Node:跑单测}else{global.DebAI=global.DebAI||{};// 浏览器:挂命名空间global.DebAI.api=factory(global);}})(typeofself!=="undefined"?self:this,function(global){/* ... */});3. 功能实现思路:三种"人格"与上下文管理
3.1 用 System Prompt 封装"教练/对手/评委"三种人格
同一个模型,靠 System Prompt 区分人格,是性价比最高的"功能实现":
// 质询模式:AI 是对方三辩,一次只问一个问题system:"你是「反方」三辩辩手,正在质询环节(盘问)。辩题是「...」。"+"你言辞犀利但礼貌,一次只问一个问题,围绕对方立论的核心漏洞逐层追问。"// 自由辩论模式:AI 是立场相反的对手system:"你是立场相反的强手。自由辩论环节每轮发言 1-3 句,先回应再攻击。"3.2 多轮上下文:把"对练"变成连贯对话
质询和自由辩论要"记得前文",所以单独维护一份history,组装请求时把历史消息序列整体带上:
constmessages=[{role:"system",content:mode.buildSystem(state.topic,{side:state.side})}];state.history.forEach((m)=>messages.push({role:m.role,content:m.content}));配套规则:切换辩题时清空历史,避免把上一场的论据带进新辩题(topicChanged判断)。这是辩论逻辑的"领域知识",AI 生成方案时不一定想得到,需要人补充。
3.3 流式渲染的用户体验
- 增量内容每到达一批,就重建一次消息气泡(条目数少,性能无压力),并给气泡加"打字光标":
.msg.ai .content.streaming::after{content:"";animation:blink 0.9s step-end infinite;} - 只有在用户位于底部时才自动滚动,避免"你想回去读上面,它偏要拉到底"的烦人体验。
4. 开发提效体验:从"写完就跑"到"测完再交付"
本次项目我执行的验证闭环是三层递进的,也希望给大家一个参考模板:
# 第 1 层:语法与单元测试(几秒钟)node--checkjs/*.jsnode--testtests/api.test.js# 10 个用例,覆盖 SSE/截断/HTTP 错误/参数 clamp# 第 2 层:接口真连(验证密钥、端点与解析链路)# 用 Node 脚本直接流式请求,打印收到的首屏文本# 第 3 层:浏览器 E2E(Playwright 无头浏览器)# 加载页面 → 切模式 → 选反方 → 真实发送 → 等待流式文本渲染 → 开设置面板 → 切主题# 全链路通过后截图留档4.1 一个真实的" AI 写代码 + 人兜底"案例
E2E 第一次跑,页面报了一个很隐蔽的错误:Cannot read properties of undefined (reading 'streamChat')。排查发现是UMD 封装作用域问题——factory内部引用了外层 IIFE 的参数global,而这个参数在factory闭包里根本不可见,浏览器直接抛global is not defined。
这正是我最想分享的体会:AI 生成"看起来对"的代码,运行时才暴露真相;而 E2E 测试 + 分步诊断脚本能在 30 秒内定位到"哪个全局变量没接上"。好几个类似问题(命名空间不一致、坏分支残留、暗色模式对比度不足)都是被这一套验证流程抓出来的。过程记录见js/api.js的修改历史与本仓库 README 的 FAQ。
4.2 UI/UX 的取舍
- 视觉主题定成"辩台红 × 讲台金 × 纸墨米白",语义贴合辩论赛的正式感;
- 深浅两套主题用 CSS 变量驱动,一套样式维护两种皮肤;
- 所有 AI 输出走自定义的安全渲染器(支持标题/列表/加粗/行内代码),只用
textContent和 DOM API 构建节点,杜绝innerHTML——这既是 UX(排版更好看),也是安全(XSS 免疫)。
5. 安全合规怎么做(评审 20 分项)
- 密钥独立成文件:地址和密钥集中在
js/config.js,并支持设置面板运行时更换,公开部署前必须替换(README 已作醒目提示); - XSS 第一性原则:全站零
innerHTML,用户输入与模型输出均走textContent; - 输入侧校验:辩题 ≤ 120 字、发言 ≤ 2000 字、空输入拦截、非法参数自动 clamp;
- 请求可中止:
AbortController让"停止生成"真正中止网络流,不为误触买单。
6. 项目落地心得与改进方向
做成的三点:
- "模式即配置"的架构让功能可以持续累加,截止投稿前,从 3 个模式扩展到 5 个模式只花了很少时间;
- 三层验证闭环(单测 / 真连 / E2E)让交付信心从"能跑"升级为"可演示",投稿演示就是一次真实流式生成;
- 纯静态、零依赖、离线可用,学弟学妹双击就能用,传播成本接近零。
想继续做的事(也是给平台的建议):
- 为比赛场景增加一个"意愿连续对话的持久记忆"能力,让 AI 记得辩手上一场的弱点,做长期陪练;
- 平台若支持 WebSocket 或 SSE 透明代理,可大幅缓解浏览器侧 CORS 对参赛选手的困扰——这也是很多纯前端 AI 应用共同的痛点。
一句话总结:AI 把"能写"的难度降到接近零,作品的质量上限取决于架构意识、领域知识和验证纪律——这三样,才是代码智能体时代人最值得投入的竞争力。
附:核心接口说明
- 模型:
deepseek-ai/DeepSeek-V4-Flash - 请求体(流式):
{ model, messages, stream: true, max_tokens, temperature, top_p, frequency_penalty, thinking_budget } - 响应:SSE,
data:携带choices[0].delta.content,以data: [DONE]结束