从 RAG 到 Agent:我在 PaperPilot 中引入智能体的完整实践

一、为什么要从 RAG 升级到 Agent?
PaperPilot 是一个基于 RAG(检索增强生成)架构的 AI 文献助手,核心功能是帮用户检索文献、管理引用、生成开题报告。在之前的版本中,我已经实现并优化了三种检索策略(向量检索、混合检索、重排序),回答质量有了明显提升。
但在实际使用中,我发现传统 RAG 有一个绕不过去的瓶颈:它只会”检索—生成”这一条固定流水线。
举个真实场景:用户问”帮我对比一下近三年 Transformer 在医学影像方向的研究进展,并给出值得投稿的方向建议”。传统 RAG 的做法是——把整句话拿去检索一次,召回几篇文献,然后让大模型基于这些文献生成回答。问题显而易见:
1.不会拆解任务:这个问题其实包含”检索文献→筛选近三年→按方法分类→对比分析→给出建议”多个步骤,单次检索根本覆盖不了;
2.不会自我修正:检索结果不理想时,它不会换关键词重试,而是”将错就错”地硬答;
3.不会使用工具:查不到的信息不会调用外部搜索,算不了的引用格式不会调用格式化工具。
于是我把 PaperPilot 升级到了 Agentic RAG——在 RAG 之上引入 Agent(智能体),让系统具备规划、工具调用、反思的能力。这篇文章记录完整的改造思路与实现过程。
二、Agentic RAG 是什么?和普通 RAG 有什么区别?
先用一句话区分:
•传统 RAG:一条直线。Query → 检索 → 拼接 Prompt → LLM 生成 → 输出。
•Agentic RAG:一个循环。LLM 作为”大脑”,自主决定什么时候检索、检索什么、用哪个工具、结果够不够好、要不要再查一轮。
核心差异在于 Agent 引入了三个能力模块:
能力 说明 在 PaperPilot 中的作用
Planning(规划) 把复杂问题拆解为子任务 把”对比研究进展”拆成多次定向检索
Tool Use(工具调用) 自主调用外部工具 文献检索、网页搜索、引用格式化、笔记读写
Reflection(反思) 评估中间结果质量并自我修正 召回文献不相关时自动改写查询重试
业界常见的实现框架有 ReAct(Reasoning + Acting)、Plan-and-Execute 等。PaperPilot 采用的是 ReAct 模式:模型在每一轮输出”思考(Thought)→ 动作(Action)→ 观察(Observation)“,循环直到得出最终答案。这个模式实现最简单、可控性强,适合文献助手这种工具链明确的场景。
三、PaperPilot 的 Agent 架构设计
整体架构分为四层:
用户提问


┌─────────────────────────────┐
│ Agent 核心(LLM + ReAct 循环)│ ← 大脑:推理与决策
├─────────────────────────────┤
│ 规划模块 │ 记忆模块 │ 反思模块 │ ← 能力组件
├─────────────────────────────┤
│ 工具层 Tools │
│ · 文献向量检索(原有 RAG) │
│ · 混合检索 + Rerank │
│ · 联网搜索 │
│ · 引用格式生成器(GB/T 7714) │
│ · 用户文献库读写 │
├─────────────────────────────┤
│ 数据层:向量数据库 + 文献元数据库 │
└─────────────────────────────┘
几个关键设计决策:

  1. 原有 RAG 检索不废弃,而是降级为”工具”。 之前优化的三种检索策略(向量、混合、Rerank)全部封装成 Tool,由 Agent 按需调用。这保证了检索质量的投资不浪费,Agent 只是在其上做调度。
  2. 引入短期记忆 + 长期记忆。 短期记忆保存当前会话的 ReAct 循环历史(Thought/Action/Observation),长期记忆记录用户的文献库、研究方向偏好,让 Agent 的回答越来越”懂”这个用户。
  3. 反思机制兜底质量。 每轮工具返回后,Agent 会先自评:“这些文献和问题的相关度够吗?覆盖了几个子问题?”不够就改写查询再来一轮,设置最大循环次数防止死循环。
    四、核心代码实现(精简版)
    以下是 Agent 主循环的伪代码级实现,实际开发中基于云函数/后端服务运行:
    TOOLS = {
    “literature_search”: vector_hybrid_search, # 文献混合检索+Rerank
    “web_search”: web_search_api, # 联网搜索
    “format_citation”: gbt7714_formatter, # 引用格式化
    “library_read”: read_user_library, # 读用户文献库
    }

REACT_PROMPT = “”"
你是文献研究助手。可用工具:{tool_desc}
按如下格式循环作答:
Thought: 当前需要做什么
Action: 工具名
Action Input: 工具参数
(系统返回 Observation 后继续)
当信息足够时输出:
Final Answer: 最终答案
问题:{question}
“”"

def agent_run(question, max_steps=8):
context = REACT_PROMPT.format(…)
for step in range(max_steps):
output = llm.generate(context)
thought, action, action_input = parse(output)
if action == “Final Answer”:
return action_input
observation = TOOLSaction
# 反思:让模型评估观察结果质量
context += f"\nThought: {thought}\nAction: {action}\n"
context += f"Action Input: {action_input}\n"
context += f"Observation: {observation}\n"
return “达到最大步数,基于现有信息给出答案…”
关键点就三个:
1.工具描述写清楚,模型才知道什么时候该调哪个工具;
2.解析要鲁棒,LLM 输出格式偶尔会跑偏,需要正则兜底和格式纠正重试;
3.限制最大步数,成本控制的第一道闸门(Agent 的 Token 消耗通常是普通 RAG 的 3~5 倍)。
五、实测效果对比
以”对比近三年 Transformer 在医学影像分割方向的研究进展并给出投稿方向建议”为例:

维度传统 RAGAgentic RAG
检索次数1 次平均 3~5 次(按子问题拆分)
召回文献相关性一般,常混入无关年份/方向高,每轮检索针对性强
回答结构单段综述分方向对比 + 趋势分析 + 方向建议
错误自我修正检索结果差时自动换词重试
响应耗时~3s10~15s
Token 成本1x3~5x

结论很现实:Agent 换来的是质的提升,付出的是时延和成本。所以 PaperPilot 里做了路由策略——简单问题(“帮我格式化这篇引用”)走单工具直调,复杂问题(综述、对比、规划类)才走完整 Agent 循环。
六、踩坑记录
1.死循环问题:模型会在”反思不通过→重试→又不通过”里打转。解法是最大步数 + 反思判定阈值放宽(比如相关度评分 ≥0.7 就放行)。
2.工具选择混乱:工具一多(超过 5~6 个),模型容易选错。解法是给每个工具写清楚”什么时候用我”,并在 Prompt 里给出 1~2 个 few-shot 示例。
3.成本失控:初期每轮都把全部历史塞进 Prompt,Token 爆炸。解法是对 Observation 做摘要压缩,只保留结论性内容。
4.解析失败:LLM 不严格按格式输出。解法是用 JSON mode / function calling(如果模型支持)替代纯文本 ReAct,稳定性大幅提升——这也是我下一步的优化方向。
七、总结与下一步
引入 Agent 后,PaperPilot 从”一个会查文献的问答机器人”进化成了”一个会拆解任务、自我修正的研究助理”。整个改造的核心收益在于:
•复杂问题的回答质量显著提升;
•原有 RAG 检索能力被完整复用,改造成本可控;
•架构具备良好的扩展性——后续加新工具(翻译、图表解析、开题报告生成)只需要注册一个 Tool。
下一步计划:
1.将 ReAct 文本协议迁移到 Function Calling,提升稳定性;
2.引入 多 Agent 协作(检索 Agent、写作 Agent、审校 Agent 分工);
3.针对文献场景微调路由模型,进一步压低成本。