Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 1 addition & 21 deletions lib/ruby_llm/agent.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,8 @@ def sync_instructions!(chat_or_id, **kwargs)
end

def render_prompt(name, chat:, inputs:, locals:)
path = prompt_path_for(name)
unless File.exist?(path)
raise RubyLLM::PromptNotFoundError,
"Prompt file not found for #{self}: #{path}. Create the file or use inline instructions."
end

resolved_locals = resolve_prompt_locals(locals, runtime: runtime_context(chat:, inputs:), chat:, inputs:)
ERB.new(File.read(path)).result_with_hash(resolved_locals)
Prompt.new("#{prompt_agent_path}/#{name}").render(**resolved_locals)
end

private
Expand Down Expand Up @@ -304,12 +298,6 @@ def runtime_context(chat:, inputs:)
end
end

def prompt_path_for(name)
filename = name.to_s
filename += '.txt.erb' unless filename.end_with?('.txt.erb')
prompt_root.join(prompt_agent_path, filename)
end

def prompt_agent_path
class_name = name || 'agent'
class_name.gsub('::', '/')
Expand All @@ -319,14 +307,6 @@ def prompt_agent_path
.downcase
end

def prompt_root
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
Rails.root.join('app/prompts')
else
Pathname.new(Dir.pwd).join('app/prompts')
end
end

def resolved_chat_model
return @resolved_chat_model if defined?(@resolved_chat_model)

Expand Down
33 changes: 33 additions & 0 deletions lib/ruby_llm/prompt.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module RubyLLM
# Renders ERB prompt templates from the prompts directory.
class Prompt
attr_reader :name, :path

def initialize(name)
@name = name.to_s
@path = self.class.root.join(
@name.end_with?('.txt.erb') ? @name : "#{@name}.txt.erb"
)
end

def render(**locals)
raise PromptNotFoundError, "Prompt file not found: #{@path}" unless File.exist?(@path)

ERB.new(File.read(@path)).result_with_hash(locals)
end

def self.render(name, **locals)
new(name).render(**locals)
end

def self.root
if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
Rails.root.join('app/prompts')
else
Pathname.new(Dir.pwd).join('app/prompts')
end
end
end
end
59 changes: 59 additions & 0 deletions spec/ruby_llm/prompt_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

require 'spec_helper'
require 'tmpdir'

RSpec.describe RubyLLM::Prompt do
let(:tmpdir) { Dir.mktmpdir }
let(:prompt_dir) { Pathname.new(tmpdir).join('app/prompts') }

before do
prompt_dir.mkpath
allow(described_class).to receive(:root).and_return(prompt_dir)
end

after do
FileUtils.rm_rf(tmpdir)
end

def create_prompt(name, content)
path = prompt_dir.join("#{name}.txt.erb")
path.dirname.mkpath
path.write(content)
end

describe '.render' do
it 'renders a prompt with locals' do
create_prompt('friend', 'Hello, <%= name %>!')
expect(described_class.render('friend', name: 'Andrey')).to eq('Hello, Andrey!')
end

it 'renders a nested prompt path' do
create_prompt('work_assistant/instructions', 'You assist <%= user %>.')
expect(described_class.render('work_assistant/instructions', user: 'Bob')).to eq('You assist Bob.')
end

it 'renders without locals' do
create_prompt('simple', 'Just a static prompt.')
expect(described_class.render('simple')).to eq('Just a static prompt.')
end

it 'raises PromptNotFoundError for missing prompts' do
expect { described_class.render('nonexistent') }.to raise_error(RubyLLM::PromptNotFoundError)
end
end

describe '#render' do
it 'renders the prompt with locals' do
create_prompt('greeting', 'Hi <%= name %>, welcome!')
prompt = described_class.new('greeting')
expect(prompt.render(name: 'Andrey')).to eq('Hi Andrey, welcome!')
end

it 'exposes name and path' do
prompt = described_class.new('greeting')
expect(prompt.name).to eq('greeting')
expect(prompt.path).to eq(prompt_dir.join('greeting.txt.erb'))
end
end
end