rubyllm-observ 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +778 -0
  3. data/Rakefile +49 -0
  4. data/app/assets/javascripts/observ/application.js +12 -0
  5. data/app/assets/javascripts/observ/controllers/autoscroll_controller.js +33 -0
  6. data/app/assets/javascripts/observ/controllers/chat_form_controller.js +93 -0
  7. data/app/assets/javascripts/observ/controllers/copy_controller.js +43 -0
  8. data/app/assets/javascripts/observ/controllers/dashboard_controller.js +58 -0
  9. data/app/assets/javascripts/observ/controllers/drawer_controller.js +58 -0
  10. data/app/assets/javascripts/observ/controllers/expandable_controller.js +33 -0
  11. data/app/assets/javascripts/observ/controllers/filter_controller.js +36 -0
  12. data/app/assets/javascripts/observ/controllers/index.js +52 -0
  13. data/app/assets/javascripts/observ/controllers/json_viewer_controller.js +260 -0
  14. data/app/assets/javascripts/observ/controllers/message_form_controller.js +58 -0
  15. data/app/assets/javascripts/observ/controllers/prompt_variables_controller.js +64 -0
  16. data/app/assets/javascripts/observ/controllers/text_select_controller.js +14 -0
  17. data/app/assets/stylesheets/observ/_annotations.scss +127 -0
  18. data/app/assets/stylesheets/observ/_card.scss +52 -0
  19. data/app/assets/stylesheets/observ/_chat.scss +156 -0
  20. data/app/assets/stylesheets/observ/_components.scss +460 -0
  21. data/app/assets/stylesheets/observ/_dashboard.scss +40 -0
  22. data/app/assets/stylesheets/observ/_datasets.scss +697 -0
  23. data/app/assets/stylesheets/observ/_drawer.scss +273 -0
  24. data/app/assets/stylesheets/observ/_json_viewer.scss +120 -0
  25. data/app/assets/stylesheets/observ/_layout.scss +256 -0
  26. data/app/assets/stylesheets/observ/_metrics.scss +99 -0
  27. data/app/assets/stylesheets/observ/_observations.scss +160 -0
  28. data/app/assets/stylesheets/observ/_pagination.scss +143 -0
  29. data/app/assets/stylesheets/observ/_prompts.scss +365 -0
  30. data/app/assets/stylesheets/observ/_table.scss +53 -0
  31. data/app/assets/stylesheets/observ/_variables.scss +53 -0
  32. data/app/assets/stylesheets/observ/application.scss +15 -0
  33. data/app/controllers/observ/annotations_controller.rb +144 -0
  34. data/app/controllers/observ/application_controller.rb +8 -0
  35. data/app/controllers/observ/chats_controller.rb +58 -0
  36. data/app/controllers/observ/dashboard_controller.rb +159 -0
  37. data/app/controllers/observ/dataset_items_controller.rb +85 -0
  38. data/app/controllers/observ/dataset_run_items_controller.rb +84 -0
  39. data/app/controllers/observ/dataset_runs_controller.rb +110 -0
  40. data/app/controllers/observ/datasets_controller.rb +74 -0
  41. data/app/controllers/observ/messages_controller.rb +26 -0
  42. data/app/controllers/observ/observations_controller.rb +59 -0
  43. data/app/controllers/observ/prompt_versions_controller.rb +148 -0
  44. data/app/controllers/observ/prompts_controller.rb +205 -0
  45. data/app/controllers/observ/sessions_controller.rb +45 -0
  46. data/app/controllers/observ/traces_controller.rb +86 -0
  47. data/app/forms/observ/prompt_form.rb +96 -0
  48. data/app/helpers/observ/application_helper.rb +9 -0
  49. data/app/helpers/observ/chats_helper.rb +47 -0
  50. data/app/helpers/observ/dashboard_helper.rb +154 -0
  51. data/app/helpers/observ/datasets_helper.rb +62 -0
  52. data/app/helpers/observ/pagination_helper.rb +38 -0
  53. data/app/jobs/observ/application_job.rb +4 -0
  54. data/app/jobs/observ/dataset_runner_job.rb +49 -0
  55. data/app/mailers/observ/application_mailer.rb +6 -0
  56. data/app/models/concerns/observ/agent_phaseable.rb +124 -0
  57. data/app/models/concerns/observ/agent_selectable.rb +50 -0
  58. data/app/models/concerns/observ/chat_enhancements.rb +109 -0
  59. data/app/models/concerns/observ/message_enhancements.rb +31 -0
  60. data/app/models/concerns/observ/observability_instrumentation.rb +124 -0
  61. data/app/models/concerns/observ/prompt_management.rb +320 -0
  62. data/app/models/concerns/observ/trace_association.rb +9 -0
  63. data/app/models/observ/annotation.rb +23 -0
  64. data/app/models/observ/application_record.rb +5 -0
  65. data/app/models/observ/dataset.rb +51 -0
  66. data/app/models/observ/dataset_item.rb +41 -0
  67. data/app/models/observ/dataset_run.rb +104 -0
  68. data/app/models/observ/dataset_run_item.rb +111 -0
  69. data/app/models/observ/generation.rb +56 -0
  70. data/app/models/observ/null_prompt.rb +59 -0
  71. data/app/models/observ/observation.rb +38 -0
  72. data/app/models/observ/prompt.rb +315 -0
  73. data/app/models/observ/score.rb +51 -0
  74. data/app/models/observ/session.rb +131 -0
  75. data/app/models/observ/span.rb +13 -0
  76. data/app/models/observ/trace.rb +135 -0
  77. data/app/presenters/observ/agent_select_presenter.rb +59 -0
  78. data/app/services/observ/agent_executor_service.rb +174 -0
  79. data/app/services/observ/agent_provider.rb +60 -0
  80. data/app/services/observ/agent_selection_service.rb +53 -0
  81. data/app/services/observ/chat_instrumenter.rb +523 -0
  82. data/app/services/observ/dataset_runner_service.rb +153 -0
  83. data/app/services/observ/evaluator_runner_service.rb +58 -0
  84. data/app/services/observ/evaluators/base_evaluator.rb +51 -0
  85. data/app/services/observ/evaluators/contains_evaluator.rb +53 -0
  86. data/app/services/observ/evaluators/exact_match_evaluator.rb +23 -0
  87. data/app/services/observ/evaluators/json_structure_evaluator.rb +44 -0
  88. data/app/services/observ/prompt_manager/cache_statistics.rb +82 -0
  89. data/app/services/observ/prompt_manager/caching.rb +167 -0
  90. data/app/services/observ/prompt_manager/comparison.rb +49 -0
  91. data/app/services/observ/prompt_manager/version_management.rb +96 -0
  92. data/app/services/observ/prompt_manager.rb +40 -0
  93. data/app/services/observ/trace_text_formatter.rb +349 -0
  94. data/app/validators/observ/prompt_config_validator.rb +187 -0
  95. data/app/views/kaminari/_first_page.html.erb +11 -0
  96. data/app/views/kaminari/_gap.html.erb +8 -0
  97. data/app/views/kaminari/_last_page.html.erb +11 -0
  98. data/app/views/kaminari/_next_page.html.erb +11 -0
  99. data/app/views/kaminari/_page.html.erb +12 -0
  100. data/app/views/kaminari/_paginator.html.erb +25 -0
  101. data/app/views/kaminari/_prev_page.html.erb +11 -0
  102. data/app/views/kaminari/observ/_first_page.html.erb +11 -0
  103. data/app/views/kaminari/observ/_gap.html.erb +8 -0
  104. data/app/views/kaminari/observ/_last_page.html.erb +11 -0
  105. data/app/views/kaminari/observ/_next_page.html.erb +11 -0
  106. data/app/views/kaminari/observ/_page.html.erb +12 -0
  107. data/app/views/kaminari/observ/_paginator.html.erb +25 -0
  108. data/app/views/kaminari/observ/_prev_page.html.erb +11 -0
  109. data/app/views/layouts/observ/application.html.erb +88 -0
  110. data/app/views/observ/annotations/_annotation.html.erb +13 -0
  111. data/app/views/observ/annotations/_form.html.erb +28 -0
  112. data/app/views/observ/annotations/index.html.erb +28 -0
  113. data/app/views/observ/annotations/sessions_index.html.erb +48 -0
  114. data/app/views/observ/annotations/traces_index.html.erb +48 -0
  115. data/app/views/observ/chats/_form.html.erb +45 -0
  116. data/app/views/observ/chats/index.html.erb +67 -0
  117. data/app/views/observ/chats/new.html.erb +17 -0
  118. data/app/views/observ/chats/show.html.erb +34 -0
  119. data/app/views/observ/dashboard/index.html.erb +236 -0
  120. data/app/views/observ/dataset_items/_form.html.erb +49 -0
  121. data/app/views/observ/dataset_items/edit.html.erb +18 -0
  122. data/app/views/observ/dataset_items/index.html.erb +95 -0
  123. data/app/views/observ/dataset_items/new.html.erb +18 -0
  124. data/app/views/observ/dataset_run_items/_score_close_drawer.html.erb +4 -0
  125. data/app/views/observ/dataset_run_items/_score_drawer.html.erb +75 -0
  126. data/app/views/observ/dataset_run_items/_score_success.html.erb +29 -0
  127. data/app/views/observ/dataset_run_items/_scores_cell.html.erb +19 -0
  128. data/app/views/observ/dataset_run_items/details_drawer.turbo_stream.erb +80 -0
  129. data/app/views/observ/dataset_run_items/score_drawer.turbo_stream.erb +7 -0
  130. data/app/views/observ/dataset_runs/index.html.erb +108 -0
  131. data/app/views/observ/dataset_runs/new.html.erb +57 -0
  132. data/app/views/observ/dataset_runs/review.html.erb +155 -0
  133. data/app/views/observ/dataset_runs/show.html.erb +166 -0
  134. data/app/views/observ/datasets/_form.html.erb +62 -0
  135. data/app/views/observ/datasets/_items_tab.html.erb +66 -0
  136. data/app/views/observ/datasets/_runs_tab.html.erb +82 -0
  137. data/app/views/observ/datasets/edit.html.erb +32 -0
  138. data/app/views/observ/datasets/index.html.erb +105 -0
  139. data/app/views/observ/datasets/new.html.erb +18 -0
  140. data/app/views/observ/datasets/show.html.erb +67 -0
  141. data/app/views/observ/messages/_content.html.erb +1 -0
  142. data/app/views/observ/messages/_form.html.erb +33 -0
  143. data/app/views/observ/messages/_message.html.erb +14 -0
  144. data/app/views/observ/messages/_tool_calls.html.erb +10 -0
  145. data/app/views/observ/messages/create.turbo_stream.erb +9 -0
  146. data/app/views/observ/observations/index.html.erb +97 -0
  147. data/app/views/observ/observations/show_generation.html.erb +195 -0
  148. data/app/views/observ/observations/show_span.html.erb +93 -0
  149. data/app/views/observ/prompts/_diff_content.html.erb +16 -0
  150. data/app/views/observ/prompts/_form.html.erb +111 -0
  151. data/app/views/observ/prompts/_new_form.html.erb +102 -0
  152. data/app/views/observ/prompts/_prompt_actions.html.erb +4 -0
  153. data/app/views/observ/prompts/_prompt_content_highlighted.html.erb +4 -0
  154. data/app/views/observ/prompts/_version_actions.html.erb +40 -0
  155. data/app/views/observ/prompts/compare.html.erb +155 -0
  156. data/app/views/observ/prompts/edit.html.erb +17 -0
  157. data/app/views/observ/prompts/index.html.erb +108 -0
  158. data/app/views/observ/prompts/new.html.erb +17 -0
  159. data/app/views/observ/prompts/show.html.erb +138 -0
  160. data/app/views/observ/prompts/versions.html.erb +87 -0
  161. data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +25 -0
  162. data/app/views/observ/sessions/drawer_test.turbo_stream.erb +49 -0
  163. data/app/views/observ/sessions/index.html.erb +91 -0
  164. data/app/views/observ/sessions/show.html.erb +251 -0
  165. data/app/views/observ/traces/add_to_dataset_drawer.turbo_stream.erb +48 -0
  166. data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +25 -0
  167. data/app/views/observ/traces/index.html.erb +87 -0
  168. data/app/views/observ/traces/show.html.erb +285 -0
  169. data/app/views/observ/traces/text_output_drawer.turbo_stream.erb +48 -0
  170. data/app/views/shared/_drawer.html.erb +26 -0
  171. data/config/routes.rb +80 -0
  172. data/db/migrate/001_create_observ_sessions.rb +21 -0
  173. data/db/migrate/002_create_observ_traces.rb +25 -0
  174. data/db/migrate/003_create_observ_observations.rb +42 -0
  175. data/db/migrate/004_add_message_id_to_observ_traces.rb +7 -0
  176. data/db/migrate/005_create_observ_prompts.rb +21 -0
  177. data/db/migrate/006_fix_prompt_config_strings.rb +23 -0
  178. data/db/migrate/007_create_observ_annotations.rb +12 -0
  179. data/db/migrate/009_add_prompt_fields_to_observ_chats.rb +11 -0
  180. data/db/migrate/010_create_observ_datasets.rb +15 -0
  181. data/db/migrate/011_create_observ_dataset_items.rb +17 -0
  182. data/db/migrate/012_create_observ_dataset_runs.rb +22 -0
  183. data/db/migrate/013_create_observ_dataset_run_items.rb +16 -0
  184. data/db/migrate/014_create_observ_scores.rb +26 -0
  185. data/lib/generators/observ/add_phase_tracking/add_phase_tracking_generator.rb +150 -0
  186. data/lib/generators/observ/add_phase_tracking/templates/migration.rb.tt +6 -0
  187. data/lib/generators/observ/install/USAGE +27 -0
  188. data/lib/generators/observ/install/install_generator.rb +270 -0
  189. data/lib/generators/observ/install_chat/install_chat_generator.rb +313 -0
  190. data/lib/generators/observ/install_chat/templates/agents/base_agent.rb.tt +147 -0
  191. data/lib/generators/observ/install_chat/templates/agents/simple_agent.rb.tt +55 -0
  192. data/lib/generators/observ/install_chat/templates/concerns/observ_chat_enhancements.rb.tt +34 -0
  193. data/lib/generators/observ/install_chat/templates/concerns/observ_message_enhancements.rb.tt +18 -0
  194. data/lib/generators/observ/install_chat/templates/initializers/observability.rb.tt +20 -0
  195. data/lib/generators/observ/install_chat/templates/jobs/chat_response_job.rb.tt +56 -0
  196. data/lib/generators/observ/install_chat/templates/migrations/add_agent_class_name.rb.tt +6 -0
  197. data/lib/generators/observ/install_chat/templates/migrations/add_observability_session_id.rb.tt +6 -0
  198. data/lib/generators/observ/install_chat/templates/tools/think_tool.rb.tt +29 -0
  199. data/lib/generators/observ/install_chat/templates/views/messages/_content.html.erb.tt +1 -0
  200. data/lib/observ/asset_installer.rb +130 -0
  201. data/lib/observ/asset_syncer.rb +104 -0
  202. data/lib/observ/configuration.rb +108 -0
  203. data/lib/observ/engine.rb +50 -0
  204. data/lib/observ/index_file_generator.rb +142 -0
  205. data/lib/observ/instrumenter/ruby_llm.rb +6 -0
  206. data/lib/observ/version.rb +3 -0
  207. data/lib/observ.rb +29 -0
  208. data/lib/tasks/observ_tasks.rake +75 -0
  209. metadata +453 -0
data/README.md ADDED
@@ -0,0 +1,778 @@
1
+ # Observ
2
+
3
+ A Rails engine providing comprehensive observability for LLM-powered applications, including session tracking, trace analysis, prompt management, and cost monitoring.
4
+
5
+ ## Features
6
+
7
+ ### Core Observability Features
8
+ - **Session Tracking**: Automatically track user sessions across LLM interactions
9
+ - **Trace Analysis**: Detailed execution traces with token usage and cost metrics
10
+ - **Prompt Management**: Version-controlled prompts with state machine (draft/production/archived)
11
+ - **Cost Monitoring**: Real-time tracking of API costs across models and providers
12
+ - **Annotation Tools**: Add notes and export data for analysis
13
+ - **Advanced Caching**: Sophisticated caching system with Redis support and monitoring
14
+
15
+ ### Optional Chat/Agent Testing Feature
16
+ - **Agent Testing UI**: Interactive chat interface for testing LLM agents at `/observ/chats`
17
+ - **Agent Management**: Create, select, and configure different agents
18
+ - **Message Streaming**: Real-time response streaming with Turbo
19
+ - **Tool Visualization**: See tool calls in action
20
+ - **RubyLLM Integration**: Full integration with RubyLLM gem for agent development
21
+
22
+ ## Installation
23
+
24
+ Observ offers **two installation modes**: Core (observability only) or Core + Chat (with agent testing).
25
+
26
+ ### Core Installation (Recommended for Most Users)
27
+
28
+ For LLM observability without the chat UI:
29
+
30
+ **1. Add to Gemfile:**
31
+
32
+ ```ruby
33
+ gem "observ"
34
+ ```
35
+
36
+ **2. Install:**
37
+
38
+ ```bash
39
+ bundle install
40
+ rails observ:install:migrations
41
+ rails db:migrate
42
+ rails generate observ:install
43
+ ```
44
+
45
+ **What you get:**
46
+ - Dashboard at `/observ`
47
+ - Session tracking and analysis
48
+ - Trace visualization
49
+ - Prompt management
50
+ - Cost monitoring
51
+ - Annotation tools
52
+
53
+ **No chat UI** - Perfect if you're instrumenting an existing application and just want observability.
54
+
55
+ ---
56
+
57
+ ### Core + Chat Installation (For Agent Testing)
58
+
59
+ For full observability + interactive agent testing UI:
60
+
61
+ **1. Add to Gemfile:**
62
+
63
+ ```ruby
64
+ gem "observ"
65
+ gem "ruby_llm" # Required for chat feature
66
+ ```
67
+
68
+ **2. Install core + chat:**
69
+
70
+ ```bash
71
+ bundle install
72
+
73
+ # Install RubyLLM infrastructure first
74
+ rails generate ruby_llm:install
75
+ rails db:migrate
76
+ rails ruby_llm:load_models
77
+
78
+ # Then install Observ
79
+ rails observ:install:migrations
80
+ rails generate observ:install # Core features
81
+ rails generate observ:install:chat # Chat feature
82
+ rails db:migrate
83
+ ```
84
+
85
+ **What you get:**
86
+ - Everything from Core installation
87
+ - Chat UI at `/observ/chats`
88
+ - Agent testing interface
89
+ - Observ enhancements on RubyLLM infrastructure
90
+ - Example agents and tools
91
+
92
+ See **[Chat Installation Guide](docs/CHAT_INSTALLATION.md)** for detailed setup.
93
+
94
+ ---
95
+
96
+ ### Asset Installation
97
+
98
+ After running either installation mode:
99
+
100
+ ```bash
101
+ # For first-time installation (recommended)
102
+ rails generate observ:install
103
+
104
+ # Or use the rake task
105
+ rails observ:install_assets
106
+ ```
107
+
108
+ This will:
109
+ - Show you the destination paths where assets will be copied
110
+ - Ask for confirmation before proceeding
111
+ - Automatically mount the engine in `config/routes.rb` (if not already present)
112
+ - Copy Observ stylesheets to `app/javascript/stylesheets/observ`
113
+ - Copy Observ JavaScript Stimulus controllers to `app/javascript/controllers/observ`
114
+ - Generate index files for easy importing
115
+ - Check if controllers are properly registered in your application
116
+
117
+ **Custom asset destinations:**
118
+
119
+ ```bash
120
+ # Install to custom locations
121
+ rails generate observ:install --styles-dest=app/assets/stylesheets/observ --js-dest=app/javascript/controllers/custom
122
+
123
+ # Or with rake task
124
+ rails observ:install_assets[app/assets/stylesheets/observ,app/javascript/controllers/custom]
125
+ ```
126
+
127
+ **Skip confirmation (useful for CI/CD or automated scripts):**
128
+
129
+ ```bash
130
+ # Skip confirmation prompt
131
+ rails generate observ:install --force
132
+
133
+ # With custom destinations
134
+ rails generate observ:install --force --styles-dest=custom/path --js-dest=custom/path
135
+
136
+ # Skip automatic route mounting (if you want to mount manually)
137
+ rails generate observ:install --skip-routes
138
+ ```
139
+
140
+ **Updating assets:**
141
+
142
+ When you update the Observ gem, sync the latest assets:
143
+
144
+ ```bash
145
+ rails observ:sync_assets
146
+ ```
147
+
148
+ This will update only changed files without regenerating index files.
149
+
150
+ ## Configuration
151
+
152
+ ### 1. Mount the Engine (Automatic)
153
+
154
+ The install generator automatically adds the engine mount to `config/routes.rb`:
155
+
156
+ ```ruby
157
+ mount Observ::Engine, at: "/observ"
158
+ ```
159
+
160
+ This makes Observ available at `/observ` in your application.
161
+
162
+ If you used `--skip-routes` during installation, manually add the route to `config/routes.rb`:
163
+
164
+ ```ruby
165
+ Rails.application.routes.draw do
166
+ mount Observ::Engine, at: "/observ"
167
+
168
+ # Your other routes...
169
+ end
170
+ ```
171
+
172
+ ### 2. Configure the Engine
173
+
174
+ Create `config/initializers/observ.rb`:
175
+
176
+ ```ruby
177
+ Observ.configure do |config|
178
+ # Prompt management
179
+ config.prompt_management_enabled = true
180
+ config.prompt_max_versions = 100
181
+ config.prompt_default_state = :production
182
+ config.prompt_allow_production_deletion = false
183
+ config.prompt_fallback_behavior = :raise # or :return_nil, :use_fallback
184
+
185
+ # Caching configuration
186
+ config.prompt_cache_ttl = 300 # 5 minutes (0 to disable)
187
+ config.prompt_cache_store = :redis_cache_store # or :memory_store
188
+ config.prompt_cache_namespace = "observ:prompt"
189
+
190
+ # Cache warming (load critical prompts on boot)
191
+ config.prompt_cache_warming_enabled = true
192
+ config.prompt_cache_critical_prompts = ["research_agent", "rpg_agent"]
193
+
194
+ # Cache monitoring (track hit rates)
195
+ config.prompt_cache_monitoring_enabled = true
196
+
197
+ # UI configuration
198
+ config.back_to_app_path = -> { Rails.application.routes.url_helpers.root_path }
199
+ config.back_to_app_label = "← Back to App"
200
+
201
+ # Chat UI (auto-detects if Chat model exists with acts_as_chat)
202
+ # Manually override if needed:
203
+ # config.chat_ui_enabled = true
204
+ end
205
+ ```
206
+
207
+ ### 3. Configure Observability Features
208
+
209
+ The `observ:install:chat` generator automatically creates `config/initializers/observability.rb`:
210
+
211
+ ```ruby
212
+ Rails.application.configure do
213
+ config.observability = ActiveSupport::OrderedOptions.new
214
+
215
+ # Enable observability instrumentation
216
+ # When enabled, sessions, traces, and observations are automatically tracked
217
+ config.observability.enabled = true
218
+
219
+ # Automatically instrument RubyLLM chats with observability
220
+ # When enabled, LLM calls, tool usage, and metrics are tracked
221
+ config.observability.auto_instrument_chats = true
222
+
223
+ # Enable debug logging for observability metrics
224
+ # When enabled, job completion metrics (tokens, cost) will be logged
225
+ config.observability.debug = Rails.env.development?
226
+ end
227
+ ```
228
+
229
+ **Environment-based configuration:**
230
+
231
+ ```ruby
232
+ # Use environment variables for production
233
+ config.observability.enabled = ENV.fetch("OBSERVABILITY_ENABLED", "true") == "true"
234
+ config.observability.auto_instrument_chats = ENV.fetch("AUTO_INSTRUMENT", "true") == "true"
235
+ config.observability.debug = ENV.fetch("OBSERVABILITY_DEBUG", "false") == "true"
236
+ ```
237
+
238
+ **Important:**
239
+ - `enabled` must be `true` for observability sessions to be created
240
+ - `auto_instrument_chats` must be `true` for automatic LLM call tracking
241
+ - Without these settings, observability features will be disabled
242
+
243
+ ### 4. Configure RubyLLM (Chat Feature Only)
244
+
245
+ **Skip this if you're using Core installation only.**
246
+
247
+ If you installed the chat feature, create `config/initializers/ruby_llm.rb`:
248
+
249
+ ```ruby
250
+ RubyLLM.configure do |config|
251
+ config.openai_api_key = ENV['OPENAI_API_KEY']
252
+ config.default_model = "gpt-4o-mini"
253
+
254
+ # Use the new association-based acts_as API (recommended)
255
+ config.use_new_acts_as = true
256
+
257
+ # Optional: Other providers
258
+ # config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
259
+ # config.google_api_key = ENV['GOOGLE_API_KEY']
260
+ end
261
+ ```
262
+
263
+ ### 5. Add Concerns to Your Models (Chat Feature Only)
264
+
265
+ **Skip this if you're using Core installation only.**
266
+
267
+ The `observ:install:chat` generator creates these models automatically. If you're manually setting up:
268
+
269
+ ```ruby
270
+ class Chat < ApplicationRecord
271
+ include Observ::ObservabilityInstrumentation
272
+
273
+ # Your existing code...
274
+ end
275
+ ```
276
+
277
+ This adds:
278
+ - `observ_session` association
279
+ - Automatic session creation on model creation
280
+ - `ask_with_observability` method for tracked LLM calls
281
+
282
+ For message models (optional, for trace linking):
283
+
284
+ ```ruby
285
+ class Message < ApplicationRecord
286
+ include Observ::TraceAssociation
287
+
288
+ # Your existing code...
289
+ end
290
+ ```
291
+
292
+ This adds `has_many :traces` relationship.
293
+
294
+ **Note:** The `observ:install:chat` generator handles all of this automatically, including migrations!
295
+
296
+ ## Usage
297
+
298
+ ### Basic Usage (Core Features)
299
+
300
+ Once installed, Observ automatically tracks:
301
+
302
+ 1. **Sessions**: Created when your instrumented models are created
303
+ 2. **Traces**: Captured when you call LLM methods (if using RubyLLM)
304
+ 3. **Observations**: Generations and spans are recorded with metadata
305
+
306
+ Visit `/observ` in your browser to see:
307
+ - Dashboard with metrics and cost analysis
308
+ - Session history
309
+ - Trace details
310
+ - Prompt management UI
311
+
312
+ ### Chat Feature Usage (If Installed)
313
+
314
+ If you installed the chat feature with `rails generate observ:install:chat`:
315
+
316
+ **1. Visit `/observ/chats`**
317
+
318
+ **2. Create a new chat:**
319
+ - Click "New Chat"
320
+ - Select an agent (e.g., SimpleAgent)
321
+ - Start chatting!
322
+
323
+ **3. Create custom agents:**
324
+
325
+ ```ruby
326
+ # app/agents/my_agent.rb
327
+ class MyAgent < BaseAgent
328
+ include AgentSelectable
329
+
330
+ def self.display_name
331
+ "My Custom Agent"
332
+ end
333
+
334
+ def self.system_prompt
335
+ "You are a helpful assistant that..."
336
+ end
337
+
338
+ def self.default_model
339
+ "gpt-4o-mini"
340
+ end
341
+ end
342
+ ```
343
+
344
+ **4. View session data:**
345
+ - All chat interactions appear in `/observ/sessions`
346
+ - Full observability of tokens, costs, and tool calls
347
+
348
+ See **[Chat Installation Guide](docs/CHAT_INSTALLATION.md)** for complete documentation.
349
+
350
+ ### Phase Tracking (Optional Chat Feature)
351
+
352
+ For multi-phase agent workflows (e.g., scoping → research → writing), add phase tracking:
353
+
354
+ **1. Add phase tracking to your installation:**
355
+
356
+ ```bash
357
+ # During initial installation
358
+ rails generate observ:install:chat --with-phase-tracking
359
+
360
+ # Or add to existing installation
361
+ rails generate observ:add_phase_tracking
362
+ rails db:migrate
363
+ ```
364
+
365
+ **2. Use phase transitions in your agents:**
366
+
367
+ ```ruby
368
+ # app/agents/research_agent.rb
369
+ class ResearchAgent < BaseAgent
370
+ def perform_research(chat, query)
371
+ # Transition to research phase
372
+ chat.transition_to_phase('research')
373
+
374
+ # Do research work...
375
+ results = research(query)
376
+
377
+ # Transition to writing phase
378
+ chat.transition_to_phase('writing', depth: 'comprehensive')
379
+
380
+ # Generate report...
381
+ end
382
+ end
383
+ ```
384
+
385
+ **3. Check current phase:**
386
+
387
+ ```ruby
388
+ chat.current_phase # => 'research'
389
+ chat.in_phase?('research') # => true
390
+ ```
391
+
392
+ **4. (Optional) Define allowed phases:**
393
+
394
+ ```ruby
395
+ # app/models/chat.rb
396
+ class Chat < ApplicationRecord
397
+ include Observ::ObservabilityInstrumentation
398
+ include Observ::AgentPhaseable
399
+
400
+ def allowed_phases
401
+ %w[scoping research writing review]
402
+ end
403
+ end
404
+ ```
405
+
406
+ **Benefits:**
407
+ - Phase transitions are automatically tracked in observability metadata
408
+ - View phase progression in `/observ/sessions`
409
+ - Analyze time and cost per phase
410
+ - Debug which phase causes issues
411
+
412
+ **Phase data in observability:**
413
+
414
+ All phase transitions are captured in session metadata:
415
+ ```ruby
416
+ session.metadata
417
+ # => {
418
+ # "agent_type" => "ResearchAgent",
419
+ # "chat_id" => 42,
420
+ # "agent_phase" => "writing",
421
+ # "phase_transition" => "research -> writing",
422
+ # "depth" => "comprehensive"
423
+ # }
424
+ ```
425
+
426
+ ### Extending Observability Metadata (Advanced)
427
+
428
+ You can extend observability metadata by overriding hook methods in your Chat model:
429
+
430
+ ```ruby
431
+ # app/models/chat.rb
432
+ class Chat < ApplicationRecord
433
+ include Observ::ObservabilityInstrumentation
434
+
435
+ # Override to add custom metadata to session
436
+ def observability_metadata
437
+ super.merge(
438
+ user_id: user_id,
439
+ subscription_tier: user.subscription_tier,
440
+ feature_flags: enabled_features
441
+ )
442
+ end
443
+
444
+ # Override to add custom context to instrumenter
445
+ def observability_context
446
+ super.merge(
447
+ locale: I18n.locale,
448
+ timezone: Time.zone.name
449
+ )
450
+ end
451
+ end
452
+ ```
453
+
454
+ This allows you to:
455
+ - Track user-specific information
456
+ - Add business logic metadata
457
+ - Include feature flags for A/B testing analysis
458
+ - Track localization and timezone data
459
+
460
+ **Note:** The `AgentPhaseable` concern uses these same hooks to inject phase data.
461
+
462
+ ### Manual Instrumentation
463
+
464
+ If not using RubyLLM, you can manually create traces:
465
+
466
+ ```ruby
467
+ session = Observ::Session.create(
468
+ session_id: SecureRandom.uuid,
469
+ user_id: current_user.id,
470
+ metadata: { agent_type: "custom" }
471
+ )
472
+
473
+ trace = session.traces.create(
474
+ name: "Custom Operation",
475
+ start_time: Time.current
476
+ )
477
+
478
+ # ... do work ...
479
+
480
+ trace.update(
481
+ end_time: Time.current,
482
+ metadata: { result: "success" }
483
+ )
484
+ ```
485
+
486
+ ### Prompt Management
487
+
488
+ Fetch prompts in your code:
489
+
490
+ ```ruby
491
+ # Fetch production version
492
+ prompt = Observ::PromptManager.fetch(name: "research_agent", state: :production)
493
+ content = prompt.content
494
+
495
+ # Fetch specific version
496
+ prompt = Observ::PromptManager.fetch(name: "research_agent", version: 5)
497
+
498
+ # With caching (automatic)
499
+ prompt = Observ::PromptManager.fetch(name: "research_agent") # Cached for 5 min
500
+ ```
501
+
502
+ Cache management:
503
+
504
+ ```ruby
505
+ # Check cache stats
506
+ Observ::PromptManager.cache_stats("research_agent")
507
+ # => { hits: 145, misses: 12, total: 157, hit_rate: 92.36 }
508
+
509
+ # Invalidate cache
510
+ Observ::PromptManager.invalidate_cache(name: "research_agent")
511
+
512
+ # Warm cache (done automatically on boot if configured)
513
+ Observ::PromptManager.warm_cache(["agent1", "agent2"])
514
+ ```
515
+
516
+ ### Annotations
517
+
518
+ Add annotations to sessions or traces:
519
+
520
+ ```ruby
521
+ session.annotations.create(
522
+ content: "Important insight",
523
+ annotator: "user@example.com",
524
+ tags: ["bug", "performance"]
525
+ )
526
+
527
+ # Export annotations
528
+ # Visit /observ/annotations/export in browser
529
+ ```
530
+
531
+ ## Asset Management
532
+
533
+ Observ provides several tools for managing assets in your Rails application:
534
+
535
+ ### Generators
536
+
537
+ ```bash
538
+ # Install assets for the first time (recommended)
539
+ rails generate observ:install
540
+
541
+ # Install to custom locations
542
+ rails generate observ:install --styles-dest=custom/path --js-dest=custom/controllers
543
+
544
+ # Skip index file generation
545
+ rails generate observ:install --skip-index
546
+ ```
547
+
548
+ ### Rake Tasks
549
+
550
+ ```bash
551
+ # Install assets (with index file generation)
552
+ rails observ:install_assets
553
+ rails observ:install # shorthand
554
+
555
+ # Sync assets (update only, no index generation)
556
+ rails observ:sync_assets
557
+ rails observ:sync # shorthand
558
+
559
+ # Custom destinations
560
+ rails observ:install_assets[app/assets/stylesheets/observ,app/javascript/controllers/custom]
561
+ ```
562
+
563
+ ### Programmatic API
564
+
565
+ You can also use the Ruby API directly:
566
+
567
+ ```ruby
568
+ require 'observ/asset_installer'
569
+
570
+ installer = Observ::AssetInstaller.new(
571
+ gem_root: Observ::Engine.root,
572
+ app_root: Rails.root
573
+ )
574
+
575
+ # Full installation with index generation
576
+ result = installer.install(
577
+ styles_dest: 'app/javascript/stylesheets/observ',
578
+ js_dest: 'app/javascript/controllers/observ',
579
+ generate_index: true
580
+ )
581
+
582
+ # Just sync existing files
583
+ result = installer.sync(
584
+ styles_dest: 'app/javascript/stylesheets/observ',
585
+ js_dest: 'app/javascript/controllers/observ'
586
+ )
587
+ ```
588
+
589
+ ## Development
590
+
591
+ After checking out the repo, run:
592
+
593
+ ```bash
594
+ cd observ
595
+ bundle install
596
+ ```
597
+
598
+ Run tests:
599
+
600
+ ```bash
601
+ bundle exec rspec
602
+ ```
603
+
604
+ ## Architecture
605
+
606
+ Observ uses:
607
+ - **Isolated namespace**: All classes under `Observ::` module
608
+ - **Engine pattern**: Mountable Rails engine for easy integration
609
+ - **STI for observations**: `Observ::Generation` and `Observ::Span` inherit from `Observ::Observation`
610
+ - **AASM for state machine**: Prompt lifecycle management
611
+ - **Kaminari for pagination**: Session and trace listings
612
+ - **Stimulus controllers**: Interactive UI components
613
+ - **Rails.cache**: Pluggable caching backend (Redis, Memory, etc.)
614
+ - **Conditional routes**: Chat routes only mount if Chat model exists (Phase 1)
615
+ - **Global namespace**: Controllers use `::Chat` and `::Message` for host app models
616
+
617
+ ## Optional Dependencies
618
+
619
+ ### Core Features
620
+ - **Redis**: For production caching (optional, can use memory cache)
621
+
622
+ ### Chat Feature (Optional Add-on)
623
+ - **RubyLLM**: Required for chat/agent testing feature
624
+ - Installed with `rails generate observ:install:chat`
625
+ - See [Chat Installation Guide](docs/CHAT_INSTALLATION.md)
626
+
627
+ ## Testing
628
+
629
+ Disable observability in tests by default:
630
+
631
+ ```ruby
632
+ # spec/rails_helper.rb
633
+ RSpec.configure do |config|
634
+ config.before(:each) do
635
+ allow(Rails.configuration.observability).to receive(:enabled).and_return(false)
636
+ end
637
+
638
+ # Enable for specific tests
639
+ config.before(:each, observability: true) do
640
+ allow(Rails.configuration.observability).to receive(:enabled).and_return(true)
641
+ end
642
+ end
643
+ ```
644
+
645
+ ## Troubleshooting
646
+
647
+ ### Routes not found
648
+
649
+ Make sure you've mounted the engine in `config/routes.rb` and restarted your server.
650
+
651
+ ### Assets not loading
652
+
653
+ First, make sure you've installed the assets:
654
+
655
+ ```bash
656
+ rails generate observ:install
657
+ ```
658
+
659
+ Then ensure you've imported them in your application:
660
+
661
+ **For JavaScript/Vite/esbuild setups:**
662
+
663
+ Add to `app/javascript/application.js`:
664
+ ```javascript
665
+ import 'observ'
666
+ ```
667
+
668
+ And ensure `app/javascript/controllers/index.js` includes:
669
+ ```javascript
670
+ import './observ'
671
+ ```
672
+
673
+ **For Sprockets (traditional asset pipeline):**
674
+
675
+ Add to `app/assets/stylesheets/application.scss`:
676
+ ```scss
677
+ @use 'observ';
678
+ ```
679
+
680
+ Or for older Sass versions:
681
+ ```scss
682
+ @import 'observ';
683
+ ```
684
+
685
+ **Verifying Stimulus controllers:**
686
+
687
+ Check your browser console for any Stimulus connection errors. Observ controllers should register with the `observ--` prefix (e.g., `observ--drawer`, `observ--copy`).
688
+
689
+ **Syncing after gem updates:**
690
+
691
+ If you've updated the Observ gem, run:
692
+ ```bash
693
+ rails observ:sync_assets
694
+ ```
695
+
696
+ ### Concerns not found
697
+
698
+ The engine loads concerns via initializer. Make sure the gem is properly bundled and the app has restarted.
699
+
700
+ ### Cache not working
701
+
702
+ Check that:
703
+ - `prompt_cache_ttl > 0`
704
+ - Rails cache store is configured (Redis recommended for production)
705
+ - Rails.cache is working: `Rails.cache.write("test", "value")` / `Rails.cache.read("test")`
706
+
707
+ ### Observability sessions not being created
708
+
709
+ If chats are created but observability sessions are not:
710
+
711
+ **1. Check observability is enabled:**
712
+
713
+ ```ruby
714
+ rails runner "puts Rails.configuration.observability.enabled.inspect"
715
+ # Should output: true
716
+ ```
717
+
718
+ If it outputs `nil` or `false`, check `config/initializers/observability.rb` exists and sets:
719
+ ```ruby
720
+ config.observability.enabled = true
721
+ ```
722
+
723
+ **2. Check the observability_session_id column exists:**
724
+
725
+ ```ruby
726
+ rails runner "puts Chat.column_names.include?('observability_session_id')"
727
+ # Should output: true
728
+ ```
729
+
730
+ If it outputs `false`, you're missing the migration. Run:
731
+ ```bash
732
+ rails generate migration AddObservabilitySessionIdToChats observability_session_id:string:index
733
+ rails db:migrate
734
+ ```
735
+
736
+ **3. Check for errors in logs:**
737
+
738
+ ```bash
739
+ tail -f log/development.log | grep Observability
740
+ ```
741
+
742
+ Look for `[Observability] Failed to initialize session:` messages.
743
+
744
+ **4. Verify the concern is included:**
745
+
746
+ ```ruby
747
+ rails runner "puts Chat.included_modules.include?(Observ::ObservabilityInstrumentation)"
748
+ # Should output: true
749
+ ```
750
+
751
+ ### Phase tracking errors
752
+
753
+ If you see `AgentPhaseable requires a 'current_phase' column`:
754
+
755
+ You're trying to use phase tracking without the database column. Run:
756
+
757
+ ```bash
758
+ rails generate observ:add_phase_tracking
759
+ rails db:migrate
760
+ ```
761
+
762
+ Or remove `include Observ::AgentPhaseable` from your Chat model if you don't need phase tracking.
763
+
764
+ ## Contributing
765
+
766
+ 1. Fork it
767
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
768
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
769
+ 4. Push to the branch (`git push origin my-new-feature`)
770
+ 5. Create new Pull Request
771
+
772
+ ## License
773
+
774
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
775
+
776
+ ## Version
777
+
778
+ Current version: 0.1.0