用 Rust 写 LLM Agent:原生工具调用、检索纠错、自适应路由、深度研究、多步规划

用 Rust 写 LLM Agent:原生工具调用、检索纠错、自适应路由、深度研究、多步规划

为什么一个 Agent 框架要实现 6 种 Agent?为什么不是 1 种通吃的?每种背后的真实痛点是什么?该选哪个?能组合吗?

本文不讲代码,只讲思路。基于 langchainrust v0.8.0 的实践。


先回答一个根本问题:为什么不是 1 种 Agent 通吃?

很多人的第一反应是:设计一个通用 Agent,能调用工具、能检索、能规划,不就完了?

问题在于,"通用"往往意味着"什么都不擅长"

  • 通用 Agent 调用工具,必须处理 FC 和非 FC 两种情况 → 代码路径分叉,每条路径都不够深
  • 通用 Agent 兼顾检索,但简单问题不需要检索 → 白白浪费嵌入和向量搜索的 API 调用
  • 通用 Agent 要能规划,但简单问答不需要规划 → 多了一次 LLM 调用,多了延迟

所以不是"1 种 Agent 通吃",而是6 种 Agent 各自解决一类特定问题,然后通过组合覆盖所有场景

这 6 种 Agent 的存在,背后是 5 个真实痛点:

痛点 哪些 Agent 在解决
LLM 调用工具的方式不统一 FunctionCallingAgent、ReActAgent
检索结果可能不可靠 CorrectiveRAG
不是所有问题都需要检索 AdaptiveRAG
研究型任务无法一次搜索完成 DeepResearchAgent
多步骤任务可能中途失败 PlanExecuteAgent

下面逐个讲透。


统一抽象:Plan-Act-Observe 循环

在讲 6 种之前,先说清楚它们共享的东西。

所有 Agent 的执行模式都是同一个循环:

复制代码
用户输入 → [规划下一步] → [执行工具] → [观察结果] → 还有动作?→ 重复 → 输出最终答案

这就是 Plan-Act-Observe 循环,来自经典的 ReAct 论文。6 种 Agent 的差异不在于循环本身,而在于三个关键决策点:

  1. 规划怎么做 --- 原生 Function Calling?文本解析?检索+评分?LLM 分解步骤?
  2. 执行什么 --- 单工具?并行工具?向量检索?Web 搜索?
  3. 何时结束 --- LLM 说完了?文档评分够了?信息没有缺口了?所有步骤都完成了?

理解了这三个决策点,6 种 Agent 的区别就一目了然了。


一、FunctionCallingAgent --- 原生工具调用

LLM 支持 Function Calling 时,这是最简单最可靠的方案。你把工具的 JSON Schema 告诉 LLM,LLM 需要调用工具时直接返回结构化的 tool_calls 字段。不需要从文本里"猜"LLM 想调用什么工具。

可靠性 。文本解析永远有失败率 --- LLM 可能输出 Action: calculator(123*456) 而不是你期望的格式。但 tool_calls 是 Provider 保证的结构化字段,不会格式错误。

并行性 。LLM 一次返回多个 tool_calls 时,所有工具可以并行执行,不需要串行等待。

能力探测 。不是所有 Provider 都支持 FC。bind_tools 返回 Option --- OpenAI 支持→走原生 FC;Ollama 不支持→回退到普通 chat。这是 trait 设计时就预留的语义:"Provider 可能不支持工具绑定"是一个合法状态,不是异常。

适用场景 :OpenAI、Anthropic、Gemini 等支持 Function Calling 的模型。只要能用 FC,就用这个,没有例外。


二、ReActAgent --- 文本推理兜底

LLM 不支持 Function Calling 时(比如某些开源模型、Ollama 部分模型),用文本 prompt 引导 LLM 输出固定格式,再用解析器提取:

复制代码
Thought: 我需要查一下北京的天气
Action: weather[北京]
Observation: 晴天,25°C
Thought: 我现在知道答案了
Final Answer: 北京今天是晴天,25°C

代价:解析脆弱。LLM 可能输出格式不对的文本,解析器永远无法 100% 覆盖所有变体。

收益:推理透明。每个 Thought 步骤都可观察,调试时能看到 LLM "在想什么"。FunctionCallingAgent 里这些推理过程是隐藏的。

维度 FunctionCallingAgent ReActAgent
工具调用方式 LLM 原生结构化字段 文本 prompt + 解析器
可靠性 接近 100% 有解析失败率
推理可见性 隐藏 每个 Thought 都可观察
适用范围 仅支持 FC 的模型 任何 chat model

适用场景 :不支持 FC 的模型,或需要观察推理过程的调试场景。能用 FC 就用 FC,不能用才用 ReAct。


三、CorrectiveRAG --- 检索纠错

传统 RAG 有一个危险的假设:检索到的文档一定有用。但现实中向量搜索经常返回完全不相关的文档。如果用垃圾文档生成答案,结果就是"看似有据,实则胡说" --- 这比直接让 LLM 回答更危险,因为用户会以为答案是有依据的。

举个例子:用户问"Rust 的所有权机制",检索到了一篇讲 Rust 真菌防治的农业论文(向量相似度碰巧很高)。传统 RAG 会把这篇论文塞给 LLM 当上下文,LLM 可能基于它生成一个似是而非的答案。

CRAG 在检索和生成之间加了一层评分:检索到的文档先让 LLM 打分,分数够才用,分数不够就纠错。

复制代码
检索 → 评分 → 分数够?
  ├── 够 → 直接生成答案
  └── 不够 → 重写查询 → Web 搜索兜底 → 重新检索 → 生成答案
       └── 幻觉检查 → 答案是否真的有据可依?

评分阈值 。默认 0.6,可调 --- 医疗法律严格场景调到 0.8,闲聊场景降到 0.4。本质是对检索质量的信心和纠错成本之间的权衡

Web 搜索兜底。本地知识库没有的内容,可以通过 DuckDuckGo 等搜索引擎补充。解决了一个常见问题:用户问的东西根本不在你的文档库里。

grader_llm 避免自我偏好。用同一个 LLM 评分和生成,它倾向于认可自己的输出。用独立的 grader_llm 做幻觉检查,打破这个偏见。这不是技术问题,是认知科学问题 --- 人不善于审查自己的判断,LLM 也一样。

CRAG 的返回值比普通 RAG 丰富得多:不只是答案,还有 grounded(是否有据可依)、grade_scores(文档评分)、grade_reasoning(评分理由)。grounded: false 比一个错误的答案更有价值 --- 它诚实地告诉你"这个答案可能不可靠"。

适用场景 :检索质量不稳定、文档库可能不完整、需要答案"有据可依"的场景。任何需要引用溯源的场景(医疗、法律、金融)都应该用 CRAG,而不是普通 RAG。


四、AdaptiveRAG --- 自适应检索路由

传统 RAG 的另一个假设:所有问题都需要检索。但"法国首都"这种常识问题,LLM 直接回答就好,检索是浪费时间和金钱。反过来,"2024年诺贝尔物理学奖授予了谁"这种时效性问题,必须检索。

AdaptiveRAG 在检索之前加了一层路由:让 LLM 先判断问题的性质,再决定用什么策略。

决策 含义 动作 省了什么
NoRetrieval 常识问题,LLM 直接回答 跳过检索 嵌入 + 向量搜索
SingleSearch 需要检索,一次足够 标准检索 + 生成
MultiQuery 复杂问题,需要多角度 生成多个查询变体 → 逐个检索 → 合并 避免单次检索遗漏

算一笔账。假设 100 个查询中 30 个常识问题、50 个需要单次检索、20 个需要多查询。对比"无脑全检索":省了 30 次嵌入 + 30 次向量搜索。如果每次嵌入+搜索耗时 200ms,那就是省了 6 秒总延迟和对应的 API 费用。在大规模生产环境中,这个差异是显著的。

与 CRAG 的关系:AdaptiveRAG 解决"要不要检索、检索几次",CRAG 解决"检索结果可不可靠"。两者可以组合 --- AdaptiveRAG 决定检索策略,CRAG 确保检索质量。就像去医院:AdaptiveRAG 是分诊台(决定你去哪个科室),CRAG 是主治医生(确保诊断准确)。

适用场景 :查询量大、混合了简单问题和复杂问题的场景。特别是成本敏感的生产环境


五、DeepResearchAgent --- 深度研究

"帮我调研 AI 对医疗行业的影响" --- 这种研究型问题,无法通过一次搜索完成。一个研究主题通常包含 3-5 个子主题,每个子主题需要单独搜索,搜索结果可能有信息缺口需要补充搜索,最终要综合成一篇有引用的报告。传统 RAG 完全不够用 --- 它只能做"一次检索 + 一次生成"。

DeepResearch 把研究过程拆成四个阶段:

1. Planner --- 分解子主题

LLM 把研究主题分解为 3-5 个子主题,每个子主题生成搜索查询。比如"AI 对医疗的影响"分解为:AI 在医疗影像中的应用、AI 在药物研发中的应用、AI 在临床决策中的应用。

2. Searcher --- 并行搜索

所有子主题的查询并行搜索,不是串行等待。多个搜索工具(DuckDuckGo、Wikipedia 等)也可以并行查询。搜索结果自动去重。

3. Synthesizer --- 综合报告 + 引用

把所有搜索结果综合成 Markdown 报告,每个论点带引用标记 [1][2][3],末尾有完整的 Citation 列表(来源、URL、片段)。

4. 信息缺口检测 --- 动态补充

综合报告后,LLM 检查是否有信息缺口。比如"子主题 X 的搜索结果只有 1 条,不足以支撑结论"。有缺口才生成补充查询继续搜索,没有缺口提前结束。很多实现是固定搜索 3 轮或 5 轮,但这是浪费 --- 简单主题 1 轮就够了,复杂主题可能需要 5 轮。信息缺口检测替代固定轮数,简单主题不会过度搜索,复杂主题不会被截断。

维度 普通 RAG DeepResearch
检索次数 1 次 多轮,按需补充
查询数量 1 个 子主题分解后 3-15 个
信息缺口 不检测 自动检测并补充
引用溯源 每个论点带引用
输出格式 一段文本 Markdown 报告 + Citation 列表

本质区别:普通 RAG 回答问题 ,DeepResearch 做研究

适用场景 :深度研究型任务:竞品分析、技术调研、文献综述、投资研报。任何需要全面、有引用、有深度的回答。


六、PlanExecuteAgent --- 多步规划与重规划

"帮我调研最新的 Rust 框架并做对比表" --- 这种任务不是一次工具调用能完成的。需要先规划步骤(搜索框架 → 对比特性 → 生成表格),逐步执行,中途某步可能失败 --- 搜索超时、结果不够、对比维度遗漏。失败后不应该放弃整个任务,而是重新规划。

PlanExecute 把任务执行分为两层:

编排层:LLM 做规划,分解步骤,监控执行,失败时重规划。

执行层:每个步骤内部又是一个完整的 Agent 循环(用 FunctionCallingAgent 驱动),可以调用工具完成单步任务。

复制代码
PlanExecuteAgent(编排层:决定做什么)
  └── 步骤 1:搜索 Rust Web 框架 → FunctionCallingAgent 执行
  └── 步骤 2:搜索 Rust CLI 框架 → FunctionCallingAgent 执行
  └── 步骤 3:对比特性 → FunctionCallingAgent 执行
  └── 步骤 3(失败:信息不足)→ Replanner 生成新计划
      └── 步骤 3':搜索更多框架 → FunctionCallingAgent 执行
      └── 步骤 4:生成对比表 → FunctionCallingAgent 执行

增量重规划。失败时不是从头开始,而是告诉 Planner 哪步失败了、为什么失败,Planner 只修改失败步骤及后续步骤,已完成的步骤保留。重规划次数有上限(默认 2 次),防止无限循环 --- 两次重规划都搞不定,说明任务超出了 LLM 的能力范围,应该返回错误而不是继续烧 token。

与 DeepResearch 的区别(最容易混淆的两个):

维度 DeepResearchAgent PlanExecuteAgent
专注领域 研究:搜索 + 综合 通用:任意多步骤任务
步骤类型 固定三阶段:plan → search → synthesize 动态:LLM 自由规划
失败处理 信息缺口 → 补充搜索 步骤失败 → 重新规划
输出 研究报告 + 引用 步骤结果汇总
工具 搜索工具 任意工具

DeepResearch 是特化的 --- 步骤类型固定为 plan/search/synthesize,专门优化了研究流程。PlanExecute 是通用的 --- 步骤由 LLM 动态规划,可以是搜索、分析、生成、调用 API、发送邮件,任何操作。

选哪个? 任务就是"研究某个主题"→ DeepResearch,更专业。任务混合多种操作(搜索 + 分析 + 生成 + 发送)→ PlanExecute,更灵活。

适用场景 :多步骤复杂任务:调研 + 分析 + 生成报告、数据收集 + 清洗 + 分析、多轮迭代优化。任何步骤不确定、可能中途失败的任务。


选择指南

你的场景 推荐 核心原因
LLM 支持 FC,需要调用工具 FunctionCallingAgent 最可靠,结构化输出
LLM 不支持 FC ReActAgent 文本解析兜底,推理透明
检索结果可能不可靠 CorrectiveRAG 评分 + 纠错 + 幻觉检查
不确定是否需要检索 AdaptiveRAG 自适应路由,省成本
需要深度研究报告 DeepResearchAgent 多轮搜索 + 引用
多步骤复杂任务 PlanExecuteAgent 规划 + 执行 + 重规划

组合使用

6 种 Agent 不是互斥的,可以嵌套组合:

  • PlanExecuteAgent 的每步执行 → 嵌套 FunctionCallingAgent(编排 + 执行分层)
  • DeepResearchAgent 的检索策略 → 可以用 AdaptiveRAG 路由(省掉不必要的检索)
  • AdaptiveRAG 的检索质量 → 可以加 CRAG 的评分纠错(检索了,但结果不可靠怎么办)

Agent 的本质是策略,策略可以组合。

进化路径

从简单到复杂,推荐这个顺序接入:

复制代码
1. 先用 FunctionCallingAgent 跑通基本工具调用
2. 加 ReActAgent 兼容更多模型
3. 加 CorrectiveRAG 保证检索质量
4. 加 AdaptiveRAG 优化成本
5. 加 DeepResearchAgent 支持研究场景
6. 加 PlanExecuteAgent 支持复杂任务

每一步都是增量,不需要推翻前面的。


统一 trait 的边界

FunctionCallingAgent 和 ReActAgent 实现了 BaseAgent trait,可以被 AgentExecutor 统一驱动。但 CRAG、AdaptiveRAG、DeepResearchAgent 没有实现这个 trait。

为什么?因为它们的返回值有特殊语义:

  • CRAG 返回 grounded: bool --- 答案是否有据可依
  • AdaptiveRAG 返回 decision: RagDecision --- 路由到了哪条路径
  • DeepResearch 返回 citations: Vec<Citation> --- 引用列表

强行塞进 BaseAgentAgentFinish(只有 outputlog)会丢失这些信息。类型系统在这里帮我们做了正确的事 --- 当输出有特殊语义时,不统一反而更好。统一是有代价的,代价就是丢失信息。

判断标准:如果返回值只是"一段文本",实现 BaseAgent;如果返回值有结构化语义,不实现,暴露自己的接口。


项目

github.com/atliliw/langchainrust --- Rust 实现的 LLM 应用框架

相关推荐
用户8356290780514 小时前
如何使用 Python 加密和保护 Word 文档
后端·python
程序喵大人4 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
Source.Liu4 小时前
【Uppsala】入口模块(lib.rs)
xml·rust
他们叫我秃子4 小时前
前端开发转 Go 全栈(五):终于遇到熟人了,Go 的闭包和高阶函数原来这么像 JavaScript
前端·后端·go
SomeB1oody4 小时前
【RustyML入门】2.6. 线性判别分析
开发语言·后端·机器学习·rust·教程
妙码生花4 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十七):AI时代对组件封装的新理解、agInput 统一入口,动态表单预备
前端·后端·go
Yao8064 小时前
SpringAI + Ollama + DeepSeek + RAG 搭建 AI 航线规划系统
后端
大勇前进4 小时前
SQL Server 分页查询多种写法对比:哪种性能最高?
后端
妙码生花4 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(五十六):附件管理、增加根据文件后缀生成 SVG 文件图标的接口
前端·后端·go
前端Hardy4 小时前
Vue 终于杀进终端界!这个开源项目让 CLI 开发像写网页一样简单
前端·javascript·后端