Prompt Rendering
Render reusable ERB prompt templates from app/prompts with RubyLLM.render_prompt
After reading this guide, you will know:
- How to store prompt templates in
app/prompts. - How to render templates with
RubyLLM.render_prompt. - How locals, nested paths, and
.txt.erbfilenames work. - How rendered prompts fit into chats and agents.
- Which errors to expect when a prompt file is missing.
Rendering a Prompt
RubyLLM.render_prompt renders a local ERB template and returns the rendered string. It does not call a model or add anything to a chat by itself.
Create app/prompts/support/instructions.txt.erb:
You are a support assistant for <%= product_name %>.
The current customer is <%= customer_name %>.
Answer with concise, practical steps.
Render it with keyword locals:
instructions = RubyLLM.render_prompt(
"support/instructions",
product_name: "BillingHub",
customer_name: current_user.name
)
chat = RubyLLM.chat
chat.with_instructions(instructions)
chat.ask("How do I update my invoice email?")
RubyLLM.render_prompt("support/instructions") resolves to:
app/prompts/support/instructions.txt.erb
In Rails apps, the path is relative to Rails.root. Outside Rails, it is relative to the current working directory.
Static Prompts
Prompts do not need locals. For example, app/prompts/reviewer.txt.erb:
You are a careful code reviewer. Focus on correctness, security, and missing tests.
instructions = RubyLLM.render_prompt("reviewer")
chat.with_instructions(instructions)
Locals
Every keyword argument passed to render_prompt is available in the ERB template. Create app/prompts/messages/welcome.txt.erb:
Welcome <%= name %>.
Your plan is <%= plan_name %>.
RubyLLM.render_prompt(
"messages/welcome",
name: "Ada",
plan_name: "Pro"
)
# => "Welcome Ada.\n\nYour plan is Pro.\n"
If the template references a local you did not pass, ERB raises an error while rendering.
Nested Paths and Extensions
Prompt names can include nested directories:
RubyLLM.render_prompt("work_assistant/instructions", display_name: "Ada")
This renders:
app/prompts/work_assistant/instructions.txt.erb
You can also pass the full filename:
RubyLLM.render_prompt("work_assistant/instructions.txt.erb", display_name: "Ada")
RubyLLM prompt templates use .txt.erb.
Prompts in Engines
RubyLLM::Prompt.roots is the ordered list of directories searched for prompt files, the same way ActionView searches view paths. The application’s app/prompts is always first, so a gem or Rails engine can ship its own prompts by appending its directory in an initializer:
# my_engine/lib/my_engine/engine.rb
module MyEngine
class Engine < ::Rails::Engine
initializer "my_engine.prompts" do
RubyLLM::Prompt.roots << MyEngine::Engine.root.join("app/prompts")
end
end
end
An agent shipped by the engine then finds its prompt in the engine:
my_engine/app/prompts/my_engine/chat_agent/instructions.txt.erb
Because the application root is searched first, a host app overrides any engine prompt by creating a file at the same relative path:
app/prompts/my_engine/chat_agent/instructions.txt.erb
Missing Prompts
If RubyLLM cannot find the prompt file, it raises RubyLLM::PromptNotFoundError:
RubyLLM.render_prompt("missing")
# => raises RubyLLM::PromptNotFoundError
Using Rendered Prompts in Chat
Use rendered prompts anywhere you would use a string:
system_prompt = RubyLLM.render_prompt(
"analysis/instructions",
timezone: Time.zone.name,
account_type: current_account.plan_name
)
chat = RubyLLM.chat(model: "gpt-5.6-luna")
chat.with_instructions(system_prompt)
response = chat.ask(
RubyLLM.render_prompt("analysis/question", topic: params[:topic])
)
Prompt rendering is local string templating. For provider-side reuse of large stable prompt prefixes, see Prompt Caching.
Using Prompts with Agents
Agents build on the same prompt renderer. Named agents automatically render their conventional prompt when it exists:
class WorkAssistant < RubyLLM::Agent
chat_model Chat
end
For WorkAssistant, RubyLLM looks for:
app/prompts/work_assistant/instructions.txt.erb
If the file exists, it is used as the agent’s system instructions. If it does not exist and the agent has no instructions macro, the agent starts without system instructions.
Reference the prompt explicitly when the file is required; RubyLLM then raises RubyLLM::PromptNotFoundError if it is missing:
class WorkAssistant < RubyLLM::Agent
chat_model Chat
instructions { prompt("instructions") }
end
You can pass locals to the conventional prompt:
class WorkAssistant < RubyLLM::Agent
chat_model Chat
instructions display_name: -> { chat.user.display_name_or_email }
end
For the full class-based conventions, see Agents.
Safety Notes
ERB templates execute Ruby code, so prompt files should be trusted application code. Do not pass untrusted user input as the prompt name; keep prompt names as application-controlled constants or map user choices to known prompt names.
Locals are inserted exactly as rendered by ERB. If your prompt format needs escaping or quoting, do that before passing the local or inside the template.