diff --git a/enterprise/lib/captain/prompt_renderer.rb b/enterprise/lib/captain/prompt_renderer.rb index 1a73ddd15..276856417 100644 --- a/enterprise/lib/captain/prompt_renderer.rb +++ b/enterprise/lib/captain/prompt_renderer.rb @@ -5,7 +5,7 @@ class Captain::PromptRenderer def render(template_name, context = {}) template = load_template(template_name) liquid_template = Liquid::Template.parse(template) - liquid_template.render(stringify_keys(context)) + liquid_template.render(stringify_keys(context), registers: { file_system: snippet_file_system }) end private @@ -18,6 +18,13 @@ class Captain::PromptRenderer File.read(template_path) end + def snippet_file_system + @snippet_file_system ||= Liquid::LocalFileSystem.new( + Rails.root.join('enterprise/lib/captain/prompts/snippets'), + '%s.liquid' + ) + end + def stringify_keys(hash) hash.deep_stringify_keys end diff --git a/enterprise/lib/captain/prompts/assistant.liquid b/enterprise/lib/captain/prompts/assistant.liquid index 7b20a089e..1ba3ba7a7 100644 --- a/enterprise/lib/captain/prompts/assistant.liquid +++ b/enterprise/lib/captain/prompts/assistant.liquid @@ -14,11 +14,11 @@ Don't digress away from your instructions, and use all the available tools at yo Here's the metadata we have about the current conversation and the contact associated with it: {% if conversation -%} -{% render 'conversation' %} +{% render 'conversation', conversation: conversation %} {% endif -%} {% if contact -%} -{% render 'contact' %} +{% render 'contact', contact: contact %} {% endif -%} {% if campaign.id -%} diff --git a/enterprise/lib/captain/prompts/scenario.liquid b/enterprise/lib/captain/prompts/scenario.liquid index 879a39f52..10eeb6fd7 100644 --- a/enterprise/lib/captain/prompts/scenario.liquid +++ b/enterprise/lib/captain/prompts/scenario.liquid @@ -14,11 +14,11 @@ If you believe the user's request is not within the scope of your role, you can Here's the metadata we have about the current conversation and the contact associated with it: {% if conversation -%} -{% render 'conversation' %} +{% render 'conversation', conversation: conversation %} {% endif -%} {% if contact -%} -{% render 'contact' %} +{% render 'contact', contact: contact %} {% endif -%} {% if campaign.id -%} diff --git a/spec/enterprise/lib/captain/prompt_renderer_spec.rb b/spec/enterprise/lib/captain/prompt_renderer_spec.rb index 761d55f99..910319253 100644 --- a/spec/enterprise/lib/captain/prompt_renderer_spec.rb +++ b/spec/enterprise/lib/captain/prompt_renderer_spec.rb @@ -58,7 +58,7 @@ RSpec.describe Captain::PromptRenderer do it 'loads and parses liquid template' do liquid_template_double = instance_double(Liquid::Template) allow(Liquid::Template).to receive(:parse).with(template_content).and_return(liquid_template_double) - allow(liquid_template_double).to receive(:render).with(hash_including('name', 'balance')).and_return('rendered') + allow(liquid_template_double).to receive(:render).with(hash_including('name', 'balance'), anything).and_return('rendered') result = described_class.render(template_name, context) @@ -67,6 +67,36 @@ RSpec.describe Captain::PromptRenderer do end end + describe 'snippet rendering' do + let(:snippets_dir) { Rails.root.join('enterprise/lib/captain/prompts/snippets') } + let(:snippet_path) { snippets_dir.join('greeting.liquid') } + + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:read).and_call_original + allow(File).to receive(:exist?).with(template_path).and_return(true) + # Create a controlled snippet to decouple from real snippet content + allow(File).to receive(:exist?).with(snippet_path.to_s).and_return(true) + allow(File).to receive(:read).with(snippet_path.to_s).and_return('Hello {{ name }}') + end + + it 'resolves render tags from the snippets directory' do + allow(File).to receive(:read).with(template_path).and_return("{% render 'greeting', name: name %}") + + result = described_class.render(template_name, { name: 'World' }) + + expect(result).to eq('Hello World') + end + + it 'outputs a liquid error for missing snippets' do + allow(File).to receive(:read).with(template_path).and_return("{% render 'nonexistent' %}") + + result = described_class.render(template_name, {}) + + expect(result).to include('Liquid error') + end + end + describe '.load_template' do it 'reads template file from correct path' do described_class.send(:load_template, template_name)