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 +4 -4
- data/README.md +99 -42
- data/lib/ragnar/cli.rb +94 -105
- data/lib/ragnar/cli_umap.rb +86 -0
- data/lib/ragnar/config.rb +101 -7
- data/lib/ragnar/embedder.rb +1 -1
- data/lib/ragnar/indexer.rb +4 -2
- data/lib/ragnar/llm_manager.rb +31 -30
- data/lib/ragnar/query_processor.rb +87 -52
- data/lib/ragnar/query_rewriter.rb +21 -18
- data/lib/ragnar/umap_processor.rb +54 -30
- data/lib/ragnar/umap_transform_service.rb +1 -1
- data/lib/ragnar/version.rb +1 -1
- data/lib/ragnar.rb +3 -1
- metadata +36 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 06c692710e5d5deb8cf5b8122050968deb459ad6feebd5bbdeabf63679a04d7c
|
|
4
|
+
data.tar.gz: dc8784c1b36aec7c473b9f93cc1929bdbd1d75ecec18d727c4e7892aeea6df30
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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.
|
|
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
|
|
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
|
|
189
|
+
ragnar umap apply
|
|
166
190
|
```
|
|
167
191
|
|
|
168
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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"
|
|
294
|
-
models_dir: "~/.cache/ragnar/models"
|
|
295
|
-
history_file: "~/.cache/ragnar/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
|
|
300
|
-
chunk_size: 512
|
|
301
|
-
chunk_overlap: 50
|
|
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
|
|
306
|
-
n_neighbors: 15
|
|
307
|
-
min_dist: 0.1
|
|
308
|
-
model_filename: umap_model.bin
|
|
329
|
+
reduced_dimensions: 64
|
|
330
|
+
n_neighbors: 15
|
|
331
|
+
min_dist: 0.1
|
|
332
|
+
model_filename: umap_model.bin
|
|
309
333
|
|
|
310
|
-
# LLM
|
|
334
|
+
# LLM profiles — switch between local and cloud models
|
|
311
335
|
llm:
|
|
312
|
-
|
|
313
|
-
|
|
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
|
|
318
|
-
enable_query_rewriting: true
|
|
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> '
|
|
323
|
-
quiet_mode: true
|
|
361
|
+
prompt: 'ragnar> '
|
|
362
|
+
quiet_mode: true
|
|
324
363
|
|
|
325
364
|
# Output settings
|
|
326
365
|
output:
|
|
327
|
-
show_progress: true
|
|
366
|
+
show_progress: true
|
|
328
367
|
```
|
|
329
368
|
|
|
330
|
-
###
|
|
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
|
-
#
|
|
335
|
-
ragnar
|
|
374
|
+
# Use a specific profile for a single command
|
|
375
|
+
ragnar --profile opus query "What is our security policy?"
|
|
336
376
|
|
|
337
|
-
#
|
|
338
|
-
ragnar
|
|
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
|
-
|
|
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
|
|
344
|
-
ragnar
|
|
345
|
-
ragnar
|
|
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
|
-
**
|
|
361
|
-
- `
|
|
362
|
-
- `
|
|
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 "
|
|
91
|
-
|
|
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: "
|
|
176
|
-
option :gguf_file, type: :string, default: "
|
|
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
|
-
|
|
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,
|
|
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] ||
|
|
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
|
-
|
|
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 "\
|
|
467
|
-
say "
|
|
468
|
-
say "
|
|
469
|
-
|
|
470
|
-
#
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
477
|
-
|
|
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,
|
|
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 =
|
|
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
|