RubyLLM升级:工具审批与可恢复Agent
RubyLLM 2.0和2.1加入工具人工审批、调用方控制的分步执行,以及数据库持久化Agent能力。更新还扩展了多模态接口、批处理和统一的供应商托管工具调用。
AI 深度解读
RubyLLM 2.0 和 2.1 的重点,是让 Rails 里的 Agent 更适合接触真实业务:写操作可以等人审批,执行循环可以拆开调度,任务状态可以存进数据库,服务重启后还能继续。模型负责提出行动,应用开始有能力管理行动的边界。
原文用商品调价串起这些能力,也介绍了批处理、模型回退、缓存和费用记录。对前端、全栈开发者,最值得看的不是 Ruby 语法,而是一个聊天式 Agent 怎样接入审批界面、后台任务和业务数据库,以及这些机制仍然没有替你解决什么。
审批和持久化,怎样把聊天变成业务流程
过去,Agent 查商品、搜市场价格、更新售价,可以在一次调用里连续完成。问题在于,读取信息和修改线上价格,承担的风险不同。
RubyLLM 2.0 允许给工具加上 requires_approval。原文的 UpdatePrice 就用它拦住价格更新:LookupProduct、SearchGoogleShopping 继续自动执行,轮到写数据库时暂停,ask 返回。
此时,awaiting_approval? 表示任务正在等审批,pending_approvals 可以查看工具名和参数。示例中的待执行操作是把 SKU 为 CHX-6 的 Chemex 6-Cup 改成 44.97 美元。人可以通过 approve 放行,也可以用 deny 拒绝;拒绝后,模型会收到反馈,再解释后续安排。
审批的价值,是在工具真正产生业务副作用之前,留下一个可检查的停顿。
但审批往往不会发生在同一个 HTTP 请求里。业务人员可能几小时后才打开页面,因此还需要持久化 Agent。
原文通过 chat_model Chat 绑定应用的 Chat 模型,用 create! 代替 new 创建记录。Agent 示例采用 gpt-5.4-mini。之后,控制器或后台任务可以重新加载记录,处理审批并继续运行:
chat = PriceMonitorAgent.find(params[:chat_id])
chat.approve(params[:tool_call_id])
chat.complete
这段代码解决跨请求恢复的问题。要注意第二行:它展示了如何提交审批,原文没有展示当前用户是否有权审批、工具调用是否属于这个会话等校验。
会话、待执行工具调用和审批决定都进入数据库,任务中断后可以恢复。不过,原文明确提醒:被打断的操作可能再次执行。重复设置同一个价格通常可以接受,发送邮件则需要先检查是否已经发过。
持久化不等于副作用只发生一次。工具仍然要能承受重试。
为什么还要把 Agent 循环拆成几步
RubyLLM 1.x 的 ask 会包办整个循环:请求模型、执行工具、回传结果,直到模型给出答案。调用方便,但应用很难在中间插入调度策略。
2.0 把它拆成四个入口:ask_later 暂存问题,generate 请求一次模型响应,run_tools 执行待处理工具,step 则推进下一次模型生成或一次工具执行。
原文用下面这段代码查询哪些咖啡商品比市场价高出 15%:
agent = PriceMonitorAgent.new
agent.ask_later "Which of our coffee products are priced more than 15% above market?"
agent.generate # 模型决定调用 LookupProduct 和 SearchGoogleShopping
agent.run_tools # 在这里执行 SerpApi 搜索
agent.generate # 模型读取结果并回答
要注意 run_tools 所在的位置:应用现在可以在这些调用之间加入计数、超时判断或后台任务边界。原文举例,可以在十轮工具调用后停止,避免一次比价任务不断追加搜索。
这只是提供了控制入口,具体限额仍要应用自己实现。
搜索工具也有两条路径。with_provider_tools 或 Agent 的 provider_tools 可以接入供应商托管的网页搜索、代码执行和远程 MCP 服务。原文以 claude-sonnet-5 配合 provider_tools :web_search 为例,通过 response.citations 读取来源。
SerpApi 则适合指定搜索引擎和参数。作者强调,Google Shopping API 提供标题、价格、来源,还能切换 Amazon、Walmart、eBay,固定地点或货币,并查看查询记录。开放式资讯查询和商品结构化比价,需要的结果形态确实不同;但原文没有提供两者准确率的对照实验。
对于每晚扫描整个目录的任务,原文先调用 SerpApi,取前 5 条结果整理成文本,再用 ask_later 准备多个会话,交给 RubyLLM.batch(chats)。保存 batch.id 后,可通过 RubyLLM::Batch.find(batch_id, provider: :openai) 查询,等待 complete?,再读取 batch.messages。
结果按提交顺序返回,方便对应商品。这里采用的是供应商批处理费率,原文没有给出折扣比例;示例中的 SerpApi 搜索也已经提前执行,并不是随着模型请求一起进入批处理。
长期运行和升级,哪些细节不能漏
with_fallbacks("claude-sonnet-5") 可以在主模型遇到临时错误时尝试备用模型。工具和设置会保留,因此备用模型必须支持这些能力,而且两家供应商都要配置凭证。
缓存方面,with_caching 配合 cache_until_here 标记可缓存前缀,也支持 cache_until_here(ttl: "1h")。固定调价政策适合放在前缀里,但最小长度、生命周期和实际是否命中仍由供应商决定,可用 response.tokens.cache_read 查看读取量。
费用通过 response.tokens.input、response.tokens.output、response.cost.total 和 chat.cost.total 获取,包含模型回退前失败尝试的消耗。未知价格保留为 nil,不会假装免费。Rails 的使用量有独立账本,之后修改模型价格也不会改写历史记录。
升级不能只换版本号。原文要求 1.x 先到 2.0,再到 2.1,并给出 bundle add ruby_llm --version 2.0.0 和 bundle update ruby_llm --version 2.1.0。
工具声明中,param 改为 parameter,desc: 改为 description:,params do ... end 改为 parameters do ... end。原文前面的工具示例还使用旧写法,复制到 2.x 项目时要按迁移说明调整。
其他改名包括:
max_tokens改为max_output_tokens,with_params和供应商专用选项改走with_provider_options。response.input_tokens等字段移到response.tokens下。on_tool_call改为before_tool_call,on_end_message改为after_message。
工具不能再自行终止循环,应改用审批或调用方控制;返回 Hash、Array 时会发送 JSON 文本,不再使用 Ruby inspect 输出。
Rails 应用仍管理 chats、messages 表,模型注册、工具调用、使用量、批处理的辅助表改由 RubyLLM 管理。bin/rails generate ruby_llm:upgrade 分阶段生成迁移,清理放到后续部署;可选 --mode copy 在验证期间保留回退至 1.16 的路径。升到 2.1 后还要再次运行生成器,添加 MCP 凭证表和新列。
其余新增入口包括视频 RubyLLM.animate、语音 RubyLLM.speak、带说话人标签和时间戳的 RubyLLM.transcribe、文档识别 RubyLLM.ocr、搜索重排 RubyLLM.rerank、深度研究 RubyLLM.research,以及用于归组观测步骤的 RubyLLM.workflow。新增供应商有 Cohere、Deepgram、ElevenLabs、Ollama Cloud,原文没有展开这些能力的实际效果。
我的判断
我认可把审批、执行边界和恢复能力放在一起设计。对需要修改业务数据的 Agent,这些机制比继续增加工具更有实际价值:每次行动可以被查看、延后和恢复。
但它们是框架提供的基础设施。审批仍依赖权限校验,恢复仍依赖工具幂等,循环控制仍依赖业务限额。前端也不能只显示一句模型总结,最好明确展示目标商品、旧价格、拟改价格和查询依据,让审批者知道自己批准了什么。
原文是功能介绍和示例演示,没有提供生产故障率、成本下降或搜索准确率的对照数据。作者对 SerpApi 的优势判断带有明显的产品推荐倾向,文末的每月 250 次免费搜索也只是原文给出的额度信息。
如果业务只有固定的查询、计算和审批步骤,普通后台任务加明确规则同样值得考虑。Agent 更适合确实需要模型理解问题、选择工具的环节,不能仅凭这篇文章就认定它更省成本。
能拿来做什么
- 在 Rails 全栈项目里,把查资料和修改数据拆成不同工具,对调价等写操作设置审批,并在界面展示具体参数与查询依据。
- 将长任务接入后台队列时,利用
generate、run_tools或step设置调度边界,同时为可能重试的写工具设计去重机制。 - 对定时批量分析,分别统计数据采集和模型调用的成本,再评估批处理、缓存和模型回退,避免把未知费用的
nil当成零。 - 已使用 RubyLLM 1.x 的团队,可以先检查工具声明、回调、token 字段和 Rails 表迁移;原文对 Node 项目的直接价值主要是流程设计参考。