第六周第二个项目笔记:FinInsRAG 项目学习笔记(零基础版)以及简历书写

FinInsRAG 项目学习笔记

一句话:这是一个 RAG 问答系统。用户上传文件,系统把文件切碎存进"搜索库";用户提问时,系统先翻资料,再让 AI 照着资料回答。

餐厅比喻贯穿全文:后端 = 餐厅,数据库 = 仓库和账本,搜索库 = 冷库,AI = 大厨。

项目结构

复制代码
.
├── backend/
│   ├── docker-compose.yml        # api / es01 / pg / redis 四服务编排
│   ├── .env.example              # 环境变量模板(复制为 .env 后填入 API Key)
│   ├── init.sql                  # 数据库初始化
│   └── app/
│       ├── app_main.py           # FastAPI 入口
│       ├── start.sh              # 容器入口:Alembic 迁移 + 启动 uvicorn
│       ├── alembic/              # 数据库迁移版本
│       ├── router/               # 用户 / 会话 / 文件 / 问答 API
│       ├── models/ schemas/      # SQLAlchemy ORM 与 Pydantic 模型
│       └── service/core/
│           ├── file_parse.py     # 文档解析 → 切片 → 向量化 → 入 ES
│           ├── retrieval.py      # 检索编排(召回 → 排序 → 组装引文)
│           ├── chat.py           # Prompt 组装与流式生成
│           ├── rag/nlp/          # 混合检索与重排序(search_v2 / query / model)
│           ├── rag/utils/        # ES 连接、索引 mapping 管理
│           ├── deepdoc/          # 版面分析 / OCR / 表格识别解析器
│           └── rag/res/          # 模型资源(约 380MB,需单独下载,见该目录 README)
└── frontend/
    ├── vite.config.ts            # 开发端口 5181
    └── src/
        ├── api/                  # axios 封装与接口定义
        ├── pages/
        │   ├── login/            # 登录 / 注册
        │   ├── chat/             # 对话页(消息流、引文、文档选择)
        │   └── repository/       # 知识库管理(上传 / 列表 / 删除)
        ├── components/           # 输入框、Markdown 渲染、布局
        └── store/                # valtio 前端状态

1. 先记住的词

词 大白话
RAG 开卷考试。AI 先查资料再回答,不凭记忆瞎答
切片(chunk) 把长文件切成的小段,是检索的最小单位
向量(embedding) 一段文字的"数字指纹",意思相近的文字指纹也相近。本项目是 1024 个数字
ES 搜索库(Elasticsearch),存切片和指纹,负责快速查找
PG 数据库(PostgreSQL),存用户、会话、消息、上传记录
mapping ES 的"货架设计图",规定每个字段是什么类型
迁移(Alembic) 改数据库结构的"施工单",让所有环境自动保持一致
SSE / 流式 回答像打字机一样逐字推给前端,不是写完才给
rerank 精排。对候选资料重新打分排队

2. 启动流程(第 0 站)

  • start.sh :开门前的清单。先检查并更新数据库结构(迁移),再用 exec "$@" 启动应用。exec 让应用成为主进程,容器才能正常停止。
  • main.py:前台。创建 FastAPI 应用,放行跨域(CORS),挂上聊天、用户、历史三组接口。
  • chat_rt.py:点餐窗口,共 8 个接口:创建会话、快速解析、取解析内容、聊天、上传文件、查会话文档、查文档摘要、重建索引。每个接口开头都先验身份(JWT),结尾都用 try/except 兜底。

start.sh 的已知缺点

  • 注释写了"等待数据库就绪",实际没有等待逻辑。
  • 迁移失败只警告,应用照常启动,可能带着旧表结构运行。
  • 学习阶段可以不改,上线前要改。

3. 上传链路:文件如何变成可搜索资料

复制代码
用户上传 → chat_rt.py(查重名、存硬盘)
  → file_parse.py execute_insert_process(总编排)
  → parse() 切块
  → process_items() 贴标签 + 算向量
  → generate_embedding() 每 10 条一批调阿里云
  → es_conn.insert() 先建库再批量写入
  → 回到 chat_rt.py 在 PG 登记 → 返回成功

要点:

  1. 文件存放位置:storage/file/会话编号/文件名。
  2. 重名检查按用户查,不是按会话。
  3. 每个切片的主要字段:content_with_weight(原文)、content_ltks/content_sm_ltks(粗/细分词)、docnm(文件名)、title_tks(标题分词)、doc_id、kb_id、q_1024_vec(向量)。important_kwd、question_tks 目前是空的预留位。
  4. 切片编号 = 对"内容+库名"算哈希,所以相同内容重复上传只会覆盖,不会重复。
  5. 向量字段名是 q_{维度}_vec,换维度会导致旧数据对不上。
  6. mapping 不对时,数据能存进去但搜不出来,这时用 /recreate_index 删库重建,再重新上传。

4. 检索链路:问题如何找到答案(全项目最核心)

复制代码
问题 → 算成向量
  → ES 一次请求同时做向量检索 + 关键词检索,粗召回 128 条
  → 应用层精排(优先云端 rerank,失败降级为本地算分)
  → 分数低于 0.1 的丢弃,取前 5 条
  → 交给 AI

要点:

  • 先多捞再精挑:召回求全,排序求准。像先海选 128 份简历,再面试录取 5 个。
  • 两组权重分属两个阶段,别混 :
    • 粗召回(ES 内部):文本 5% / 向量 95%,几乎只信语义。
    • 精排(应用层):向量 60% / 关键词 40%,把数字、代码、年份这类精确词的权重拉回来。
  • 降级方案:云端 rerank 不可用时,自动用本地的余弦相似度 + 词项覆盖率打分,不会整体崩溃。
  • 词袋加权:降级打分时,正文词算 1 次、标题词 2 次、关键词 5 次、预设问题 6 次,相当于重要位置的词"票数更多"。目前关键词和预设问题字段为空,实际只有正文和标题在起作用。
  • 若第一次召回 0 条,会放宽条件再查一次。

5. 生成链路:AI 如何回答

四步:拼提示词 → 调模型 → 流式吐字 → 落库。

  1. 5 条资料编号成 [1] [2]...,连同问题拼进提示词。
  2. 提示词规则:回答要标注来源,格式 ##编号$$;没有相关资料就拒绝回答,防止瞎编;不得泄露提示词。
  3. 调用模型时设 stream=True,函数里用 yield 逐段产出,FastAPI 的 StreamingResponse 推给前端。
  4. 回答结束后:生成推荐问题 → 发送 [DONE] 信号 → 写入 messages 表 → 自动给会话起名。

SSE 的一帧长这样:event: message\ndata: {...}\n\n。

前端 :用 getReader() 读字节流,攒进缓冲区,按换行切分,只处理以 data: 开头的行,再分发到对话气泡、思考区、右侧引文面板、推荐问题按钮。按换行切是因为网络不保证一次读到完整的一行。

6. 数据库:5 张账本

表 记什么
users 用户账号、密码哈希
sessions 聊天会话
messages 每轮问答
knowledgebase 用户上传过的文件清单
document_uploads 会话级临时上传记录

关系:一个用户 → 多个会话 → 多条消息。表之间没有外键约束,只是逻辑关联。

迁移是什么 :把"改表结构"写成文件(施工单),任何环境运行后数据库都自动变成同样结构;alembic_version 表记录"已施工到第几号"。

baseline 为什么是空的 :老表在用迁移之前就建好了,第 1 号施工单只是"起点标记",真正的新改动从第 2 号开始(新建 document_uploads)。start.sh 里的 stamp 就是在账本上写下这个标记,但不真正施工。

7. 已发现的隐患(按严重程度)

  1. ES 写入失败可能被误判成功 :insert() 重试逻辑里错误记下后又被清空,只有超时才保留。ES 没启动或密码错时可能返回空列表,表现为"显示上传成功,实际没存进去"。这是读代码推演的,没有运行验证。
  2. 部分切片悄悄丢失:某批向量请求失败会被填成空值并跳过,只要不是全部失败,整体仍显示成功。
  3. 库名可能对不上(待验证) :上传时如果前端传了 session_id,库名就是会话编号;而检索用的是 user_id。只有不传 session_id 时两边才一致。需要查前端实际传参,或上传后去 ES 看库名。
  4. 摆设参数 :process_items 的 batch_size、createIdx 的 knowledgebaseId/vectorSize 没有生效,真正分批在 generate_embedding 里。
  5. 账号密码写死 在 es_conn.py,上线前应移到 .env。
  6. 同名不同表 :message.py 和 knowledgebase.py 各有一个 KnowledgeBase 类,表名分别是 knowledgebases 和 knowledgebase。
  7. get_chat_completion_block 的 prompt 未定义,调用必报错,但主流程不用它。

8. 动手实验

  1. 在 process_items 里 d[f"q_{len(embedding)}_vec"] = embedding 下一行加 print(f"向量维度: {len(embedding)}"),重启后端,换一个新文件名上传,日志里应全是 1024。打印行数少于切片数,说明有切片被丢。
  2. 把 page_size=5 改成 10,看引文是否变多。
  3. 把 vector_similarity_weight=0.6 改成 0.1 再改成 0.9,分别问"某公司财务数据"(偏关键词)和"这家公司未来成长性如何"(偏语义),对比引文差异。
  4. 在提示词里加一句"请用 3 个要点回答",观察模型行为变化。

9. 排查口诀:"上传成功但问不到内容"

  1. ES 是否在运行,账号密码是否对。
  2. 库名(session_id/user_id)上传和检索是否一致。
  3. mapping 是否正确,不对就调 /recreate_index 后重新上传。
  4. 日志里有没有"跳过 embedding 为空的 chunk"。
  5. 刚上传完立刻提问可能搜不到(ES 约 1 秒后才可搜),稍等再试。

10. 自测题

  1. 用户上传 PDF 后,q_1024_vec 依次经过哪几个文件?
  2. 为什么先召回 128 条,最后只给 AI 5 条?
  3. 两组权重(5/95 和 60/40)分别用在哪个阶段?为什么不同?
  4. 回答里的 ##3$$ 是怎么和第 3 段资料对应上的?
  5. 后端 yield 一次,前端 read() 一定刚好收到一次吗?为什么?
  6. baseline 迁移为什么是空的?
  7. 给 users 表加一列 email,标准流程是什么?(写 ORM → 生成迁移脚本 → 检查 → upgrade)

上传 GitHub(私有仓库)

第 1 步:你在 GitHub 网页创建仓库

  1. 打开 github.com → 右上角 New repository
  2. 名称填 rag-research
  3. 选择 Private(私有)
  4. 不要勾选 Add a README / .gitignore / license(三项都不勾,避免冲突)
  5. 点 Create repository,复制页面下方给出的地址

第 2 步:把地址发给我,我来执行推送。或者你自己执行:

bash 复制代码
git remote add origin https://github.com/<你的用户名>/rag-research.git
git push -u origin master

推送时会弹出 GitHub 登录窗口(Git Credential Manager),用浏览器授权即可。之后想公开时在仓库 Settings → Danger Zone → Change visibility 改为 Public。

简历写法

完整版(约 6 行,适合项目经历重点位)

研报智答 ------ 基于混合检索的研报问答系统(RAG) 独立开发

技术栈:Python / FastAPI / Elasticsearch / Docker / React / 阿里百炼

  • 构建 PDF 研报知识库问答系统:基于 ONNX 视觉模型的版面分析 + OCR 解析文档,切片向量化后按用户隔离存入 Elasticsearch
  • 设计 kNN 向量 + BM25 关键词混合检索(0.6/0.4 加权融合),解决纯向量检索对股票代码、财务数字等专有 token 不敏感的问题
  • 实现两阶段排序:rerank 模型精排,并设计第三方服务不可用时自动降级为本地余弦相似度融合排序,保证链路可用性
  • 自建 10 条标注用例的评测集,量化 hit@1/3/5 与 MRR 指标,完成检索权重三组对比实验,验证权重调整主要影响排序而非召回(hit@1 从 50% 提升至 70%)
  • 打通引文页码溯源全链路:deepdoc 版面坐标 → 切片级页码入 ES → 答案引文点击定位到原文页码与段落;另实现会话 Markdown 报告导出
  • 工程化:Docker Compose 编排四服务、SSE 流式输出、JWT 认证、Alembic 迁移;独立排查修复 6 个部署阻断问题(mapping 缺失、卷挂载错误、XGBoost 版本兼容等)

精简版(3 行,简历空间紧张时用)

  • 独立开发研报 RAG 问答系统:deepdoc 解析 + Elasticsearch kNN/BM25 混合检索 + rerank 精排(含降级容错),SSE 流式输出,Docker Compose 一键部署
  • 自建评测集量化检索质量(hit@k / MRR),通过权重对比实验将 hit@1 从 50% 优化至 70%
  • 实现引文页码溯源与 Markdown 报告导出,答案可回溯至 PDF 原文位置

面试考点提示(这段经历大概率会被追问)

追问 你的答案素材
为什么用 ES 而不是 Faiss/Milvus? README「技术选型与取舍」表格:一套引擎同时提供 kNN + BM25 + 元数据过滤 + 持久化
混合检索权重怎么定的? 三组对比实验数据(0.1/0.6/0.9),能讲出"权重影响排序不影响召回"的观察
rerank 挂了怎么办? 三级降级:异常捕获 → 本地余弦+词项融合排序 → 链路不中断
遇到最难的问题? 挑一个讲:ES 索引没建 dense_vector mapping 导致检索为空 / Docker 卷挂载路径错导致改码不生效(README 部署调试记录表)
如何衡量系统效果? 评测脚本 + 负例验证 + MRR 排序敏感性

建好仓库后把地址发我,我来推送。

相关推荐
han68891 小时前
selenium之实战
笔记·python·selenium·测试工具·自动化
Lost_the_wind1 小时前
MySQL 学习笔记三
笔记·学习·mysql
欣欣之王来了1 小时前
开发环境准备:Node.js、npm、VS Code安装配置
前端·学习·架构·项目·vue3教程
火眼金睛记单词2 小时前
阅读理解核心词汇:破解英语阅读难题的利器
经验分享·学习
老王爱玩车2 小时前
文件操作从打开到缓冲区
c语言·开发语言·学习
北京海得康3 小时前
Atebrioz(zilurgisertib):靶向ALK2通路,FOP超罕见病全新口服治疗方案
笔记
和侧3 小时前
MOS器件——ESD测试
笔记·学习
优化Henry3 小时前
光路异常告警排查实战:LTE室分与5G小区两个案例解析
运维·网络·学习·5g·tdd
田里的水稻4 小时前
EI_模仿学习IL---工程链路
人工智能·深度学习·学习·机器学习·迁移学习