ragnar-cli 0.1.0.pre.4 → 0.1.0.pre.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0837b5d907d7b5336d938a4fa94c53b4dacdb96b56ffc753144dfaa4f476133
4
- data.tar.gz: 7cd9d94241f8dc38a7dd4b3b2732966f21f9783b818087b30db3f03b5e6c2dfd
3
+ metadata.gz: 06c692710e5d5deb8cf5b8122050968deb459ad6feebd5bbdeabf63679a04d7c
4
+ data.tar.gz: dc8784c1b36aec7c473b9f93cc1929bdbd1d75ecec18d727c4e7892aeea6df30
5
5
  SHA512:
6
- metadata.gz: 2a87f654f8502b292d3bfbea31c5f6bb5ba6f02638cd024e8efd623ec88c69f528c59ca4f2604437df9169ec4ad11ffcf4f6223441f9787b8d94ab312c149192
7
- data.tar.gz: 133364ec6142c14ded8c58c7041aab342b290e9a196da54c9164f3dfc83d466410d173cb35aa5a5988b201dac251f34e441346f2debfcc0d8bb3e95e24476d1c
6
+ metadata.gz: cd6e88640e7629725fef32214f088d11362ddbcbbf52a90aa6d315ecc0b08c0d8f487501fd802221c9b647f1e22b55aa91a97f8eefa7046b38c2c49eff3d1bd3
7
+ data.tar.gz: b58174568e4b76d6cce4aeb19e7674cddb9f8c11c0c6e44a1da0e9f545263a3874b0f99cc16fdc3723e516ffb7c4ffee6660540c891cd595b805ded4f3ff0a49
data/README.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  A complete Ruby implementation of Retrieval-Augmented Generation (RAG) pipeline using native Ruby ML/NLP gems.
4
4
 
5
+ <p align="center">
6
+ <img src="/docs/assets/screenshot.png" alt="ragnar TUI" width="600">
7
+ </p>
8
+
5
9
  ## Overview
6
10
 
7
11
  Ragnar provides a production-ready RAG pipeline for Ruby applications, integrating:
@@ -151,21 +155,41 @@ ragnar index ./documents \
151
155
  --chunk-overlap 100
152
156
  ```
153
157
 
154
- ### 2. Train UMAP (Optional)
158
+ ### 2. Interactive Mode (TUI)
159
+
160
+ Running `ragnar` with no arguments launches an interactive TUI powered by [ratatui](https://ratatui.rs/):
161
+
162
+ ```bash
163
+ # Launch the TUI (default when no command given)
164
+ ragnar
165
+
166
+ # Or explicitly
167
+ ragnar interactive
168
+ ```
169
+
170
+ The TUI provides:
171
+ - **Auto-completion** for commands and options
172
+ - **Persistent history** across sessions
173
+ - **Live output** — see indexing progress, query results, and topic analysis inline
174
+ - **All CLI commands** available via `/command` syntax (e.g., `/index .`, `/umap train`, `/query "my question"`)
175
+ - **`/verbose`** — toggle verbose mode to see query pipeline details (retrieval, reranking, context)
176
+ - **`/profile`** — list or switch LLM profiles mid-session
177
+
178
+ ### 3. Train UMAP (Optional)
155
179
 
156
180
  Reduce embedding dimensions for faster search:
157
181
 
158
182
  ```bash
159
183
  # Train UMAP model (auto-adjusts parameters based on data)
160
- ragnar train-umap \
184
+ ragnar umap train \
161
185
  --n-components 50 \
162
186
  --n-neighbors 15
163
187
 
164
188
  # Apply to all embeddings
165
- ragnar apply-umap
189
+ ragnar umap apply
166
190
  ```
167
191
 
168
- ### 3. Extract Topics
192
+ ### 4. Extract Topics
169
193
 
170
194
  Perform topic modeling to discover themes in your indexed documents:
171
195
 
@@ -194,7 +218,7 @@ The HTML export includes:
194
218
  - **Topic Bubbles**: Interactive bubble chart showing topic sizes and coherence
195
219
  - **Embedding Scatter Plot**: Visualization of all documents in embedding space, colored by cluster
196
220
 
197
- ### 4. Query the System
221
+ ### 5. Query the System
198
222
 
199
223
  ```bash
200
224
  # Basic query
@@ -226,7 +250,7 @@ When using `--verbose` or `-v`, you'll see:
226
250
  6. **Response Generation**: The final LLM prompt and response
227
251
  7. **Final Results**: Confidence score and source attribution
228
252
 
229
- ### 5. Check Statistics
253
+ ### 6. Check Statistics
230
254
 
231
255
  ```bash
232
256
  ragnar stats
@@ -290,81 +314,112 @@ Example `.ragnar.yml` file:
290
314
  ```yaml
291
315
  # Storage paths (all support ~ expansion)
292
316
  storage:
293
- database_path: "~/.cache/ragnar/database" # Vector database location
294
- models_dir: "~/.cache/ragnar/models" # Downloaded model files
295
- history_file: "~/.cache/ragnar/history" # Interactive mode history
317
+ database_path: "~/.cache/ragnar/database"
318
+ models_dir: "~/.cache/ragnar/models"
319
+ history_file: "~/.cache/ragnar/history"
296
320
 
297
321
  # Embedding configuration
298
322
  embeddings:
299
- model: jinaai/jina-embeddings-v2-base-en # Embedding model to use
300
- chunk_size: 512 # Tokens per chunk
301
- chunk_overlap: 50 # Token overlap between chunks
323
+ model: jinaai/jina-embeddings-v2-base-en
324
+ chunk_size: 512
325
+ chunk_overlap: 50
302
326
 
303
327
  # UMAP dimensionality reduction
304
328
  umap:
305
- reduced_dimensions: 64 # Target dimensions (2-100)
306
- n_neighbors: 15 # UMAP neighbors parameter
307
- min_dist: 0.1 # UMAP minimum distance
308
- model_filename: umap_model.bin # Saved model filename
329
+ reduced_dimensions: 64
330
+ n_neighbors: 15
331
+ min_dist: 0.1
332
+ model_filename: umap_model.bin
309
333
 
310
- # LLM configuration
334
+ # LLM profiles — switch between local and cloud models
311
335
  llm:
312
- default_model: TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF
313
- default_gguf_file: tinyllama-1.1b-chat-v1.0.q4_k_m.gguf
336
+ default_profile: red_candle
337
+ profiles:
338
+ red_candle:
339
+ provider: red_candle
340
+ model: MaziyarPanahi/Qwen3-4B-GGUF
341
+ opus:
342
+ provider: anthropic
343
+ model: claude-opus-4-6
344
+ api_key: sk-ant-... # or set ANTHROPIC_API_KEY env var
345
+ sonnet:
346
+ provider: anthropic
347
+ model: claude-sonnet-4-6
348
+ ollama:
349
+ provider: ollama
350
+ model: llama3.1:8b
314
351
 
315
352
  # Query processing
316
353
  query:
317
- top_k: 3 # Number of documents to retrieve
318
- enable_query_rewriting: true # Use LLM to improve queries
354
+ top_k: 3 # Number of documents to retrieve
355
+ enable_query_rewriting: true # Use LLM to improve queries
356
+ enable_reranking: true # Cross-encoder reranking (disable for small corpora)
357
+ reranker_model: BAAI/bge-reranker-base # Reranker model
319
358
 
320
359
  # Interactive mode
321
360
  interactive:
322
- prompt: 'ragnar> ' # Command prompt
323
- quiet_mode: true # Suppress verbose output
361
+ prompt: 'ragnar> '
362
+ quiet_mode: true
324
363
 
325
364
  # Output settings
326
365
  output:
327
- show_progress: true # Show progress bars during indexing
366
+ show_progress: true
328
367
  ```
329
368
 
330
- ### Viewing Configuration
369
+ ### LLM Profiles
370
+
371
+ Profiles let you switch between LLM providers without editing config. Use local models for development and cloud models for production quality:
331
372
 
332
- Check current configuration:
333
373
  ```bash
334
- # Show all configuration settings
335
- ragnar config
374
+ # Use a specific profile for a single command
375
+ ragnar --profile opus query "What is our security policy?"
336
376
 
337
- # Show LLM model information
338
- ragnar model
377
+ # In TUI mode, switch profiles mid-session
378
+ ragnar> /profile # List available profiles
379
+ ragnar> /profile opus # Switch to Opus
380
+ ragnar> /profile red_candle # Switch back to local
339
381
  ```
340
382
 
341
- In interactive mode:
383
+ Ragnar supports any [RubyLLM](https://rubyllm.com/) provider: `red_candle` (local), `anthropic`, `openai`, `ollama`, and more. API keys can be set per-profile in the config or via environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, etc.).
384
+
385
+ ### Viewing Configuration
386
+
342
387
  ```bash
343
- ragnar interactive
344
- ragnar> config # Show configuration
345
- ragnar> model # Show model details
388
+ ragnar config # Show all settings including active profile
389
+ ragnar model # Show LLM model information
390
+ ragnar profile # List all LLM profiles
391
+ ```
392
+
393
+ In interactive mode (launch with `ragnar`):
394
+ ```
395
+ ragnar> /config # Show configuration
396
+ ragnar> /profile # List profiles
346
397
  ```
347
398
 
348
399
  ### Environment Variables
349
400
 
350
401
  Configuration values can be overridden with environment variables:
351
402
  - `XDG_CACHE_HOME` - Override default cache directory (~/.cache)
403
+ - `ANTHROPIC_API_KEY` - Anthropic API key (used by anthropic profiles)
404
+ - `OPENAI_API_KEY` - OpenAI API key (used by openai profiles)
352
405
 
353
406
  ### Supported Models
354
407
 
408
+ **LLM Providers** (via RubyLLM):
409
+ - `red_candle` — Local GGUF models (default): `MaziyarPanahi/Qwen3-4B-GGUF`, `MaziyarPanahi/Qwen3-8B-GGUF`
410
+ - `anthropic` — Claude models: `claude-opus-4-6`, `claude-sonnet-4-6`
411
+ - `openai` — GPT models: `gpt-4o`, `gpt-4o-mini`
412
+ - `ollama` — Local Ollama models: `llama3.1:8b`, `mistral:7b`
413
+ - Any other [RubyLLM provider](https://rubyllm.com/providers/)
414
+
355
415
  **Embedding Models** (via red-candle):
356
416
  - `jinaai/jina-embeddings-v2-base-en` (default, 768 dimensions)
357
417
  - `BAAI/bge-base-en-v1.5`
358
418
  - `sentence-transformers/all-MiniLM-L6-v2`
359
419
 
360
- **LLM Models** (via red-candle, GGUF format):
361
- - `TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF` (default, fast)
362
- - `TheBloke/Qwen2.5-1.5B-Instruct-GGUF`
363
- - `TheBloke/phi-2-GGUF`
364
-
365
- **Reranker Models** (via red-candle):
366
- - `BAAI/bge-reranker-base`
367
- - `cross-encoder/ms-marco-MiniLM-L-6-v2`
420
+ **Reranker Models** (via red-candle, configurable):
421
+ - `BAAI/bge-reranker-base` (default, XLM-RoBERTa)
422
+ - `cross-encoder/ms-marco-MiniLM-L-12-v2` (smaller, BERT-based)
368
423
 
369
424
  ## Advanced Usage
370
425
 
@@ -586,5 +641,7 @@ This project integrates several excellent Ruby gems:
586
641
  - [red-candle](https://github.com/assaydepot/red-candle) - Ruby ML/LLM toolkit
587
642
  - [lancelot](https://github.com/scientist-labs/lancelot) - Lance database bindings
588
643
  - [clusterkit](https://github.com/scientist-labs/clusterkit) - UMAP and clustering implementation
644
+ - [thor-interactive](https://github.com/scientist-labs/thor-interactive) - Interactive TUI for Thor CLIs
645
+ - [ratatui_ruby](https://github.com/nicholasgasior/ratatui-ruby) - Ratatui terminal UI bindings
589
646
  - [parsekit](https://github.com/scientist-labs/parsekit) - Content extraction
590
647
  - [baran](https://github.com/moeki0/baran) - Text splitting utilities
data/lib/ragnar/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative "cli_visualization"
2
+ require_relative "cli_umap"
2
3
  require_relative "config"
3
4
  require "thor/interactive"
4
5
  require "stringio"
@@ -9,11 +10,16 @@ module Ragnar
9
10
  include CLIVisualization
10
11
  include Thor::Interactive::Command
11
12
 
13
+ default_command :interactive
14
+
15
+ class_option :profile, type: :string, aliases: "-p", desc: "LLM profile to use (e.g., red_candle, opus, sonnet)"
16
+
12
17
  # Configure interactive mode
13
18
  configure_interactive(
14
19
  prompt: Config.instance.interactive_prompt,
15
20
  allow_nested: false,
16
21
  history_file: Config.instance.history_file,
22
+ ui_mode: :tui,
17
23
  default_handler: proc do |input, thor_instance|
18
24
  puts "[DEBUG] Default handler called: #{input}" if ENV["DEBUG"]
19
25
 
@@ -37,6 +43,7 @@ module Ragnar
37
43
  class_variable_set(:@@cached_llm_manager, nil)
38
44
  class_variable_set(:@@cached_query_processor, nil)
39
45
  class_variable_set(:@@cached_db_path, nil)
46
+ class_variable_set(:@@verbose_mode, false)
40
47
 
41
48
  desc "index PATH", "Index text files from PATH (file or directory)"
42
49
  option :db_path, type: :string, desc: "Path to Lance database (default from config)"
@@ -87,83 +94,8 @@ module Ragnar
87
94
  end
88
95
  end
89
96
 
90
- desc "train-umap", "Train UMAP model on existing embeddings"
91
- option :db_path, type: :string, desc: "Path to Lance database (default from config)"
92
- option :n_components, type: :numeric, default: 50, desc: "Number of dimensions for reduction"
93
- option :n_neighbors, type: :numeric, default: 15, desc: "Number of neighbors for UMAP"
94
- option :min_dist, type: :numeric, default: 0.1, desc: "Minimum distance for UMAP"
95
- option :model_path, type: :string, desc: "Path to save UMAP model"
96
- def train_umap
97
- say "Training UMAP model on embeddings...", :green
98
-
99
- config = Config.instance
100
- # Use model_path from options if provided, otherwise use config models_dir
101
- model_path = if options[:model_path]
102
- options[:model_path]
103
- else
104
- File.join(config.models_dir, "umap_model.bin")
105
- end
106
-
107
- processor = UmapProcessor.new(
108
- db_path: options[:db_path] || config.database_path,
109
- model_path: model_path
110
- )
111
-
112
- begin
113
- stats = processor.train(
114
- n_components: options[:n_components] || 50,
115
- n_neighbors: options[:n_neighbors] || 15,
116
- min_dist: options[:min_dist] || 0.1
117
- )
118
-
119
- say "\nUMAP training complete!", :green
120
- say "Embeddings processed: #{stats[:embeddings_count]}"
121
- say "Original dimensions: #{stats[:original_dims]}"
122
- say "Reduced dimensions: #{stats[:reduced_dims]}"
123
- say "Model saved to: #{processor.model_path}"
124
- rescue => e
125
- say "Error during UMAP training: #{e.message}", :red
126
- exit 1
127
- end
128
- end
129
-
130
- desc "apply-umap", "Apply trained UMAP model to reduce embedding dimensions"
131
- option :db_path, type: :string, desc: "Path to Lance database (default from config)"
132
- option :model_path, type: :string, desc: "Path to UMAP model"
133
- option :batch_size, type: :numeric, default: 100, desc: "Batch size for processing"
134
- def apply_umap
135
- config = Config.instance
136
- model_path = if options[:model_path]
137
- options[:model_path]
138
- else
139
- File.join(config.models_dir, "umap_model.bin")
140
- end
141
-
142
- unless File.exist?(model_path)
143
- say "Error: UMAP model not found at: #{model_path}", :red
144
- say "Please run 'train-umap' first to create a model.", :yellow
145
- exit 1
146
- end
147
-
148
- say "Applying UMAP model to embeddings...", :green
149
-
150
- processor = UmapProcessor.new(
151
- db_path: options[:db_path] || config.database_path,
152
- model_path: model_path
153
- )
154
-
155
- begin
156
- stats = processor.apply(batch_size: options[:batch_size] || 100)
157
-
158
- say "\nUMAP application complete!", :green
159
- say "Embeddings processed: #{stats[:processed]}"
160
- say "Already processed: #{stats[:skipped]}"
161
- say "Errors: #{stats[:errors]}" if stats[:errors] > 0
162
- rescue => e
163
- say "Error applying UMAP: #{e.message}", :red
164
- exit 1
165
- end
166
- end
97
+ desc "umap SUBCOMMAND ...ARGS", "UMAP dimensionality reduction commands"
98
+ subcommand "umap", Umap
167
99
 
168
100
  desc "topics", "Extract and display topics from indexed documents"
169
101
  option :db_path, type: :string, desc: "Path to Lance database (default from config)"
@@ -172,9 +104,10 @@ module Ragnar
172
104
  option :export, type: :string, desc: "Export topics to file (json or html)"
173
105
  option :verbose, type: :boolean, default: false, aliases: "-v", desc: "Show detailed processing"
174
106
  option :summarize, type: :boolean, default: false, aliases: "-s", desc: "Generate human-readable topic summaries using LLM"
175
- option :llm_model, type: :string, default: "TheBloke/TinyLlama-1.1B-Chat-v1.0-GGUF", desc: "LLM model for summarization"
176
- option :gguf_file, type: :string, default: "tinyllama-1.1b-chat-v1.0.q4_k_m.gguf", desc: "GGUF file name for LLM model"
107
+ option :llm_model, type: :string, default: "MaziyarPanahi/Qwen3-4B-GGUF", desc: "LLM model for summarization"
108
+ option :gguf_file, type: :string, default: "Qwen3-4B.Q4_K_M.gguf", desc: "GGUF file name for LLM model"
177
109
  def topics
110
+ apply_profile!
178
111
  require_relative 'topic_modeling'
179
112
 
180
113
  say "Extracting topics from indexed documents...", :green
@@ -241,16 +174,12 @@ module Ragnar
241
174
  if options[:summarize] && topics.any?
242
175
  say "Generating topic summaries with LLM...", :yellow
243
176
  begin
244
- require 'red-candle'
245
-
246
- # Initialize LLM for summarization once
247
- say "Loading model: #{options[:llm_model]}", :cyan if options[:verbose]
248
- llm = Candle::LLM.from_pretrained(options[:llm_model], gguf_file: options[:gguf_file])
177
+ chat = LLMManager.instance.default_chat
249
178
 
250
179
  # Add summaries to topics
251
180
  topics.each_with_index do |topic, i|
252
181
  say " Summarizing topic #{i+1}/#{topics.length}...", :yellow if options[:verbose]
253
- topic.instance_variable_set(:@summary, summarize_topic(topic, llm))
182
+ topic.instance_variable_set(:@summary, summarize_topic(topic, chat))
254
183
  end
255
184
 
256
185
  say "Topic summaries generated!", :green
@@ -316,8 +245,10 @@ module Ragnar
316
245
  option :db_path, type: :string, desc: "Path to Lance database (default from config)"
317
246
  option :top_k, type: :numeric, default: 3, desc: "Number of top documents to use"
318
247
  option :verbose, type: :boolean, default: false, aliases: "-v", desc: "Show detailed processing steps"
248
+ option :rerank, type: :boolean, default: nil, desc: "Enable cross-encoder reranking (default from config)"
319
249
  option :json, type: :boolean, default: false, desc: "Output as JSON"
320
250
  def query(question)
251
+ apply_profile!
321
252
  puts "Debug - Query called with: #{question.inspect}" if ENV['DEBUG']
322
253
  puts "Debug - Options: #{options.inspect}" if ENV['DEBUG']
323
254
 
@@ -327,10 +258,11 @@ module Ragnar
327
258
  begin
328
259
  config = Config.instance
329
260
  result = processor.query(
330
- question,
331
- top_k: options[:top_k] || config.query_top_k,
332
- verbose: options[:verbose] || false,
333
- enable_rewriting: config.enable_query_rewriting?
261
+ question,
262
+ top_k: options[:top_k] || config.query_top_k,
263
+ verbose: options[:verbose] || @@verbose_mode,
264
+ enable_rewriting: config.enable_query_rewriting?,
265
+ enable_reranking: options[:rerank].nil? ? config.enable_reranking? : options[:rerank]
334
266
  )
335
267
  puts "Debug - Result keys: #{result.keys}" if ENV['DEBUG']
336
268
 
@@ -443,8 +375,12 @@ module Ragnar
443
375
  say " Chunk overlap: #{config.chunk_overlap}"
444
376
 
445
377
  say "\nLLM:", :cyan
378
+ say " Active profile: #{config.llm_profile_name}", :green
379
+ say " Provider: #{config.llm_provider}"
446
380
  say " Model: #{config.llm_model}"
447
- say " GGUF file: #{config.llm_gguf_file}"
381
+ if config.available_profiles.size > 1
382
+ say " Available profiles: #{config.available_profiles.join(', ')}"
383
+ end
448
384
 
449
385
  say "\nUMAP:", :cyan
450
386
  say " Reduced dimensions: #{config.get('umap.reduced_dimensions', Ragnar::DEFAULT_REDUCED_DIMENSIONS)}"
@@ -454,30 +390,77 @@ module Ragnar
454
390
  say "\nQuery:", :cyan
455
391
  say " Top K: #{config.query_top_k}"
456
392
  say " Query rewriting: #{config.enable_query_rewriting?}"
393
+ say " Reranking: #{config.enable_reranking?}"
394
+ say " Reranker model: #{config.reranker_model}" if config.enable_reranking?
457
395
  end
458
396
 
459
397
  desc "model", "Show current LLM model information"
460
398
  def model
461
399
  config = Config.instance
462
-
400
+
463
401
  say "\nLLM Model Configuration:", :cyan
464
402
  say "-" * 40
465
-
466
- say "\nModel:", :green
467
- say " Repository: #{config.llm_model}"
468
- say " GGUF file: #{config.llm_gguf_file}"
469
-
470
- # Check if model files exist
471
- model_path = File.join(config.models_dir, config.llm_gguf_file)
472
- if File.exist?(model_path)
473
- size_mb = (File.size(model_path) / 1024.0 / 1024.0).round(2)
474
- say "\nModel file exists: #{model_path} (#{size_mb} MB)", :green
403
+
404
+ say "\nProfile: #{config.llm_profile_name}", :green
405
+ say " Provider: #{config.llm_provider}"
406
+ say " Model: #{config.llm_model}"
407
+
408
+ # Only show GGUF/local file info for local providers
409
+ if config.llm_provider == 'red_candle'
410
+ say "\nEmbedding Model: #{config.embedding_model}"
411
+
412
+ # Check if model files exist in HuggingFace cache
413
+ hf_cache = File.expand_path("~/.cache/huggingface/hub")
414
+ model_dir = config.llm_model.gsub("/", "--")
415
+ model_cache = File.join(hf_cache, "models--#{model_dir}")
416
+ if Dir.exist?(model_cache)
417
+ say "\nModel cached: #{model_cache}", :green
418
+ else
419
+ say "\nModel not yet downloaded (will download on first use)", :yellow
420
+ end
475
421
  else
476
- say "\nModel file not found: #{model_path}", :yellow
477
- say "Run 'ragnar query' to download automatically", :yellow
422
+ api_key = config.llm_api_key
423
+ env_key = case config.llm_provider
424
+ when 'anthropic' then ENV['ANTHROPIC_API_KEY']
425
+ when 'openai' then ENV['OPENAI_API_KEY']
426
+ end
427
+ has_key = api_key || env_key
428
+ say "\nAPI key: #{has_key ? 'configured' : 'not set'}", has_key ? :green : :red
478
429
  end
479
430
  end
480
431
 
432
+ desc "profile [NAME]", "Show or switch LLM profile"
433
+ def profile(name = nil)
434
+ config = Config.instance
435
+
436
+ if name
437
+ begin
438
+ config.set_active_profile(name)
439
+ LLMManager.instance.clear_cache
440
+ say "Switched to profile: #{name}", :green
441
+ say " Provider: #{config.llm_provider}"
442
+ say " Model: #{config.llm_model}"
443
+ rescue ArgumentError => e
444
+ say e.message, :red
445
+ end
446
+ else
447
+ say "\nLLM Profiles:", :cyan
448
+ say "-" * 40
449
+ config.llm_profiles.each do |pname, pconfig|
450
+ active = pname == config.llm_profile_name ? " (active)" : ""
451
+ say " #{pname}#{active}", active.empty? ? :white : :green
452
+ say " Provider: #{pconfig['provider']}"
453
+ say " Model: #{pconfig['model']}"
454
+ end
455
+ end
456
+ end
457
+
458
+ desc "verbose", "Toggle verbose mode on/off"
459
+ def verbose
460
+ @@verbose_mode = !@@verbose_mode
461
+ say "Verbose mode: #{@@verbose_mode ? 'on' : 'off'}", @@verbose_mode ? :green : :yellow
462
+ end
463
+
481
464
  desc "clear-cache", "Clear cached instances (useful in interactive mode)"
482
465
  def clear_cache_command
483
466
  clear_cache
@@ -665,6 +648,12 @@ module Ragnar
665
648
 
666
649
  private
667
650
 
651
+ def apply_profile!
652
+ return unless options[:profile]
653
+ Config.instance.set_active_profile(options[:profile])
654
+ LLMManager.instance.clear_cache
655
+ end
656
+
668
657
  # Cached instance helpers for interactive mode
669
658
  def get_cached_database(db_path = nil)
670
659
  # Use config default if no path provided
@@ -711,7 +700,7 @@ module Ragnar
711
700
  end
712
701
 
713
702
 
714
- def summarize_topic(topic, llm)
703
+ def summarize_topic(topic, chat)
715
704
  # Get representative documents for context
716
705
  sample_docs = topic.representative_docs(k: 3)
717
706
 
@@ -728,7 +717,7 @@ module Ragnar
728
717
  PROMPT
729
718
 
730
719
  begin
731
- summary = llm.generate(prompt).strip
720
+ summary = chat.ask(prompt).content.strip
732
721
  # Clean up common artifacts
733
722
  summary = summary.lines.first&.strip || "Related documents"
734
723
  summary = summary.gsub(/^(Summary:|Topic:|Documents:)/i, '').strip
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+
5
+ module Ragnar
6
+ class CLI < Thor
7
+ class Umap < Thor
8
+ desc "train", "Train UMAP model on existing embeddings"
9
+ option :db_path, type: :string, desc: "Path to Lance database (default from config)"
10
+ option :n_components, type: :numeric, default: 50, desc: "Number of dimensions for reduction"
11
+ option :n_neighbors, type: :numeric, default: 15, desc: "Number of neighbors for UMAP"
12
+ option :min_dist, type: :numeric, default: 0.1, desc: "Minimum distance for UMAP"
13
+ option :model_path, type: :string, desc: "Path to save UMAP model"
14
+ def train
15
+ say "Training UMAP model on embeddings...", :green
16
+
17
+ config = Config.instance
18
+ model_path = if options[:model_path]
19
+ options[:model_path]
20
+ else
21
+ File.join(config.models_dir, "umap_model.bin")
22
+ end
23
+
24
+ processor = UmapProcessor.new(
25
+ db_path: options[:db_path] || config.database_path,
26
+ model_path: model_path
27
+ )
28
+
29
+ begin
30
+ stats = processor.train(
31
+ n_components: options[:n_components] || 50,
32
+ n_neighbors: options[:n_neighbors] || 15,
33
+ min_dist: options[:min_dist] || 0.1
34
+ )
35
+
36
+ say "\nUMAP training complete!", :green
37
+ say "Embeddings processed: #{stats[:embeddings_count]}"
38
+ say "Original dimensions: #{stats[:original_dims]}"
39
+ say "Reduced dimensions: #{stats[:reduced_dims]}"
40
+ say "Model saved to: #{processor.model_path}"
41
+ rescue => e
42
+ say "Error during UMAP training: #{e.message}", :red
43
+ exit 1
44
+ end
45
+ end
46
+
47
+ desc "apply", "Apply trained UMAP model to reduce embedding dimensions"
48
+ option :db_path, type: :string, desc: "Path to Lance database (default from config)"
49
+ option :model_path, type: :string, desc: "Path to UMAP model"
50
+ option :batch_size, type: :numeric, default: 100, desc: "Batch size for processing"
51
+ def apply
52
+ config = Config.instance
53
+ model_path = if options[:model_path]
54
+ options[:model_path]
55
+ else
56
+ File.join(config.models_dir, "umap_model.bin")
57
+ end
58
+
59
+ unless File.exist?(model_path)
60
+ say "Error: UMAP model not found at: #{model_path}", :red
61
+ say "Please run 'ragnar umap train' first to create a model.", :yellow
62
+ exit 1
63
+ end
64
+
65
+ say "Applying UMAP model to embeddings...", :green
66
+
67
+ processor = UmapProcessor.new(
68
+ db_path: options[:db_path] || config.database_path,
69
+ model_path: model_path
70
+ )
71
+
72
+ begin
73
+ stats = processor.apply(batch_size: options[:batch_size] || 100)
74
+
75
+ say "\nUMAP application complete!", :green
76
+ say "Embeddings processed: #{stats[:processed]}"
77
+ say "Already processed: #{stats[:skipped]}"
78
+ say "Errors: #{stats[:errors]}" if stats[:errors] > 0
79
+ rescue => e
80
+ say "Error applying UMAP: #{e.message}", :red
81
+ exit 1
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end