【Agent项目】既是一个Agent项目,又能用来学习Agent
项目GitHub地址:https://github.com/Earth-OL-Player/ai_learn_project
文章目录
- 一、先说这个项目到底是什么
- 二、为什么我没有只做一个题库
- 三、项目模块总览
- 四、整体架构:Java 管业务,Python 管 AI
- 五、AI 智能刷题是项目里最像 Agent 的部分
- 六、评分 Agent:不是让模型随便打个分
- 七、讨论 Agent:带着题目上下文追问
- 八、SSE 流式输出:别让用户等一整段答案
- 九、兜底策略:模型不可用时流程不能直接断
- 十、成长体系:评分之后要留下痕迹
- 十一、学习路线:内容不是堆链接
- 十二、为什么说它也能学习 AGENTS.md
- 十三、项目文档也是工程的一部分
- 十四、关键代码阅读路线
- 1. 先看启动入口和路由
- 2. 再看刷题主链路
- 3. 然后看 AI 服务
- 4. 最后看成长和题库
- 十五、本地启动大概需要哪些东西
- 十六、这个项目适合怎么用
- 十七、我从这个项目里得到的几个经验
- 十八、结尾
一、先说这个项目到底是什么
最近我把一个 AI Agent 学习平台整理成了开源项目。
它表面上是一个学习平台:有学习路线、有 AI 面试题、有 AI 智能刷题、有成长体系、有建议评论区,也有管理后台。往里看一层,它又是一个完整的 Agent 工程样例:Vue 3 前端负责交互,Spring Boot 后端负责业务状态、鉴权、题库和成长结算,FastAPI 服务负责模型调用、评分 Agent、讨论 Agent 和本地兜底。
我更愿意把它理解成两个学习入口:
入口一:学习 AI Agent、RAG、大模型应用开发 入口二:学习如何用 AGENTS.md 约束 AI 编程助手做项目这两个入口刚好在一个仓库里碰上了。
项目首页大概是这样:
二、为什么我没有只做一个题库
刚开始整理 AI 应用开发资料时,我也想过做成一个简单资料站。放一堆链接,按 LangChain、LangGraph、RAG、向量数据库分组,看起来很快就能上线。
但这类资料站有一个问题:收藏很容易,真正学会很难。
AI Agent 和 RAG 这类知识,很多时候不是“知道概念”就够了。面试或者做项目时,更常遇到这些问题:
| 问题 | 真正考察的东西 |
|---|---|
| RAG 效果不好怎么排查? | 检索、切分、召回、重排序、提示词和评测链路 |
| Agent 工具调用失败怎么办? | 超时、重试、降级、上下文恢复和日志追踪 |
| LangGraph 适合什么场景? | 状态机、多步骤推理、条件分支和可观测性 |
| 大模型输出格式不稳定怎么处理? | 结构化输出、校验、兜底和提示词边界 |
| AI 服务接入业务系统后怎么控成本? | 限流、模型分级、调用日志和用户权益 |
这些题只看标准答案,通常看不出自己能不能讲清楚。
于是我把项目做成了一个学习闭环:
学习路线 -> 热门面试题 -> AI 智能刷题 -> AI 评分和继续追问 -> 成长体系和刷题记录 -> 回到薄弱点继续补用户先回答,再得到反馈,最后回到个人中心复盘。资料不再是孤零零的一页链接。
三、项目模块总览
当前仓库里主要有三个工程。
ai_learn_project ├── ai-learn-web # Vue 3 前端,负责页面、路由、刷题工作台和管理端 ├── ai-learn-backend # Spring Boot 后端,负责认证、题库、互动、成长和 AI 服务调用 ├── ai-service # FastAPI AI 服务,负责评分 Agent、讨论 Agent 和模型适配 ├── doc # 需求、架构、接口、中间件、迭代和截图资料 ├── xuanchuan # 各平台宣传稿 └── QUICK_START.md # 本地启动说明从功能上看,它已经覆盖了这些模块:
| 模块 | 做了什么 |
|---|---|
| 首页 | 展示项目定位、学习入口和功能导航 |
| 学习路线 | 用 Markdown 管理 AI 应用开发路线和资料 |
| 热门面试题 | 按 AI Agent、RAG、向量检索等方向整理高频题 |
| AI 智能刷题 | 抽题、答题、AI 评分、本题追问、SSE 流式输出 |
| 成长体系 | 经验、等级、段位、徽章、刷题记录和薄弱题 |
| 建议评论区 | 收集学习资料、题库和功能反馈 |
| 管理后台 | 用户、题库、模型配置、兑换码和日志级别管理 |
| AI 服务 | FastAPI 内部接口,接入 LangChain、LangGraph 和 OpenAI 兼容模型 |
这不是一个“只有前端页面”的展示项目。它有数据库迁移,有内部 AI 服务鉴权,有模型调用兜底,有流式接口,也有本地启动文档。
四、整体架构:Java 管业务,Python 管 AI
这个项目的一个核心取舍是:没有让前端直接调用模型,也没有把所有 AI 逻辑塞进 Java 后端。
整体调用链路如下:
用户浏览器 -> Vue 3 + Vite 前端 -> Spring Boot 业务后端 -> MySQL 业务数据库 -> FastAPI AI 服务 -> OpenAI 兼容模型或本地规则兜底我这样拆有两个原因。
第一,业务状态应该在后端手里。题库、用户、登录态、刷题阶段、经验结算、徽章发放,这些都是业务规则,不应该交给前端或者模型服务自由处理。
第二,AI 生态在 Python 里更顺手。模型适配、LangChain、LangGraph、Pydantic schema、流式输出,这些放在 FastAPI 服务里改起来比较轻。
边界最后定成了这样:
| 层 | 主要职责 | 不做什么 |
|---|---|---|
| 前端 | 页面展示、登录引导、SSE 接收、用户交互 | 不直接调 AI 服务,不保存业务规则 |
| Java 后端 | 鉴权、题库、会话、评分结果校验、成长结算 | 不直接暴露模型 Key,不让 AI 服务写业务库 |
| Python AI 服务 | 构造提示词、调用模型、结构化评分、流式讨论 | 不直接访问 MySQL,不绕过后端鉴权 |
这个边界听起来普通,放到 AI 应用里却很管用。模型输出只是一个“建议结果”,最终能不能落库、怎么展示、怎么结算,仍然要由业务系统判断。
五、AI 智能刷题是项目里最像 Agent 的部分
智能刷题页面长这样:
一次完整刷题链路如下:
用户选择题目分类 -> 后端按权重抽题 -> 用户提交答案 -> Java 后端请求 Python AI 服务评分 -> Python 评分 Agent 输出结构化结果 -> Java 后端校验结果并结算经验 -> 前端展示评分、问题点、参考答案和成长反馈 -> 用户围绕当前题继续追问 -> Python 讨论 Agent 通过 SSE 流式回复它不是一个开放式聊天框。它有明确阶段。
| 阶段 | 用户能做什么 | 后端怎么处理 |
|---|---|---|
QUESTIONING | 选择分类,请求出题 | 抽题并创建当前刷题会话 |
ANSWERING | 输入自己的答案 | 进入评分流程 |
DISCUSSING | 追问当前题,或下一题、重答 | 带上题目和评分上下文继续讨论 |
前端类型也把阶段写死了,避免页面变成一堆散落的布尔值:
exporttypePracticePhase='QUESTIONING'|'ANSWERING'|'DISCUSSING';exporttypePracticeAction='QUESTION'|'GRADING'|'DISCUSSION'|'TIP';这点对聊天类产品很有用。只要页面有“阶段”,按钮、输入框、提示语和接口行为就能跟着阶段走,后面不会越写越乱。
六、评分 Agent:不是让模型随便打个分
评分接口在 Python 服务里,路径是:
POST /internal/v1/practice/answer/gradeJava 后端会把这些字段传给 AI 服务:
{"userId":"用户ID占位符","questionCode":"SYS-RAG-0001","question":"RAG 的核心流程是什么?","questionType":"RAG","standardAnswer":"参考答案占位符","userAnswer":"用户答案占位符","modelConfig":{"modelName":"模型名占位符","modelProvider":"供应商占位符"}}评分 Agent 的提示词不是简单一句“请给这段答案打分”。它会明确告诉模型:
题目、参考答案、用户答案使用标签分隔。 只能根据用户答案判断命中点。 不能把参考答案里出现但用户没写的内容算成命中。 如果用户只是复述题目,必须给 0 分。 输出必须包含 score、hitPoints、missingPoints、problems、improvementAdvice。对应代码在:
ai-service/app/practice/prompts.py ai-service/app/practice/grading_agent.py ai-service/app/schemas/practice.py核心代码简化后大概是这样:
defgrade_answer(self,request:PracticeGradeRequest,trace_id:str)->PracticeGradeAgentResult:"""使用结构化模型非流式完成答案评分。"""messages=self._prompt_builder.build_grade_messages(request)result=self._model_factory.grading_model(request.modelConfig).invoke([SystemMessage(content=GRADE_SYSTEM_PROMPT),*messages])grading=self._structured_grading_result(result,request.standardAnswer)returnPracticeGradeAgentResult(grading=grading,metrics=metrics)这里做了两个工程化处理。
一是结构化输出。评分结果不是一段散文,而是一组固定字段。前端要展示命中点、缺失点、问题点、建议和参考答案,没有结构化结果就很难稳定。
二是观测指标。评分链路会记录 traceId、模型名、耗时、token 用量和错误分类。AI 功能一旦上线,排查问题不能只靠“用户说不好用”,日志里必须能看到是哪次调用、哪个模型、耗时多少、是否兜底。
七、讨论 Agent:带着题目上下文追问
评分结束后,用户可以继续问:
我这个答案扣分主要在哪里? 面试时怎么讲得自然一点? 能不能给一个真实项目里的回答版本? 这道题如果继续深挖,面试官可能问什么?这个功能看起来像聊天,但它不是普通聊天。
讨论 Agent 会拿到题目、参考答案、用户最近一次答案、评分摘要和当前题短期历史。也就是说,用户问“我哪里答得不好”,模型知道“我”指的是哪道题、哪次答案、哪次评分。
构造上下文的代码在PracticePromptBuilder:
defbuild_discuss_context_prompt(self,request:PracticeDiscussRequest)->str:"""构造本题讨论上下文提示词。"""grading_summary=request.gradingSummaryor"暂无评分摘要"last_answer=request.lastUserAnsweror"暂无"return(f"题目分类:{request.questionType}\n"f"题目:{request.question}\n"f"参考答案:{request.standardAnswer}\n"f"用户最近一次答案:{last_answer}\n"f"AI评分结果摘要:{grading_summary}\n""下面会继续给出当前题历史追问消息和本轮最新疑问。")这段代码不花哨,但解决了一个常见问题:AI 讨论不能丢上下文。
如果只把用户最后一句话发给模型,回复很容易变泛。把题目、答案和评分摘要都带上,模型才会围绕这次练习继续讲。
八、SSE 流式输出:别让用户等一整段答案
讨论接口使用 SSE 流式返回:
POST /api/v1/practice/messages/stream前端接收事件时,只关心三类事件:
exportfunctionsendPracticeMessageStream(payload:PracticeMessagePayload,handlers:PracticeMessageStreamHandlers,):Promise<void>{returnpostStream<PracticeMessagePayload>('/practice/messages/stream',payload,(event)=>{if(event.event==='message'){handlers.onMessageChunk(event.data);return;}if(event.event==='result'){handlers.onResult(JSON.parse(event.data)asPracticeMessageResult);return;}if(event.event==='error'){thrownewError(event.data||'发送失败');}});}Java 后端这边没有把流式请求扔到公共线程池,而是配置了专用线程池:
ai-learn-backend/src/main/java/com/earth/online/player/ailearn/common/config/AiStreamExecutorConfig.java它做了几件事:
专用线程名前缀:ai-sse-stream- 核心线程数、最大线程数、队列长度支持配置 线程池满时直接拒绝请求 暴露 active、pool.size、queue.size、rejected 等监控指标 关闭时等待正在读流的任务释放AI 流式接口比普通接口慢,也更贵。如果没有线程池、限流和断开取消,用户连续点几次按钮,就可能把后端拖住。
九、兜底策略:模型不可用时流程不能直接断
AI 项目最容易被低估的地方,是失败处理。
模型服务可能超时,Key 可能没配,供应商可能限流,结构化输出可能解析失败。用户不关心这些细节,他只知道“我点了提交,页面卡住了”。
项目里做了两层兜底。
第一层在 Java 后端。调用 Python AI 服务评分失败时,后端会回退到本地规则评分:
Optional<PracticeAiGradingResult>aiGradingResult=practiceAiClient.grade(userId,question,userAnswer,modelConfig);booleanfallbackUsed=aiGradingResult.map(PracticeAiGradingResult::fallbackUsed).orElse(true);GradingResultgradingResult=aiGradingResult.map(PracticeAiGradingResult::gradingResult).orElseGet(()->answerGradingPort.grade(userId,question.getId(),question.getQuestion(),question.getStandardAnswer(),List.of(question.getQuestionType()),userAnswer));第二层在 Python AI 服务。讨论 Agent 流式失败时,会生成本地兜底提示,而不是假装模型给出了完整回答。
这里我比较在意的是诚实。兜底不是“伪造一个完美 AI 答案”,而是告诉用户服务暂时不可用,并尽量提供可解释的替代结果。
十、成长体系:评分之后要留下痕迹
很多刷题系统只做到“本次得分”。这不够。
用户更关心的是:
这次比上次好吗? 我有没有突破历史最高分? 我练过哪些题? 哪类题最薄弱? 长期看有没有进步?项目因此做了成长体系:
经验规则没有设计成“每答一次就加固定分”。规则是:
本次新增经验 = max(0, 本次得分 - 历史最高分)举几个例子:
| 场景 | 历史最高分 | 本次得分 | 新增经验 |
|---|---|---|---|
| 首次回答 | 0 | 60 | 60 |
| 第二次变好 | 60 | 82 | 22 |
| 第三次退步 | 82 | 70 | 0 |
| 第四次追平 | 82 | 82 | 0 |
| 第五次突破 | 82 | 95 | 13 |
这套规则避免了重复刷同一道题刷经验,也能鼓励用户把答案越答越好。
后端结算代码在:
ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/PracticeGrowthSettlementService.java核心代码很好懂:
intpreviousBest=NumberUtils.toIntOrZero(oldStat.getBestScore());intscore=NumberUtils.clampPercentScore(gradingResult.score());intearnedExperience=Math.max(0,score-previousBest);practiceMapper.updateLockedStatAfterAnswer(oldStat.getId(),score);刷题记录页面也能看到长期复盘数据:
十一、学习路线:内容不是堆链接
项目里还有一块学习路线页面:
它的技术难度不高,但确实有必要。很多人学 AI Agent 时不是没资料,而是资料太散。
当前路线大致按这个顺序组织:
Python -> LangChain -> LangGraph -> RAG -> LlamaIndex -> 向量数据库 -> Agent 和 RAG 项目实践内容存放在:
ai-learn-web/src/content/learning-roadmap/AI应用开发学习路线和资料集.md前端用markdown-it渲染,用DOMPurify做 HTML 清洗,再用目录组件处理长文导航。相关文件:
ai-learn-web/src/pages/learning-roadmap/LearningRoadmapPage.vue ai-learn-web/src/components/common/MarkdownToc.vue ai-learn-web/src/utils/safeMarkdown.ts ai-learn-web/src/utils/markdownToc.ts ai-learn-web/src/styles/markdown.scss这里有个设计取舍:学习资料不要写死在 Vue 组件里。
资料是内容,组件是交互。用 Markdown 管内容,后面补资料、换图片、改章节,成本低很多。
十二、为什么说它也能学习 AGENTS.md
现在很多人都在用 AI 写代码,但很多项目会遇到同一个问题:AI 能写,但它不知道项目的边界。
比如它可能会:
随手新增一个中间件,却不补文档 把真实 Token 写进示例配置 直接修改历史 Flyway 脚本 前端风格突然变成另一个产品 把 Controller 写成万能业务类 为了“完整性”顺手加一堆测试,和项目约定冲突这个项目的AGENTS.md就是为了解决这类问题。它把项目要求写成明确规则。
节选几条:
新增或修改功能开始依赖 MySQL、Redis、Qdrant 等中间件时,必须同步补充中间件说明文档。 开源仓库内不得提交真实密码、Token、密钥、生产连接地址。 代码开发过程中禁止写单元测试代码,后续统一补。 前端界面 UI 风格要求清新简约大气。 涉及数据库修改,不要动历史 migration,而是新增新版本。这类规则对人有用,对 Agent 更有用。
我的感受是,想让 AI 编程助手稳定参与项目,不能只在聊天框里临时说一句“按我的项目规范写”。规则要沉淀进仓库,让每次任务都有可读的项目上下文。
这个仓库能学两件事:
看业务代码:学习一个 AI Agent 刷题项目怎么做 看 AGENTS.md:学习怎么给 AI 编程助手设置项目边界十三、项目文档也是工程的一部分
这个仓库里文档不是摆设。
主要文档如下:
| 文档 | 用途 |
|---|---|
README.md | 项目介绍、功能模块、截图和技术栈 |
QUICK_START.md | 本地启动步骤、环境变量和健康检查 |
doc/1.需求文档/1.1需求文档.md | 产品需求和功能边界 |
doc/2.架构设计/2.1架构设计文档.md | 三端架构、边界、部署和数据设计 |
doc/2.架构设计/2.2开发规范.md | DDD 分层、代码质量、配置和日志规范 |
doc/2.架构设计/2.3接口规范.md | API 路径、响应结构、错误码和内部接口 |
doc/中间件/MySQL.md | MySQL 本地和部署说明 |
doc/中间件/AI模型服务.md | 模型服务配置和安全说明 |
doc/4.功能梳理/【AI智能刷题】功能及代码梳理.md | AI 智能刷题调用链路和关键文件 |
我现在越来越觉得,项目文档不只是给后来的人看的,也是给 AI 看的。
当文档里写清楚“AI 服务不直接访问业务库”“内部接口使用X-Internal-Token”“新增中间件要补文档”,AI 在改代码时就更不容易乱跑。
十四、关键代码阅读路线
读源码时,我不建议从首页开始乱翻。可以按下面顺序看。
1. 先看启动入口和路由
ai-learn-web/src/router/index.ts ai-learn-backend/src/main/java/com/earth/online/player/ailearn/AiLearnBackendApplication.java ai-service/app/main.py先知道页面怎么进、后端怎么启动、AI 服务怎么暴露接口。
2. 再看刷题主链路
ai-learn-web/src/pages/practice-agent/PracticeAgentPage.vue ai-learn-web/src/pages/practice-agent/usePracticeChat.ts ai-learn-web/src/api/practice.ts ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/interfaces/PracticeController.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/PracticeService.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/PracticeGradingService.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/PracticeDiscussionService.java这条线能看到前端如何发起 SSE、后端如何根据阶段处理用户输入。
3. 然后看 AI 服务
ai-service/app/api/practice.py ai-service/app/practice/agent_service.py ai-service/app/practice/grading_agent.py ai-service/app/practice/discussion_agent.py ai-service/app/practice/prompts.py ai-service/app/practice/model_factory.py这里能看到提示词、结构化评分、讨论 Agent、供应商适配和兜底策略。
4. 最后看成长和题库
ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/QuestionSelectionService.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/practice/application/PracticeGrowthSettlementService.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/growth/application/GrowthService.java ai-learn-backend/src/main/java/com/earth/online/player/ailearn/growth/application/GrowthAwardService.java ai-learn-backend/src/main/resources/db/migration/这部分能看到抽题权重、经验结算、徽章发放和数据库迁移。
十五、本地启动大概需要哪些东西
项目本地跑起来需要:
| 环境 | 推荐版本 | 用途 |
|---|---|---|
| JDK | 17 | 运行 Spring Boot 后端 |
| Maven | 3.9.x 或兼容版本 | 构建后端 |
| Node.js | 20 LTS 或 22 LTS | 运行前端 |
| Python | 3.11+ | 运行 FastAPI AI 服务 |
| MySQL | 8.4 LTS | 保存用户、题库、刷题和成长数据 |
启动顺序可以按这个来:
# 1. 启动 MySQL,创建 ai_learn 数据库和本地账号# 2. 启动 AI 服务cd ai-service python-m venv.venv.\.venv\Scripts\Activate.ps1 pip install-r requirements.txt uvicorn app.main:app--host 0.0.0.0--port 8000# 3. 启动后端cd..\ai-learn-backend mvn spring-boot:run# 4. 启动前端cd..\ai-learn-web npm install npm run dev环境变量不要直接写真实值进仓库。示例配置里都应该使用占位符:
DATABASE_URL="jdbc:mysql://127.0.0.1:3306/ai_learn?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useSSL=false" DATABASE_USERNAME="本地MySQL用户名占位符" DATABASE_PASSWORD="本地MySQL密码占位符" JWT_SECRET="至少32字节本地JWT随机密钥占位符" AI_SERVICE_ENABLED="true" AI_SERVICE_BASE_URL="本地AI服务地址占位符,例如 127.0.0.1:8000" AI_SERVICE_TOKEN="AI_SERVICE_TOKEN本地占位符" AI_GRADING_BASE_URL="模型服务地址占位符,例如 模型服务域名/v1/chat/completions" AI_GRADING_API_KEY="AI_GRADING_API_KEY占位符" AI_GRADING_MODEL="LOCAL_RULE"如果没有配置真实模型,AI_GRADING_MODEL=LOCAL_RULE可以走本地规则兜底,至少能把基础刷题流程跑通。
十六、这个项目适合怎么用
想学 AI Agent,可以先按这个顺序用项目:
先看学习路线 -> 挑一个方向看热门面试题 -> 进入 AI 智能刷题自己答一遍 -> 看评分里的缺失点 -> 围绕当前题追问 -> 回个人中心看刷题记录想学工程实现,可以反过来:
先跑项目 -> 看一次完整刷题请求 -> 跟 PracticeController 到 PracticeService -> 跟 PracticeAiClient 到 FastAPI -> 看 grading_agent.py 的结构化评分 -> 看 discussion_agent.py 的流式输出 -> 看 GrowthSettlement 怎么结算经验想学怎么让 AI 编程助手参与项目,就看:
AGENTS.md doc/2.架构设计/2.2开发规范.md doc/2.架构设计/2.3接口规范.md doc/中间件/这些文档比“某次聊天里的口头要求”更稳定,也更适合反复约束 Agent。
十七、我从这个项目里得到的几个经验
第一,Agent 项目不要只关注模型调用。真正麻烦的是业务状态、失败兜底、日志、限流、配置安全和上下文管理。
第二,评分类功能一定要结构化输出。前端要展示命中点、缺失点和建议,后端也要校验结果,散文式回答不适合直接进业务链路。
第三,讨论 Agent 必须带上下文。题目、参考答案、用户答案、评分摘要和短期历史都要传进去,否则回答会飘。
第四,AGENTS.md不是装饰文件。规则写得越清楚,AI 编程助手越不容易越界。尤其是数据库迁移、中间件文档、敏感配置、测试约束这类内容,最好直接沉淀到仓库里。
第五,学习平台不能只堆资料。能答题、能反馈、能复盘,用户才更容易知道自己哪里没掌握。
十八、结尾
这个项目还会继续迭代,但现在已经可以当成一个可运行的 AI Agent 学习样例来看。
它一边回答“AI Agent 学习平台怎么做”,一边也回答另一个问题:当我们开始让 AI 参与写代码时,项目应该怎样把规则、文档、边界和上下文准备好。
后一个问题会越来越绕不开。
