Generators and App Conventions
Scaffold chats, a chat UI, agents, tools, and schemas with RubyLLM’s Rails generators and conventions.
After reading this guide, you will know:
- What the
ruby_llm:installandruby_llm:chat_uigenerators create for you. - How RubyLLM’s conventional app directory structure is organized.
- How to generate starter agents, tools, and schemas.
- How the generated chat UI renders messages, tool calls, and tool results.
- How to customize model names and set up ActiveStorage.
RubyLLM’s generators write models, migrations, controllers, jobs, and views into your application. Start with persistence, then add the chat UI or generate individual agents, tools, and schemas as you need them.
Quick Setup with Generator
Run the install generator:
bin/rails generate ruby_llm:install
The generator:
- Creates application migrations and models for Chat and Message
- Creates one migration for RubyLLM’s internal model, tool-call, usage, and batch tables
- Adds the
acts_as_chatandacts_as_messagedeclarations - Installs ActiveStorage for file attachments
- Creates an initializer for provider configuration
- Creates conventional AI app directories
After running the generator:
bin/rails db:migrate
bin/rails ruby_llm:load_models
You can now create a persisted chat with Chat.create! and send a message with chat.ask.
Install Generator Options
The generator uses Rails-like syntax for custom model names:
# Default - creates the application Chat and Message models
bin/rails generate ruby_llm:install
bin/rails generate ruby_llm:install chat:Conversation message:ChatMessage
bin/rails generate ruby_llm:install chat:Discussion message:DiscussionMessage
# Skip ActiveStorage if you don't need file attachments
bin/rails generate ruby_llm:install --skip-active-storage
The name:ClassName syntax follows Rails conventions - specify only what you want to customize.
For most apps, keep the default behavior (install ActiveStorage) so file attachments work out of the box. Use --skip-active-storage only when you’re sure you won’t send files to models.
Adding a Chat UI
Run the chat UI generator to add controllers, views, and a background job:
bin/rails generate ruby_llm:chat_ui
The generator creates:
- Controllers: Create chats and enqueue message processing
- Views: Render messages and update them with Turbo Streams
- Jobs: Request AI responses in the background
- Routes: RESTful routes for chats and messages
Start your server and visit http://localhost:3000/chats.
The UI generator also supports custom model names:
bin/rails generate ruby_llm:chat_ui chat:Conversation message:ChatMessage
Upgrading an Existing Integration
For an application on RubyLLM 1.16, use ruby_llm:upgrade. It generates preparation, backfill, and finish migrations. Generate one phase with --phase prepare, --phase backfill, or --phase finish when you need to schedule them separately. Generate --phase cleanup in a later deployment to remove legacy message columns.
The default --mode rename renames the existing supporting tables. --mode copy retains them and generates compatibility files for a controlled return to 1.16. Use the same mode for every phase. Read the copy-mode requirements and rollback limits before choosing it.
Keep affected activity paused until the upgrade and application-specific data conversion are complete. Review the upgrade and recovery procedure before running these migrations.
Conventional Directory Structure
RubyLLM’s Rails generators establish a default app structure:
app/
|-- agents/
|-- prompts/
|-- schemas/
`-- tools/
The install generator creates these directories with .gitkeep files so teams start from one shared convention.
These are conventions, not hard requirements:
- Agents, tools, and schemas can live anywhere in your app autoload paths.
- Prompt lookup convention: named agents automatically resolve
instructions.txt.erbfrom the agent class name when the file exists.
For prompt lookup, RubyLLM uses class name conventions:
WorkAssistant->app/prompts/work_assistant/instructions.txt.erbAdmin::SupportAgent->app/prompts/admin/support_agent/instructions.txt.erb
See the Agents guide for how agent instruction rendering works, or Prompt Rendering for direct RubyLLM.render_prompt usage.
Rails Generators for Agents, Tools, and Schemas
Alongside ruby_llm:install and ruby_llm:chat_ui, Rails apps can generate starter classes for common AI building blocks:
bin/rails generate ruby_llm:agent Support
bin/rails generate ruby_llm:tool Weather
bin/rails generate ruby_llm:schema Product
What each generator creates:
ruby_llm:agent:app/agents/support_agent.rbandapp/prompts/support_agent/instructions.txt.erbruby_llm:tool:app/tools/weather_tool.rbplus tool-specific chat UI partials underapp/views/messages/tool_callsandapp/views/messages/tool_resultsruby_llm:schema:app/schemas/product_schema.rb
ruby_llm:chat_ui and ruby_llm:tool accept --ui scaffold or --ui tailwind. The default, --ui auto, picks Tailwind when your app has it. An app coming from 1.16 runs ruby_llm:upgrade instead of ruby_llm:install. See Upgrading.
If your chat UI uses a custom message model, pass the same mapping you gave ruby_llm:chat_ui so the partials land where the UI looks for them:
bin/rails generate ruby_llm:tool Weather message:ChatMessage
Chat UI View Conventions
The generated chat UI follows one convention: each message partial uses the local that matches its partial name.
messages/_user.html.erbgetsusermessages/_assistant.html.erbgetsassistantmessages/_system.html.erbgetssystemmessages/_tool.html.erbgetstoolmessages/_tool_calls.html.erbgetstool_calls
This comes from Rails partial rendering: render @chat.messages calls to_partial_path, and Rails injects a local named after that partial.
For compatibility with model broadcasts (broadcasts_to), generated message partials also accept a message local as a fallback.
Tool Call and Tool Result Partials
Tool-specific partials are generated under app/views/messages/tool_calls and app/views/messages/tool_results:
app/views/messages/
|-- tool_calls/
| |-- _default.html.erb
| `-- _your_tool.html.erb
`-- tool_results/
|-- _default.html.erb
`-- _your_tool.html.erb
Locals passed to those partials:
messages/tool_calls/_your_tool.html.erbreceivesmessageandtool_callmessages/tool_results/_your_tool.html.erbreceivestool
ruby_llm:tool creates _your_tool.html.erb files with the correct names so custom rendering hooks up automatically.
Using fixed locals keeps the templates dumb and predictable.
Turbo Stream templates used by the generated chat UI:
messages/create.turbo_stream.erbresets the message form forMessagesController#create.
Setting Up ActiveStorage
The generator automatically configures ActiveStorage for file attachments. If you skipped it during generation, add it manually:
bin/rails active_storage:install
bin/rails db:migrate
Then add to your Message model:
# app/models/message.rb
class Message < ApplicationRecord
acts_as_message
has_many_attached :attachments # Required for file attachments
end
This :attachments association is only required on RubyLLM message records. The ActiveStorage attachments you pass to with: from your own models can use any name.
Next Steps
- Persistence with acts_as - what the generated models do once they
acts_as_chat. - Streaming with Hotwire/Turbo - the streaming flow behind the generated chat UI.
- Agents - build on the generated agent and prompt conventions.
- Advanced Rails Configuration - provider overrides and custom contexts for the generated app.