跳转到内容

Liquid 模板引擎在 AI Agent 中的深度集成

Liquid 模板引擎在 AI Agent 中的深度集成

学习目标

读完本章后,你将能够:

  • 理解 Liquid 模板从视图层扩展到配置层的设计动机
  • 掌握自定义过滤器、标签和块的注册方法
  • 设计上下文栈系统支持动态插值
  • 识别模板注入攻击的防御策略

核心概念

为什么在 Agent 系统中引入模板引擎

AI Agent 系统通常面临一个问题:如何让用户在不编写代码的情况下,灵活定义 Agent 的行为参数

Liquid 模板引擎的优势:

  • 安全沙箱:不能执行任意代码,只能访问显式暴露的变量和方法
  • 用户友好{{ variable }}{% if condition %} 语法直观
  • 可扩展:支持自定义过滤器、标签和块
  • 纯字符串操作:输出总是字符串,不会意外执行副作用

三层扩展模式

将模板引擎引入 Agent 系统需要三个层次的扩展:


上下文系统设计

插值上下文栈

模板引擎的上下文不是一个扁平的字典,而是一个栈结构,支持动态推送和弹出作用域:

关键设计决策

  1. Agent 始终可用_agent_ 变量在所有上下文中都指向当前 Agent,可在模板中引用 {{ _agent_.name }}
  2. 事件驱动作用域interpolate_with(event) 将事件 payload 推入环境栈顶部,使 {{ field_name }} 直接访问事件字段
  3. 批量迭代interpolate_with_each(array) 遍历事件数组,每次迭代自动设置 current_event
  4. 共享状态:通过 KeyValueStoreAgent 注入的变量在所有模板中可用

递归插值

当 Agent 的 options 是嵌套的 Hash/Array 结构时,interpolate_options 递归遍历所有层级,对字符串值应用模板解析:

原始 options:
{
"url": "https://api.example.com/{{ city }}/weather",
"headers": {
"Authorization": "Bearer {{ api_key }}"
},
"extract": ["title", "body"]
}
插值后 (假设 city=Tokyo, api_key=abc123):
{
"url": "https://api.example.com/Tokyo/weather",
"headers": {
"Authorization": "Bearer abc123"
},
"extract": ["title", "body"]
}

自定义扩展

自定义过滤器

# 注册到 Liquid::Environment
module Filters
def uri_escape(string)
CGI.escape(string)
rescue StandardError
string
end
def hmac_sha256(input, key)
OpenSSL::HMAC.hexdigest('sha256', key.to_s, input.to_s)
end
def as_object(object)
# 通过 throw/catch 返回非字符串对象
throw :as_object, object.as_json
end
end
Liquid::Environment.default.register_filter(Filters)

as_object 的特殊处理

Liquid 默认总是返回字符串。as_object 过滤器通过 throw :as_object 跳出模板渲染,将原始 Ruby 对象(如 Hash/Array)返回给调用者。这是必须作为最后一个过滤器的原因——后续的字符串操作会破坏对象结构。

自定义标签

class Credential < Liquid::Tag
def render(context)
# 通过 context.registers[:agent] 访问当前 Agent
context.registers[:agent].credential(@credential_name) || ""
end
end
Liquid::Environment.default.register_tag('credential', Credential)

自定义块

正则替换块是典型的自定义块实现,包含两个子块(inwith):

{% regex_replace "\w+" in %}Use me like this.{% with %}{{ match | capitalize }}{% endregex_replace %}

实现要点:

  • parse 方法分别解析 in 块和 with 块的主体
  • render 方法对 in 块渲染结果应用正则匹配
  • 每次匹配时将 match 变量推入上下文栈,渲染 with
  • 支持命名捕获组自动暴露为变量

安全考量

模板注入防御

Liquid 模板天然安全,但仍需注意:

风险防御策略
访问不应该的数据只通过 to_liquid 暴露安全的属性和方法
无限循环Liquid 的循环次数限制
拒绝服务(复杂正则)regex_replace 在 Agent 端执行,受 Agent 超时限制
凭据泄露credential 标签绑定到当前用户的凭据,无法跨用户访问

验证前置

模板错误应在保存时捕获,而非运行时:

validate :validate_interpolation
def validate_interpolation
interpolated # 尝试插值,抛出 Liquid::Error 则添加验证错误
rescue Liquid::Error => e
errors.add(:options, "has an error with Liquid templating: #{e.message}")
end

设计取舍

Liquid vs 其他模板引擎

维度LiquidERBJinja2自定义 DSL
安全性沙箱隔离可执行任意 Ruby沙箱(需配置)取决于实现
扩展性过滤器/标签/块Ruby 方法过滤器/测试完全自定义
用户学习成本
性能取决于实现
生态Shopify 社区Ruby 社区Python 社区

选择 Liquid 的原因:安全沙箱是核心需求——Agent 的配置由用户定义,不能允许执行任意代码。

模板引擎 vs 代码编辑

维度模板引擎代码编辑(JavaScriptAgent)
安全边界沙箱限制完整语言访问权限
灵活性有限(字符串操作为主)完全(任意逻辑)
适用场景配置参数、简单转换复杂业务逻辑

最佳实践:模板引擎处理 80% 的配置需求,代码编辑处理剩余 20% 的复杂场景


参考来源