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.
- checksums.yaml +7 -0
- data/README.md +778 -0
- data/Rakefile +49 -0
- data/app/assets/javascripts/observ/application.js +12 -0
- data/app/assets/javascripts/observ/controllers/autoscroll_controller.js +33 -0
- data/app/assets/javascripts/observ/controllers/chat_form_controller.js +93 -0
- data/app/assets/javascripts/observ/controllers/copy_controller.js +43 -0
- data/app/assets/javascripts/observ/controllers/dashboard_controller.js +58 -0
- data/app/assets/javascripts/observ/controllers/drawer_controller.js +58 -0
- data/app/assets/javascripts/observ/controllers/expandable_controller.js +33 -0
- data/app/assets/javascripts/observ/controllers/filter_controller.js +36 -0
- data/app/assets/javascripts/observ/controllers/index.js +52 -0
- data/app/assets/javascripts/observ/controllers/json_viewer_controller.js +260 -0
- data/app/assets/javascripts/observ/controllers/message_form_controller.js +58 -0
- data/app/assets/javascripts/observ/controllers/prompt_variables_controller.js +64 -0
- data/app/assets/javascripts/observ/controllers/text_select_controller.js +14 -0
- data/app/assets/stylesheets/observ/_annotations.scss +127 -0
- data/app/assets/stylesheets/observ/_card.scss +52 -0
- data/app/assets/stylesheets/observ/_chat.scss +156 -0
- data/app/assets/stylesheets/observ/_components.scss +460 -0
- data/app/assets/stylesheets/observ/_dashboard.scss +40 -0
- data/app/assets/stylesheets/observ/_datasets.scss +697 -0
- data/app/assets/stylesheets/observ/_drawer.scss +273 -0
- data/app/assets/stylesheets/observ/_json_viewer.scss +120 -0
- data/app/assets/stylesheets/observ/_layout.scss +256 -0
- data/app/assets/stylesheets/observ/_metrics.scss +99 -0
- data/app/assets/stylesheets/observ/_observations.scss +160 -0
- data/app/assets/stylesheets/observ/_pagination.scss +143 -0
- data/app/assets/stylesheets/observ/_prompts.scss +365 -0
- data/app/assets/stylesheets/observ/_table.scss +53 -0
- data/app/assets/stylesheets/observ/_variables.scss +53 -0
- data/app/assets/stylesheets/observ/application.scss +15 -0
- data/app/controllers/observ/annotations_controller.rb +144 -0
- data/app/controllers/observ/application_controller.rb +8 -0
- data/app/controllers/observ/chats_controller.rb +58 -0
- data/app/controllers/observ/dashboard_controller.rb +159 -0
- data/app/controllers/observ/dataset_items_controller.rb +85 -0
- data/app/controllers/observ/dataset_run_items_controller.rb +84 -0
- data/app/controllers/observ/dataset_runs_controller.rb +110 -0
- data/app/controllers/observ/datasets_controller.rb +74 -0
- data/app/controllers/observ/messages_controller.rb +26 -0
- data/app/controllers/observ/observations_controller.rb +59 -0
- data/app/controllers/observ/prompt_versions_controller.rb +148 -0
- data/app/controllers/observ/prompts_controller.rb +205 -0
- data/app/controllers/observ/sessions_controller.rb +45 -0
- data/app/controllers/observ/traces_controller.rb +86 -0
- data/app/forms/observ/prompt_form.rb +96 -0
- data/app/helpers/observ/application_helper.rb +9 -0
- data/app/helpers/observ/chats_helper.rb +47 -0
- data/app/helpers/observ/dashboard_helper.rb +154 -0
- data/app/helpers/observ/datasets_helper.rb +62 -0
- data/app/helpers/observ/pagination_helper.rb +38 -0
- data/app/jobs/observ/application_job.rb +4 -0
- data/app/jobs/observ/dataset_runner_job.rb +49 -0
- data/app/mailers/observ/application_mailer.rb +6 -0
- data/app/models/concerns/observ/agent_phaseable.rb +124 -0
- data/app/models/concerns/observ/agent_selectable.rb +50 -0
- data/app/models/concerns/observ/chat_enhancements.rb +109 -0
- data/app/models/concerns/observ/message_enhancements.rb +31 -0
- data/app/models/concerns/observ/observability_instrumentation.rb +124 -0
- data/app/models/concerns/observ/prompt_management.rb +320 -0
- data/app/models/concerns/observ/trace_association.rb +9 -0
- data/app/models/observ/annotation.rb +23 -0
- data/app/models/observ/application_record.rb +5 -0
- data/app/models/observ/dataset.rb +51 -0
- data/app/models/observ/dataset_item.rb +41 -0
- data/app/models/observ/dataset_run.rb +104 -0
- data/app/models/observ/dataset_run_item.rb +111 -0
- data/app/models/observ/generation.rb +56 -0
- data/app/models/observ/null_prompt.rb +59 -0
- data/app/models/observ/observation.rb +38 -0
- data/app/models/observ/prompt.rb +315 -0
- data/app/models/observ/score.rb +51 -0
- data/app/models/observ/session.rb +131 -0
- data/app/models/observ/span.rb +13 -0
- data/app/models/observ/trace.rb +135 -0
- data/app/presenters/observ/agent_select_presenter.rb +59 -0
- data/app/services/observ/agent_executor_service.rb +174 -0
- data/app/services/observ/agent_provider.rb +60 -0
- data/app/services/observ/agent_selection_service.rb +53 -0
- data/app/services/observ/chat_instrumenter.rb +523 -0
- data/app/services/observ/dataset_runner_service.rb +153 -0
- data/app/services/observ/evaluator_runner_service.rb +58 -0
- data/app/services/observ/evaluators/base_evaluator.rb +51 -0
- data/app/services/observ/evaluators/contains_evaluator.rb +53 -0
- data/app/services/observ/evaluators/exact_match_evaluator.rb +23 -0
- data/app/services/observ/evaluators/json_structure_evaluator.rb +44 -0
- data/app/services/observ/prompt_manager/cache_statistics.rb +82 -0
- data/app/services/observ/prompt_manager/caching.rb +167 -0
- data/app/services/observ/prompt_manager/comparison.rb +49 -0
- data/app/services/observ/prompt_manager/version_management.rb +96 -0
- data/app/services/observ/prompt_manager.rb +40 -0
- data/app/services/observ/trace_text_formatter.rb +349 -0
- data/app/validators/observ/prompt_config_validator.rb +187 -0
- data/app/views/kaminari/_first_page.html.erb +11 -0
- data/app/views/kaminari/_gap.html.erb +8 -0
- data/app/views/kaminari/_last_page.html.erb +11 -0
- data/app/views/kaminari/_next_page.html.erb +11 -0
- data/app/views/kaminari/_page.html.erb +12 -0
- data/app/views/kaminari/_paginator.html.erb +25 -0
- data/app/views/kaminari/_prev_page.html.erb +11 -0
- data/app/views/kaminari/observ/_first_page.html.erb +11 -0
- data/app/views/kaminari/observ/_gap.html.erb +8 -0
- data/app/views/kaminari/observ/_last_page.html.erb +11 -0
- data/app/views/kaminari/observ/_next_page.html.erb +11 -0
- data/app/views/kaminari/observ/_page.html.erb +12 -0
- data/app/views/kaminari/observ/_paginator.html.erb +25 -0
- data/app/views/kaminari/observ/_prev_page.html.erb +11 -0
- data/app/views/layouts/observ/application.html.erb +88 -0
- data/app/views/observ/annotations/_annotation.html.erb +13 -0
- data/app/views/observ/annotations/_form.html.erb +28 -0
- data/app/views/observ/annotations/index.html.erb +28 -0
- data/app/views/observ/annotations/sessions_index.html.erb +48 -0
- data/app/views/observ/annotations/traces_index.html.erb +48 -0
- data/app/views/observ/chats/_form.html.erb +45 -0
- data/app/views/observ/chats/index.html.erb +67 -0
- data/app/views/observ/chats/new.html.erb +17 -0
- data/app/views/observ/chats/show.html.erb +34 -0
- data/app/views/observ/dashboard/index.html.erb +236 -0
- data/app/views/observ/dataset_items/_form.html.erb +49 -0
- data/app/views/observ/dataset_items/edit.html.erb +18 -0
- data/app/views/observ/dataset_items/index.html.erb +95 -0
- data/app/views/observ/dataset_items/new.html.erb +18 -0
- data/app/views/observ/dataset_run_items/_score_close_drawer.html.erb +4 -0
- data/app/views/observ/dataset_run_items/_score_drawer.html.erb +75 -0
- data/app/views/observ/dataset_run_items/_score_success.html.erb +29 -0
- data/app/views/observ/dataset_run_items/_scores_cell.html.erb +19 -0
- data/app/views/observ/dataset_run_items/details_drawer.turbo_stream.erb +80 -0
- data/app/views/observ/dataset_run_items/score_drawer.turbo_stream.erb +7 -0
- data/app/views/observ/dataset_runs/index.html.erb +108 -0
- data/app/views/observ/dataset_runs/new.html.erb +57 -0
- data/app/views/observ/dataset_runs/review.html.erb +155 -0
- data/app/views/observ/dataset_runs/show.html.erb +166 -0
- data/app/views/observ/datasets/_form.html.erb +62 -0
- data/app/views/observ/datasets/_items_tab.html.erb +66 -0
- data/app/views/observ/datasets/_runs_tab.html.erb +82 -0
- data/app/views/observ/datasets/edit.html.erb +32 -0
- data/app/views/observ/datasets/index.html.erb +105 -0
- data/app/views/observ/datasets/new.html.erb +18 -0
- data/app/views/observ/datasets/show.html.erb +67 -0
- data/app/views/observ/messages/_content.html.erb +1 -0
- data/app/views/observ/messages/_form.html.erb +33 -0
- data/app/views/observ/messages/_message.html.erb +14 -0
- data/app/views/observ/messages/_tool_calls.html.erb +10 -0
- data/app/views/observ/messages/create.turbo_stream.erb +9 -0
- data/app/views/observ/observations/index.html.erb +97 -0
- data/app/views/observ/observations/show_generation.html.erb +195 -0
- data/app/views/observ/observations/show_span.html.erb +93 -0
- data/app/views/observ/prompts/_diff_content.html.erb +16 -0
- data/app/views/observ/prompts/_form.html.erb +111 -0
- data/app/views/observ/prompts/_new_form.html.erb +102 -0
- data/app/views/observ/prompts/_prompt_actions.html.erb +4 -0
- data/app/views/observ/prompts/_prompt_content_highlighted.html.erb +4 -0
- data/app/views/observ/prompts/_version_actions.html.erb +40 -0
- data/app/views/observ/prompts/compare.html.erb +155 -0
- data/app/views/observ/prompts/edit.html.erb +17 -0
- data/app/views/observ/prompts/index.html.erb +108 -0
- data/app/views/observ/prompts/new.html.erb +17 -0
- data/app/views/observ/prompts/show.html.erb +138 -0
- data/app/views/observ/prompts/versions.html.erb +87 -0
- data/app/views/observ/sessions/annotations_drawer.turbo_stream.erb +25 -0
- data/app/views/observ/sessions/drawer_test.turbo_stream.erb +49 -0
- data/app/views/observ/sessions/index.html.erb +91 -0
- data/app/views/observ/sessions/show.html.erb +251 -0
- data/app/views/observ/traces/add_to_dataset_drawer.turbo_stream.erb +48 -0
- data/app/views/observ/traces/annotations_drawer.turbo_stream.erb +25 -0
- data/app/views/observ/traces/index.html.erb +87 -0
- data/app/views/observ/traces/show.html.erb +285 -0
- data/app/views/observ/traces/text_output_drawer.turbo_stream.erb +48 -0
- data/app/views/shared/_drawer.html.erb +26 -0
- data/config/routes.rb +80 -0
- data/db/migrate/001_create_observ_sessions.rb +21 -0
- data/db/migrate/002_create_observ_traces.rb +25 -0
- data/db/migrate/003_create_observ_observations.rb +42 -0
- data/db/migrate/004_add_message_id_to_observ_traces.rb +7 -0
- data/db/migrate/005_create_observ_prompts.rb +21 -0
- data/db/migrate/006_fix_prompt_config_strings.rb +23 -0
- data/db/migrate/007_create_observ_annotations.rb +12 -0
- data/db/migrate/009_add_prompt_fields_to_observ_chats.rb +11 -0
- data/db/migrate/010_create_observ_datasets.rb +15 -0
- data/db/migrate/011_create_observ_dataset_items.rb +17 -0
- data/db/migrate/012_create_observ_dataset_runs.rb +22 -0
- data/db/migrate/013_create_observ_dataset_run_items.rb +16 -0
- data/db/migrate/014_create_observ_scores.rb +26 -0
- data/lib/generators/observ/add_phase_tracking/add_phase_tracking_generator.rb +150 -0
- data/lib/generators/observ/add_phase_tracking/templates/migration.rb.tt +6 -0
- data/lib/generators/observ/install/USAGE +27 -0
- data/lib/generators/observ/install/install_generator.rb +270 -0
- data/lib/generators/observ/install_chat/install_chat_generator.rb +313 -0
- data/lib/generators/observ/install_chat/templates/agents/base_agent.rb.tt +147 -0
- data/lib/generators/observ/install_chat/templates/agents/simple_agent.rb.tt +55 -0
- data/lib/generators/observ/install_chat/templates/concerns/observ_chat_enhancements.rb.tt +34 -0
- data/lib/generators/observ/install_chat/templates/concerns/observ_message_enhancements.rb.tt +18 -0
- data/lib/generators/observ/install_chat/templates/initializers/observability.rb.tt +20 -0
- data/lib/generators/observ/install_chat/templates/jobs/chat_response_job.rb.tt +56 -0
- data/lib/generators/observ/install_chat/templates/migrations/add_agent_class_name.rb.tt +6 -0
- data/lib/generators/observ/install_chat/templates/migrations/add_observability_session_id.rb.tt +6 -0
- data/lib/generators/observ/install_chat/templates/tools/think_tool.rb.tt +29 -0
- data/lib/generators/observ/install_chat/templates/views/messages/_content.html.erb.tt +1 -0
- data/lib/observ/asset_installer.rb +130 -0
- data/lib/observ/asset_syncer.rb +104 -0
- data/lib/observ/configuration.rb +108 -0
- data/lib/observ/engine.rb +50 -0
- data/lib/observ/index_file_generator.rb +142 -0
- data/lib/observ/instrumenter/ruby_llm.rb +6 -0
- data/lib/observ/version.rb +3 -0
- data/lib/observ.rb +29 -0
- data/lib/tasks/observ_tasks.rake +75 -0
- metadata +453 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module Observ
|
|
7
|
+
module Generators
|
|
8
|
+
# Generator for installing Observ chat/agent testing feature
|
|
9
|
+
#
|
|
10
|
+
# This generator enhances RubyLLM infrastructure with Observ-specific
|
|
11
|
+
# agent capabilities and observability features.
|
|
12
|
+
#
|
|
13
|
+
# Prerequisites:
|
|
14
|
+
# - RubyLLM gem installed (gem 'ruby_llm')
|
|
15
|
+
# - rails generate ruby_llm:install (run first)
|
|
16
|
+
# - rails db:migrate
|
|
17
|
+
# - rails ruby_llm:load_models
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# rails generate observ:install:chat
|
|
21
|
+
# rails generate observ:install:chat --skip-tools
|
|
22
|
+
# rails generate observ:install:chat --skip-migrations
|
|
23
|
+
class InstallChatGenerator < Rails::Generators::Base
|
|
24
|
+
include ActiveRecord::Generators::Migration
|
|
25
|
+
|
|
26
|
+
source_root File.expand_path("templates", __dir__)
|
|
27
|
+
|
|
28
|
+
class_option :skip_tools,
|
|
29
|
+
type: :boolean,
|
|
30
|
+
desc: "Skip tool class generation",
|
|
31
|
+
default: false
|
|
32
|
+
|
|
33
|
+
class_option :skip_migrations,
|
|
34
|
+
type: :boolean,
|
|
35
|
+
desc: "Skip migration generation",
|
|
36
|
+
default: false
|
|
37
|
+
|
|
38
|
+
class_option :skip_job,
|
|
39
|
+
type: :boolean,
|
|
40
|
+
desc: "Skip ChatResponseJob generation",
|
|
41
|
+
default: false
|
|
42
|
+
|
|
43
|
+
class_option :with_phase_tracking,
|
|
44
|
+
type: :boolean,
|
|
45
|
+
desc: "Include phase tracking for multi-phase agents",
|
|
46
|
+
default: false
|
|
47
|
+
|
|
48
|
+
def check_prerequisites
|
|
49
|
+
say "\n"
|
|
50
|
+
say "=" * 80, :cyan
|
|
51
|
+
say "Observ Chat Feature Installation", :cyan
|
|
52
|
+
say "=" * 80, :cyan
|
|
53
|
+
say "\n"
|
|
54
|
+
|
|
55
|
+
check_ruby_llm_gem
|
|
56
|
+
check_ruby_llm_models_installed
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def create_migrations
|
|
60
|
+
return if options[:skip_migrations]
|
|
61
|
+
|
|
62
|
+
say "Creating Observ-specific migrations...", :cyan
|
|
63
|
+
say "-" * 80, :cyan
|
|
64
|
+
|
|
65
|
+
migration_template "migrations/add_agent_class_name.rb.tt",
|
|
66
|
+
"db/migrate/add_agent_class_name_to_chats.rb"
|
|
67
|
+
|
|
68
|
+
migration_template "migrations/add_observability_session_id.rb.tt",
|
|
69
|
+
"db/migrate/add_observability_session_id_to_chats.rb"
|
|
70
|
+
|
|
71
|
+
say " ✓ Created agent_class_name migration", :green
|
|
72
|
+
say " ✓ Created observability_session_id migration", :green
|
|
73
|
+
say "\n"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def enhance_models
|
|
77
|
+
say "Enhancing RubyLLM models with Observ functionality...", :cyan
|
|
78
|
+
say "-" * 80, :cyan
|
|
79
|
+
|
|
80
|
+
enhance_chat_model
|
|
81
|
+
enhance_message_model
|
|
82
|
+
|
|
83
|
+
say "\n"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def create_agent_infrastructure
|
|
87
|
+
say "Creating agent infrastructure...", :cyan
|
|
88
|
+
say "-" * 80, :cyan
|
|
89
|
+
|
|
90
|
+
template "agents/base_agent.rb.tt", "app/agents/base_agent.rb"
|
|
91
|
+
|
|
92
|
+
say " ✓ Created BaseAgent", :green
|
|
93
|
+
say " ℹ AgentProvider is now available as Observ::AgentProvider (no generation needed)", :yellow
|
|
94
|
+
say " ℹ AgentSelectable is now available as Observ::AgentSelectable (no generation needed)", :yellow
|
|
95
|
+
say " ℹ PromptManagement is now available as Observ::PromptManagement (no generation needed)", :yellow
|
|
96
|
+
say "\n"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def create_example_agent
|
|
100
|
+
say "Creating example agent...", :cyan
|
|
101
|
+
say "-" * 80, :cyan
|
|
102
|
+
|
|
103
|
+
template "agents/simple_agent.rb.tt", "app/agents/simple_agent.rb"
|
|
104
|
+
|
|
105
|
+
say " ✓ Created SimpleAgent", :green
|
|
106
|
+
say "\n"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def create_job
|
|
110
|
+
return if options[:skip_job]
|
|
111
|
+
|
|
112
|
+
say "Creating ChatResponseJob...", :cyan
|
|
113
|
+
say "-" * 80, :cyan
|
|
114
|
+
|
|
115
|
+
template "jobs/chat_response_job.rb.tt", "app/jobs/chat_response_job.rb"
|
|
116
|
+
|
|
117
|
+
say " ✓ Created ChatResponseJob", :green
|
|
118
|
+
say "\n"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def create_tools
|
|
122
|
+
return if options[:skip_tools]
|
|
123
|
+
|
|
124
|
+
say "Creating tool classes...", :cyan
|
|
125
|
+
say "-" * 80, :cyan
|
|
126
|
+
|
|
127
|
+
template "tools/think_tool.rb.tt", "app/tools/think_tool.rb"
|
|
128
|
+
|
|
129
|
+
say " ✓ Created ThinkTool (basic example)", :green
|
|
130
|
+
say " ℹ For advanced tools (web search, etc.), see documentation", :yellow
|
|
131
|
+
say "\n"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def create_view_partials
|
|
135
|
+
say "Creating view partials...", :cyan
|
|
136
|
+
say "-" * 80, :cyan
|
|
137
|
+
|
|
138
|
+
template "views/messages/_content.html.erb.tt", "app/views/messages/_content.html.erb"
|
|
139
|
+
|
|
140
|
+
say " ✓ Created messages/_content partial", :green
|
|
141
|
+
say "\n"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def create_initializer
|
|
145
|
+
say "Creating observability initializer...", :cyan
|
|
146
|
+
say "-" * 80, :cyan
|
|
147
|
+
|
|
148
|
+
template "initializers/observability.rb.tt", "config/initializers/observability.rb"
|
|
149
|
+
|
|
150
|
+
say " ✓ Created observability initializer (debug logging enabled)", :green
|
|
151
|
+
say "\n"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def add_phase_tracking
|
|
155
|
+
return unless options[:with_phase_tracking]
|
|
156
|
+
|
|
157
|
+
say "Adding phase tracking support...", :cyan
|
|
158
|
+
say "-" * 80, :cyan
|
|
159
|
+
|
|
160
|
+
# Call the add_phase_tracking generator
|
|
161
|
+
generate "observ:add_phase_tracking"
|
|
162
|
+
|
|
163
|
+
say "\n"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def show_post_install_instructions
|
|
167
|
+
say "\n"
|
|
168
|
+
say "=" * 80, :green
|
|
169
|
+
say "Observ Chat Feature Installation Complete!", :green
|
|
170
|
+
say "=" * 80, :green
|
|
171
|
+
say "\n"
|
|
172
|
+
|
|
173
|
+
say "Next steps:", :cyan
|
|
174
|
+
say "\n"
|
|
175
|
+
|
|
176
|
+
say "1. Run migrations:", :cyan
|
|
177
|
+
say " rails db:migrate", :white
|
|
178
|
+
say "\n"
|
|
179
|
+
|
|
180
|
+
say "2. Start your Rails server and visit:", :cyan
|
|
181
|
+
say " http://localhost:3000/observ/chats", :white
|
|
182
|
+
say "\n"
|
|
183
|
+
|
|
184
|
+
say "3. Create your first agent by extending BaseAgent:", :cyan
|
|
185
|
+
say " See app/agents/simple_agent.rb for an example", :white
|
|
186
|
+
say "\n"
|
|
187
|
+
|
|
188
|
+
unless options[:with_phase_tracking]
|
|
189
|
+
say "Optional: Add phase tracking for multi-phase agents:", :cyan
|
|
190
|
+
say " rails generate observ:add_phase_tracking", :white
|
|
191
|
+
say "\n"
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
say "Documentation:", :cyan
|
|
195
|
+
say " • Agent development: observ/docs/AGENT_DEVELOPMENT.md", :white
|
|
196
|
+
say " • Tool development: observ/docs/TOOL_DEVELOPMENT.md", :white
|
|
197
|
+
say "\n"
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
private
|
|
201
|
+
|
|
202
|
+
def check_ruby_llm_gem
|
|
203
|
+
unless gem_installed?("ruby_llm")
|
|
204
|
+
raise Thor::Error, <<~ERROR
|
|
205
|
+
RubyLLM gem not found!
|
|
206
|
+
|
|
207
|
+
This generator requires RubyLLM to be installed first.
|
|
208
|
+
|
|
209
|
+
Please run:
|
|
210
|
+
1. Add to Gemfile: gem 'ruby_llm'
|
|
211
|
+
2. bundle install
|
|
212
|
+
3. rails generate ruby_llm:install
|
|
213
|
+
4. rails db:migrate
|
|
214
|
+
5. rails ruby_llm:load_models
|
|
215
|
+
#{' '}
|
|
216
|
+
Then run this generator again.
|
|
217
|
+
ERROR
|
|
218
|
+
end
|
|
219
|
+
say " ✓ RubyLLM gem found", :green
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def check_ruby_llm_models_installed
|
|
223
|
+
models_to_check = %w[Chat Message ToolCall Model]
|
|
224
|
+
missing_models = []
|
|
225
|
+
|
|
226
|
+
models_to_check.each do |model_name|
|
|
227
|
+
model_path = Rails.root.join("app/models/#{model_name.underscore}.rb")
|
|
228
|
+
unless File.exist?(model_path)
|
|
229
|
+
missing_models << model_name
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if missing_models.any?
|
|
234
|
+
raise Thor::Error, <<~ERROR
|
|
235
|
+
RubyLLM models not found: #{missing_models.join(', ')}
|
|
236
|
+
|
|
237
|
+
This generator requires ruby_llm:install to be run first.
|
|
238
|
+
|
|
239
|
+
Please run:
|
|
240
|
+
1. rails generate ruby_llm:install
|
|
241
|
+
2. rails db:migrate
|
|
242
|
+
3. rails ruby_llm:load_models
|
|
243
|
+
#{' '}
|
|
244
|
+
Then run this generator again.
|
|
245
|
+
ERROR
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
say " ✓ RubyLLM models found (Chat, Message, ToolCall, Model)", :green
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def enhance_chat_model
|
|
252
|
+
# Include the concern from gem in Chat model if not already included
|
|
253
|
+
chat_content = File.read(Rails.root.join("app/models/chat.rb"))
|
|
254
|
+
|
|
255
|
+
unless chat_content.include?("Observ::ChatEnhancements")
|
|
256
|
+
inject_into_file "app/models/chat.rb", after: /class Chat < ApplicationRecord\n/ do
|
|
257
|
+
" include Observ::ChatEnhancements\n\n"
|
|
258
|
+
end
|
|
259
|
+
say " ✓ Included Observ::ChatEnhancements in Chat model", :green
|
|
260
|
+
else
|
|
261
|
+
say " ⚠ Chat model already includes Observ::ChatEnhancements", :yellow
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Add agent_class method if agent_class_name column exists
|
|
265
|
+
unless chat_content.include?("def agent_class")
|
|
266
|
+
inject_into_file "app/models/chat.rb", before: /^end\s*$/ do
|
|
267
|
+
<<~RUBY
|
|
268
|
+
|
|
269
|
+
# Return the agent class for this chat
|
|
270
|
+
# Override this method if you need custom agent class resolution
|
|
271
|
+
def agent_class
|
|
272
|
+
return BaseAgent if agent_class_name.blank?
|
|
273
|
+
|
|
274
|
+
agent_class_name.constantize
|
|
275
|
+
rescue NameError
|
|
276
|
+
Rails.logger.warn "Agent class \#{agent_class_name} not found, using BaseAgent"
|
|
277
|
+
BaseAgent
|
|
278
|
+
end
|
|
279
|
+
RUBY
|
|
280
|
+
end
|
|
281
|
+
say " ✓ Added agent_class method to Chat model", :green
|
|
282
|
+
else
|
|
283
|
+
say " ⚠ Chat model already has agent_class method", :yellow
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def enhance_message_model
|
|
288
|
+
# Include the concern from gem in Message model if not already included
|
|
289
|
+
message_content = File.read(Rails.root.join("app/models/message.rb"))
|
|
290
|
+
|
|
291
|
+
unless message_content.include?("Observ::MessageEnhancements")
|
|
292
|
+
inject_into_file "app/models/message.rb", after: /class Message < ApplicationRecord\n/ do
|
|
293
|
+
" include Observ::MessageEnhancements\n\n"
|
|
294
|
+
end
|
|
295
|
+
say " ✓ Included Observ::MessageEnhancements in Message model", :green
|
|
296
|
+
else
|
|
297
|
+
say " ⚠ Message model already includes Observ::MessageEnhancements", :yellow
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def gem_installed?(gem_name)
|
|
302
|
+
Gem::Specification.find_all_by_name(gem_name).any?
|
|
303
|
+
rescue Gem::LoadError
|
|
304
|
+
false
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Helper for migration timestamps
|
|
308
|
+
def migration_version
|
|
309
|
+
"[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Base interface for all agents
|
|
2
|
+
# Defines the contract that all agents must implement
|
|
3
|
+
#
|
|
4
|
+
# Responsibilities:
|
|
5
|
+
# - Define required interface methods (system_prompt, default_model)
|
|
6
|
+
# - Define optional interface methods (tools, initial_greeting)
|
|
7
|
+
# - Provide setup methods that work with the interface
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# class MyAgent < BaseAgent
|
|
11
|
+
# def self.system_prompt
|
|
12
|
+
# "You are a helpful assistant."
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# def self.default_model
|
|
16
|
+
# "gpt-4o-mini"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# def self.tools
|
|
20
|
+
# [MyTool]
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# For agents that want prompt management, include the Observ::PromptManagement concern:
|
|
25
|
+
# class MyAgent < BaseAgent
|
|
26
|
+
# include Observ::PromptManagement
|
|
27
|
+
# # ...
|
|
28
|
+
# end
|
|
29
|
+
class BaseAgent
|
|
30
|
+
# ============================================
|
|
31
|
+
# INTERFACE METHODS - Override in subclasses
|
|
32
|
+
# ============================================
|
|
33
|
+
|
|
34
|
+
# System prompt for the agent
|
|
35
|
+
# @return [String] The system prompt
|
|
36
|
+
def self.system_prompt
|
|
37
|
+
raise NotImplementedError, "#{name} must implement .system_prompt"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Default model for this agent
|
|
41
|
+
# @return [String] The default model identifier
|
|
42
|
+
def self.default_model
|
|
43
|
+
raise NotImplementedError, "#{name} must implement .default_model"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Tools available to this agent
|
|
47
|
+
# @return [Array<Class>] Array of tool classes
|
|
48
|
+
def self.tools
|
|
49
|
+
[]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Initial greeting message when chat starts
|
|
53
|
+
# @return [String, nil] The greeting message or nil
|
|
54
|
+
def self.initial_greeting
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Model to use for this agent
|
|
59
|
+
# Can be overridden by concerns (e.g., Observ::PromptManagement)
|
|
60
|
+
# @return [String] The model identifier to use
|
|
61
|
+
def self.model
|
|
62
|
+
default_model
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Default model parameters for this agent (temperature, max_tokens, etc.)
|
|
66
|
+
# @return [Hash] Hash of model parameters
|
|
67
|
+
def self.default_model_parameters
|
|
68
|
+
{} # Override in subclasses for custom defaults
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Model parameters to use for this agent
|
|
72
|
+
# Can be overridden by concerns (e.g., Observ::PromptManagement)
|
|
73
|
+
# @return [Hash] The model parameters to use
|
|
74
|
+
def self.model_parameters
|
|
75
|
+
default_model_parameters
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# ============================================
|
|
79
|
+
# SETUP METHODS - Work with the interface
|
|
80
|
+
# ============================================
|
|
81
|
+
|
|
82
|
+
# Setup instructions for the chat session
|
|
83
|
+
# @param chat [Chat] The chat session
|
|
84
|
+
# @return [Chat] The configured chat session
|
|
85
|
+
def self.setup_instructions(chat)
|
|
86
|
+
chat.with_instructions(system_prompt) if system_prompt.present?
|
|
87
|
+
chat
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Setup tools for the chat session
|
|
91
|
+
# @param chat [Chat] The chat session
|
|
92
|
+
# @return [Chat] The configured chat session
|
|
93
|
+
def self.setup_tools(chat)
|
|
94
|
+
if tools.any?
|
|
95
|
+
instantiated_tools = tools.map do |tool|
|
|
96
|
+
if tool.instance_of?(Class)
|
|
97
|
+
observ_session = chat.observ_session if chat.respond_to?(:observ_session)
|
|
98
|
+
tool.new(observ_session)
|
|
99
|
+
else
|
|
100
|
+
if tool.respond_to?(:observability=) && chat.respond_to?(:observ_session)
|
|
101
|
+
tool.observability = chat.observ_session
|
|
102
|
+
end
|
|
103
|
+
tool
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
chat.with_tools(*instantiated_tools)
|
|
107
|
+
end
|
|
108
|
+
chat
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Setup model parameters for the chat session
|
|
112
|
+
# @param chat [Chat] The chat session
|
|
113
|
+
# @return [Chat] The configured chat session
|
|
114
|
+
def self.setup_parameters(chat)
|
|
115
|
+
params = model_parameters
|
|
116
|
+
|
|
117
|
+
# Convert string numeric values to proper types for API compatibility
|
|
118
|
+
# This is necessary because prompt configs may return string values
|
|
119
|
+
normalized_params = params.transform_values do |value|
|
|
120
|
+
case value
|
|
121
|
+
when String
|
|
122
|
+
# Convert numeric strings to numbers
|
|
123
|
+
if value.match?(/\A-?\d+\.?\d*\z/)
|
|
124
|
+
value.include?('.') ? value.to_f : value.to_i
|
|
125
|
+
else
|
|
126
|
+
value
|
|
127
|
+
end
|
|
128
|
+
else
|
|
129
|
+
value
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
chat.with_params(**normalized_params) if normalized_params.any?
|
|
134
|
+
chat
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Send initial greeting message
|
|
138
|
+
# @param chat [Chat] The chat session
|
|
139
|
+
def self.send_initial_greeting(chat)
|
|
140
|
+
return unless initial_greeting
|
|
141
|
+
|
|
142
|
+
chat.messages.create!(
|
|
143
|
+
role: :assistant,
|
|
144
|
+
content: initial_greeting
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# SimpleAgent - A basic agent example
|
|
2
|
+
#
|
|
3
|
+
# This agent demonstrates the minimum required implementation
|
|
4
|
+
# for a working agent in the Observ chat UI.
|
|
5
|
+
#
|
|
6
|
+
# To create your own agent:
|
|
7
|
+
# 1. Extend BaseAgent
|
|
8
|
+
# 2. Include Observ::AgentSelectable to make it appear in the UI
|
|
9
|
+
# 3. Implement required methods: system_prompt, default_model, display_name
|
|
10
|
+
# 4. Optionally add tools and initial_greeting
|
|
11
|
+
class SimpleAgent < BaseAgent
|
|
12
|
+
include Observ::AgentSelectable
|
|
13
|
+
|
|
14
|
+
# Display name shown in the Observ UI
|
|
15
|
+
def self.display_name
|
|
16
|
+
"Simple Agent"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Description shown in agent selection
|
|
20
|
+
def self.description
|
|
21
|
+
"A basic conversational agent"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# System prompt that defines the agent's behavior
|
|
25
|
+
def self.system_prompt
|
|
26
|
+
<<~PROMPT
|
|
27
|
+
You are a helpful AI assistant.
|
|
28
|
+
|
|
29
|
+
Your role is to:
|
|
30
|
+
- Answer questions clearly and concisely
|
|
31
|
+
- Be friendly and professional
|
|
32
|
+
- Admit when you don't know something
|
|
33
|
+
|
|
34
|
+
Current date: #{Time.current.strftime("%B %d, %Y")}
|
|
35
|
+
PROMPT
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Default model to use for this agent
|
|
39
|
+
def self.default_model
|
|
40
|
+
"gpt-4o-mini"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Optional: Greeting message when chat starts
|
|
44
|
+
def self.initial_greeting
|
|
45
|
+
<<~GREETING
|
|
46
|
+
Hello! I'm a simple AI assistant. How can I help you today?
|
|
47
|
+
GREETING
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Optional: Tools available to this agent
|
|
51
|
+
# Uncomment and add tool classes as needed:
|
|
52
|
+
# def self.tools
|
|
53
|
+
# [ThinkTool]
|
|
54
|
+
# end
|
|
55
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ObservChatEnhancements
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
include Observ::ObservabilityInstrumentation
|
|
8
|
+
|
|
9
|
+
# Consolidated callback for agent initialization to reduce redundant queries
|
|
10
|
+
after_create :initialize_agent, if: -> { agent_class_name.present? }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def agent_class
|
|
14
|
+
return BaseAgent if agent_class_name.blank?
|
|
15
|
+
|
|
16
|
+
agent_class_name.constantize
|
|
17
|
+
rescue NameError
|
|
18
|
+
Rails.logger.warn "Agent class #{agent_class_name} not found, using BaseAgent"
|
|
19
|
+
BaseAgent
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def setup_tools
|
|
23
|
+
agent_class.setup_tools(self)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def initialize_agent
|
|
29
|
+
# Execute both agent setup steps in one consolidated callback
|
|
30
|
+
# This prevents redundant association loading between callbacks
|
|
31
|
+
agent_class.setup_instructions(self)
|
|
32
|
+
agent_class.send_initial_greeting(self)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ObservMessageEnhancements
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
include Observ::TraceAssociation
|
|
8
|
+
|
|
9
|
+
broadcasts_to ->(message) { "chat_#{message.chat_id}" }, partial: "observ/messages/message"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def broadcast_append_chunk(content)
|
|
13
|
+
broadcast_append_to "chat_#{chat_id}",
|
|
14
|
+
target: "message_#{id}_content",
|
|
15
|
+
partial: "observ/messages/content",
|
|
16
|
+
locals: { content: content }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Observability configuration
|
|
4
|
+
# This file is optional and is used to configure observability settings
|
|
5
|
+
|
|
6
|
+
Rails.application.configure do
|
|
7
|
+
config.observability = ActiveSupport::OrderedOptions.new
|
|
8
|
+
|
|
9
|
+
# Enable observability instrumentation
|
|
10
|
+
# When enabled, sessions, traces, and observations are automatically tracked
|
|
11
|
+
config.observability.enabled = true
|
|
12
|
+
|
|
13
|
+
# Automatically instrument RubyLLM chats with observability
|
|
14
|
+
# When enabled, LLM calls, tool usage, and metrics are tracked
|
|
15
|
+
config.observability.auto_instrument_chats = true
|
|
16
|
+
|
|
17
|
+
# Enable debug logging for observability metrics
|
|
18
|
+
# When enabled, job completion metrics (tokens, cost) will be logged
|
|
19
|
+
config.observability.debug = Rails.env.development?
|
|
20
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class ChatResponseJob < ApplicationJob
|
|
2
|
+
retry_on RubyLLM::BadRequestError, wait: 2.seconds, attempts: 1
|
|
3
|
+
|
|
4
|
+
def perform(chat_id, content)
|
|
5
|
+
chat = Chat.find(chat_id)
|
|
6
|
+
|
|
7
|
+
# Observability is automatically enabled via after_find callback
|
|
8
|
+
# All LLM calls, tool calls, and metrics are tracked automatically
|
|
9
|
+
|
|
10
|
+
chat.setup_tools
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
# Model parameters (temperature, max_tokens, etc.) are automatically configured
|
|
14
|
+
# via the initialize_agent callback when the chat is created
|
|
15
|
+
chat.ask(content) do |chunk|
|
|
16
|
+
if chunk.content && !chunk.content.blank?
|
|
17
|
+
message = chat.messages.last
|
|
18
|
+
message.broadcast_append_chunk(chunk.content)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
rescue RubyLLM::BadRequestError => e
|
|
22
|
+
Rails.logger.error "[ChatResponseJob] BadRequestError: #{e.message}"
|
|
23
|
+
|
|
24
|
+
error_message = chat.messages.create!(
|
|
25
|
+
role: :assistant,
|
|
26
|
+
content: "I apologize, but I encountered an error while processing your request. This might be due to a tool call issue. Please try rephrasing your question or try again."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
error_message.broadcast_replace_to(
|
|
30
|
+
"chat_#{chat.id}",
|
|
31
|
+
target: "messages",
|
|
32
|
+
partial: "observ/messages/message",
|
|
33
|
+
locals: { message: error_message }
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
raise
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
chat.finalize_observability_session if chat.observ_session
|
|
40
|
+
|
|
41
|
+
if observability_debug_enabled? && chat.observ_session
|
|
42
|
+
metrics = chat.observ_session.session_metrics
|
|
43
|
+
Rails.logger.info "[Observability] Job completed - Tokens: #{metrics[:total_tokens]}, Cost: $#{metrics[:total_cost]}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def observability_debug_enabled?
|
|
50
|
+
Rails.configuration.respond_to?(:observability) &&
|
|
51
|
+
Rails.configuration.observability.respond_to?(:debug) &&
|
|
52
|
+
Rails.configuration.observability.debug
|
|
53
|
+
rescue NoMethodError
|
|
54
|
+
false
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "ruby_llm"
|
|
2
|
+
|
|
3
|
+
# ThinkTool - A basic tool for agent reflection
|
|
4
|
+
#
|
|
5
|
+
# This tool allows agents to "think out loud" and reflect on their
|
|
6
|
+
# reasoning process. Useful for debugging and understanding agent behavior.
|
|
7
|
+
#
|
|
8
|
+
# To create your own tools:
|
|
9
|
+
# 1. Extend RubyLLM::Tool
|
|
10
|
+
# 2. Add description and params
|
|
11
|
+
# 3. Implement execute method
|
|
12
|
+
# 4. Add tool to agent's tools array
|
|
13
|
+
class ThinkTool < RubyLLM::Tool
|
|
14
|
+
description "Reflect on current progress and plan next steps. Use this to organize your thoughts."
|
|
15
|
+
|
|
16
|
+
param :reflection,
|
|
17
|
+
desc: "Your thoughts on what you've learned so far and what to do next",
|
|
18
|
+
type: :string
|
|
19
|
+
|
|
20
|
+
attr_accessor :observability
|
|
21
|
+
|
|
22
|
+
def initialize(observability = nil)
|
|
23
|
+
@observability = observability
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute(reflection:)
|
|
27
|
+
"Reflection noted: #{reflection}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%%= content %>
|