用 Rust 开发 AI Agent:先别急着堆工具,把 Loop 跑起来

总结「使用 Rust 开发 AI Agent - 简介」:从 LLM 的泛化能力、Agent Loop、工作流与 Agent 的边界,到 GAIA、上下文工程,以及一套适合 Rust 的落地结构。

提到 AI Agent,很多人的第一反应是:选一个模型,塞十几个工具,再写一段「你是一个全能助手」的提示词。然后按下运行键,期待一个数字员工从终端里站起来。

通常站起来的是报错。

B 站 UP 主「软件工艺师」的《使用 Rust 开发 AI Agent - 简介》没有急着写代码。它先花近 24 分钟回答更基础的问题:什么才算 Agent?它与普通 LLM 调用、固定工作流有何区别?为什么上下文不是越多越好?又该怎样判断系统真的在进步?

这其实是很合适的开场。因为 Agent 最难的从来不是发出一次 HTTP 请求,而是让模型在一个受约束、可观察、能停止的循环里持续做决定。

先给结论

一个可用的 AI Agent,不等于「LLM + 很多 API」。更准确的表达是:模型在目标和边界内,根据当前状态选择下一步行动;程序执行行动、返回观察,再由模型继续判断,直到完成或触发停止条件。 Rust 的价值,正是在这个循环开始变长、变并发、变得不能随便出错以后出现。

说明:本文主要总结这期「简介」视频,并结合 UP 主公开的 rust-agent 配套仓库做工程化延伸。视频本身以概念为主,不是一节完整的 Rust 编码课。

从聊天模型到 Agent,差的不是一个名字

视频先从 LLM 的泛化能力讲起。

传统程序需要开发者把规则写出来:输入是什么、走哪个分支、返回什么结果。LLM 则能从自然语言指令和上下文中识别任务。视频用 Zero-shot 与 Few-shot 举例:你可以直接要求模型翻译一个词,也可以先放几个输入输出样例,让它从样例中学会任务格式。

这意味着我们不必把每一种情况都改写成 if/else。但「能理解任务」还不是 Agent。一次模型调用更像请顾问回答一个问题;Agent 则是把目标交给一个执行者,让它在运行过程中不断观察、判断和行动。

视频展示的生态也可以看成一条向上的应用栈:

  • 模型与通用助手:ChatGPT、Claude、Gemini 等提供理解、生成与推理能力。
  • 编排与能力层:负责提示词、工具、状态、检索、路由和执行过程。
  • 垂直 Agent 应用:Claude Code、Cursor 等把循环落到编程、研究或其他具体任务中。

真正值得关心的,不是产品都叫了什么,而是谁在决定「下一步」。

Agent 的心脏是一只 Loop

视频中最关键的一页是 Agent 循环。把装饰拿掉,它大致是这样:

agent-loop.txttext
用户目标
   ↓
LLM 读取指令、历史与当前状态
   ↓
决定:直接回答,还是调用工具?
   ├─ 直接回答 ───────────────→ 完成
   └─ 调用工具 → 程序执行 → 返回观察
                         ↑          ↓
                         └─ 更新上下文

一次循环通常包含四种东西:

  1. 目标与约束:用户到底要什么,哪些事情不能做。
  2. 模型决策:基于当前上下文选择回答、调用工具或继续规划。
  3. 外部行动:搜索、读取文件、执行程序、访问数据库或调用业务 API。
  4. 状态与观察:把工具结果、错误和中间进度带回下一轮。

少了模型,它只是普通工作流;少了工具,它只是多轮聊天;少了状态,它会不断失忆;少了停止条件,它可能像扫地机器人卡在桌腿边,一圈一圈认真撞墙。

循环必须有护栏

至少给 Agent 设置最大步数、超时、工具白名单、参数校验、重试上限和人工确认点。模型负责提出行动,程序负责判断这个行动能不能真的发生。

工作流和 Agent 不是一刀切

视频用一张从「代码」到「多步骤自主执行」的连续谱解释工作流与 Agent。判断系统有多“Agentic”,可以观察三种控制权:

  • 当前步骤的输出由谁决定?
  • 下一步由谁选择?
  • 可用步骤或能力由谁限定?

越靠近固定代码,开发者掌握的控制权越多;经过单次 LLM 调用、链式调用、路由、工具调用和多步执行后,模型获得的运行时决策权逐渐增大。

形态下一步怎么来优点主要风险
固定代码开发者写死稳定、便宜、可预测难处理开放问题
LLM 工作流开发者编排,模型完成局部任务易测试、边界清楚分支扩张后维护复杂
路由 / 工具调用模型在候选项中选择更灵活选错工具或参数
多步 Agent模型根据观察持续决定能处理开放、复杂任务成本、延迟和失控风险更高

所以,不要为了“看起来先进”把确定流程改成 Agent。退款规则、字段校验、审批权限这类可以写清楚的逻辑,代码通常比模型靠谱。Agent 更适合那些步骤难以提前穷举、需要根据新信息调整路径、但结果又能被工具或人验证的任务。

我的判断标准很简单:如果流程图能提前画完整,就先做工作流;如果只能定义目标、工具和边界,而路径必须运行时发现,再考虑 Agent。

别只看演示:用 GAIA 检查它会不会干活

视频随后介绍了 GAIA。这个基准由 Meta AI、Hugging Face 等研究者提出,用一组对人类相对自然、却要求 AI 同时具备推理、工具使用和信息处理能力的问题,测试通用 AI 助手。

它给 Agent 开发带来的提醒比榜单本身更重要:

  • 演示成功不等于系统可靠。挑一道恰好会做的题,很容易拍出漂亮视频。
  • 最终答案要可判定。否则模型写得越长,越难知道它究竟有没有完成任务。
  • 失败也要结构化记录:是知识不足、工具缺失、调用失败,还是推理走偏?
  • 模型、提示词或工具一变,就应该重新跑同一组任务,而不是凭感觉宣布“更聪明了”。

配套仓库已经体现了这条思路:它会读取 GAIA Level 1 数据,把模型输出约束成强类型结构,记录 is_solvablefinal_answer 与错误,并按模型统计正确率。Agent 不是写完就结束,它需要一个持续回归的考场。

上下文工程:不是把整个仓库塞进 Prompt

视频后半段转向上下文工程。它给出的核心提醒是:上下文窗口更长,不代表上下文应该更满。

当历史对话、网页全文、工具输出、代码、日志和系统说明一起涌进模型,重要信息会被噪声包围。模型也许“看见了”,却不一定能在正确位置把它用出来。视频用 Context Rot 的曲线说明:随着上下文增长,多种模型的任务表现都可能下降,只是下降速度不同。

这像给一名工程师发来 200 个附件,然后补一句:“答案就在里面。”附件确实都送到了,生产力不一定也到了。

视频最后归纳了五种上下文工程策略:

策略要解决的问题在 Rust Agent 中可以怎么做
写入重要信息不能只留在短期对话里把计划、观察和结论写入结构化状态或持久存储
选择每轮不需要读取全部资料按任务检索相关记录,只注入 Top-K 片段
压缩历史越来越长对旧轮次做摘要,保留结论、证据和未完成事项
隔离不同子任务互相污染为子 Agent、工具调用或阶段使用独立上下文
缓存稳定前缀被反复计算复用 Prompt / KV Cache,减少延迟与费用
上下文工程到底在工程什么?

不是“写一段更玄学的 Prompt”,而是在正确时间,以正确格式,把完成当前决策所需的最少充分信息交给模型。

为什么偏偏用 Rust

Python 和 TypeScript 的 Agent 生态更成熟,教程、框架和集成都更多。如果目标是两小时做出原型,我大概率仍会先选它们。Rust 并不会让模型推理得更聪明,也不会自动治好幻觉。

但当 Agent 从 Demo 进入长期运行的程序,Rust 的优势开始变得具体:

1. 把模型输出变成可检查的数据

模型返回的 JSON 本质上仍是不可信输入。Rust 可以用 serdeschemars 和枚举,把“回答”“工具调用”“失败”收敛成明确类型。字段缺失、类型错误或未知动作,应当在边界处失败,而不是一路以松散字典传到执行器。

decision.rsrust
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
enum Decision {
    Final {
        answer: String,
    },
    ToolCall {
        name: String,
        arguments: serde_json::Value,
    },
}

类型安全不能保证模型做对决定,但可以保证程序知道它究竟提交了什么决定。

2. 异步与并发更适合被认真管理

Agent 常常同时碰到网络请求、模型流式输出、工具执行和多个子任务。tokio 的异步任务、超时、信号量和取消机制很适合做这类编排。

配套仓库没有无限并发地把请求砸向供应商,而是为不同 Provider 建立 Semaphore,限制并发数;流式调用失败时,再用 backon 做有上限的指数退避。这些不炫,但比“再请求一次试试”可靠得多。

3. 单二进制、低运行时负担

对于 CLI、桌面端、本地工具和边缘服务,Rust 可以交付一个启动快、依赖少的二进制。Agent 如果需要在用户机器上读文件、跑命令或集成 Tauri,部署体验会比携带一整套动态语言环境更干净。

4. 错误不会轻易被揉成一团字符串

模型服务超时、限流、JSON 解析失败、工具退出码非零,这些错误的处理策略并不相同。Rust 的 Result 与错误类型迫使我们在调用链上面对失败,而不是让异常从循环深处突然冒出来。

当然也有代价:编译时间更长,AI 框架和现成集成少于 Python,很多新 SDK 会晚一拍。我的建议不是“所有 Agent 都该用 Rust”,而是:先确认你需要的是一个长期运行的软件系统,而不只是一次实验。

一套适合 Rust 的最小项目结构

配套仓库选择从底层调用开始,而不是一上来套一个大而全的 Agent 框架。当前公开代码使用 async-openaitokioserdeschemarstracingbackonreqwest,并按章节拆分 LLM 调用、结构化输出、流式响应和 GAIA 评测。

沿着视频的概念,一套最小结构可以这样长出来:

project-layout.txttext
rust-agent/
├─ src/
│  ├─ main.rs          # 入口、配置、优雅退出
│  ├─ agent.rs         # Loop、最大步数、停止条件
│  ├─ llm.rs           # 模型请求、流式输出、结构化响应
│  ├─ context.rs       # 选择、压缩、隔离与状态装配
│  ├─ tools/
│  │  ├─ mod.rs        # 工具注册表与权限
│  │  └─ search.rs     # 具体工具
│  └─ eval.rs          # 固定任务集与回归评测
├─ tests/
└─ Cargo.toml

最小循环不需要先设计成“自主公司”。把供应商细节删掉后,它更像下面这段 Rust 风格骨架:

agent_loop.rsrust
async fn run_agent(task: &str, max_steps: usize) -> anyhow::Result<String> {
    let mut state = AgentState::new(task);

    for step in 0..max_steps {
        let context = state.context_for_next_step()?;
        let decision = llm_decide(context).await?;

        match decision {
            Decision::Final { answer } => return Ok(answer),
            Decision::ToolCall { name, arguments } => {
                let observation = tools()
                    .execute_checked(&name, arguments)
                    .await?;
                state.record(step, name, observation);
            }
        }
    }

    anyhow::bail!("agent exceeded max_steps without a final answer")
}

这段骨架刻意做了几件事:

  • Decision 必须能被反序列化和校验;
  • 工具只能从注册表中调用;
  • 每次观察都进入状态,而不是偷偷打印后丢掉;
  • 循环有明确上限;
  • 没有最终答案就返回错误,不伪装成成功。

接下来再加流式输出、重试、记忆、RAG、多 Agent 或 MCP,系统仍然围绕同一只 Loop 生长。顺序反过来,很容易得到一棵挂满功能、却没有树干的圣诞树。

从 Demo 走向可用系统的顺序

1. 先跑通一次强类型 LLM 调用

确认鉴权、超时、错误和结构化输出都能稳定处理。不要第一天就接十家模型。

2. 只接一个只读工具

例如搜索或读取公开资料。先验证工具选择、参数校验和观察回传。

3. 加入有界循环

设置最大步数、总超时与停止条件,并完整记录每轮决策。

4. 建立十几道固定评测题

不必等到完整 GAIA。先覆盖你的真实任务、失败路径和拒答场景。

5. 做上下文预算

为系统指令、当前任务、历史摘要、检索片段和工具输出分别设上限。

6. 最后才增加写操作和自主性

涉及文件修改、外部发送、支付或生产环境时,加入权限与人工确认。

最后:Rust 负责让边界变硬

这期视频最有价值的地方,不是证明 Rust 也能请求大模型。任何能发 HTTP 的语言都能做到。

它先把 Agent 拆回几个朴素问题:模型为什么能理解任务?谁决定下一步?工具结果如何回到循环?什么时候该用 Agent?怎么评测?上下文太长怎么办?这些问题想不清楚,换语言只是换一种方式制造不确定性。

Rust 也不会消灭不确定性。它做的是把不确定性关在边界内:模型可以自由提出下一步,但动作必须通过类型、权限和校验;网络可以失败,但重试有上限;任务可以很长,但状态可追踪;上下文可以增长,但每一轮都有预算;Agent 可以探索,但一定有刹车。

这才是我理解的「用 Rust 开发 AI Agent」:不是让螃蟹替模型思考,而是让螃蟹把护栏焊牢。模型负责往前走,程序负责别让它走丢。

新故事即将发生
Vercel AI SDK 入门:从 0 搭一个会流式回复的聊天页

评论区

评论加载中...