从零搭建 AI 智能体陪练系统:基于 FastAPI + DeepSeek-V3 的题库生成 MVP 全记录
发布时间:2026/8/14 20:38:19 作者:尧图编辑部 阅读量:1,286

从零搭建 AI 智能体陪练系统基于 FastAPI DeepSeek-V3 的题库生成 MVP 全记录一个周末一行代码都不浪费——用 OpenAI 兼容接口调用国产大模型打造可落地的 AI 出题引擎。一、项目背景与定位在 CTFCapture The Flag安全竞赛培训场景中有一个持续存在的痛点出题成本极高。一道高质量的 CTF 题目从构思环境、编写 Dockerfile、埋设 Flag 到撰写题解往往需要数小时甚至数天。对于培训方而言手动出题根本跟不上学员的练习节奏。于是就有了这个项目的核心命题能不能让 AI 来出题但这里有一个现实约束——很多开发者没有 OpenAI 的 API Key信用卡门槛、地区限制、费用顾虑却拥有国产模型平台的 API 额度硅基流动、DeepSeek 官方、阿里百炼等。于是技术方案聚焦为一个验证性问题用 OpenAI SDK 国产大模型 API硅基流动 / DeepSeek-V3能不能搭出一套可用的 AI 出题系统答案是完全可以而且比想象中简单得多。本文将完整记录tiku_mvp从零搭建的全过程包括后端架构、前端交互、模型选型、部署配置以及开发过程中踩过的坑和解决方案。二、系统架构总览┌─────────────────────────────────────────────────────┐ │ Frontend (5501) │ │ vanilla HTML/CSS/JS │ │ http://127.0.0.1:5501/index.html │ └────────────────────┬────────────────────────────────┘ │ HTTP REST (CORS) ▼ ┌─────────────────────────────────────────────────────┐ │ Backend (8000) │ │ FastAPI Uvicorn │ │ │ │ POST /generate ──▶ _call_openai_json() ──▶ LLM │ │ GET /history ──▶ _read_history_raw() │ │ GET /history/{id} │ └────────────────────┬────────────────────────────────┘ │ OpenAI SDK (base_url override) ▼ ┌─────────────────────────────────────────────────────┐ │ SiliconFlow API (OpenAI 兼容) │ │ Model: deepseek-ai/DeepSeek-V3 │ └─────────────────────────────────────────────────────┘核心设计原则前端零框架依赖纯 HTML/CSS/JS一个index.html搞定无需 npm/webpack后端极简FastAPI 3 个 REST 端点 本地 JSON 文件持久化模型可替换通过base_url 环境变量任何 OpenAI 兼容接口都能即插即用三、技术栈详解3.1 后端FastAPI OpenAI SDK# backend/main.py 核心依赖fromfastapiimportFastAPIfromopenaiimportOpenAIfromdotenvimportload_dotenvimportuvicorn选择 FastAPI 的理由原生异步支持与 Uvicorn 搭配性能优秀自动生成 Swagger 文档/docs调试体验极佳Pydantic 数据校验请求/响应格式一目了然3.2 大模型DeepSeek-V3通过硅基流动# backend/.envOPENAI_API_KEYsk-xxxxxxxx# 硅基流动 API KeyOPENAI_MODELdeepseek-ai/DeepSeek-V3# main.py 第 28 行clientOpenAI(api_keyOPENAI_API_KEY,base_urlhttps://api.siliconflow.cn/v1# 关键覆写 base_url)为什么选择硅基流动完全兼容 OpenAI SDK一行base_url替换即可提供 DeepSeek-V3 等国产模型无需 OpenAI 账号国内网络直连延迟低3.3 前端原生 HTML JavaScript!-- frontend/index.html — 单文件应用 --scriptconstAPI_BASEhttp://127.0.0.1:8000;// 表单提交 → fetch /generate → 渲染题目 → fetch /history → 历史列表/script前端采用 SPA单页应用思路没有任何构建工具学科选择难度选择easy/medium/hard逐题作答一次生成 5 题每次展示 1 题答完切换下一题历史题库左侧卡片列表点击可回顾任意历史题集四、核心代码走读4.1 Prompt 工程_build_promptdef_build_prompt(subject:str,difficulty:str)-List[dict]:sys{role:system,content:(You are a test item writer. Return exactly 5 multiple-choice questions as strict JSON (utf-8), with keys: items:[{id,subject,difficulty,stem,choices:[{key,text}],answer,explanation}]. Choices must be A,B,C,D; answer must be one of A-D. Keep stems concise; explanations correct.)}user{role:user,content:(fSubject:{subject}\nDifficulty:{difficulty}\nWrite 5 diverse multiple-choice questions covering the core knowledge points.)}return[sys,user]设计要点强约束 JSON 输出通过response_format{type: json_object}确保结构化选项规范化强制 4 选项 A-D前端 UI 依赖此约定解释必填每道题都要求explanation提升学习价值4.2 调用 容错_call_openai_jsondef_call_openai_json(subject:str,difficulty:str)-List[QAItem]:respclient.chat.completions.create(modelOPENAI_MODEL,messagesmessages,response_format{type:json_object},temperature0.7,)contentresp.choices[0].message.content datajson.loads(content)itemsdata.get(items,[])# 兜底不足 4 选项自动补全foritinitems[:5]:choicesit.get(choices,[])iflen(choices)!4:keys[A,B,C,D]fixed[]fori,kinenumerate(keys):ifilen(choices)andisinstance(choices[i],dict):fixed.append(choices[i])else:fixed.append({key:k,text:fOption{k}})choicesfixed...容错策略选项数量不匹配→ 自动用 A/B/C/D 补全不足 5 题→ 抛出 RuntimeError 提示重试JSON 解析失败→ 透传异常给前端展示4.3 REST API 设计端点方法功能/generatePOST接收{subject, difficulty}返回 5 道选择题 存入 history.json/historyGET返回所有历史题集摘要学科、难度、题数、前 3 题预览/history/{batch_id}GET返回指定题集的完整内容含答案和解析4.4 数据持久化HISTORY_PATHDATA_DIR/history.jsondef_write_history_raw(data:List[dict]):HISTORY_PATH.write_text(json.dumps(data,ensure_asciiFalse,indent2),encodingutf-8)MVP 阶段使用本地 JSON 文件持久化每次/generate调用后insert(0, record)写入文件头部最新题集在前。对于单用户本地使用场景完全够用未来可平滑迁移到 SQLite 或 PostgreSQL。五、前端交互设计5.1 逐题作答流[选择学科难度] → [点击生成] → [展示第1题] ↓ [选择选项A/B/C/D] ↓ [提交答案] → [揭晓答案解析] ↓ [下一题] → [展示第2题] ↓ ... 重复 ... ↓ [第5题完成] → [显示总分 5/5]核心 JS 逻辑// mountQuestion(i) — 每次只渲染 1 题functionmountQuestion(i){constitemcurrentBatch.items[i];currentIndexi;selectedKeynull;nextBtn.style.displaynone;submitBtn.style.displayinline-block;stemEl.textContentitem.stem;choicesEl.innerHTML;item.choices.forEach(ch{constdivdocument.createElement(div);div.classNamechoice;div.textContent${ch.key}.${ch.text};div.dataset.keych.key;div.addEventListener(click,(){selectedKeych.key;[...choicesEl.children].forEach(nn.classList.remove(selected));div.classList.add(selected);});choicesEl.appendChild(div);});}5.2 答案标色逻辑functionrevealAnswer(){constitemcurrentBatch.items[currentIndex];[...choicesEl.children].forEach(n{constkn.dataset.key;if(kitem.answer){n.classList.add(correct);// 正确答案 → 绿色}if(selectedKeykselectedKeyselectedKey!item.answer){n.classList.add(wrong);// 选错 → 红色}});}视觉效果正确答案绿色高亮如果用户选错则红色标记自己的选择对比一目了然。六、开发踩坑实录6.1 CORS 跨域问题最耗时现象前端http://127.0.0.1:5501访问后端http://127.0.0.1:8000时浏览器报错Access to fetch at http://127.0.0.1:8000/history from origin http://127.0.0.1:5501 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource.排查过程第一反应检查main.py是否有 CORS 中间件 → 有allow_origins[*]怀疑规范冲突allow_credentialsTrueallow_origins[*]在 CORS 规范中不被允许 → 改为显式[http://127.0.0.1:5501, http://localhost:5501]改了还是不行→ 用curl直接测试发现 CORS 头正常返回最终根因lsof -i :8000发现/tmp/ctf-agent-server进程抢占 8000 端口所有请求被它拦截解决方案kill61235# 停掉 ctf-agent-serverkill98899895# 停掉旧的 main.py 进程python main.py# 重新启动教训CORS 中间件配置正确但仍然报错时优先排查端口是否被其他进程抢占而非反复改中间件配置。lsof -i :PORT是排查利器。6.2 浏览器缓存导致问题残留现象CORS 修好后无痕模式正常正常模式依然报错。原因浏览器缓存了之前失败的 OPTIONS 预检响应。解决Cmd Shift Delete清除浏览器缓存或 DevTools → Network → Disable cache。6.3 模型替换从 DeepSeek API 到硅基流动项目原本base_url指向https://api.deepseek.com需要 DeepSeek 官方的 API Key。迁移到硅基流动只需改动两处- base_urlhttps://api.deepseek.com base_urlhttps://api.siliconflow.cn/v1 - OPENAI_MODELdeepseek-chat OPENAI_MODELdeepseek-ai/DeepSeek-V3模型名称前缀规则{提供商}/{模型名}。硅基流动支持的模型列表可在其控制台查看。6.4 依赖安装注意如果使用.venv虚拟环境务必先source .venv/bin/activate然后在 venv 内执行pip install -r requirements.txt。全局安装的包不会被 venv 内的 Python 解释器识别。七、部署指南7.1 环境准备# 1. 克隆项目cdtiku_mvp# 2. 创建虚拟环境推荐python-mvenv tiku_venvsourcetiku_venv/bin/activate# macOS# tiku_venv\Scripts\activate # Windows# 3. 安装依赖pipinstall-rrequirements.txt7.2 配置 API Key编辑backend/.envOPENAI_API_KEYsk-your-siliconflow-api-key OPENAI_MODELdeepseek-ai/DeepSeek-V37.3 启动服务# 终端 1启动后端cdbackend python main.py# → http://127.0.0.1:8000/docs 可查看 API 文档# 终端 2启动前端cdfrontend python-mhttp.server5501# → http://127.0.0.1:5501/index.html7.4 验证打开http://127.0.0.1:5501/index.html选择学科和难度点击「生成题库」应该能看到 AI 生成的 5 道选择题。八、项目结构与文件清单tiku_mvp/ ├── backend/ │ ├── main.py # FastAPI 后端242 行 │ ├── .env # API Key 和模型配置 │ └── data/ │ └── history.json # 题库历史记录自动生成 ├── frontend/ │ └── index.html # 前端单页应用289 行 ├── requirements.txt # Python 依赖 └── readme.txt # 部署说明总代码量约 530 行非常适合作为 AI 应用开发的入门参考项目。九、系统截图9.1 首页包含学科/难度选择表单、生成按钮、历史题库卡片列表。9.2 答题界面AI 生成的题目4 个选项可点击选择顶部进度条显示当前进度。9.3 答案揭晓提交后显示正确答案绿色和详细解析如果选错会红色标记。9.4 API 文档FastAPI 自动生成的 Swagger UI可直接在线调试接口。9.5 项目目录极简的目录结构核心文件仅 4 个。十、总结与展望10.1 核心收获OpenAI 兼容接口的生态价值国产大模型平台普遍提供 OpenAI 兼容 API这意味着换个base_url就能复用整个 OpenAI SDK 生态迁移成本几乎为零。FastAPI 的极致开发体验自动生成的/docs页面让前后端联调变得轻松Pydantic 的类型校验省去了大量参数检查代码。MVP 思维本地 JSON 文件做持久化、单文件 HTML 做前端——这些简陋的选择在原型验证阶段反而是最高效的。10.2 未来扩展方向数据库迁移从 JSON 文件升级到 SQLite支持多用户、题目搜索、统计分析出题质量优化引入 Few-shot 示例 难度校准减少模型生成水题的概率题型扩展支持判断题、填空题、简答题用户系统接入 OAuth支持多用户各自维护题库进度CTF 专项出题从通用题库转向 CTF 场景支持 Web/Misc/Crypto/Reverse/Pwn 五个方向的定向出题技术栈FastAPI SiliconFlow API (DeepSeek-V3) Vanilla JS