从零开始做一个 AI Agent:以 Java Web RAG 学习助手为例
本文是一套面向技术博客专栏的完整教程。它不是只讲概念,而是以当前项目为真实案例,从一个最小后端 API 出发,逐步扩展到课程资料知识库、RAG 问答、轻量级 Agent Harness、工具注册表、执行 Trace、答案校验、学习记忆和前端工作台。
示例项目定位:
text
面向 Java Web 课程资料的 RAG Agent 学习助手
用户可以上传课程课件、实验指导书、代码文件和配置文件。系统会解析资料、切块、建立检索索引;用户提交学习任务后,Agent 会判断任务类型、规划步骤、调用工具、生成回答、校验引用,并把执行过程展示给前端。
专栏总目录
- 项目总览:从普通问答到课程学习 Agent
- 技术栈和工程结构:FastAPI、Vue、SQLite、RAG、Agent Harness
- 后端基础设施:配置、数据库、模型和 Schema
- 资料上传:文件存储、文档记录和重建索引
- 文档解析:PDF、Word、PPT、Markdown、代码文件如何进入系统
- 文本切块:chunk、metadata、语义类型和 embedding 状态
- 检索系统:关键词检索、向量检索、query rewrite 和 rerank
- LLM 与 Embedding Provider:stub、OpenAI-compatible API 和本地模型接入
- Chat 问答入口:兼容普通问答,同时接入 Agent 主链路
- Agent Harness:一次 Agent run 的生命周期
- Planner、Executor 与 Tool Registry:Agent 如何规划和调用工具
- Agent 校验、安全边界与资料不足处理
- Agent 记忆:短期上下文、长期学习画像和推荐下一步
- 前端工作台:资料管理、Agent 任务、Trace、历史和健康状态
- 测试、局限和演进:从教学项目走向生产级 Agent SaaS
第 7 篇:检索系统:关键词检索、向量检索、query rewrite 和 rerank
7.1 检索入口
文件:
text
backend/app/services/retrieval.py
核心函数:
python
def retrieve_chunks(question: str, top_k: int = 5) -> list[RetrievedChunk]:
整体流程:
text
question
-> rewrite_retrieval_queries
-> keyword scoring
-> vector search
-> merge ranked chunks
-> rerank_chunks_with_llm
-> top_k
7.2 RetrievedChunk
检索结果使用 RetrievedChunk,包含:
text
chunk_id
document_id
content
source_title
source_path
source_page
language
score
retrieval_source
retrieval_source 用于区分:
text
keyword
vector
7.3 中文和代码友好的 tokenize
token 规则:
python
TOKEN_RE = re.compile(r"[A-Za-z0-9_]+|[\u4e00-\u9fff]+", re.UNICODE)
它能同时处理:
text
LoginServlet
doPost
web.xml
Servlet 生命周期
登录流程
中文会拆成单字和 bigram,英文和代码标识符会转小写。
7.4 LLM query rewrite
rewrite_retrieval_queries 会把原始问题改写为更适合检索的查询。
例如:
text
原问题:登录功能为什么跳转失败?
可能改写:
1. 登录 跳转 失败
2. LoginServlet doPost redirect
3. web.xml servlet mapping
4. session login error
如果 LLM 不可用,就返回原始问题作为 fallback。
7.5 关键词检索
关键词检索会对每个 chunk 计算 overlap:
text
query tokens
vs
content tokens + source_path tokens + metadata tokens
得分:
text
len(overlap) / sqrt(content_token_count)
这个公式避免长文本天然得分过高。
7.6 向量检索
文件:
text
backend/app/services/vector_store.py
向量检索流程:
text
问题 -> embedding
-> 遍历已 embedded chunks
-> cosine_similarity
-> 按分数排序
当前向量存在 SQLite 的 JSON 字段里。按照路线B实现,这适合教学和小规模 MVP,不适合大规模生产。
路线 A :专用向量库(pgvector / Milvus / Chroma)
PostgreSQL + pgvector:专门加了 vector 类型字段,数据库原生支持向量相似度计算(余弦、欧氏距离),SQL 里直接 ORDER BY embedding <-> '...' 做检索,数据库底层优化向量索引,适合百万级以上向量。向量 + 元数据统一持久化,数据库层内置向量索引与相似度计算。
路线 B :SQLite 存 JSON 向量,SQLite JSON 方案是全量拉取向量到 Python 内存,代码里手动算相似度;检索逻辑全在 Python 代码:全量 / 过滤后取出所有向量,loads 转数组,numpy 手动算余弦相似度;
路线 C:FAISS 向量检索库(内存索引,配套外部数据库存业务数据);代表:faiss(搭配 SQLite/PostgreSQL 做元数据存储);核心特点:只做高速向量计算,不负责持久化、不存业务元数据。
- 小规模 demo:SQLite / Numpy
- 中等 RAG 应用:Chroma
- 大规模高性能向量检索:FAISS / Milvus / Qdrant
7.7 LLM rerank
混合检索后会把候选 chunk 交给 LLM rerank:
text
question
candidates
-> LLM 返回 chunk_ids 顺序
如果 LLM 不可用或返回无效 JSON,就使用原始排序。
7.8 为什么这个检索设计适合课程 Agent
课程资料同时包含自然语言和代码。纯向量检索可能漏掉精确类名,纯关键词检索又不理解语义。混合检索能同时照顾:
text
课程概念
实验步骤
代码文件
配置文件
报错信息
复习题线索