2023年AI工程领域有个冷数据:Python在机器学习代码库占比87%,但Ruby在特定场景下的开发效率比Python快2.3倍——这个结论来自GitHub年度语言报告,却被多数人忽略。当所有人都在卷Python的async/await时,一小波开发者正在用Ruby的面向对象特性做一件更底层的事:把混乱的提示词工程变成可维护的代码架构。
这不是语言之争。Ruby的class、module、mixin这套组合拳,恰好击中了AI开发的一个隐秘痛点——提示词模板的管理失控。一个中等规模的AI应用,动辄几十上百个提示词版本,散落在JSON、YAML、字符串拼接里,改一处崩三处。Ruby的面向对象设计,本质上是在用软件工程的老办法,解决AI工程的新问题。
PromptTemplate:把字符串变成对象
最原始的提示词管理长什么样?字符串拼接,或者更高级一点,用Jinja2模板。但模板和数据的边界模糊,编译时出错还是运行时出错,全看运气。Ruby的做法是:把模板封装成对象,状态和行为绑定在一起。
PromptTemplate类的设计很直白:初始化时接收模板字符串和变量哈希,compile方法用正则替换占位符,to_s负责延迟求值。这套模式的好处在于,模板变成了一等公民——你可以把它传给方法,存进数组,或者嵌套组合。代码里的正则`/\{\{(\w+)\}\}/`匹配双花括号变量,找不到对应值时保留原样,这比直接抛异常更宽容。
实际用起来是这样的:
```ruby
greeting = PromptTemplate.new("Hello, {{name}}! Your task is {{task}}.", {
"name" => "Ruby Developer",
"task" => "build AI agents"
})
puts greeting.compile
# => Hello, Ruby Developer! Your task is build AI agents.
```
对比Python的f-string或Jinja2,Ruby版本多了一层抽象,但这层抽象在复杂场景下能救命。比如你需要版本控制、A/B测试、或者动态加载模板时,对象化的设计让扩展点清晰可见。2024年开源的LangChain.rb项目,核心架构就借鉴了这个思路。
attr_reader与attr_accessor:访问控制的精确手术
Ruby的宏系统(macro)常被低估。attr_reader和attr_accessor这两个方法,本质上是在运行时动态生成getter和setter,省去了样板代码。对于AI应用中的消息对象,这种精确控制尤其重要。
看ChatMessage类的设计:role、content、timestamp用attr_reader锁定只读,metadata用attr_accessor开放读写。这个区分不是随意的——消息的三要素一旦创建就不该变,但元数据(token消耗、模型版本、延迟指标)需要动态更新。
```ruby
class ChatMessage
attr_reader :role, :content, :timestamp
attr_accessor :metadata
def initialize(role:, content:, metadata: {})
@role = role
@content = content
@timestamp = Time.now
@metadata = metadata
end
end
```
to_h方法的存在暴露了Ruby的一个实用主义传统:对象需要能无缝序列化为哈希,才能和JSON API打交道。AI工程里90%的数据交换格式是JSON,这个设计细节省去了无数转换代码。message.metadata = {tokens: 15, model: "gpt-4"}这行,在Python里需要显式定义__setattr__,Ruby用一行attr_accessor搞定。
这种简洁性在快速迭代阶段是优势。但批评者会指出,动态生成方法增加了认知负担——你不知道某个类有哪些方法,除非看完整段代码或文档。2024年Ruby 3.3引入的RBS类型签名,某种程度上是在回应这个批评。
继承体系:从BaseAgent到专用智能体
AI应用的核心抽象是什么?不是模型,不是API,是Agent(智能体)。一个Agent需要维护对话历史、处理系统提示词、调用外部工具。Ruby的继承机制在这里展现了一种经典但有效的组织方式。
BaseAgent作为基类,封装了通用能力:name和system_prompt的读取,conversation_history的管理,add_message和conversation_context的接口。子类CodeReviewAgent通过super调用父类初始化,同时注入专属的系统提示词:"You are an expert code reviewer. Focus on security, performance, and readability."
```ruby
class CodeReviewAgent < BaseAgent
def initialize(name: "CodeReviewer")
super(
name: name,
system_prompt: "You are an expert code reviewer..."
)
@review_count = 0
end
end
```
@review_count这个实例变量的存在,暗示了继承的一个危险:子类可以随意扩展状态,但父类对此一无所知。在大型项目中,这种隐式依赖会导致调试地狱。Ruby社区的对策是模块(module)和mixin,但这段代码没有展示——可能是为了保持示例简洁,也可能是作者认为继承在此场景足够。
review_code方法的实现很有意思:它把代码片段包装进Markdown代码块,再作为用户消息加入对话历史。这种"自包含"的消息设计,让LLM能识别代码边界,比纯文本拼接更可靠。返回值模拟了LLM调用,实际生产环境会替换为HTTP请求或SDK调用。
被省略的关键拼图:模块与组合
原文到此为止,但懂Ruby的人会追问:module去哪了?这是Ruby区别于Java、Python的核心特性——mixin机制允许把功能模块插入任意类,突破单继承的限制。对于AI工程,这意味着你可以把"工具调用能力"、"记忆管理"、"流式输出"拆成独立模块,按需组合。
一个合理的推测是,作者故意留了这个口子。2024年Ruby for AI的生态正在爆发,LangChain.rb、Boxcars.ai、RubyLLM等项目都在探索最佳实践。模块系统的深度使用,可能是下一篇文章的主题。
另一个被省略的话题是并发。Ruby的全局解释器锁(GIL)长期被诟病,但2024年的Ractor(Actor模型实现)和Async gem已经能让AI应用高效处理I/O密集型任务。这不是Ruby的短板,而是认知滞后——多数人还停留在"Ruby慢"的2010年代印象。
数据层面有个反直觉的事实:根据Stack Overflow 2024开发者调查,Ruby开发者的中位薪资比Python高12%,但岗位数量只有后者的1/8。供需失衡意味着,会用Ruby做AI工程的开发者,议价空间远大于红海竞争的Python圈。
回到技术本身。面向对象的价值在AI时代被重新发现,不是因为OOP有多先进,而是因为提示词工程的本质是状态管理——版本、上下文、元数据、历史记录,这些正是OOP擅长表达的领域。Ruby的语法糖让这种表达足够轻量,不至于像Java那样臃肿。
一个细节值得玩味:原文所有代码示例都省略了错误处理。生产级的PromptTemplate需要处理缺失变量、循环依赖、注入攻击;ChatMessage需要验证role的枚举值;BaseAgent需要限制history长度防止token爆炸。这些 omitted 的复杂度,恰恰是区分demo和产品的分水岭。
2024年4月,LangChain.rb的核心贡献者Andrei Bondarev在RubyConf发表演讲,提到一个数据:用Ruby重构后的AI流水线,代码量比Python版本少40%,测试覆盖率反而更高。他没有解释原因,但现场听众心照不宣——Ruby的块(block)、符号到proc(Symbol#to_proc)、以及这里展示的面向对象抽象,让代码意图更直白。
最后一个问题留给读者:当Python生态的护城河是"库多",Ruby的赌注是"代码好改",AI工程进入维护期后,哪种优势会更值钱?
热门跟贴