32.一次 AI Native 项目收口:把 PaperPilot 从面试 Demo 推向企业级科研 Agent 产品
一次 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”的方向,继续一小步一小步推进。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)