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
@@ -0,0 +1,82 @@
1
+ <section class="observ-card">
2
+ <div class="observ-card__header">
3
+ <h2 class="observ-card__title">Dataset Runs</h2>
4
+ <%= link_to "New Run", new_dataset_run_path(@dataset), class: "observ-button observ-button--sm observ-button--primary" %>
5
+ </div>
6
+ <div class="observ-card__body">
7
+ <% if @runs.any? %>
8
+ <table class="observ-table">
9
+ <thead class="observ-table__header">
10
+ <tr class="observ-table__row">
11
+ <th class="observ-table__cell">Name</th>
12
+ <th class="observ-table__cell">Status</th>
13
+ <th class="observ-table__cell">Progress</th>
14
+ <th class="observ-table__cell observ-table__cell--numeric">Cost</th>
15
+ <th class="observ-table__cell observ-table__cell--numeric">Tokens</th>
16
+ <th class="observ-table__cell">Created</th>
17
+ <th class="observ-table__cell observ-table__cell--actions"></th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% @runs.each do |run| %>
22
+ <tr class="observ-table__row">
23
+ <td class="observ-table__cell">
24
+ <%= link_to run.name, dataset_run_path(@dataset, run), class: "observ-datasets-table__link" %>
25
+ <% if run.description.present? %>
26
+ <p class="observ-datasets-table__description"><%= truncate(run.description, length: 50) %></p>
27
+ <% end %>
28
+ </td>
29
+ <td class="observ-table__cell">
30
+ <span class="observ-badge <%= run_status_badge_class(run.status) %>">
31
+ <%= run.status %>
32
+ </span>
33
+ </td>
34
+ <td class="observ-table__cell">
35
+ <div class="observ-datasets__progress">
36
+ <div class="observ-datasets__progress-bar">
37
+ <div class="observ-datasets__progress-fill observ-datasets__progress-fill--success"
38
+ style="width: <%= run.success_rate %>%"></div>
39
+ <div class="observ-datasets__progress-fill observ-datasets__progress-fill--danger"
40
+ style="width: <%= run.failure_rate %>%"></div>
41
+ </div>
42
+ <span class="observ-datasets__progress-text">
43
+ <%= run.completed_items %>/<%= run.total_items %> completed
44
+ <% if run.failed_items > 0 %>
45
+ (<%= run.failed_items %> failed)
46
+ <% end %>
47
+ </span>
48
+ </div>
49
+ </td>
50
+ <td class="observ-table__cell observ-table__cell--numeric">
51
+ $<%= number_with_precision(run.total_cost, precision: 4) %>
52
+ </td>
53
+ <td class="observ-table__cell observ-table__cell--numeric">
54
+ <%= number_with_delimiter(run.total_tokens) %>
55
+ </td>
56
+ <td class="observ-table__cell">
57
+ <%= time_ago_in_words(run.created_at) %> ago
58
+ </td>
59
+ <td class="observ-table__cell observ-table__cell--actions">
60
+ <div class="observ-datasets-table__action-group">
61
+ <%= link_to "View", dataset_run_path(@dataset, run), class: "observ-button observ-button--sm" %>
62
+ <%= button_to "Delete", dataset_run_path(@dataset, run),
63
+ method: :delete,
64
+ class: "observ-button observ-button--sm observ-button--danger",
65
+ data: { confirm: "Are you sure you want to delete this run?" } %>
66
+ </div>
67
+ </td>
68
+ </tr>
69
+ <% end %>
70
+ </tbody>
71
+ </table>
72
+
73
+ <%= observ_pagination(@runs) %>
74
+ <% else %>
75
+ <div class="observ-card__empty">
76
+ <p class="observ-card__empty-text">No runs yet</p>
77
+ <p class="observ-card__empty-subtext">Run your dataset to evaluate the agent's performance</p>
78
+ <%= link_to "Start a new run", new_dataset_run_path(@dataset), class: "observ-button observ-button--primary" %>
79
+ </div>
80
+ <% end %>
81
+ </div>
82
+ </section>
@@ -0,0 +1,32 @@
1
+ <% content_for :title, "Edit #{@dataset.name}" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Dataset", dataset_path(@dataset), class: "observ-datasets__back-link" %>
7
+ <h1 class="observ-page-header__title">Edit Dataset</h1>
8
+ </div>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="observ-container">
13
+ <section class="observ-card">
14
+ <div class="observ-card__body">
15
+ <%= render "form", dataset: @dataset, agents: @agents %>
16
+ </div>
17
+ </section>
18
+
19
+ <!-- Danger Zone -->
20
+ <section class="observ-card observ-card--danger">
21
+ <div class="observ-card__header">
22
+ <h2 class="observ-card__title">Danger Zone</h2>
23
+ </div>
24
+ <div class="observ-card__body">
25
+ <p class="observ-text--muted">Deleting this dataset will permanently remove all items and runs.</p>
26
+ <%= button_to "Delete Dataset", dataset_path(@dataset),
27
+ method: :delete,
28
+ class: "observ-button observ-button--danger",
29
+ data: { confirm: "Are you sure you want to delete this dataset? This action cannot be undone." } %>
30
+ </div>
31
+ </section>
32
+ </div>
@@ -0,0 +1,105 @@
1
+ <% content_for :title, "Datasets" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <h1 class="observ-page-header__title">Datasets</h1>
6
+ </div>
7
+ <div class="observ-page-header__actions">
8
+ <%= link_to "New Dataset", new_dataset_path, class: "observ-button observ-button--primary" %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="observ-container">
13
+ <!-- Search -->
14
+ <section class="observ-card observ-datasets-filters">
15
+ <div class="observ-card__body">
16
+ <%= form_with url: datasets_path, method: :get, class: "observ-datasets-filters__form" do |f| %>
17
+ <div class="observ-datasets-filters__field observ-datasets-filters__field--search">
18
+ <%= f.label :search, "Search datasets", class: "observ-datasets-filters__label" %>
19
+ <%= f.text_field :search,
20
+ value: params[:search],
21
+ placeholder: "Search by name...",
22
+ class: "observ-datasets-filters__input" %>
23
+ </div>
24
+
25
+ <div class="observ-datasets-filters__actions">
26
+ <%= f.submit "Search", class: "observ-button observ-button--secondary" %>
27
+ <%= link_to "Clear", datasets_path, class: "observ-button" %>
28
+ </div>
29
+ <% end %>
30
+ </div>
31
+ </section>
32
+
33
+ <!-- Datasets Table -->
34
+ <section class="observ-card">
35
+ <div class="observ-card__body">
36
+ <% if @datasets.any? %>
37
+ <table class="observ-table">
38
+ <thead class="observ-table__header">
39
+ <tr class="observ-table__row">
40
+ <th class="observ-table__cell">Name</th>
41
+ <th class="observ-table__cell">Agent</th>
42
+ <th class="observ-table__cell observ-table__cell--numeric">Items</th>
43
+ <th class="observ-table__cell observ-table__cell--numeric">Runs</th>
44
+ <th class="observ-table__cell">Last Run</th>
45
+ <th class="observ-table__cell">Updated</th>
46
+ <th class="observ-table__cell observ-table__cell--actions"></th>
47
+ </tr>
48
+ </thead>
49
+ <tbody>
50
+ <% @datasets.each do |dataset| %>
51
+ <tr class="observ-table__row">
52
+ <td class="observ-table__cell">
53
+ <%= link_to dataset.name, dataset_path(dataset), class: "observ-datasets-table__link" %>
54
+ <% if dataset.description.present? %>
55
+ <p class="observ-datasets-table__description"><%= truncate(dataset.description, length: 60) %></p>
56
+ <% end %>
57
+ </td>
58
+ <td class="observ-table__cell">
59
+ <span class="observ-badge observ-badge--info"><%= dataset.agent_class %></span>
60
+ </td>
61
+ <td class="observ-table__cell observ-table__cell--numeric">
62
+ <%= dataset.items_count %>
63
+ <% if dataset.active_items_count != dataset.items_count %>
64
+ <span class="observ-text--muted">(<%= dataset.active_items_count %> active)</span>
65
+ <% end %>
66
+ </td>
67
+ <td class="observ-table__cell observ-table__cell--numeric">
68
+ <%= dataset.runs_count %>
69
+ </td>
70
+ <td class="observ-table__cell">
71
+ <% if dataset.last_run %>
72
+ <span class="observ-badge <%= run_status_badge_class(dataset.last_run.status) %>">
73
+ <%= dataset.last_run.status %>
74
+ </span>
75
+ <span class="observ-text--muted"><%= time_ago_in_words(dataset.last_run.created_at) %> ago</span>
76
+ <% else %>
77
+ <span class="observ-text--muted">Never</span>
78
+ <% end %>
79
+ </td>
80
+ <td class="observ-table__cell">
81
+ <%= time_ago_in_words(dataset.updated_at) %> ago
82
+ </td>
83
+ <td class="observ-table__cell observ-table__cell--actions">
84
+ <div class="observ-datasets-table__action-group">
85
+ <%= link_to "View", dataset_path(dataset), class: "observ-button observ-button--sm" %>
86
+ <%= link_to "Edit", edit_dataset_path(dataset), class: "observ-button observ-button--sm" %>
87
+ </div>
88
+ </td>
89
+ </tr>
90
+ <% end %>
91
+ </tbody>
92
+ </table>
93
+ <% else %>
94
+ <div class="observ-card__empty">
95
+ <p class="observ-card__empty-text">No datasets found</p>
96
+ <p class="observ-card__empty-subtext">Create a dataset to start evaluating your LLM applications</p>
97
+ <%= link_to "Create your first dataset", new_dataset_path, class: "observ-button observ-button--primary" %>
98
+ </div>
99
+ <% end %>
100
+ </div>
101
+ </section>
102
+
103
+ <!-- Pagination -->
104
+ <%= observ_pagination(@datasets) %>
105
+ </div>
@@ -0,0 +1,18 @@
1
+ <% content_for :title, "New Dataset" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div>
6
+ <%= link_to "← Back to Datasets", datasets_path, class: "observ-datasets__back-link" %>
7
+ <h1 class="observ-page-header__title">New Dataset</h1>
8
+ </div>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="observ-container">
13
+ <section class="observ-card">
14
+ <div class="observ-card__body">
15
+ <%= render "form", dataset: @dataset, agents: @agents %>
16
+ </div>
17
+ </section>
18
+ </div>
@@ -0,0 +1,67 @@
1
+ <% content_for :title, @dataset.name %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <div class="observ-page-header__breadcrumb">
6
+ <%= link_to "Datasets", datasets_path, class: "observ-link" %> /
7
+ </div>
8
+ <h1 class="observ-page-header__title"><%= @dataset.name %></h1>
9
+ <% if @dataset.description.present? %>
10
+ <p class="observ-page-header__subtitle"><%= @dataset.description %></p>
11
+ <% end %>
12
+ </div>
13
+ <div class="observ-page-header__actions">
14
+ <%= link_to "New Run", new_dataset_run_path(@dataset), class: "observ-button observ-button--primary" %>
15
+ <%= link_to "Add Item", new_dataset_item_path(@dataset), class: "observ-button observ-button--secondary" %>
16
+ <%= link_to "Edit", edit_dataset_path(@dataset), class: "observ-button" %>
17
+ </div>
18
+ <% end %>
19
+
20
+ <div class="observ-container">
21
+ <!-- Dataset Info Card -->
22
+ <section class="observ-card observ-datasets__info">
23
+ <div class="observ-card__body">
24
+ <dl class="observ-datasets__metadata">
25
+ <div class="observ-datasets__metadata-item">
26
+ <dt class="observ-datasets__metadata-label">Agent</dt>
27
+ <dd class="observ-datasets__metadata-value">
28
+ <span class="observ-badge observ-badge--info"><%= @dataset.agent_class %></span>
29
+ </dd>
30
+ </div>
31
+ <div class="observ-datasets__metadata-item">
32
+ <dt class="observ-datasets__metadata-label">Total Items</dt>
33
+ <dd class="observ-datasets__metadata-value"><%= @dataset.items_count %></dd>
34
+ </div>
35
+ <div class="observ-datasets__metadata-item">
36
+ <dt class="observ-datasets__metadata-label">Active Items</dt>
37
+ <dd class="observ-datasets__metadata-value"><%= @dataset.active_items_count %></dd>
38
+ </div>
39
+ <div class="observ-datasets__metadata-item">
40
+ <dt class="observ-datasets__metadata-label">Total Runs</dt>
41
+ <dd class="observ-datasets__metadata-value"><%= @dataset.runs_count %></dd>
42
+ </div>
43
+ <div class="observ-datasets__metadata-item">
44
+ <dt class="observ-datasets__metadata-label">Created</dt>
45
+ <dd class="observ-datasets__metadata-value"><%= @dataset.created_at.strftime("%b %d, %Y") %></dd>
46
+ </div>
47
+ </dl>
48
+ </div>
49
+ </section>
50
+
51
+ <!-- Tabs -->
52
+ <div class="observ-tabs">
53
+ <%= link_to "Items (#{@dataset.items_count})",
54
+ dataset_path(@dataset, tab: "items"),
55
+ class: "observ-tabs__tab #{@active_tab == 'items' ? 'observ-tabs__tab--active' : ''}" %>
56
+ <%= link_to "Runs (#{@dataset.runs_count})",
57
+ dataset_path(@dataset, tab: "runs"),
58
+ class: "observ-tabs__tab #{@active_tab == 'runs' ? 'observ-tabs__tab--active' : ''}" %>
59
+ </div>
60
+
61
+ <!-- Tab Content -->
62
+ <% if @active_tab == "items" %>
63
+ <%= render "items_tab" %>
64
+ <% else %>
65
+ <%= render "runs_tab" %>
66
+ <% end %>
67
+ </div>
@@ -0,0 +1 @@
1
+ <%= content %>
@@ -0,0 +1,33 @@
1
+ <%= form_with(model: message, url: chat_messages_path(chat), id: "new_message", data: { controller: "observ--message-form" }) do |form| %>
2
+ <% if message.errors.any? %>
3
+ <div class="observ-alert observ-alert--danger">
4
+ <h3 class="observ-alert__title"><%= pluralize(message.errors.count, "error") %> prohibited this message from being saved:</h3>
5
+ <ul class="observ-alert__list">
6
+ <% message.errors.each do |error| %>
7
+ <li><%= error.full_message %></li>
8
+ <% end %>
9
+ </ul>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="observ-form-group">
14
+ <%= form.text_field :content,
15
+ class: "observ-input observ-input--wide",
16
+ placeholder: "Type your message...",
17
+ autofocus: true,
18
+ data: {
19
+ observ__message_form_target: "input",
20
+ action: "input->observ--message-form#toggleSubmit"
21
+ } %>
22
+ </div>
23
+
24
+ <div class="observ-form-actions">
25
+ <%= form.submit "Send message",
26
+ class: "observ-button observ-button--primary",
27
+ data: { observ__message_form_target: "submit" } %>
28
+ <span class="observ-loading-indicator" data-observ--message-form-target="loadingIndicator" style="display: none;">
29
+ <span class="observ-spinner"></span>
30
+ <span>Waiting for response...</span>
31
+ </span>
32
+ </div>
33
+ <% end %>
@@ -0,0 +1,14 @@
1
+ <div id="message_<%= message.id %>" class="observ-chat-message observ-chat-message--<%= message.role %>">
2
+ <div class="observ-chat-message__header">
3
+ <span class="observ-chat-message__role"><%= message.role&.capitalize %></span>
4
+ <span class="observ-chat-message__time"><%= message.created_at&.strftime("%I:%M %p") %></span>
5
+ </div>
6
+
7
+ <div id="message_<%= message.id %>_content" class="observ-chat-message__content">
8
+ <%= simple_format(message.content) %>
9
+ </div>
10
+
11
+ <% if message.tool_call? %>
12
+ <%= render "observ/messages/tool_calls", message: message %>
13
+ <% end %>
14
+ </div>
@@ -0,0 +1,10 @@
1
+ <div class="observ-chat-message__tool-calls">
2
+ <div class="observ-chat-message__tool-calls-label">
3
+ Tool Calls:
4
+ </div>
5
+ <% message.tool_calls.each do |tool_call| %>
6
+ <div class="observ-chat-message__tool-call">
7
+ <span class="observ-chat-message__tool-call-name"><%= tool_call.name %></span>(<%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)
8
+ </div>
9
+ <% end %>
10
+ </div>
@@ -0,0 +1,9 @@
1
+ <%= turbo_stream.append "messages" do %>
2
+ <% @chat.messages.last(2).each do |message| %>
3
+ <%= render message %>
4
+ <% end %>
5
+ <% end %>
6
+
7
+ <%= turbo_stream.replace "new_message" do %>
8
+ <%= render "observ/messages/form", chat: @chat, message: @chat.messages.build %>
9
+ <% end %>
@@ -0,0 +1,97 @@
1
+ <% content_for :title, "Observations" %>
2
+
3
+ <% content_for :page_header do %>
4
+ <div class="observ-page-header__content">
5
+ <h1 class="observ-page-header__title">Observations</h1>
6
+ <div class="observ-page-header__actions">
7
+ <%= link_to "Generations", generations_observations_path, class: "observ-button observ-button--sm" %>
8
+ <%= link_to "Spans", spans_observations_path, class: "observ-button observ-button--sm" %>
9
+ </div>
10
+ </div>
11
+ <% end %>
12
+
13
+ <div class="observ-container">
14
+ <section class="observ-card">
15
+ <div class="observ-card__body">
16
+ <% if @observations.any? %>
17
+ <table class="observ-table">
18
+ <thead class="observ-table__header">
19
+ <tr class="observ-table__row">
20
+ <th class="observ-table__cell">Observation ID</th>
21
+ <th class="observ-table__cell">Type</th>
22
+ <th class="observ-table__cell">Name</th>
23
+ <th class="observ-table__cell">Model</th>
24
+ <th class="observ-table__cell">Trace ID</th>
25
+ <th class="observ-table__cell">Start Time</th>
26
+ <th class="observ-table__cell">Duration</th>
27
+ <th class="observ-table__cell observ-table__cell--numeric">Tokens</th>
28
+ <th class="observ-table__cell observ-table__cell--numeric">Cost</th>
29
+ <th class="observ-table__cell"></th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ <% @observations.each do |observation| %>
34
+ <tr class="observ-table__row">
35
+ <td class="observ-table__cell">
36
+ <code class="observ-code observ-code--inline"><%= truncate_id(observation.observation_id, 12) %></code>
37
+ </td>
38
+ <td class="observ-table__cell">
39
+ <span class="observ-badge observ-badge--<%= observation.type.demodulize.downcase %>">
40
+ <%= observation.type.demodulize %>
41
+ </span>
42
+ </td>
43
+ <td class="observ-table__cell">
44
+ <%= observation.name %>
45
+ </td>
46
+ <td class="observ-table__cell">
47
+ <% if observation.is_a?(Observ::Generation) %>
48
+ <%= observation.model || "—" %>
49
+ <% else %>
50
+ <span class="observ-text--muted">—</span>
51
+ <% end %>
52
+ </td>
53
+ <td class="observ-table__cell">
54
+ <%= link_to truncate_id(observation.trace.trace_id, 8), trace_path(observation.trace), class: "observ-link" %>
55
+ </td>
56
+ <td class="observ-table__cell">
57
+ <%= observ_timestamp(observation.start_time) %>
58
+ </td>
59
+ <td class="observ-table__cell">
60
+ <% if observation.duration_ms %>
61
+ <%= format_duration_ms(observation.duration_ms) %>
62
+ <% else %>
63
+ <span class="observ-text--muted">—</span>
64
+ <% end %>
65
+ </td>
66
+ <td class="observ-table__cell observ-table__cell--numeric">
67
+ <% if observation.is_a?(Observ::Generation) %>
68
+ <%= format_tokens(observation.total_tokens) %>
69
+ <% else %>
70
+ <span class="observ-text--muted">—</span>
71
+ <% end %>
72
+ </td>
73
+ <td class="observ-table__cell observ-table__cell--numeric">
74
+ <% if observation.is_a?(Observ::Generation) %>
75
+ <%= format_currency(observation.cost_usd) %>
76
+ <% else %>
77
+ <span class="observ-text--muted">—</span>
78
+ <% end %>
79
+ </td>
80
+ <td class="observ-table__cell observ-table__cell--actions">
81
+ <%= link_to "View", observation_path(observation), class: "observ-button observ-button--sm" %>
82
+ </td>
83
+ </tr>
84
+ <% end %>
85
+ </tbody>
86
+ </table>
87
+ <% else %>
88
+ <div class="observ-card__empty">
89
+ <p>No observations found.</p>
90
+ </div>
91
+ <% end %>
92
+ </section>
93
+
94
+ <%= observ_pagination(@observations) %>
95
+ </div>
96
+ </section>
97
+ </div>