Module: Vangrail::Prompt
- Defined in:
- lib/vangrail/prompt.rb
Overview
The slice of Jinja that guardrail prompts actually use.
NeMo prompt files address the turn through {{ user_input }} and
{{ bot_response }}, occasionally with a {% if %} around an optional
section. Rendering that needs variable substitution and one conditional, not
a template engine, and a guardrail prompt is the last place to want
arbitrary evaluation: the text being substituted is attacker-influenced by
construction.
Supported, and nothing else:
{{ name }} substitute, HTML untouched
{{ name | upper }} upper, lower, trim
{% if name %} ... {% endif %} include when truthy and non-empty
An unknown variable renders empty. An unknown filter or tag raises, because a prompt that silently drops the rule you wrote is worse than one that fails to load.
Constant Summary collapse
- FILTERS =
{ 'upper' => lambda(&:upcase), 'lower' => lambda(&:downcase), 'trim' => lambda(&:strip) }.freeze
- TAG =
/\{%\s*(\w+)\s*([^%]*?)\s*%\}/- VAR =
/\{\{\s*([\w.]+)\s*(?:\|\s*(\w+)\s*)?\}\}/
Class Method Summary collapse
- .apply(filter, value) ⇒ Object
- .check_tags(text) ⇒ Object
-
.conditionals(text, vars) ⇒ Object
Only
{% if x %}...{% endif %}, innermost first so nesting resolves. - .lookup(vars, name) ⇒ Object
- .render(template, vars = {}) ⇒ Object
- .truthy?(value) ⇒ Boolean
Class Method Details
.apply(filter, value) ⇒ Object
72 73 74 75 76 77 78 79 80 |
# File 'lib/vangrail/prompt.rb', line 72 def apply(filter, value) text = value.to_s return text if filter.nil? fn = FILTERS[filter] raise ArgumentError, "unsupported template filter |#{filter}" unless fn fn.call(text) end |
.check_tags(text) ⇒ Object
57 58 59 60 61 62 |
# File 'lib/vangrail/prompt.rb', line 57 def (text) text.scan(TAG) do |tag, _rest| raise ArgumentError, "unsupported template tag {% #{tag} %}" unless tag == 'raw' end text end |
.conditionals(text, vars) ⇒ Object
Only {% if x %}...{% endif %}, innermost first so nesting resolves.
44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/vangrail/prompt.rb', line 44 def conditionals(text, vars) out = text loop do replaced = out.sub(/\{%\s*if\s+([\w.]+)\s*%\}(.*?)\{%\s*endif\s*%\}/m) do truthy?(lookup(vars, Regexp.last_match(1))) ? Regexp.last_match(2) : '' end break out if replaced == out out = replaced end (out) end |
.lookup(vars, name) ⇒ Object
64 65 66 67 68 69 70 |
# File 'lib/vangrail/prompt.rb', line 64 def lookup(vars, name) name.split('.').reduce(vars) do |acc, part| break nil unless acc.respond_to?(:[]) acc[part] || (acc.respond_to?(:key?) ? acc[part.to_sym] : nil) end end |
.render(template, vars = {}) ⇒ Object
34 35 36 37 38 39 40 41 |
# File 'lib/vangrail/prompt.rb', line 34 def render(template, vars = {}) text = conditionals(template.to_s, vars) text.gsub(VAR) do name = Regexp.last_match(1) filter = Regexp.last_match(2) apply(filter, lookup(vars, name)) end end |
.truthy?(value) ⇒ Boolean
82 83 84 85 86 |
# File 'lib/vangrail/prompt.rb', line 82 def truthy?(value) return false if value.nil? || value == false !(value.respond_to?(:empty?) && value.empty?) end |