ragdoll-rails 0.0.1
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 +501 -0
- data/Rakefile +40 -0
- data/app/models/ragdoll/document.rb +120 -0
- data/app/models/ragdoll/embedding.rb +31 -0
- data/app/models/ragdoll/search.rb +201 -0
- data/config/initializers/ragdoll.rb +6 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20250218123456_create_documents.rb +46 -0
- data/db/migrate/20250219123456_create_ragdoll_embeddings.rb +41 -0
- data/db/migrate/20250220123456_update_embeddings_vector_column.rb +41 -0
- data/db/migrate/20250223123457_add_metadata_and_foreign_key_to_ragdoll_tables.rb +37 -0
- data/db/migrate/20250225123456_add_summary_to_ragdoll_documents.rb +17 -0
- data/db/migrate/20250226123456_add_usage_tracking_to_ragdoll_embeddings.rb +28 -0
- data/lib/generators/ragdoll/init/init_generator.rb +26 -0
- data/lib/generators/ragdoll/init/templates/INSTALL +56 -0
- data/lib/generators/ragdoll/init/templates/ragdoll_config.rb +96 -0
- data/lib/ragdoll/rails/configuration.rb +33 -0
- data/lib/ragdoll/rails/engine.rb +32 -0
- data/lib/ragdoll/rails/version.rb +9 -0
- data/lib/ragdoll/rails.rb +29 -0
- data/lib/ragdoll-rails.rb +11 -0
- data/lib/tasks/rspec.rake +19 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2ac0f85d4a125cc90ca0a5187ae327eeb1c88de8380c3a5929ec79c52d7db54a
|
4
|
+
data.tar.gz: 7eb055ea9faecbdbdb5a51cce6256ed5350de814447de02d20d09e6a7dd3a86f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e52d30d90f0641b02f7b32fab536f3465666c38ab06d9008ae90a67c0fec9e8037614049adb63479607dad320d15e6a51d32e469eca7a80b4439029c5aa031a
|
7
|
+
data.tar.gz: a5397b0ae44775dd35bddf5a4551bfc456f6c856958695ee9fae0da0117b4ccfef89c612ca0e9182eb310866b61c0b42d5e7302b2160186c6144297a075239d6
|
data/README.md
ADDED
@@ -0,0 +1,501 @@
|
|
1
|
+
<div align="center" style="background-color: yellow; color: black; padding: 20px; margin: 20px 0; border: 2px solid black; font-size: 48px; font-weight: bold;">
|
2
|
+
⚠️ CAUTION ⚠️<br />
|
3
|
+
Software Under Development by a Crazy Man
|
4
|
+
</div>
|
5
|
+
<br />
|
6
|
+
<div align="center">
|
7
|
+
<table>
|
8
|
+
<tr>
|
9
|
+
<td width="50%">
|
10
|
+
<a href="https://research.ibm.com/blog/retrieval-augmented-generation-RAG" target="_blank">
|
11
|
+
<img src="ragdoll-rails.png" alt="Ragdoll Riding the Rails" width="800">
|
12
|
+
</a>
|
13
|
+
</td>
|
14
|
+
<td width="50%" valign="top">
|
15
|
+
<p>Multi-modal RAG (Retrieval-Augmented Generation) is an architecture that integrates multiple data types (such as text, images, and audio) to enhance AI response generation. It combines retrieval-based methods, which fetch relevant information from a knowledge base, with generative large language models (LLMs) that create coherent and contextually appropriate outputs. This approach allows for more comprehensive and engaging user interactions, such as chatbots that respond with both text and images or educational tools that incorporate visual aids into learning materials. By leveraging various modalities, multi-modal RAG systems improve context understanding and user experience.</p>
|
16
|
+
</td>
|
17
|
+
</tr>
|
18
|
+
</table>
|
19
|
+
</div>
|
20
|
+
|
21
|
+
# Ragdoll::Rails
|
22
|
+
|
23
|
+
**Ragdoll** is a powerful Rails engine that adds **Multi-modal Retrieval Augmented Generation (RAG)** capabilities to any Rails application. It provides semantic search, document ingestion, and context-enhanced AI prompts using vector embeddings and PostgreSQL with pgvector. With support for multiple LLM providers through [ruby_llm](https://rubyllm.com), you can use OpenAI, Anthropic, Google, Azure, Ollama, and more.
|
24
|
+
|
25
|
+
See Also:
|
26
|
+
|
27
|
+
- [Ragdoll::Core](https://github.com/MadBomber/ragdoll)
|
28
|
+
- [Ragdoll::CLI](https://github.com/MadBomber/ragdoll-cli)
|
29
|
+
- [Ragdoll::Rails](https://github.com/MadBomber/ragdoll-rails) this gem
|
30
|
+
- [Demo Rails App](https://github.com/madbomber/ragdoll_demo_app)
|
31
|
+
|
32
|
+
## ✨ Features
|
33
|
+
|
34
|
+
- 🔍 **Semantic Search** - Vector similarity search with flexible embedding models and pgvector
|
35
|
+
- 🤖 **Multi-Provider Support** - OpenAI, Anthropic, Google, Azure, Ollama, HuggingFace via ruby_llm
|
36
|
+
- 📄 **Multi-format Support** - PDF, DOCX, text, HTML, JSON, XML, CSV document parsing
|
37
|
+
- 🧠 **Context Enhancement** - Automatically enhance AI prompts with relevant context
|
38
|
+
- ⚡ **Background Processing** - Asynchronous document processing with Sidekiq
|
39
|
+
- 🎛️ **Simple API** - Clean, intuitive interface for Rails integration
|
40
|
+
- 📊 **Analytics** - Search analytics and document management insights
|
41
|
+
- 🔧 **Configurable** - Flexible chunking, embedding, and search parameters
|
42
|
+
- 🔄 **Flexible Vectors** - Variable-length embeddings for different models
|
43
|
+
|
44
|
+
## 🚀 Quick Start
|
45
|
+
|
46
|
+
### Installation
|
47
|
+
|
48
|
+
Add Ragdoll to your Rails application:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# Gemfile
|
52
|
+
gem 'ragdoll-rails'
|
53
|
+
gem 'ragdoll-cli' # Optional CLI tool for managing documents and embeddings
|
54
|
+
```
|
55
|
+
|
56
|
+
```bash
|
57
|
+
bundle install
|
58
|
+
```
|
59
|
+
|
60
|
+
### Database Setup
|
61
|
+
|
62
|
+
Ragdoll requires PostgreSQL with the pgvector extension:
|
63
|
+
|
64
|
+
```bash
|
65
|
+
# Run migrations
|
66
|
+
rails ragdoll:install:migrations
|
67
|
+
rails db:migrate
|
68
|
+
```
|
69
|
+
|
70
|
+
### Configuration
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
# config/initializers/ragdoll.rb
|
74
|
+
Ragdoll.configure do |config|
|
75
|
+
# LLM Provider Configuration
|
76
|
+
config.llm_provider = :openai # or :anthropic, :google, :azure, :ollama, :huggingface
|
77
|
+
config.embedding_provider = :openai # optional, defaults to llm_provider
|
78
|
+
|
79
|
+
# Provider-specific API keys
|
80
|
+
config.llm_config = {
|
81
|
+
openai: { api_key: ENV['OPENAI_API_KEY'] },
|
82
|
+
anthropic: { api_key: ENV['ANTHROPIC_API_KEY'] },
|
83
|
+
google: { api_key: ENV['GOOGLE_API_KEY'], project_id: ENV['GOOGLE_PROJECT_ID'] }
|
84
|
+
}
|
85
|
+
|
86
|
+
# Embedding and processing settings
|
87
|
+
config.embedding_model = 'text-embedding-3-small'
|
88
|
+
config.chunk_size = 1000
|
89
|
+
config.search_similarity_threshold = 0.7
|
90
|
+
config.max_embedding_dimensions = 3072 # supports variable-length vectors
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
### Basic Usage
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
# Add documents
|
98
|
+
Ragdoll.add_document('/path/to/manual.pdf')
|
99
|
+
Ragdoll.add_directory('/path/to/directory_of_documents', recursive: true)
|
100
|
+
|
101
|
+
# Enhance AI prompts with context
|
102
|
+
enhanced = Ragdoll.enhance_prompt(
|
103
|
+
'How do I configure the database?',
|
104
|
+
context_limit: 5
|
105
|
+
)
|
106
|
+
|
107
|
+
# Use enhanced prompt with RubyLLM
|
108
|
+
ai_response = RubyLLM.ask(enhanced[:enhanced_prompt])
|
109
|
+
```
|
110
|
+
|
111
|
+
## 📖 API Reference
|
112
|
+
|
113
|
+
### Context Enhancement for AI
|
114
|
+
|
115
|
+
The primary method for RAG applications - automatically finds relevant context and enhances prompts:
|
116
|
+
|
117
|
+
```ruby
|
118
|
+
enhanced = Ragdoll.enhance_prompt(
|
119
|
+
"How do I deploy to production?",
|
120
|
+
context_limit: 3,
|
121
|
+
threshold: 0.8
|
122
|
+
)
|
123
|
+
|
124
|
+
# Returns:
|
125
|
+
{
|
126
|
+
enhanced_prompt: "...", # Prompt with context injected
|
127
|
+
original_prompt: "...", # Original user prompt
|
128
|
+
context_sources: [...], # Source documents
|
129
|
+
context_count: 2 # Number of context chunks
|
130
|
+
}
|
131
|
+
```
|
132
|
+
|
133
|
+
### Semantic Search
|
134
|
+
|
135
|
+
```ruby
|
136
|
+
# Search for similar content
|
137
|
+
results = Ragdoll.search(
|
138
|
+
"database configuration",
|
139
|
+
limit: 10,
|
140
|
+
threshold: 0.6,
|
141
|
+
filters: { document_type: 'pdf' }
|
142
|
+
)
|
143
|
+
|
144
|
+
# Get raw context without prompt enhancement
|
145
|
+
context = Ragdoll.client.get_context(
|
146
|
+
"API authentication",
|
147
|
+
limit: 5
|
148
|
+
)
|
149
|
+
```
|
150
|
+
|
151
|
+
### Document Management
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
# Add documents
|
155
|
+
Ragdoll.add_file('/docs/manual.pdf')
|
156
|
+
Ragdoll.add_text('Content', title: 'Guide')
|
157
|
+
Ragdoll.add_directory('/knowledge-base', recursive: true)
|
158
|
+
|
159
|
+
# Manage documents
|
160
|
+
client = Ragdoll::Client.new
|
161
|
+
client.update_document(123, title: 'New Title')
|
162
|
+
client.delete_document(123)
|
163
|
+
client.list_documents(limit: 50)
|
164
|
+
|
165
|
+
# Bulk operations
|
166
|
+
client.reprocess_failed
|
167
|
+
client.add_directory('/docs', recursive: true)
|
168
|
+
```
|
169
|
+
|
170
|
+
## 🏗️ Rails Integration Examples
|
171
|
+
|
172
|
+
### Chat Controller
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
class ChatController < ApplicationController
|
176
|
+
def ask
|
177
|
+
enhanced = Ragdoll.enhance_prompt(
|
178
|
+
params[:question],
|
179
|
+
context_limit: 5
|
180
|
+
)
|
181
|
+
|
182
|
+
ai_response = OpenAI.complete(enhanced[:enhanced_prompt])
|
183
|
+
|
184
|
+
render json: {
|
185
|
+
answer: ai_response,
|
186
|
+
sources: enhanced[:context_sources],
|
187
|
+
context_used: enhanced[:context_count] > 0
|
188
|
+
}
|
189
|
+
end
|
190
|
+
end
|
191
|
+
```
|
192
|
+
|
193
|
+
### Support Bot Service
|
194
|
+
|
195
|
+
```ruby
|
196
|
+
class SupportBot
|
197
|
+
def initialize
|
198
|
+
@ragdoll = Ragdoll::Client.new
|
199
|
+
end
|
200
|
+
|
201
|
+
def answer_question(question, category: nil)
|
202
|
+
filters = { document_type: 'pdf' } if category == 'manual'
|
203
|
+
|
204
|
+
context = @ragdoll.get_context(
|
205
|
+
question,
|
206
|
+
limit: 3,
|
207
|
+
threshold: 0.8,
|
208
|
+
filters: filters
|
209
|
+
)
|
210
|
+
|
211
|
+
if context[:total_chunks] > 0
|
212
|
+
prompt = build_prompt(question, context[:combined_context])
|
213
|
+
ai_response = call_ai_service(prompt)
|
214
|
+
|
215
|
+
{
|
216
|
+
answer: ai_response,
|
217
|
+
confidence: :high,
|
218
|
+
sources: context[:context_chunks]
|
219
|
+
}
|
220
|
+
else
|
221
|
+
fallback_response(question)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
```
|
226
|
+
|
227
|
+
### Background Processing
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
class ProcessDocumentsJob < ApplicationJob
|
231
|
+
def perform(file_paths)
|
232
|
+
ragdoll = Ragdoll::Client.new
|
233
|
+
|
234
|
+
file_paths.each do |path|
|
235
|
+
ragdoll.add_file(path, process_immediately: true)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
```
|
240
|
+
|
241
|
+
## 🛠️ Command Line Tools
|
242
|
+
|
243
|
+
### Thor Commands
|
244
|
+
|
245
|
+
```bash
|
246
|
+
# Document management
|
247
|
+
thor ragdoll:document:add /path/to/file.pdf --process_now
|
248
|
+
thor ragdoll:document:list --status completed --limit 20
|
249
|
+
thor ragdoll:document:show 123
|
250
|
+
thor ragdoll:document:delete 123 --confirm
|
251
|
+
|
252
|
+
# Import operations
|
253
|
+
thor ragdoll:import:import /docs --recursive --jobs 4
|
254
|
+
```
|
255
|
+
|
256
|
+
### Rake Tasks
|
257
|
+
|
258
|
+
```bash
|
259
|
+
# Add documents
|
260
|
+
rake ragdoll:document:add[/path/to/file.pdf] PROCESS_NOW=true
|
261
|
+
TITLE="Manual" rake ragdoll:document:add[content.txt]
|
262
|
+
|
263
|
+
# Bulk operations
|
264
|
+
rake ragdoll:document:bulk:reprocess_failed
|
265
|
+
rake ragdoll:document:bulk:cleanup_orphaned
|
266
|
+
STATUS=failed rake ragdoll:document:bulk:delete_by_status[failed]
|
267
|
+
|
268
|
+
# List and search
|
269
|
+
LIMIT=50 rake ragdoll:document:list
|
270
|
+
rake ragdoll:document:show[123]
|
271
|
+
```
|
272
|
+
|
273
|
+
## 📋 Supported Document Types
|
274
|
+
|
275
|
+
| Format | Extension | Features |
|
276
|
+
|--------|-----------|----------|
|
277
|
+
| PDF | `.pdf` | Text extraction, metadata, page info |
|
278
|
+
| DOCX | `.docx` | Paragraphs, tables, document properties |
|
279
|
+
| Text | `.txt`, `.md` | Plain text, markdown |
|
280
|
+
| HTML | `.html`, `.htm` | Tag stripping, content extraction |
|
281
|
+
| Data | `.json`, `.xml`, `.csv` | Structured data parsing |
|
282
|
+
|
283
|
+
## ⚙️ Configuration Options
|
284
|
+
|
285
|
+
### Multi-Provider Configuration
|
286
|
+
|
287
|
+
```ruby
|
288
|
+
Ragdoll.configure do |config|
|
289
|
+
# Primary LLM provider for chat/completion
|
290
|
+
config.llm_provider = :anthropic
|
291
|
+
|
292
|
+
# Separate provider for embeddings (optional)
|
293
|
+
config.embedding_provider = :openai
|
294
|
+
|
295
|
+
# Provider-specific configurations
|
296
|
+
config.llm_config = {
|
297
|
+
openai: {
|
298
|
+
api_key: ENV['OPENAI_API_KEY'],
|
299
|
+
organization: ENV['OPENAI_ORGANIZATION'], # optional
|
300
|
+
project: ENV['OPENAI_PROJECT'] # optional
|
301
|
+
},
|
302
|
+
anthropic: {
|
303
|
+
api_key: ENV['ANTHROPIC_API_KEY']
|
304
|
+
},
|
305
|
+
google: {
|
306
|
+
api_key: ENV['GOOGLE_API_KEY'],
|
307
|
+
project_id: ENV['GOOGLE_PROJECT_ID']
|
308
|
+
},
|
309
|
+
azure: {
|
310
|
+
api_key: ENV['AZURE_API_KEY'],
|
311
|
+
endpoint: ENV['AZURE_ENDPOINT'],
|
312
|
+
api_version: ENV['AZURE_API_VERSION']
|
313
|
+
},
|
314
|
+
ollama: {
|
315
|
+
endpoint: ENV['OLLAMA_ENDPOINT'] || 'http://localhost:11434'
|
316
|
+
},
|
317
|
+
huggingface: {
|
318
|
+
api_key: ENV['HUGGINGFACE_API_KEY']
|
319
|
+
}
|
320
|
+
}
|
321
|
+
end
|
322
|
+
```
|
323
|
+
|
324
|
+
### Model and Processing Settings
|
325
|
+
|
326
|
+
```ruby
|
327
|
+
Ragdoll.configure do |config|
|
328
|
+
# Embedding configuration
|
329
|
+
config.embedding_model = 'text-embedding-3-small'
|
330
|
+
config.max_embedding_dimensions = 3072 # supports variable dimensions
|
331
|
+
config.default_model = 'gpt-4' # for chat/completion
|
332
|
+
|
333
|
+
# Text chunking settings
|
334
|
+
config.chunk_size = 1000
|
335
|
+
config.chunk_overlap = 200
|
336
|
+
|
337
|
+
# Search and similarity settings
|
338
|
+
config.search_similarity_threshold = 0.7
|
339
|
+
config.max_search_results = 10
|
340
|
+
|
341
|
+
# Analytics and performance
|
342
|
+
config.enable_search_analytics = true
|
343
|
+
config.cache_embeddings = true
|
344
|
+
|
345
|
+
# Custom prompt template
|
346
|
+
config.prompt_template = <<~TEMPLATE
|
347
|
+
Context: {{context}}
|
348
|
+
Question: {{prompt}}
|
349
|
+
Answer:
|
350
|
+
TEMPLATE
|
351
|
+
end
|
352
|
+
```
|
353
|
+
|
354
|
+
### Provider Examples
|
355
|
+
|
356
|
+
```ruby
|
357
|
+
# OpenAI Configuration
|
358
|
+
Ragdoll.configure do |config|
|
359
|
+
config.llm_provider = :openai
|
360
|
+
config.llm_config = {
|
361
|
+
openai: { api_key: ENV['OPENAI_API_KEY'] }
|
362
|
+
}
|
363
|
+
config.embedding_model = 'text-embedding-3-small'
|
364
|
+
end
|
365
|
+
|
366
|
+
# Anthropic + OpenAI Embeddings
|
367
|
+
Ragdoll.configure do |config|
|
368
|
+
config.llm_provider = :anthropic
|
369
|
+
config.embedding_provider = :openai
|
370
|
+
config.llm_config = {
|
371
|
+
anthropic: { api_key: ENV['ANTHROPIC_API_KEY'] },
|
372
|
+
openai: { api_key: ENV['OPENAI_API_KEY'] }
|
373
|
+
}
|
374
|
+
end
|
375
|
+
|
376
|
+
# Local Ollama Setup
|
377
|
+
Ragdoll.configure do |config|
|
378
|
+
config.llm_provider = :ollama
|
379
|
+
config.llm_config = {
|
380
|
+
ollama: { endpoint: 'http://localhost:11434' }
|
381
|
+
}
|
382
|
+
config.embedding_model = 'nomic-embed-text'
|
383
|
+
end
|
384
|
+
```
|
385
|
+
|
386
|
+
## 🏗️ Database Schema
|
387
|
+
|
388
|
+
Ragdoll creates three main tables:
|
389
|
+
|
390
|
+
- **`ragdoll_documents`** - Document metadata and content
|
391
|
+
- **`ragdoll_embeddings`** - Vector embeddings with pgvector (variable dimensions)
|
392
|
+
- **`ragdoll_searches`** - Search analytics and performance tracking
|
393
|
+
|
394
|
+
### Key Features
|
395
|
+
|
396
|
+
- **Variable Vector Dimensions**: Supports different embedding models with different dimensions
|
397
|
+
- **Model Tracking**: Tracks which embedding model was used for each vector
|
398
|
+
- **Performance Indexes**: Optimized for similarity search and filtering
|
399
|
+
- **Search Analytics**: Comprehensive search performance and usage tracking
|
400
|
+
|
401
|
+
## 📊 Analytics and Monitoring
|
402
|
+
|
403
|
+
```ruby
|
404
|
+
# Document statistics
|
405
|
+
stats = Ragdoll.client.stats
|
406
|
+
# => { total_documents: 150, total_embeddings: 1250, ... }
|
407
|
+
|
408
|
+
# Search analytics
|
409
|
+
analytics = Ragdoll::Search.analytics(days: 30)
|
410
|
+
# => {
|
411
|
+
# total_searches: 500,
|
412
|
+
# unique_queries: 350,
|
413
|
+
# average_results: 8.5,
|
414
|
+
# average_search_time: 0.15,
|
415
|
+
# success_rate: 85.2,
|
416
|
+
# most_common_queries: [...],
|
417
|
+
# search_types: { semantic: 450, keyword: 50 },
|
418
|
+
# models_used: { "text-embedding-3-small": 400, "text-embedding-3-large": 100 },
|
419
|
+
# performance_stats: { fastest: 0.05, slowest: 2.3, median: 0.12 }
|
420
|
+
# }
|
421
|
+
|
422
|
+
# Performance monitoring
|
423
|
+
slow_searches = Ragdoll::Search.slow_searches(2.0) # > 2 seconds
|
424
|
+
failed_searches = Ragdoll::Search.failed
|
425
|
+
|
426
|
+
# Health check
|
427
|
+
healthy = Ragdoll.client.healthy?
|
428
|
+
# => true/false
|
429
|
+
```
|
430
|
+
|
431
|
+
## 🧪 Testing
|
432
|
+
|
433
|
+
```ruby
|
434
|
+
# spec/support/ragdoll_helpers.rb
|
435
|
+
module RagdollHelpers
|
436
|
+
def setup_test_documents
|
437
|
+
@ragdoll = Ragdoll::Client.new
|
438
|
+
@doc = @ragdoll.add_text(
|
439
|
+
"Rails is a web framework",
|
440
|
+
title: "Rails Guide",
|
441
|
+
process_immediately: true
|
442
|
+
)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# In your specs
|
447
|
+
RSpec.describe ChatController do
|
448
|
+
include RagdollHelpers
|
449
|
+
|
450
|
+
before { setup_test_documents }
|
451
|
+
|
452
|
+
it "enhances prompts with context" do
|
453
|
+
enhanced = Ragdoll.enhance_prompt("What is Rails?")
|
454
|
+
expect(enhanced[:context_count]).to be > 0
|
455
|
+
end
|
456
|
+
end
|
457
|
+
```
|
458
|
+
|
459
|
+
## 📦 Dependencies
|
460
|
+
|
461
|
+
- **Rails** 8.0+
|
462
|
+
- **PostgreSQL** with pgvector extension
|
463
|
+
- **Sidekiq** for background processing
|
464
|
+
- **ruby_llm** for multi-provider LLM support
|
465
|
+
- **LLM Provider APIs** (OpenAI, Anthropic, Google, etc.)
|
466
|
+
|
467
|
+
### Supported LLM Providers
|
468
|
+
|
469
|
+
| Provider | Chat/Completion | Embeddings | Notes |
|
470
|
+
|----------|----------------|------------|---------|
|
471
|
+
| OpenAI | ✅ | ✅ | GPT models, text-embedding-3-* |
|
472
|
+
| Anthropic | ✅ | ❌ | Claude models |
|
473
|
+
| Google | ✅ | ✅ | Gemini models |
|
474
|
+
| Azure OpenAI | ✅ | ✅ | Azure-hosted OpenAI |
|
475
|
+
| Ollama | ✅ | ✅ | Local models |
|
476
|
+
| HuggingFace | ✅ | ✅ | Various open-source models |
|
477
|
+
|
478
|
+
## 🤝 Contributing
|
479
|
+
|
480
|
+
1. Fork the repository
|
481
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
482
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
483
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
484
|
+
5. Open a Pull Request
|
485
|
+
|
486
|
+
## 📄 License
|
487
|
+
|
488
|
+
This gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
489
|
+
|
490
|
+
## 🆘 Support
|
491
|
+
|
492
|
+
- 📖 [Documentation](https://github.com/MadBomber/ragdoll)
|
493
|
+
- 🐛 [Issues](https://github.com/MadBomber/ragdoll/issues)
|
494
|
+
- 💬 [Discussions](https://github.com/MadBomber/ragdoll/discussions)
|
495
|
+
|
496
|
+
---
|
497
|
+
|
498
|
+
<div align="center">
|
499
|
+
<p>Made with ❤️ for the Rails community</p>
|
500
|
+
<p>⭐ Star this repo if you find it useful!</p>
|
501
|
+
</div>
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# This file defines the Rake tasks for the Ragdoll gem, including tasks for testing.
|
2
|
+
|
3
|
+
# frozen_string_literal: true
|
4
|
+
|
5
|
+
require "bundler/gem_tasks"
|
6
|
+
require "minitest/test_task"
|
7
|
+
|
8
|
+
Minitest::TestTask.create
|
9
|
+
|
10
|
+
require "bundler/gem_tasks"
|
11
|
+
require "rake"
|
12
|
+
|
13
|
+
require "bundler/gem_tasks"
|
14
|
+
require "rake"
|
15
|
+
require "active_record/railtie"
|
16
|
+
|
17
|
+
require "bundler/gem_tasks"
|
18
|
+
require "minitest/test_task"
|
19
|
+
|
20
|
+
Minitest::TestTask.create
|
21
|
+
|
22
|
+
# Load any additional tasks from the lib/tasks directory
|
23
|
+
Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
|
24
|
+
|
25
|
+
task default: :test
|
26
|
+
|
27
|
+
# Load any additional tasks from the lib/tasks directory
|
28
|
+
Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
|
29
|
+
|
30
|
+
task default: :test
|
31
|
+
|
32
|
+
# Load any additional tasks from the lib/tasks directory
|
33
|
+
Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
|
34
|
+
|
35
|
+
task default: :test
|
36
|
+
|
37
|
+
# Load any additional tasks from the lib/tasks directory
|
38
|
+
Dir.glob('lib/tasks/**/*.rake').each { |r| load r }
|
39
|
+
|
40
|
+
task default: :test
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# This file defines the Rails-specific Document model for the Ragdoll Rails engine.
|
2
|
+
# This model is separate from Ragdoll::Core::Models::Document to avoid conflicts.
|
3
|
+
|
4
|
+
# frozen_string_literal: true
|
5
|
+
|
6
|
+
module Ragdoll
|
7
|
+
module Rails
|
8
|
+
class Document < ApplicationRecord
|
9
|
+
self.table_name = 'ragdoll_documents'
|
10
|
+
|
11
|
+
# Associations
|
12
|
+
has_many :ragdoll_embeddings, class_name: 'Ragdoll::Rails::Embedding', foreign_key: 'document_id', dependent: :destroy
|
13
|
+
has_one_attached :file if respond_to?(:has_one_attached)
|
14
|
+
|
15
|
+
# Validations
|
16
|
+
validates :location, presence: true, uniqueness: true
|
17
|
+
validates :status, inclusion: { in: %w[pending processing completed failed] }
|
18
|
+
validates :chunk_size, numericality: { greater_than: 0 }, allow_nil: true
|
19
|
+
validates :chunk_overlap, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
20
|
+
|
21
|
+
# Scopes
|
22
|
+
scope :completed, -> { where(status: 'completed') }
|
23
|
+
scope :failed, -> { where(status: 'failed') }
|
24
|
+
scope :processing, -> { where(status: 'processing') }
|
25
|
+
scope :pending, -> { where(status: 'pending') }
|
26
|
+
scope :by_type, ->(type) { where(document_type: type) }
|
27
|
+
scope :with_summaries, -> { where.not(summary: nil) }
|
28
|
+
scope :needs_summary, -> { where(summary: nil).completed }
|
29
|
+
|
30
|
+
# Search configuration
|
31
|
+
searchkick text_middle: [:title, :summary, :content, :metadata_name, :metadata_summary] if defined?(Searchkick)
|
32
|
+
|
33
|
+
def search_data
|
34
|
+
return {} unless defined?(Searchkick)
|
35
|
+
|
36
|
+
{
|
37
|
+
title: title,
|
38
|
+
summary: summary,
|
39
|
+
content: content,
|
40
|
+
metadata_name: metadata&.dig('name'),
|
41
|
+
metadata_summary: metadata&.dig('summary'),
|
42
|
+
document_type: document_type,
|
43
|
+
status: status
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
# Summary-related methods
|
48
|
+
def has_summary?
|
49
|
+
summary.present?
|
50
|
+
end
|
51
|
+
|
52
|
+
def summary_stale?
|
53
|
+
return false unless has_summary?
|
54
|
+
return true unless summary_generated_at
|
55
|
+
|
56
|
+
# Consider summary stale if document was updated after summary generation
|
57
|
+
updated_at > summary_generated_at
|
58
|
+
end
|
59
|
+
|
60
|
+
def needs_summary?
|
61
|
+
return false unless content.present?
|
62
|
+
# Business logic should be handled by ragdoll gem
|
63
|
+
# TODO: Delegate to Ragdoll.needs_summary?(content, summary, summary_generated_at)
|
64
|
+
|
65
|
+
!has_summary? || summary_stale?
|
66
|
+
end
|
67
|
+
|
68
|
+
def summary_word_count
|
69
|
+
return 0 unless summary.present?
|
70
|
+
summary.split.length
|
71
|
+
end
|
72
|
+
|
73
|
+
def regenerate_summary!
|
74
|
+
# Business logic for summary generation should be handled by the ragdoll gem
|
75
|
+
# This is a placeholder that delegates to the core ragdoll functionality
|
76
|
+
return false unless content.present?
|
77
|
+
|
78
|
+
# TODO: Delegate to Ragdoll gem's summarization functionality
|
79
|
+
# summarization_result = Ragdoll.generate_summary(content, options)
|
80
|
+
# Update the model with the result
|
81
|
+
|
82
|
+
Rails.logger.warn "Summary regeneration not implemented - should delegate to ragdoll gem"
|
83
|
+
false
|
84
|
+
end
|
85
|
+
|
86
|
+
# Processing status helpers
|
87
|
+
def completed?
|
88
|
+
status == 'completed'
|
89
|
+
end
|
90
|
+
|
91
|
+
def failed?
|
92
|
+
status == 'failed'
|
93
|
+
end
|
94
|
+
|
95
|
+
def processing?
|
96
|
+
status == 'processing'
|
97
|
+
end
|
98
|
+
|
99
|
+
def pending?
|
100
|
+
status == 'pending'
|
101
|
+
end
|
102
|
+
|
103
|
+
# Content helpers
|
104
|
+
def word_count
|
105
|
+
return 0 unless content.present?
|
106
|
+
content.split.length
|
107
|
+
end
|
108
|
+
|
109
|
+
def character_count
|
110
|
+
return 0 unless content.present?
|
111
|
+
content.length
|
112
|
+
end
|
113
|
+
|
114
|
+
def processing_duration
|
115
|
+
return nil unless processing_started_at && processing_finished_at
|
116
|
+
processing_finished_at - processing_started_at
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# This file defines the Rails-specific Embedding model for the Ragdoll Rails engine.
|
2
|
+
# This model is separate from Ragdoll::Core::Models::Embedding to avoid conflicts.
|
3
|
+
|
4
|
+
# frozen_string_literal: true
|
5
|
+
|
6
|
+
module Ragdoll
|
7
|
+
module Rails
|
8
|
+
class Embedding < ApplicationRecord
|
9
|
+
searchkick text_middle: [:metadata_content, :metadata_propositions] if defined?(Searchkick)
|
10
|
+
|
11
|
+
belongs_to :document, class_name: 'Ragdoll::Rails::Document'
|
12
|
+
|
13
|
+
# Override dangerous attribute to allow access to model_name column
|
14
|
+
def self.dangerous_attribute_method?(name)
|
15
|
+
name.to_s == 'model_name' ? false : super
|
16
|
+
end
|
17
|
+
|
18
|
+
def search_data
|
19
|
+
return {} unless defined?(Searchkick)
|
20
|
+
|
21
|
+
{
|
22
|
+
metadata_content: metadata['content'],
|
23
|
+
metadata_propositions: metadata['propositions']
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Assuming the vector column is named 'vector'
|
28
|
+
neighbor :vector, method: :euclidean if respond_to?(:neighbor)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|