【Agent项目】既是一个Agent项目,又能用来学习Agent
项目GitHub地址:https://github.com/Earth-OL-Player/ai_learn_project
文章目录
-
- 一、先说这个项目到底是什么
- 二、为什么我没有只做一个题库
- 三、项目模块总览
- 四、整体架构:Java 管业务,Python 管 AI
- 五、AI 智能刷题是项目里最像 Agent 的部分
- 六、评分 Agent:不是让模型随便打个分
- 七、讨论 Agent:带着题目上下文追问
- 八、SSE 流式输出:别让用户等一整段答案
- 九、兜底策略:模型不可用时流程不能直接断
- 十、成长体系:评分之后要留下痕迹
- 十一、学习路线:内容不是堆链接
- 十二、为什么说它也能学习 AGENTS.md
- 十三、项目文档也是工程的一部分
- 十四、关键代码阅读路线
- 十五、本地启动大概需要哪些东西
- 十六、这个项目适合怎么用
- 十七、我从这个项目里得到的几个经验
- 十八、结尾
一、先说这个项目到底是什么
最近我把一个 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 |
追问当前题,或下一题、重答 | 带上题目和评分上下文继续讨论 |
前端类型也把阶段写死了,避免页面变成一堆散落的布尔值:
export type PracticePhase = 'QUESTIONING' | 'ANSWERING' | 'DISCUSSING';
export type PracticeAction = 'QUESTION' | 'GRADING' | 'DISCUSSION' | 'TIP';
这点对聊天类产品很有用。只要页面有“阶段”,按钮、输入框、提示语和接口行为就能跟着阶段走,后面不会越写越乱。
六、评分 Agent:不是让模型随便打个分
评分接口在 Python 服务里,路径是:
POST /internal/v1/practice/answer/grade
Java 后端会把这些字段传给 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
核心代码简化后大概是这样:
def grade_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)
return PracticeGradeAgentResult(grading=grading, metrics=metrics)
这里做了两个工程化处理。
一是结构化输出。评分结果不是一段散文,而是一组固定字段。前端要展示命中点、缺失点、问题点、建议和参考答案,没有结构化结果就很难稳定。
二是观测指标。评分链路会记录 traceId、模型名、耗时、token 用量和错误分类。AI 功能一旦上线,排查问题不能只靠“用户说不好用”,日志里必须能看到是哪次调用、哪个模型、耗时多少、是否兜底。
七、讨论 Agent:带着题目上下文追问
评分结束后,用户可以继续问:
我这个答案扣分主要在哪里?
面试时怎么讲得自然一点?
能不能给一个真实项目里的回答版本?
这道题如果继续深挖,面试官可能问什么?
这个功能看起来像聊天,但它不是普通聊天。
讨论 Agent 会拿到题目、参考答案、用户最近一次答案、评分摘要和当前题短期历史。也就是说,用户问“我哪里答得不好”,模型知道“我”指的是哪道题、哪次答案、哪次评分。
构造上下文的代码在 PracticePromptBuilder:
def build_discuss_context_prompt(self, request: PracticeDiscussRequest) -> str:
"""构造本题讨论上下文提示词。"""
grading_summary = request.gradingSummary or "暂无评分摘要"
last_answer = request.lastUserAnswer or "暂无"
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
前端接收事件时,只关心三类事件:
export function sendPracticeMessageStream(
payload: PracticeMessagePayload,
handlers: PracticeMessageStreamHandlers,
): Promise<void> {
return postStream<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) as PracticeMessageResult);
return;
}
if (event.event === 'error') {
throw new Error(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);
boolean fallbackUsed = aiGradingResult.map(PracticeAiGradingResult::fallbackUsed).orElse(true);
GradingResult gradingResult = 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
核心代码很好懂:
int previousBest = NumberUtils.toIntOrZero(oldStat.getBestScore());
int score = NumberUtils.clampPercentScore(gradingResult.score());
int earnedExperience = 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 参与写代码时,项目应该怎样把规则、文档、边界和上下文准备好。
后一个问题会越来越绕不开。
更多推荐



所有评论(0)