一次 AI Native 项目收口:把 PaperPilot 从面试 Demo 推向企业级科研 Agent 产品

今天继续推进了我的个人项目 PaperPilot:论文 RAG 问答与多工具 Agent 系统

这个项目最初是为了支撑 AI 应用工程 / RAG / Agent 方向的求职面试,但随着最近在真实业务项目中的实习经历,我越来越明显地感觉到:未来的软件工程师不再只是“手写代码的人”,而是要能够基于 Cursor、Claude Code、Codex、Gemini CLI 等 AI Coding 工具,完成需求理解、架构设计、代码生成、审查、测试和持续演进的人。

所以我对 PaperPilot 的定位也发生了变化:

它不只是一个面试项目,而是希望逐步打磨成一个面向高校 / 科研场景的成熟企业级 AI Agent / RAG 产品,未来具备 SaaS 化能力。

但这个目标不能一口气完成。今天的重点不是堆功能,而是做一次工程收口:明确长期方向,修复关键稳定性问题,并整理项目文档。


一、项目当前定位

PaperPilot 当前是一个面向科研论文阅读和文献知识回溯的轻量级 RAG + 多工具 Agent 系统。

当前核心链路是:

上传论文
→ 重建知识库
→ 发起问答
→ Agent 选择工具
→ RAG 检索
→ LLM Rerank
→ 双层证据充分性判断
→ 基于上下文生成回答
→ 展示 Retrieved Context
→ 展示 Agent Trace
→ 保存历史会话

技术栈包括:

FastAPI:AI 推理服务,提供 /ask、/reload_kb、/clear
Django:产品壳层,负责上传、聊天页面、历史会话、Retrieved Context、Agent Trace 展示
LangGraph:Agent workflow 编排
RAGSystem:检索、rerank、证据充分性判断、生成回答
VectorStore:FAISS / Milvus Lite 抽象
SQLite:Django 侧历史会话保存
session_manager.py:FastAPI 侧短期内存上下文

项目当前最重要的设计原则是:

Django 不写 RAG / Agent / LangGraph 核心逻辑
FastAPI 保持 AI 推理服务边界
LangGraph 只负责编排
RAGSystem 负责检索、rerank、证据判断和回答生成
VectorStore 保持 FAISS / Milvus 可切换
Agent Trace 和 Retrieved Context 不能丢

二、重新明确长期目标:不是 Demo,而是科研 SaaS 产品雏形

今天新增并修订了 SAAS_ROADMAP.md,用于明确 PaperPilot 的长期演进方向。

这份文档的核心观点是:

PaperPilot 当前不是生产级 SaaS,但长期要向面向高校科研场景的 SaaS 化 AI Agent / RAG 平台演进。

这里有一个很重要的判断:

当前不应该马上重构成复杂 SaaS。
长期 SaaS 化又肯定绕不开。

所以路线应该分阶段:

Stage 0:当前 Demo / 面试资产
保持 /ask、/reload_kb、Agent Trace、Retrieved Context 可运行

Stage 1:作品级科研 RAG Agent
提升 RAG 质量、source-aware retrieval、eval 闭环、页面演示体验

Stage 2:SaaS 原型
引入用户、项目空间、文档库、任务状态、对象存储、Milvus Server、Redis 等

Stage 3:企业级 SaaS
权限、租户隔离、监控、日志、成本统计、CI/CD、Docker/K8s、SLA 等

同时,文档里也特别强调:

SaaS 化不是堆中间件。
真正重要的是:
用户体系
项目空间
权限边界
数据隔离
任务状态
可观测性
稳定部署

这一步对后续 AI Coding 很重要,因为 Cursor、Claude Code、Codex、Gemini CLI 都会读项目文档。如果不提前写清楚长期方向,它们很可能只把 PaperPilot 当成普通 Demo 来优化;如果写得太激进,它们又可能直接建议引入 K8s、微服务、多租户,导致项目过早复杂化。


三、AI Coding 工具分工

今天也把后续 AI Native 开发的工具分工固定了下来:

Cursor:主力改代码
用于读项目、做小步重构、改文件、跑验证。

Claude Code:架构审查 + 复杂改造判断
用于判断 source-aware retrieval 怎么设计、Agent Trace 怎么保留、哪些地方不能动。

Codex:单点任务执行
用于补测试、改 bug、生成脚本、整理 API。

Gemini CLI:代码审查 + 风险发现
用于检查 Cursor / Claude Code 的方案是否过度设计、是否破坏项目分层。

这个分工的意义是:不要让多个 AI 工具同时改代码,而是让它们各自承担不同角色。

今天的实际流程也是这样:

Cursor 负责生成和修改文档、执行代码小步重构
Claude Code 负责审查 SaaS 路线是否符合当前架构
Gemini CLI 负责检查是否存在过度设计风险
我负责最终判断是否合并、是否继续推进

这比“把项目丢给 AI 随便优化”要稳定得多。


四、P0-1:FastAPI startup 迁移到 lifespan

第一个代码任务是把 FastAPI 的:

@app.on_event("startup")

迁移到新的 lifespan 写法。

原来的启动逻辑大概是:

服务启动
→ load_pdfs(DATA_DIR)
→ process_documents(docs)
→ RAGSystem(chunks)
→ rag.build_index()
→ AgentWorkflow(TOOLS, rag=rag)

这部分逻辑在 /reload_kb 里也有重复,所以这次重构抽出了一个内部函数:

def _init_rag_and_workflow() -> tuple[int, int]:
    """Load PDFs, build RAG index, and initialize AgentWorkflow. Return (total_docs, total_chunks)."""
    ...

然后 lifespan 和 /reload_kb 都复用它。

重构后:

@asynccontextmanager
async def lifespan(app: FastAPI):
    _init_rag_and_workflow()
    yield
    logger.info("FastAPI lifespan shutdown.")

app = FastAPI(lifespan=lifespan)

这次修改严格限制在 app/main.py,没有动:

RAGSystem
LangGraph nodes
Django
VectorStore
Agent Trace
/ask 响应结构
/reload_kb 响应结构

验收时测试了:

Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8000/reload_kb"

返回:

status: success
total_docs: 3
total_chunks: 35

又测试了:

Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8000/clear/p0-lifespan"

以及 /ask

Invoke-RestMethod `
  -Method POST `
  -Uri "http://127.0.0.1:8000/ask" `
  -ContentType "application/json" `
  -Body '{"session_id":"p0-lifespan","question":"What is the main contribution of paper1?"}'

返回结果中包含:

answer
chunks
agent_trace
tool_used=rag
context_sufficient=True
fallback_used=False

说明主链路没有被破坏。

这次提交:

git commit -m "refactor: migrate FastAPI startup to lifespan"

五、修复 Milvus Lite 下 /reload_kb 反复失败的问题

第二个问题更实际。

.env 中设置:

VECTOR_STORE=milvus

调用 /reload_kb 时,Milvus Lite 会在 Windows 下报错:

[WinError 183] 当文件已存在时,无法创建该文件
manifest.json.tmp -> manifest.json

最初我每次都只能手动删除根目录下的 milvus_demo.db/,然后重新启动服务。这显然不符合 /reload_kb 自动重建知识库的设计。

分析后发现问题在于:

MilvusVectorStore.build()
每次 build 都会 drop_collection
Windows + Milvus Lite 下 drop_collection 会触发本地 manifest 文件重命名
旧 MilvusClient 可能仍占用本地 DB 文件
最终导致 drop_collection 失败

这不是 RAG 主链路的问题,而是 VectorStore 层的 Milvus Lite 重建策略问题。

修复策略是:

按 uri 管理当前进程内 active MilvusClient
新建同 uri 的 MilvusVectorStore 前,尝试关闭旧 client
drop_collection 增加轻量 retry
仍保持固定 collection_name + 全量重建
不自动删除 milvus_demo.db
不引入 Milvus Server
不改 RAGSystem / API / Agent Trace

修改范围只在:

app/vector_store/milvus_store.py

测试方式:

Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8000/reload_kb"
Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8000/reload_kb"
Invoke-RestMethod -Method POST -Uri "http://127.0.0.1:8000/reload_kb"

连续 3 次都返回:

status: success
total_docs: 3
total_chunks: 35

然后测试 /ask

Invoke-RestMethod `
  -Method POST `
  -Uri "http://127.0.0.1:8000/ask" `
  -ContentType "application/json" `
  -Body '{"session_id":"milvus-reload-test","question":"What is the main contribution of paper1?"}'

返回中仍然包含:

chunks
source
text
distance
retrieval_rank
agent_trace
tool_used=rag
context_sufficient=True

说明 Milvus Lite 下知识库重建和 RAG 问答都恢复正常。

这次提交:

git commit -m "fix: stabilize Milvus Lite reload"

六、整理项目文档结构

今天还做了项目文档整理。

之前根目录里有很多长文档,看起来有些混乱。整理后的原则是:

根目录保留入口文件:
README.md
AGENTS.md
requirements.txt
.gitignore
app/
django_shell/
data/
tests/
docs/

长文档移动到 docs/project/
eval 相关文件移动到 docs/eval/

整理后结构大致是:

docs/project/
  PROJECT_CONTEXT.md
  PROJECT_CONTEXT_FOR_CURSOR.md
  PRODUCT_VISION.md
  SAAS_ROADMAP.md
  AI_AGENT_WORKING_PROMPTS.md

docs/eval/
  eval_questions.json
  eval_run_result_top_k20.md
  eval_run_result_top_k40.md

AGENTS.md 仍保留在根目录,因为它是给 AI Coding Agent 每次开工前快速读取的规则文件。

同时 .gitignore 中也需要确保不提交:

.env
.venv/
__pycache__/
*.pyc
milvus_demo.db/
django_shell/db.sqlite3

这次文档整理没有改任何 Python 代码,只是移动和整理项目说明文档。

提交:

git commit -m "docs: organize project documentation"

七、今天暴露出的下一个 P0 问题:会话来源不一致

今天还发现一个架构问题:项目里现在有两套会话管理。

第一套是 Django SQLite:

ChatSession
ChatMessage

它负责页面历史展示和持久化。

第二套是 FastAPI 侧的:

app/session_manager.py

它用内存保存最近 3 轮对话,用于 LLM 推理上下文。

这两套不是完全重复,原始职责不同:

Django SQLite:产品层长期历史
FastAPI session_manager:推理层短期上下文

但问题在于它们现在会分裂:

用户连续问几轮
Django 页面能看到完整历史
FastAPI 内存也有最近 3 轮

如果 FastAPI 重启
Django 页面历史还在
但 LLM 推理上下文丢失

这会导致:

页面看起来有历史
模型实际不知道前文

所以下一步 P0 任务应该是:

让 Django SQLite 成为 Django 页面请求的历史来源,FastAPI session_manager 退化为旧客户端 / 直接 API 调用的 fallback。

计划是:

/ask 请求模型新增可选 chat_history 字段
如果 req.chat_history 存在,FastAPI 优先用它
否则继续用 session_manager.get_history(session_id)

Django chat/views.py 在用户提问前,从 ChatMessage 取最近 N 轮
转换成 [{"role": "user", "content": "..."}, {"role": "assistant", "content": "..."}]
传给 FastAPI /ask

不要删除 session_manager.py
不要引入 Redis / PostgreSQL
不要改 Agent Trace / Retrieved Context / /ask 响应结构

这个问题会作为明天的 P0-3 继续推进。


八、今天的收获

今天这轮推进的意义,不只是改了几个文件,而是把 PaperPilot 从“能跑的 Demo”往“可持续演进的企业级项目”推进了一步。

今天完成了:

1. 明确长期 SaaS 化方向,但不马上重构成复杂 SaaS
2. 用 SAAS_ROADMAP.md 固定项目长期路线
3. 完成 FastAPI lifespan 改造
4. 修复 Milvus Lite 下 /reload_kb 不稳定问题
5. 整理项目文档目录
6. 明确下一步会话一致性问题

更重要的是,今天形成了一种比较稳定的 AI Native 开发节奏:

先写清楚项目目标
再让 Cursor 做小步修改
让 Claude Code 做架构审查
让 Gemini CLI 做风险审查
自己判断是否采纳
每次只做一个任务
每次都验证
每次都提交

这和传统开发最大的不同是:AI 可以加速很多细节工作,但真正决定项目质量的,仍然是人对架构边界、产品方向和风险的判断。

PaperPilot 还远远不是成熟产品,但它已经不再只是一个简单的 RAG Demo。接下来要做的,是沿着“科研 AI 工作台 / 高校科研 SaaS”的方向,继续一小步一小步推进。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐