引言:当 AI 遇见遗留 Rails 单体
在当今 AI 驱动的开发浪潮中,许多企业面临着一个共同的困境:如何在已有 7 年甚至更久历史的 Rails 单体应用中,安全、高效地集成 AI 代理功能?这些遗留系统往往积累了大量的技术债务,数据库模式复杂多变,业务逻辑紧密耦合,而 AI 代理的引入又带来了全新的挑战 —— 实时推理、工具调用、上下文管理,以及与现有系统的无缝集成。
根据 Evil Martians 团队在 2025 年 9 月的实践分享,Rails 生态系统正在快速适应 AI 革命的需求。从早期的 ruby-openai SDK 到专门的 AI 库如 Raix 和 RubyLLM,再到最新的 Active Agent 框架,Rails 开发者正在寻找符合 Rails 哲学的原生 AI 抽象。然而,在遗留单体中集成 AI 代理远不止选择一个库那么简单,它涉及到数据库模式兼容性、实时推理流水线设计、测试策略、监控体系等一系列工程挑战。
Active Agent:Rails 原生的 AI 抽象框架
Active Agent 框架代表了 Rails 社区对 AI 集成问题的最新思考。它借鉴了 Action Mailer 的设计模式,将 AI 代理封装为类似控制器的对象,支持回调、模板渲染等熟悉的 Rails 特性。这种设计让 Rails 开发者能够以最小的认知成本开始构建 AI 功能。
基础架构设计
Active Agent 的核心思想是将 AI 生成逻辑封装在专门的代理类中。以下是一个简单的示例:
class JokerAgent < ApplicationAgent
after_generation :notify_user
def dad_joke(topic = "beer")
prompt(body: "Generate a dad joke for the topic: #{topic}")
end
def nerd_joke(topic = "Apple keynote")
prompt(body: "Generate a nerd joke for the topic: #{topic}")
end
private
def notify_user
return unless params[:user]
UserMailer.with(user: params[:user]).joke_generated(response.message.content).deliver_later
end
end
这种设计模式的优势在于:
- 熟悉的开发体验:Rails 开发者立即理解代码结构
- 内置异步支持:通过
generate_later方法轻松实现后台处理 - 回调机制:在生成前后执行自定义逻辑
- 模板系统:使用 Action View 渲染复杂的提示模板
与遗留系统的集成策略
在遗留 Rails 单体中,最大的挑战是如何让 AI 代理与现有的业务逻辑协同工作。Active Agent 通过params参数传递上下文信息,允许代理访问当前用户、记录对象等业务实体。这种设计虽然简单,但在遗留系统中需要特别注意副作用的管理。
数据库模式兼容性:适配遗留数据模型
7 年历史的 Rails 应用通常伴随着复杂的数据库演化历史。迁移可能不完整,表结构可能包含废弃字段,关联关系可能以非标准方式实现。AI 代理需要能够理解并适应这些数据模式。
数据访问层的抽象
为了避免 AI 代理直接依赖具体的数据库实现,建议在代理和数据模型之间建立抽象层:
class TranslateAgent < ApplicationAgent
after_generation :update_record
def translate(content, locale)
@locale = locale
@content = content
prompt
end
private
def update_record
return unless params[:record]
record = params[:record]
result = response.message.content
# 解析AI响应并更新记录
_, original_locale, locale, content = result.split(/^(\w{2})->(\w{2}):\s*/)
return unless original_locale.present? && locale.present? && content.present?
# 调用模型方法而非直接操作数据库
record.translated!(original_locale, locale, content)
end
end
模式演化策略
在遗留系统中集成 AI 代理时,需要考虑以下模式演化策略:
- 只读访问优先:初始阶段让 AI 代理以只读方式访问数据,避免直接修改
- 版本化 API:为 AI 代理提供版本化的数据访问接口
- 数据转换层:在代理和数据源之间建立转换层,处理字段映射和格式转换
- 渐进式迁移:逐步将 AI 相关的数据操作迁移到新的表结构中
实时推理流水线:同步与异步的权衡
AI 代理的推理过程可能耗时较长,特别是在处理复杂任务或调用外部工具时。在 Web 请求中直接进行同步推理可能导致超时和糟糕的用户体验。然而,某些场景又需要实时响应。
同步推理模式
对于简单的、低延迟的任务,可以使用同步推理:
class TranslationsController < ApplicationController
def create
comment = Comment.find(params[:id])
unless comment.translated_to?(params[:locale])
TranslateAgent.with(record: comment)
.translate(comment.body, params[:locale]).generate_now
end
render json: comment
end
end
这种模式的优点是简单直接,但需要注意:
- 设置合理的超时时间
- 监控推理延迟
- 考虑使用连接池管理 AI 服务连接
异步推理模式
对于耗时的任务,应该使用异步处理:
def regenerate_translations
return if translations.empty?
translations.each do |translation|
TranslateAgent.with(record: self)
.translate(translatable_content.to_s, translation.locale)
.generate_later
end
end
异步模式的关键考虑因素:
- 作业队列选择:Active Job 支持多种后端(Sidekiq、Resque 等)
- 重试策略:AI 服务可能不稳定,需要合理的重试机制
- 进度通知:通过 Action Cable 或轮询 API 通知用户处理进度
- 结果缓存:缓存 AI 生成结果,避免重复计算
混合模式:流式响应
对于需要逐步显示结果的场景,可以考虑流式响应。虽然 Active Agent 本身不直接支持流式输出,但可以通过以下方式实现:
- 使用 Server-Sent Events(SSE)逐步推送部分结果
- 将复杂任务分解为多个子任务
- 在前端展示处理进度
工程化实践:测试、监控与安全
在遗留系统中引入 AI 功能,必须建立完善的工程实践体系,确保系统的可靠性、可维护性和安全性。
测试策略
测试 AI 功能面临独特挑战:LLM 输出具有不确定性,外部 API 可能不稳定。Active Agent 社区推荐使用专门的测试提供者:
# 自定义的测试LLM提供者
module ActiveAgent
module GenerationProvider
class FakeLLMProvider < Base
attr_reader :response
class << self
def generate_response_content
raise NotImplementedError, "必须通过stub指定响应内容"
end
def generations
Thread.current[:generations] ||= []
end
end
def initialize(*)
end
def generate(prompt)
@prompt = prompt
message = ActiveAgent::ActionPrompt::Message.new(
content: self.class.generate_response_content,
role: "assistant"
)
# 记录执行的提示,用于验证
self.class.generations << prompt
@response = ActiveAgent::GenerationProvider::Response.new(
prompt: prompt,
message: message,
raw_response: {}
)
end
end
end
end
测试辅助方法:
module LLMHelpers
def stub_llm_response(content)
allow(ActiveAgent::GenerationProvider::FakeLLMProvider)
.to receive(:generate_response_content).and_return(content)
end
def assert_llm_has_been_called(times: 1)
expect(ActiveAgent::GenerationProvider::FakeLLMProvider)
.to have_received(:generate_response_content).exactly(times).times
end
end
使用监控与配额管理
AI 服务通常按使用量计费,需要建立完善的监控和配额体系:
class ApplicationAgent < ActiveAgent::Base
before_generation :ensure_has_credits
before_generation :track_start_time
after_generation :track_usage
private
def identity = (params[:account] || Current.account)&.ai_identity
def ensure_has_credits
return unless identity
raise "No credits available" unless identity.has_credits?
end
def track_start_time = @start_time = Time.current
def track_usage
return unless response
if identity
identity.generations.create!(
purpose: [agent_name.underscore, action_name].join("/"),
time_spent: (Time.current - @start_time).to_i,
tokens_used: response.tokens_used,
model: generation_provider.config["model"],
provider: generation_provider.config["service"]
)
end
end
end
监控指标应该包括:
- 每次调用的 token 消耗
- 推理延迟分布
- 错误率和重试次数
- 成本分析和预测
安全防护
AI 代理引入了新的安全风险,需要特别注意:
- 提示注入防护:验证和清理用户输入,避免恶意提示覆盖系统指令
- 输出验证:对 AI 生成的内容进行格式和内容验证
- 访问控制:确保 AI 代理只能访问授权数据
- 审计日志:记录所有 AI 操作,便于安全审计
工具集成与结构化输出
复杂的 AI 代理需要调用外部工具和返回结构化数据。Active Agent 提供了相应的机制,但在遗留系统中需要特别注意集成方式。
工具调用
class ReviewAgent < ApplicationAgent
def review(proposal)
@proposal = proposal
prompt(output_schema: "review_schema")
end
def search_talks
query = params[:query]
results = TrieveClient.search(query)
prompt(instructions: "") do |format|
format.html { render partial: "search_talks_results", locals: {results: results} }
end
end
end
工具定义需要 JSON Schema,这在一定程度上违背了 Rails 的约定优于配置原则。社区正在探索使用 RBS 类型签名等更优雅的方案。
结构化输出
通过指定输出模式,可以让 LLM 返回结构化数据:
result = ReviewAgent.review(proposal).generate_now
JSON.parse(result.message.content)
#=> {
# "scores": {"novelty":4,"relevance":4,"quality":5},
# "feedback": "This one is a good fit for the conference",
# "notes": "I've searched for the topic and couldn't find a lot of talks like this one"
# }
未来可能支持更优雅的结构化输出方式,如直接返回 Ruby 对象。
未来方向与最佳实践
在遗留 Rails 单体中集成 AI 代理是一个持续演进的过程。基于当前实践,我们总结出以下最佳实践:
渐进式集成策略
- 从只读功能开始:先实现不修改数据的 AI 功能,如内容分析、分类等
- 逐步引入写操作:在验证可靠性和安全性后,逐步引入数据修改功能
- 建立回滚机制:确保每个 AI 功能都有对应的手动回滚路径
- A/B 测试:通过 A/B 测试验证 AI 功能的实际效果
架构演进建议
- 服务边界清晰化:明确 AI 代理的职责边界,避免成为新的上帝对象
- 依赖倒置:通过接口抽象 AI 服务依赖,便于切换提供商
- 可观测性优先:在开发初期就建立完善的监控和日志体系
- 文档驱动:为每个 AI 代理编写清晰的文档,说明其输入、输出和行为
团队能力建设
- 跨职能协作:AI 开发需要产品、工程、数据科学等多方协作
- 持续学习:AI 技术快速演进,团队需要建立持续学习机制
- 经验分享:建立内部知识库,分享 AI 集成的最佳实践和教训
结论
在 7 年历史的 Rails 单体应用中集成 AI 代理是一项充满挑战但回报丰厚的工程实践。通过采用 Active Agent 等 Rails 原生框架,遵循渐进式集成策略,建立完善的测试和监控体系,团队可以在不破坏现有系统稳定性的前提下,逐步引入 AI 能力。
关键的成功因素包括:对遗留系统的深入理解、谨慎的架构决策、完善的工程实践,以及团队的持续学习能力。随着 AI 技术的不断成熟和 Rails 生态的持续演进,我们有理由相信,更多的遗留系统将成功转型为智能化的现代应用。
正如 Evil Martians 团队所观察到的,Rails AI 生态系统仍处于早期阶段,但 Active Agent 等框架已经展示了将 AI 功能以 Rails 方式集成的巨大潜力。对于面临类似挑战的团队,现在正是开始探索和实践的最佳时机。
资料来源:
- Evil Martians, "Exploring Active Agent, or can we build AI features the Rails way?", September 2025
- 关于 Rails 单体应用现代化和数据库迁移的最佳实践指南