htm 0.0.11 → 0.0.15
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 +4 -4
- data/.dictate.toml +46 -0
- data/.envrc +2 -0
- data/CHANGELOG.md +85 -2
- data/README.md +348 -79
- data/Rakefile +14 -2
- data/bin/htm_mcp.rb +94 -0
- data/config/database.yml +20 -13
- data/db/migrate/00003_create_file_sources.rb +5 -0
- data/db/migrate/00004_create_nodes.rb +17 -0
- data/db/migrate/00005_create_tags.rb +7 -0
- data/db/migrate/00006_create_node_tags.rb +2 -0
- data/db/migrate/00007_create_robot_nodes.rb +7 -0
- data/db/schema.sql +69 -100
- data/docs/api/index.md +1 -1
- data/docs/api/yard/HTM/Configuration.md +54 -0
- data/docs/api/yard/HTM/Database.md +13 -10
- data/docs/api/yard/HTM/EmbeddingService.md +5 -1
- data/docs/api/yard/HTM/LongTermMemory.md +18 -277
- data/docs/api/yard/HTM/PropositionError.md +18 -0
- data/docs/api/yard/HTM/PropositionService.md +66 -0
- data/docs/api/yard/HTM/QueryCache.md +88 -0
- data/docs/api/yard/HTM/RobotGroup.md +481 -0
- data/docs/api/yard/HTM/SqlBuilder.md +108 -0
- data/docs/api/yard/HTM/TagService.md +4 -0
- data/docs/api/yard/HTM/Telemetry/NullInstrument.md +13 -0
- data/docs/api/yard/HTM/Telemetry/NullMeter.md +15 -0
- data/docs/api/yard/HTM/Telemetry.md +109 -0
- data/docs/api/yard/HTM/WorkingMemoryChannel.md +176 -0
- data/docs/api/yard/HTM.md +8 -22
- data/docs/api/yard/index.csv +102 -25
- data/docs/api/yard-reference.md +8 -0
- data/docs/architecture/index.md +1 -1
- data/docs/assets/images/multi-provider-failover.svg +51 -0
- data/docs/assets/images/robot-group-architecture.svg +65 -0
- data/docs/database/README.md +3 -3
- data/docs/database/public.file_sources.svg +29 -21
- data/docs/database/public.node_tags.md +2 -0
- data/docs/database/public.node_tags.svg +53 -41
- data/docs/database/public.nodes.md +2 -0
- data/docs/database/public.nodes.svg +52 -40
- data/docs/database/public.robot_nodes.md +2 -0
- data/docs/database/public.robot_nodes.svg +30 -22
- data/docs/database/public.robots.svg +16 -12
- data/docs/database/public.tags.md +3 -0
- data/docs/database/public.tags.svg +41 -33
- data/docs/database/schema.json +66 -0
- data/docs/database/schema.svg +60 -48
- data/docs/development/index.md +14 -1
- data/docs/development/rake-tasks.md +1068 -0
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/quick-start.md +144 -155
- data/docs/guides/adding-memories.md +2 -3
- data/docs/guides/context-assembly.md +185 -184
- data/docs/guides/getting-started.md +154 -148
- data/docs/guides/index.md +8 -1
- data/docs/guides/long-term-memory.md +60 -92
- data/docs/guides/mcp-server.md +617 -0
- data/docs/guides/multi-robot.md +249 -345
- data/docs/guides/recalling-memories.md +153 -163
- data/docs/guides/robot-groups.md +604 -0
- data/docs/guides/search-strategies.md +61 -58
- data/docs/guides/working-memory.md +103 -136
- data/docs/images/telemetry-architecture.svg +153 -0
- data/docs/index.md +30 -26
- data/docs/telemetry.md +391 -0
- data/examples/README.md +46 -1
- data/examples/cli_app/README.md +1 -1
- data/examples/cli_app/htm_cli.rb +1 -1
- data/examples/robot_groups/robot_worker.rb +1 -2
- data/examples/robot_groups/same_process.rb +1 -4
- data/examples/sinatra_app/app.rb +1 -1
- data/examples/telemetry/README.md +147 -0
- data/examples/telemetry/SETUP_README.md +169 -0
- data/examples/telemetry/demo.rb +498 -0
- data/examples/telemetry/grafana/dashboards/htm-metrics.json +457 -0
- data/lib/htm/configuration.rb +261 -70
- data/lib/htm/database.rb +46 -22
- data/lib/htm/embedding_service.rb +24 -14
- data/lib/htm/errors.rb +15 -1
- data/lib/htm/jobs/generate_embedding_job.rb +19 -0
- data/lib/htm/jobs/generate_propositions_job.rb +103 -0
- data/lib/htm/jobs/generate_tags_job.rb +24 -0
- data/lib/htm/loaders/markdown_chunker.rb +79 -0
- data/lib/htm/loaders/markdown_loader.rb +41 -15
- data/lib/htm/long_term_memory/fulltext_search.rb +138 -0
- data/lib/htm/long_term_memory/hybrid_search.rb +324 -0
- data/lib/htm/long_term_memory/node_operations.rb +209 -0
- data/lib/htm/long_term_memory/relevance_scorer.rb +355 -0
- data/lib/htm/long_term_memory/robot_operations.rb +34 -0
- data/lib/htm/long_term_memory/tag_operations.rb +428 -0
- data/lib/htm/long_term_memory/vector_search.rb +109 -0
- data/lib/htm/long_term_memory.rb +51 -1153
- data/lib/htm/models/node.rb +35 -2
- data/lib/htm/models/node_tag.rb +31 -0
- data/lib/htm/models/robot_node.rb +31 -0
- data/lib/htm/models/tag.rb +44 -0
- data/lib/htm/proposition_service.rb +169 -0
- data/lib/htm/query_cache.rb +214 -0
- data/lib/htm/robot_group.rb +721 -0
- data/lib/htm/sql_builder.rb +178 -0
- data/lib/htm/tag_service.rb +16 -6
- data/lib/htm/tasks.rb +8 -2
- data/lib/htm/telemetry.rb +224 -0
- data/lib/htm/version.rb +1 -1
- data/lib/htm/working_memory_channel.rb +250 -0
- data/lib/htm.rb +66 -3
- data/lib/tasks/doc.rake +1 -1
- data/lib/tasks/htm.rake +259 -13
- data/mkdocs.yml +98 -96
- metadata +55 -20
- data/.aigcm_msg +0 -1
- data/.claude/settings.local.json +0 -95
- data/CLAUDE.md +0 -603
- data/db/migrate/00009_add_working_memory_to_robot_nodes.rb +0 -12
- data/examples/cli_app/temp.log +0 -93
- data/examples/robot_groups/lib/robot_group.rb +0 -419
- data/examples/robot_groups/lib/working_memory_channel.rb +0 -140
- data/lib/htm/loaders/paragraph_chunker.rb +0 -112
- data/notes/ARCHITECTURE_REVIEW.md +0 -1167
- data/notes/IMPLEMENTATION_SUMMARY.md +0 -606
- data/notes/MULTI_FRAMEWORK_IMPLEMENTATION.md +0 -451
- data/notes/next_steps.md +0 -100
- data/notes/plan.md +0 -627
- data/notes/tag_ontology_enhancement_ideas.md +0 -222
- data/notes/timescaledb_removal_summary.md +0 -200
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# HTM Telemetry Demo with Grafana Visualization
|
|
5
|
+
#
|
|
6
|
+
# This demo shows HTM metrics in a live Grafana dashboard using
|
|
7
|
+
# locally installed Prometheus and Grafana (via Homebrew).
|
|
8
|
+
#
|
|
9
|
+
# Prerequisites:
|
|
10
|
+
# brew install grafana prometheus
|
|
11
|
+
# gem install prometheus-client webrick
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# cd examples/telemetry
|
|
15
|
+
# ruby demo.rb
|
|
16
|
+
#
|
|
17
|
+
# The demo will:
|
|
18
|
+
# 1. Check/start Prometheus and Grafana
|
|
19
|
+
# 2. Clean up any previous demo data
|
|
20
|
+
# 3. Run HTM operations and export metrics
|
|
21
|
+
# 4. Open Grafana in your browser
|
|
22
|
+
|
|
23
|
+
require 'fileutils'
|
|
24
|
+
|
|
25
|
+
ROBOT_NAME = "Telemetry Demo Robot"
|
|
26
|
+
METRICS_PORT = 9394
|
|
27
|
+
|
|
28
|
+
class TelemetryDemo
|
|
29
|
+
def initialize
|
|
30
|
+
@metrics_server = nil
|
|
31
|
+
@metrics_server_thread = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def run
|
|
35
|
+
puts banner
|
|
36
|
+
check_ruby_dependencies
|
|
37
|
+
load_htm
|
|
38
|
+
check_brew_services
|
|
39
|
+
start_services
|
|
40
|
+
configure_prometheus_scrape
|
|
41
|
+
setup_metrics_server
|
|
42
|
+
cleanup_previous_data
|
|
43
|
+
import_grafana_dashboard
|
|
44
|
+
open_grafana
|
|
45
|
+
run_demo_loop
|
|
46
|
+
ensure
|
|
47
|
+
stop_metrics_server
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def banner
|
|
53
|
+
<<~BANNER
|
|
54
|
+
|
|
55
|
+
╔══════════════════════════════════════════════════════════════════╗
|
|
56
|
+
║ HTM Telemetry Demo - Live Grafana Visualization ║
|
|
57
|
+
╚══════════════════════════════════════════════════════════════════╝
|
|
58
|
+
|
|
59
|
+
BANNER
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# =========================================================================
|
|
63
|
+
# Dependency Checks
|
|
64
|
+
# =========================================================================
|
|
65
|
+
|
|
66
|
+
def check_ruby_dependencies
|
|
67
|
+
puts "Checking Ruby dependencies..."
|
|
68
|
+
|
|
69
|
+
missing = []
|
|
70
|
+
|
|
71
|
+
# Check for prometheus-client (installed as system gem)
|
|
72
|
+
begin
|
|
73
|
+
gem 'prometheus-client'
|
|
74
|
+
require 'prometheus/client'
|
|
75
|
+
require 'prometheus/client/formats/text'
|
|
76
|
+
puts " [OK] prometheus-client gem"
|
|
77
|
+
rescue LoadError, Gem::MissingSpecError
|
|
78
|
+
missing << 'prometheus-client'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check for webrick (installed as system gem)
|
|
82
|
+
begin
|
|
83
|
+
gem 'webrick'
|
|
84
|
+
require 'webrick'
|
|
85
|
+
puts " [OK] webrick gem"
|
|
86
|
+
rescue LoadError, Gem::MissingSpecError
|
|
87
|
+
missing << 'webrick'
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless missing.empty?
|
|
91
|
+
puts
|
|
92
|
+
puts " Missing gems. Install with:"
|
|
93
|
+
puts " gem install #{missing.join(' ')}"
|
|
94
|
+
puts
|
|
95
|
+
exit 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
puts
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def load_htm
|
|
102
|
+
puts "Loading HTM..."
|
|
103
|
+
|
|
104
|
+
# Use bundler for HTM and its dependencies
|
|
105
|
+
require 'bundler/setup'
|
|
106
|
+
require_relative '../../lib/htm'
|
|
107
|
+
|
|
108
|
+
puts " [OK] HTM #{HTM::VERSION}"
|
|
109
|
+
puts
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def check_brew_services
|
|
113
|
+
puts "Checking Homebrew services..."
|
|
114
|
+
|
|
115
|
+
%w[prometheus grafana].each do |service|
|
|
116
|
+
print " #{service}: "
|
|
117
|
+
|
|
118
|
+
# Check if installed
|
|
119
|
+
result = `brew list #{service} 2>/dev/null`
|
|
120
|
+
if $?.success?
|
|
121
|
+
puts "installed"
|
|
122
|
+
else
|
|
123
|
+
puts "NOT INSTALLED"
|
|
124
|
+
puts
|
|
125
|
+
puts " Install with:"
|
|
126
|
+
puts " brew install #{service}"
|
|
127
|
+
puts
|
|
128
|
+
exit 1
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
puts
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def start_services
|
|
136
|
+
puts "Starting services..."
|
|
137
|
+
|
|
138
|
+
%w[prometheus grafana].each do |service|
|
|
139
|
+
print " #{service}: "
|
|
140
|
+
|
|
141
|
+
if service_running?(service)
|
|
142
|
+
puts "already running"
|
|
143
|
+
else
|
|
144
|
+
# Start the service
|
|
145
|
+
`brew services start #{service} 2>/dev/null`
|
|
146
|
+
sleep 2
|
|
147
|
+
|
|
148
|
+
# Verify it started
|
|
149
|
+
if service_running?(service)
|
|
150
|
+
puts "started"
|
|
151
|
+
else
|
|
152
|
+
puts "FAILED TO START"
|
|
153
|
+
puts " Try: brew services start #{service}"
|
|
154
|
+
exit 1
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Give services a moment to fully initialize
|
|
160
|
+
sleep 2
|
|
161
|
+
puts
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def service_running?(service)
|
|
165
|
+
status = `brew services info #{service} --json 2>/dev/null`
|
|
166
|
+
# Handle both "running": true and "running":true formats
|
|
167
|
+
status.include?('"running": true') || status.include?('"running":true')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# =========================================================================
|
|
171
|
+
# Prometheus Configuration
|
|
172
|
+
# =========================================================================
|
|
173
|
+
|
|
174
|
+
def configure_prometheus_scrape
|
|
175
|
+
puts "Configuring Prometheus to scrape demo metrics..."
|
|
176
|
+
|
|
177
|
+
require 'yaml'
|
|
178
|
+
|
|
179
|
+
# The actual prometheus.yml location
|
|
180
|
+
prometheus_yml = "/opt/homebrew/etc/prometheus.yml"
|
|
181
|
+
prometheus_yml = "/usr/local/etc/prometheus.yml" unless File.exist?(prometheus_yml)
|
|
182
|
+
|
|
183
|
+
unless File.exist?(prometheus_yml)
|
|
184
|
+
puts " [WARN] Could not find prometheus.yml"
|
|
185
|
+
puts " Metrics may not be scraped automatically."
|
|
186
|
+
puts " Add this to your prometheus.yml:"
|
|
187
|
+
puts
|
|
188
|
+
puts " scrape_configs:"
|
|
189
|
+
puts " - job_name: 'htm-demo'"
|
|
190
|
+
puts " static_configs:"
|
|
191
|
+
puts " - targets: ['localhost:#{METRICS_PORT}']"
|
|
192
|
+
puts
|
|
193
|
+
return
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Parse YAML properly
|
|
197
|
+
config = YAML.load_file(prometheus_yml)
|
|
198
|
+
config['scrape_configs'] ||= []
|
|
199
|
+
|
|
200
|
+
# Check if our job already exists
|
|
201
|
+
job_exists = config['scrape_configs'].any? do |job|
|
|
202
|
+
job['job_name'] == 'htm-demo'
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
if job_exists
|
|
206
|
+
puts " [OK] htm-demo job already configured"
|
|
207
|
+
else
|
|
208
|
+
# Add our scrape config
|
|
209
|
+
htm_job = {
|
|
210
|
+
'job_name' => 'htm-demo',
|
|
211
|
+
'scrape_interval' => '5s',
|
|
212
|
+
'static_configs' => [
|
|
213
|
+
{ 'targets' => ["localhost:#{METRICS_PORT}"] }
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
config['scrape_configs'] << htm_job
|
|
217
|
+
|
|
218
|
+
# Write back with proper YAML formatting
|
|
219
|
+
File.write(prometheus_yml, YAML.dump(config))
|
|
220
|
+
puts " [OK] Added htm-demo scrape job"
|
|
221
|
+
|
|
222
|
+
# Restart Prometheus to pick up new config
|
|
223
|
+
print " Restarting Prometheus... "
|
|
224
|
+
`brew services restart prometheus 2>/dev/null`
|
|
225
|
+
sleep 3
|
|
226
|
+
puts "done"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
puts
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# =========================================================================
|
|
233
|
+
# Metrics Server (exposes /metrics for Prometheus to scrape)
|
|
234
|
+
# =========================================================================
|
|
235
|
+
|
|
236
|
+
def setup_metrics_server
|
|
237
|
+
puts "Starting metrics server on port #{METRICS_PORT}..."
|
|
238
|
+
|
|
239
|
+
# Create Prometheus registry and metrics
|
|
240
|
+
@registry = Prometheus::Client.registry
|
|
241
|
+
|
|
242
|
+
@jobs_counter = @registry.counter(
|
|
243
|
+
:htm_jobs_total,
|
|
244
|
+
docstring: 'HTM job execution counts',
|
|
245
|
+
labels: [:job, :status]
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
@embedding_histogram = @registry.histogram(
|
|
249
|
+
:htm_embedding_latency_milliseconds,
|
|
250
|
+
docstring: 'Embedding generation latency',
|
|
251
|
+
labels: [:provider, :status],
|
|
252
|
+
buckets: [10, 25, 50, 100, 250, 500, 1000, 2500, 5000]
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
@tag_histogram = @registry.histogram(
|
|
256
|
+
:htm_tag_latency_milliseconds,
|
|
257
|
+
docstring: 'Tag extraction latency',
|
|
258
|
+
labels: [:provider, :status],
|
|
259
|
+
buckets: [100, 250, 500, 1000, 2500, 5000, 10000]
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
@search_histogram = @registry.histogram(
|
|
263
|
+
:htm_search_latency_milliseconds,
|
|
264
|
+
docstring: 'Search operation latency',
|
|
265
|
+
labels: [:strategy],
|
|
266
|
+
buckets: [5, 10, 25, 50, 100, 250, 500, 1000]
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
@cache_counter = @registry.counter(
|
|
270
|
+
:htm_cache_operations_total,
|
|
271
|
+
docstring: 'Cache hit/miss counts',
|
|
272
|
+
labels: [:operation]
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
# Start WEBrick server in background thread
|
|
276
|
+
@metrics_server = WEBrick::HTTPServer.new(
|
|
277
|
+
Port: METRICS_PORT,
|
|
278
|
+
Logger: WEBrick::Log.new("/dev/null"),
|
|
279
|
+
AccessLog: []
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
@metrics_server.mount_proc '/metrics' do |req, res|
|
|
283
|
+
res['Content-Type'] = 'text/plain; version=0.0.4'
|
|
284
|
+
res.body = Prometheus::Client::Formats::Text.marshal(@registry)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
@metrics_server_thread = Thread.new { @metrics_server.start }
|
|
288
|
+
|
|
289
|
+
puts " [OK] Metrics available at http://localhost:#{METRICS_PORT}/metrics"
|
|
290
|
+
puts
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def stop_metrics_server
|
|
294
|
+
if @metrics_server
|
|
295
|
+
@metrics_server.shutdown
|
|
296
|
+
@metrics_server_thread&.join(2)
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# =========================================================================
|
|
301
|
+
# Data Cleanup
|
|
302
|
+
# =========================================================================
|
|
303
|
+
|
|
304
|
+
def cleanup_previous_data
|
|
305
|
+
puts "Cleaning up previous demo data..."
|
|
306
|
+
|
|
307
|
+
begin
|
|
308
|
+
# Find the robot
|
|
309
|
+
robot = HTM::Models::Robot.find_by(name: ROBOT_NAME)
|
|
310
|
+
|
|
311
|
+
if robot
|
|
312
|
+
# Find all nodes associated with this robot
|
|
313
|
+
node_ids = HTM::Models::RobotNode.where(robot_id: robot.id).pluck(:node_id)
|
|
314
|
+
|
|
315
|
+
if node_ids.any?
|
|
316
|
+
# Hard delete the nodes
|
|
317
|
+
deleted_count = HTM::Models::Node.where(id: node_ids).delete_all
|
|
318
|
+
|
|
319
|
+
# Clean up robot_nodes join table
|
|
320
|
+
HTM::Models::RobotNode.where(robot_id: robot.id).delete_all
|
|
321
|
+
|
|
322
|
+
# Clean up any orphaned node_tags
|
|
323
|
+
HTM::Models::NodeTag.where(node_id: node_ids).delete_all
|
|
324
|
+
|
|
325
|
+
puts " [OK] Deleted #{deleted_count} previous demo nodes"
|
|
326
|
+
else
|
|
327
|
+
puts " [OK] No previous demo data found"
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
puts " [OK] No previous demo robot found"
|
|
331
|
+
end
|
|
332
|
+
rescue => e
|
|
333
|
+
puts " [WARN] Cleanup failed: #{e.message}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
puts
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# =========================================================================
|
|
340
|
+
# Grafana Dashboard
|
|
341
|
+
# =========================================================================
|
|
342
|
+
|
|
343
|
+
def import_grafana_dashboard
|
|
344
|
+
puts "Grafana dashboard setup..."
|
|
345
|
+
puts " Dashboard JSON: examples/telemetry/grafana/dashboards/htm-metrics.json"
|
|
346
|
+
puts " To import: Grafana > Dashboards > Import > Upload JSON"
|
|
347
|
+
puts
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def open_grafana
|
|
351
|
+
puts "Opening Grafana..."
|
|
352
|
+
system("open http://localhost:3000/d/htm-metrics/htm-metrics 2>/dev/null || " \
|
|
353
|
+
"xdg-open http://localhost:3000 2>/dev/null || " \
|
|
354
|
+
"echo ' Open http://localhost:3000 in your browser'")
|
|
355
|
+
puts " Default login: admin / admin"
|
|
356
|
+
puts
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# =========================================================================
|
|
360
|
+
# Demo Loop
|
|
361
|
+
# =========================================================================
|
|
362
|
+
|
|
363
|
+
def run_demo_loop
|
|
364
|
+
puts "=" * 60
|
|
365
|
+
puts "Starting demo loop..."
|
|
366
|
+
puts " Metrics: http://localhost:#{METRICS_PORT}/metrics"
|
|
367
|
+
puts " Grafana: http://localhost:3000"
|
|
368
|
+
puts "=" * 60
|
|
369
|
+
puts
|
|
370
|
+
puts "Press Ctrl+C to stop"
|
|
371
|
+
puts
|
|
372
|
+
|
|
373
|
+
# Quiet loggers
|
|
374
|
+
HTM.configure do |config|
|
|
375
|
+
config.logger = Logger.new(File::NULL)
|
|
376
|
+
end
|
|
377
|
+
RubyLLM.logger = Logger.new(File::NULL) if defined?(RubyLLM)
|
|
378
|
+
|
|
379
|
+
htm = HTM.new(robot_name: ROBOT_NAME)
|
|
380
|
+
iteration = 0
|
|
381
|
+
|
|
382
|
+
loop do
|
|
383
|
+
iteration += 1
|
|
384
|
+
puts "[#{Time.now.strftime('%H:%M:%S')}] Iteration #{iteration}"
|
|
385
|
+
|
|
386
|
+
# Remember something (track embedding + tag metrics)
|
|
387
|
+
content = sample_content(iteration)
|
|
388
|
+
print " > Remember: #{content[0..45]}... "
|
|
389
|
+
|
|
390
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
391
|
+
node_id = htm.remember(content)
|
|
392
|
+
puts "node #{node_id}"
|
|
393
|
+
|
|
394
|
+
# Wait for background jobs and record metrics
|
|
395
|
+
sleep 3
|
|
396
|
+
|
|
397
|
+
# Simulate job completion metrics (in real app, jobs would record these)
|
|
398
|
+
record_job_metrics
|
|
399
|
+
|
|
400
|
+
# Search with different strategies
|
|
401
|
+
%w[fulltext vector hybrid].each do |strategy|
|
|
402
|
+
query = sample_query(iteration)
|
|
403
|
+
print " > Recall (#{strategy.ljust(8)}): '#{query.ljust(12)}' "
|
|
404
|
+
|
|
405
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
406
|
+
results = htm.recall(query, strategy: strategy.to_sym, limit: 3)
|
|
407
|
+
elapsed_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000).round
|
|
408
|
+
|
|
409
|
+
@search_histogram.observe(elapsed_ms, labels: { strategy: strategy })
|
|
410
|
+
|
|
411
|
+
# Simulate cache behavior
|
|
412
|
+
if rand < 0.3
|
|
413
|
+
@cache_counter.increment(labels: { operation: 'hit' })
|
|
414
|
+
else
|
|
415
|
+
@cache_counter.increment(labels: { operation: 'miss' })
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
puts "-> #{results.length} results (#{elapsed_ms}ms)"
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
puts " Metrics exported to Prometheus"
|
|
422
|
+
puts
|
|
423
|
+
|
|
424
|
+
sleep 5
|
|
425
|
+
end
|
|
426
|
+
rescue Interrupt
|
|
427
|
+
puts
|
|
428
|
+
puts "Shutting down..."
|
|
429
|
+
ask_to_stop_services
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
def ask_to_stop_services
|
|
433
|
+
print "\nStop Prometheus and Grafana services? (y/N): "
|
|
434
|
+
response = $stdin.gets&.strip&.downcase
|
|
435
|
+
|
|
436
|
+
if response == 'y' || response == 'yes'
|
|
437
|
+
print " Stopping prometheus... "
|
|
438
|
+
`brew services stop prometheus 2>/dev/null`
|
|
439
|
+
puts "done"
|
|
440
|
+
|
|
441
|
+
print " Stopping grafana... "
|
|
442
|
+
`brew services stop grafana 2>/dev/null`
|
|
443
|
+
puts "done"
|
|
444
|
+
|
|
445
|
+
puts " Services stopped."
|
|
446
|
+
else
|
|
447
|
+
puts " Services left running."
|
|
448
|
+
puts " To stop later: brew services stop prometheus grafana"
|
|
449
|
+
end
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
def record_job_metrics
|
|
453
|
+
# Record successful embedding job
|
|
454
|
+
@jobs_counter.increment(labels: { job: 'embedding', status: 'success' })
|
|
455
|
+
@embedding_histogram.observe(
|
|
456
|
+
rand(50..200),
|
|
457
|
+
labels: { provider: 'ollama', status: 'success' }
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Record successful tag job
|
|
461
|
+
@jobs_counter.increment(labels: { job: 'tags', status: 'success' })
|
|
462
|
+
@tag_histogram.observe(
|
|
463
|
+
rand(500..2000),
|
|
464
|
+
labels: { provider: 'ollama', status: 'success' }
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Occasionally record failures for demo variety
|
|
468
|
+
if rand < 0.1
|
|
469
|
+
@jobs_counter.increment(labels: { job: 'embedding', status: 'error' })
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def sample_content(iteration)
|
|
474
|
+
contents = [
|
|
475
|
+
"PostgreSQL supports vector similarity search through the pgvector extension.",
|
|
476
|
+
"OpenTelemetry provides unified observability for distributed systems.",
|
|
477
|
+
"Ruby on Rails is a popular web application framework.",
|
|
478
|
+
"Machine learning models can be used for semantic search.",
|
|
479
|
+
"The HTM gem provides intelligent memory management for LLM applications.",
|
|
480
|
+
"Grafana is an open-source analytics and monitoring platform.",
|
|
481
|
+
"Prometheus is a systems monitoring and alerting toolkit.",
|
|
482
|
+
"Background job processing improves application responsiveness.",
|
|
483
|
+
"Hierarchical tags help organize information semantically.",
|
|
484
|
+
"Vector embeddings capture semantic meaning of text."
|
|
485
|
+
]
|
|
486
|
+
contents[(iteration - 1) % contents.length]
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def sample_query(iteration)
|
|
490
|
+
queries = %w[database observability ruby search memory monitoring metrics jobs tags vectors]
|
|
491
|
+
queries[(iteration - 1) % queries.length]
|
|
492
|
+
end
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Run the demo
|
|
496
|
+
if __FILE__ == $PROGRAM_NAME
|
|
497
|
+
TelemetryDemo.new.run
|
|
498
|
+
end
|