【Agent 实战】Phase 2.5:工具 Agent + RAG 合体(一个 Agent 同时会「动手」和「查资料」)

摘要:Phase 1 给 Agent 装上了「手」(工具调用),Phase 2 给 Agent 装上了「图书馆」(RAG 检索)。Phase 2.5 把两者合体——将 RAG 封装成 search_knowledge_base 工具,接入 Phase 1 的 agent ↔ tools 循环,实现一个 Agent 统一调度:问天气走 API、问制度走知识库、问计算走本地函数。本文记录合体架构设计、源码实现与学习心得。

关键词:Agent、RAG、LangGraph、Function Calling、工具路由、ReAct、通义千问、Chroma


一、前言:为什么要合体?

完成 Phase 1 和 Phase 2 后,我手上有两个独立项目:

项目能力局限
FirstAgent查天气、算数、报时间不懂公司制度
agent-rag查员工手册、报销政策不能查实时天气

真实场景里,用户不会分开问——他可能说:

「帮我查北京天气,顺便看看我们年假有几天。」

工业级 Agent 的常见形态是:一个大脑,多种工具,统一调度。Phase 2.5 就是完成这一步。


二、合体思路:RAG 也是一种工具

2.1 关键洞察

Phase 2 的 RAG 是独立工作流:

retrieve → generate → 回答

Phase 2.5 的核心改造:把 retrieve 封装成 @tool 函数,塞回 Phase 1 的 agent ↔ tools 循环:

agent 决策 → search_knowledge_base(RAG 检索)→ agent 汇总 → 回答

RAG 不再是一条独立流水线,而是工具列表里的第 4 个工具

2.2 合体前后对比

Phase 1Phase 2Phase 2.5
架构agent ↔ toolsretrieve → generateagent ↔ tools(含 RAG 工具)
工具数3 个无工具概念4 个
路由方式LLM 自动选择固定流水线LLM 自动选择
适用场景实时 API文档问答全能助理

2.3 架构图

用户提问
  ↓
agent 节点(LLM + ReAct 决策)
  ↓
┌──────────────┬──────────────┬──────────────┬─────────────────────┐
│ get_weather  │  calculator  │get_current_time│search_knowledge_base│
│  实时 API    │  本地计算     │  系统时间       │  RAG 向量检索        │
└──────────────┴──────────────┴──────────────┴─────────────────────┘
  ↓
agent 汇总 → 输出回答

三、核心机制:@tool + docstring 路由

3.1 @tool 装饰器做了什么?

在函数上方使用 @tool 装饰器,LangChain 会自动将其识别为标准工具,并生成三样东西:

@tool
def search_knowledge_base(query: str) -> str:
    """搜索企业内部知识库文档。当用户询问公司制度、员工手册、年假、病假、报销、考勤、培训政策等问题时使用此工具。

    Args:
        query: 检索关键词或问题,如「年假有几天」「报销时限」
    """
自动生成作用
工具名 search_knowledge_baseLLM 调用时的标识
参数 Schema {"query": "string"}LLM 知道传什么参数
描述(来自 docstring)LLM 判断什么时候该用这个工具

3.2 路由决策:docstring + System Prompt 双保险

Agent 依靠工具函数的 docstring 描述自主推理路由,但工业实践中不只靠 docstring——还有 System Prompt 约束:

SYSTEM_PROMPT = """你是一个有帮助的个人助理 Agent。
- 当用户询问天气时,使用 get_weather 工具查询,不要编造天气数据。
- 当用户需要计算时,使用 calculator 工具,不要心算。
- 当用户询问当前时间或日期时,使用 get_current_time 工具,不要猜测。
- 当用户询问公司制度、员工手册、年假、报销、考勤等政策问题时,使用 search_knowledge_base 工具检索知识库,不要编造。
- 知识库无相关内容时,明确告知用户「知识库中没有找到相关信息」。
...
"""

路由决策 = docstring(工具自述)+ System Prompt(全局规则)+ 用户问题

3.3 数据类型与工具映射

基于 ReAct 思考链,LLM 读取全部 tools 列表后自动决策:

数据类型特征路由工具
静态知识存在文档里,变化慢RAG 检索search_knowledge_base
实时数据每次要问最新值外部 APIget_weather
精确计算数学运算本地函数calculator
系统状态当前时间本地函数get_current_time

一句话:纯规章制度走 RAG,实时数据走 API,计算走本地函数。


四、源码实现

4.1 项目结构(新增部分)

FirstAgent/
├── agent.py              # 合体 Agent 主图(更新 System Prompt)
├── config.py             # 新增 RAG 相关配置
├── rag_client.py         # ★ 新增:RAG 检索客户端
├── tools/
│   ├── weather.py
│   ├── calculator.py
│   ├── time_tool.py
│   └── knowledge_base.py # ★ 新增:search_knowledge_base 工具
└── ...

4.2 config.py:RAG_CHROMA_DIR 全局配置

向量库路径在 config.py 中作为全局常量统一管理,不散落在各处:

# 默认指向同级 agent-rag 项目的向量库
_DEFAULT_CHROMA = _ROOT.parent / "agent-rag" / "chroma_db"
CHROMA_DIR = Path(os.getenv("RAG_CHROMA_DIR", str(_DEFAULT_CHROMA)))
COLLECTION_NAME = "knowledge_base"
TOP_K = int(os.getenv("RAG_TOP_K", "4"))
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "text-embedding-v3")

设计要点:

  • 集中管理rag_client.py 只引用 config.CHROMA_DIR,不硬编码路径
  • 环境可覆盖.env 中设置 RAG_CHROMA_DIR 可改路径,不动代码
  • 项目解耦:FirstAgent 和 agent-rag 是两个独立仓库,通过路径约定协作

4.3 rag_client.py:检索逻辑

从 Phase 2 的 retriever.py 提取核心逻辑,指向 agent-rag 的向量库:

def retrieve(question: str, top_k: int = TOP_K) -> tuple[str, list[str]]:
    store = get_vectorstore()
    docs = store.similarity_search(question, k=top_k)

    if not docs:
        return "", []

    # 格式化每个片段,附加来源标识
    for i, doc in enumerate(docs, 1):
        parts.append(f"【片段{i} | 来源: {src}】\n{doc.page_content}")

    return context, sources

与 Phase 2 的区别:只负责检索,不负责生成。生成回答的工作交还给 agent 节点。

4.4 tools/knowledge_base.py:RAG 工具

@tool
def search_knowledge_base(query: str) -> str:
    """搜索企业内部知识库文档。当用户询问公司制度、员工手册、年假..."""
    context, sources = retrieve(query)
    if not context:
        return "知识库中未检索到与问题相关的资料。"

    source_line = "、".join(sources)
    return f"检索到的资料:\n\n{context}\n\n(来源:{source_line})"

工具返回值会作为 Observation 回到 agent 节点,LLM 据此生成最终回答。

4.5 注册到工具列表

# tools/__init__.py
ALL_TOOLS = [get_weather, calculator, get_current_time, search_knowledge_base]

agent 节点通过 bind_tools(ALL_TOOLS) 一次性注册,LLM 看到完整工具清单。

4.6 agent ↔ tools 循环不变

Phase 2.5 没有改变工作流结构,只是在工具列表里加了一个 RAG 工具:

workflow.add_node("agent", call_agent)
workflow.add_node("tools", call_tools)
workflow.add_edge(START, "agent")
workflow.add_conditional_edges("agent", tools_condition)
workflow.add_edge("tools", "agent")

这就是合体的优雅之处:架构不变,能力扩展。


五、ReAct 在合体 Agent 中的完整流程

以「入职满 1 年年假几天,顺便查北京天气」为例:

[输入] 用户: 入职满1年年假几天,顺便查北京天气

>> 进入节点: agent
[决策] Agent 将调用工具: search_knowledge_base
<< 完成节点: agent

>> 进入节点: tools
[RAG] 命中 2 个片段,来源: company_handbook.md
[工具] search_knowledge_base({'query': '年假几天'}) -> 检索到的资料...
<< 完成节点: tools

>> 进入节点: agent
[决策] Agent 将调用工具: get_weather
<< 完成节点: agent

>> 进入节点: tools
[工具] get_weather({'city': '北京'}) -> 北京:Sunny,气温 28°C...
<< 完成节点: tools

>> 进入节点: agent
[决策] Agent 直接回答,无需工具
<< 完成节点: agent

🤖 Agent:入职满 1 年的员工享有 15 天带薪年假(来源:company_handbook.md)。
北京今天晴,28°C。

对应 ReAct 链条:

Thought → 需要查制度 + 查天气
Action  → search_knowledge_base("年假几天")
Observe → 15 天带薪年假...
Thought → 还需要天气
Action  → get_weather("北京")
Observe → 北京:晴,28°C
Thought → 信息够了
Answer  → 汇总输出

六、环境准备与运行

6.1 前置条件

Phase 2.5 依赖 Phase 2 的向量库,需先完成入库:

cd agent-rag
python ingest.py --rebuild

6.2 安装新依赖

FirstAgent 新增了 RAG 相关依赖:

cd FirstAgent
.venv\Scripts\activate
python -m pip install -r requirements.txt

新增包:langchain-communitychromadbdashscope

6.3 运行

.\run.ps1
# 或 python agent.py

6.4 测试用例

提问预期工具考察点
北京天气怎么样get_weatherAPI 工具
算一下 5000 的 13%calculator本地计算
现在几点了get_current_time系统状态
入职满1年的员工有多少天年假?search_knowledge_baseRAG 工具
现在几点,顺便查上海天气get_current_time + get_weather多工具
年假几天,再算 5000 的 13% 税费search_knowledge_base + calculatorRAG + API 混合
公司上市代码是多少?search_knowledge_base → 说不知道防幻觉

七、Phase 2 vs Phase 2.5:RAG 的两种用法

Phase 2 独立 RAGPhase 2.5 RAG 工具
工作流retrieve → generate(固定)agent 决策后调用(动态)
检索每次都检索只在需要时检索
生成独立 generate 节点agent 节点统一生成
灵活性只能问答档可混合 API + RAG
适用纯知识库场景全能助理场景

Phase 2 像「专科医生」(只看文档),Phase 2.5 像「全科医生」(什么都看,按需调工具)。


八、踩坑记录

8.1 向量库不存在

FileNotFoundError: 向量库不存在: ../agent-rag/chroma_db

解决:先在 agent-rag 目录运行 python ingest.py --rebuild

8.2 新增依赖未安装

ModuleNotFoundError: No module named 'langchain_community'

解决python -m pip install -r requirements.txt(Phase 2.5 新增了 3 个包)。

8.3 LLM 该走 RAG 却直接回答

原因:System Prompt 约束不够,或 docstring 描述不清晰。

解决:加强 Prompt 中「必须使用 search_knowledge_base 查制度」的规则。

8.4 LLM 该走 API 却调了 RAG

原因:用户问「今天天气怎么样」,LLM 可能在知识库里搜到无关片段并编造。

解决:各工具 docstring 中明确边界——get_weather 写「当用户询问天气时使用」,search_knowledge_base 写「当用户询问公司制度时使用」。


九、学习总结

9.1 我的理解(学习检验)

在函数上方使用 @tool 装饰器,LangChain 识别为标准工具。Agent 依靠工具函数 docstring 描述自主推理路由,基于 ReAct 思考链自动决策:LLM 读取全部 tools 列表,纯规章制度走 RAG,实时数据走 API。RAG_CHROMA_DIR 作为全局常量统一管理向量库持久化目录。

这个理解是正确的。补充两点:

  1. 路由决策 = docstring + System Prompt 双保险,不只有 docstring
  2. agent ↔ tools 循环结构不变,只是工具从 3 个扩展到 4 个

9.2 Phase 2.5 通关清单

  • 理解 RAG 如何封装成 @tool 函数
  • 理解 Agent 如何在 API 工具和 RAG 工具之间自动选择
  • 能看懂 Trace 里 [RAG][工具] 日志
  • 理解 CHROMA_DIR / RAG_CHROMA_DIR 的配置设计
  • 能解释 Phase 2 独立 RAG 与 Phase 2.5 RAG 工具的区别

9.3 系列进化路线

Phase 0  test.py(规则 if-else)
  → Phase 1  工具 Agent(装「手」)
    → Phase 2  RAG 问答(装「图书馆」)
      → Phase 2.5  合体(手 + 图书馆)← 本文
        → Phase 3  复杂工作流
          → Phase 4  多 Agent 协作
            → Phase 5  可观测性与评测
              → Phase 6  生产化部署

十、下一步:Phase 3 复杂工作流

Phase 2.5 的 Agent 已经能处理多种问题,但工作流仍是简单的 agent ↔ tools 循环。真实工业场景需要:

  • 多节点流水线:代码审查 = 解析 → 安全扫描 → 风格检查 → 人工确认 → 生成报告
  • 条件分支:有安全问题走紧急通道,无安全问题走常规通道
  • 人机协同:敏感操作需人工确认后才继续

Phase 3 将在 LangGraph 上搭建多节点、多条件边的复杂工作流。


十一、参考资料


系列文章导航


如果这篇文章对你有帮助,欢迎点赞收藏。有问题欢迎在评论区交流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值