这次,我把 Rust 里最烦的那段样板代码,删掉了

  • King
  • 发布于 4小时前
  • 阅读 30

做agent-io这段时间,我越来越强烈地感觉到一件事:很多时候,真正劝退用户的,不是“大功能没有”,而是——明明只想做一件小事,却先要写一堆不重要的代码。这次更新,想解决的就是这个问题。事情的起因也很简单。前阵子,有小伙伴在issue里提了一个建议:https://githu

agent-io 这段时间,我越来越强烈地感觉到一件事:

很多时候,真正劝退用户的,不是“大功能没有”,而是 —— 明明只想做一件小事,却先要写一堆不重要的代码。

这次更新,想解决的就是这个问题。

事情的起因也很简单。

前阵子,有小伙伴在 issue 里提了一个建议:

https://github.com/lispking/agent-io/issues/1

能不能引入宏?这样定义工具会方便很多。

我看完第一反应就是:

对,这个需求太真实了。

因为在 Rust 里做 Agent,工具调用本来就是高频动作。

可如果每次加一个工具,都要先铺一层样板代码,那体验就会很割裂。

你明明只是想告诉 Agent:

  • 这个工具叫什么
  • 它接收什么参数
  • 它做什么事

结果却要先去写:

  • 参数结构体
  • JSON Schema
  • FunctionTool::new(...)
  • Box::pin(...)
  • 各种包装和适配

这些东西不能说没必要。 但它们确实不该每次都让用户自己亲手写一遍。

所以这次,我们把它改了。


现在,定义一个工具,终于更像“写函数”了

这次更新后,agent-io 支持直接用 #[tool] 宏来定义工具。

你可以这么写:

use agent_io::{tool, Agent, llm::ChatOpenAI};
use std::sync::Arc;

/// Get the current weather for a city
#[tool(location = "The city name to fetch weather for")]
async fn get_weather(location: String) -> agent_io::Result<String> {
    Ok(format!("Weather in {location}: Sunny, 25°C"))
}

然后直接这样挂到 Agent 上:

let agent = Agent::builder()
    .with_llm(Arc::new(llm))
    .tool(get_weather())
    .build()?;

是的,get_weather() 现在就能直接变成一个工具。

没有手写 schema。 没有自己包 Arc。 没有那层又一层的工具样板。

这件事最大的变化不是“代码更短了”,而是:

你终于可以把“定义工具”这件事,重新理解成“写一个普通的业务函数”。

这个感觉,差别其实很大。


为什么这个更新这么重要?

因为“工具定义”不是一个低频边角需求。

在 Agent 开发里,它恰恰是最常发生的事之一。

你会一直写工具:

  • 查天气
  • 算表达式
  • 读文件
  • 调接口
  • 查数据库
  • 发通知
  • 做编排

如果这件事每次都很重,用户就会越来越容易产生一种感觉:

框架能力挺强,但写起来总有点累。

而这种“累”,并不会出现在 README 的功能列表里。

它只会出现在真正开始写代码的那一刻。

所以这次我们做的,不只是加一个宏。

本质上是在做一件更重要的事:

把高频动作的摩擦降下来。


这个宏,帮你自动做了哪些脏活累活?

别看表面上只是一个 #[tool],背后其实是把一整段重复劳动接过去了。

1. 自动拿函数注释当工具描述

你写在函数上的文档注释:

/// Get the current weather for a city

会直接变成工具 description。

这点很细,但特别有价值。 因为你写给人的说明,顺手也能成为写给模型的说明。

少写一次,信息也更统一。


2. 自动把参数说明塞进 Schema

比如:

#[tool(location = "The city name to fetch weather for")]

这里写的参数描述,会自动进入生成的 JSON Schema。

你不用再手动拼一大段 JSON。 函数签名负责结构,宏属性负责说明,代码本身就很像一份自然语言接口文档。


3. 自动做 Rust 类型到 JSON Schema 的映射

这次宏已经能自动处理一些最常见的类型:

  • String"string"
  • bool"boolean"
  • f32 / f64"number"
  • 整数类型 → "integer"
  • Vec<T>"array"
  • Option<T> → 自动识别成可选参数

也就是说,很多以前必须手写 schema 的地方,现在都可以直接省掉了。

对于日常 Agent 工具来说,这已经能覆盖掉很大一部分场景。


4. 自动生成参数解析和 Tool 实现

这其实是最省脑子的部分。

宏会自动生成:

  • 内部参数结构体
  • 参数反序列化逻辑
  • Tool trait 实现
  • definition()
  • execute()
  • 以及返回 Arc<dyn Tool> 的构造器

所以你看起来只是写了一个普通函数, 实际上已经把一整套工具接入链路都走完了。


说白了,这次是在删“没必要让用户自己写的代码”

我一直觉得,一个好用的 SDK,不是把所有能力都堆出来, 而是要不断追问一件事:

哪些复杂度,用户真的必须自己承担? 哪些复杂度,其实应该由框架接过去?

这次 #[tool] 的引入,就是很典型的一次“把复杂度往框架内部收”。

以前用户要自己写的很多内容,本质上并不属于业务逻辑。 它们只是“为了满足框架接入格式而存在”。

这种代码写一次还行,写十次就会让人烦。 而一旦让人烦,体验就开始打折。

所以与其让用户每次重复劳动, 不如让宏把这些固定模式吃掉。

这也是这次更新我最满意的地方:

它不是在炫技,而是在认真减少使用成本。


不只是加了宏,这次连文档和示例也一起补上了

我一直不太喜欢一种更新方式: 功能上线了,但文档没动,示例没补,最后用户还得自己猜怎么用。

所以这次除了宏本身,还顺手把几件配套的事一起做了。

README 里正式把 #[tool] 放进核心能力

现在用户第一次打开项目 README,就能直接看到:

  • 这个项目支持宏定义工具
  • 怎么写
  • 它会自动生成什么
  • 如果不想用宏,也还有手动定义方式

这比“功能已经有了,但你得翻提交记录才知道”要友好得多。


新增了 macro_tools 示例

这次还加了一个可以直接跑的例子,专门演示宏定义工具。

这个示例不只是告诉你“语法长什么样”,而是把一整条使用链路串起来了:

  • 定义工具
  • 挂到 Agent 上
  • 发起查询
  • 观察 ToolCall / ToolResult
  • 最后看到完整响应和 token usage

这种例子对新用户特别重要。 因为很多时候,最好的文档不是长篇解释,而是一个“你复制就能跑”的工程示范。


这次更新背后,我其实更在意一件事

如果只看提交内容,这次可以概括成:

  • 引入 #[tool]
  • 改善工具定义体验
  • 补充 README 和示例
  • 修正 CI 环境依赖

但如果从更大的角度看,我更想说的是:

agent-io 正在从“能用”,往“更顺手、更自然”去走。

这很重要。

因为一个框架能不能让人持续喜欢,不只看它支不支持多少 provider, 也不只看它功能列表有多长。

真正决定你会不会继续用它的,往往是这些瞬间:

  • 我第一次上手的时候,顺不顺?
  • 我第一次写工具的时候,烦不烦?
  • 我看文档的时候,能不能马上明白?
  • 我跑 CI 的时候,会不会被环境问题卡住?

这次更新,针对的就是这些“真实使用时刻”。


最后

这次改动,最早只是从一个 issue 开始。

一个很简单、也很真诚的建议:

“想引入宏,这样方便使用。”

我很喜欢这种反馈。 因为它提醒我们,做工具和做框架,最终不是为了展示设计有多完整, 而是为了让真正用它的人,少一点别扭,多一点顺手。

所以这次,我们把那段最容易让人烦的样板代码,删掉了一大块。

现在你写的,不再是一层层工具包装。 你写的,就是一个普通的 Rust async fn。 然后它自然变成 Agent 能用的工具。

这件事,我觉得挺值。

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论