摘要: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 1 | Phase 2 | Phase 2.5 | |
|---|---|---|---|
| 架构 | agent ↔ tools | retrieve → generate | agent ↔ 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_base | LLM 调用时的标识 |
参数 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 |
| 实时数据 | 每次要问最新值 | 外部 API | get_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-community、chromadb、dashscope
6.3 运行
.\run.ps1
# 或 python agent.py
6.4 测试用例
| 提问 | 预期工具 | 考察点 |
|---|---|---|
北京天气怎么样 | get_weather | API 工具 |
算一下 5000 的 13% | calculator | 本地计算 |
现在几点了 | get_current_time | 系统状态 |
入职满1年的员工有多少天年假? | search_knowledge_base | RAG 工具 |
现在几点,顺便查上海天气 | get_current_time + get_weather | 多工具 |
年假几天,再算 5000 的 13% 税费 | search_knowledge_base + calculator | RAG + API 混合 |
公司上市代码是多少? | search_knowledge_base → 说不知道 | 防幻觉 |
七、Phase 2 vs Phase 2.5:RAG 的两种用法
| Phase 2 独立 RAG | Phase 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作为全局常量统一管理向量库持久化目录。
这个理解是正确的。补充两点:
- 路由决策 = docstring + System Prompt 双保险,不只有 docstring
- 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 上搭建多节点、多条件边的复杂工作流。
十一、参考资料
- LangGraph 官方文档
- LangChain Tool 概念
- 本项目源码:
- Phase 1 + 2.5:
FirstAgent/→ https://gitee.com/w_lin.oschina/FirstAgent - Phase 2 RAG:
agent-rag/→ https://gitee.com/w_lin.oschina/agent-rag
- Phase 1 + 2.5:
系列文章导航
- Phase 1:工具 Agent(LangGraph + Function Calling)
- Phase 2:RAG 文档问答(LangGraph + Chroma + Embedding)
- Phase 2.5:工具 + RAG 合体(本文)
- Phase 3:复杂工作流 —— 待发布
- Phase 4+:多 Agent 协作、生产化部署 —— 规划中
如果这篇文章对你有帮助,欢迎点赞收藏。有问题欢迎在评论区交流。
&spm=1001.2101.3001.5002&articleId=162196539&d=1&t=3&u=62abc85a706a47d7ba255df65ff8f5e3)
2343

被折叠的 条评论
为什么被折叠?



