smart_rag 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.env.example +252 -0
- data/.rspec +2 -0
- data/AGENTS.md +33 -0
- data/API_DOCUMENTATION.md +828 -0
- data/CHANGELOG.md +11 -1
- data/ER-diagram.mmd +144 -0
- data/Gemfile +50 -0
- data/Gemfile.lock +381 -0
- data/Hybrid_Reranking.md +171 -0
- data/README.en.md +420 -28
- data/README.md +534 -63
- data/Rakefile +268 -0
- data/SETUP_GUIDE.md +650 -0
- data/SmartChunking.md +180 -0
- data/USAGE_EXAMPLES.md +1002 -0
- data/config/llm_config.yml +4 -2
- data/config/smart_rag.yml +45 -1
- data/config.ru +15 -0
- data/db/migrations/006_create_text_search_configs.rb +3 -2
- data/db/migrations/008_create_embeddings.rb +5 -4
- data/db/migrations/012_add_metadata_to_source_sections.rb +11 -0
- data/db/migrations/013_create_media_jobs.rb +25 -0
- data/db/migrations/014_add_media_job_operations_indexes.rb +11 -0
- data/db/migrations/015_add_media_leases_and_objects.rb +80 -0
- data/db/migrations/016_add_document_principals_and_staging_references.rb +38 -0
- data/db/migrations/017_add_media_job_request_fingerprint.rb +48 -0
- data/db/seeds/text_search_configs.sql +3 -3
- data/design.md +1057 -0
- data/docs/API_DOCUMENTATION.md +838 -0
- data/docs/DOCUMENTATION_INDEX.en.md +60 -0
- data/docs/DOCUMENTATION_INDEX.md +65 -0
- data/docs/FIX_SUMMARY.md +256 -0
- data/docs/FIX_SUMMARY_COMPLETE.md +273 -0
- data/docs/Hybrid_Reranking.md +171 -0
- data/docs/MIGRATION_GUIDE.md +151 -0
- data/docs/PERFORMANCE_GUIDE.md +58 -0
- data/docs/SETUP_GUIDE.md +659 -0
- data/docs/SmartChunking.md +180 -0
- data/docs/USAGE_EXAMPLES.md +1008 -0
- data/docs/design.md +1057 -0
- data/docs/evidence_pack.md +211 -0
- data/docs/requirements.md +376 -0
- data/docs/retrieval_plan.md +251 -0
- data/docs/smartrag_improvement_plan.md +201 -0
- data/docs/smartrag_refactor.md +216 -0
- data/docs/todo.md +931 -0
- data/examples/common.rb +1 -1
- data/exe/smart-rag-db +163 -0
- data/exe/smart-rag-media-worker +34 -0
- data/lib/smart_rag/config.rb +12 -0
- data/lib/smart_rag/core/document_processor.rb +80 -16
- data/lib/smart_rag/core/local_content_store.rb +51 -0
- data/lib/smart_rag/core/media_extractors.rb +140 -0
- data/lib/smart_rag/core/media_job_queue.rb +353 -0
- data/lib/smart_rag/core/media_metadata_extractor.rb +188 -0
- data/lib/smart_rag/core/media_object_registry.rb +79 -0
- data/lib/smart_rag/core/media_processor.rb +228 -0
- data/lib/smart_rag/core/media_safety_policy.rb +61 -0
- data/lib/smart_rag/core/s3_content_store.rb +78 -0
- data/lib/smart_rag/core/transcript_normalizer.rb +44 -0
- data/lib/smart_rag/core/video_semantic_extractor.rb +130 -0
- data/lib/smart_rag/http_access_policy.rb +86 -0
- data/lib/smart_rag/http_app.rb +188 -0
- data/lib/smart_rag/models/embedding.rb +1 -1
- data/lib/smart_rag/models/research_topic.rb +1 -1
- data/lib/smart_rag/models/research_topic_section.rb +5 -0
- data/lib/smart_rag/models/research_topic_tag.rb +5 -0
- data/lib/smart_rag/models/search_log.rb +1 -1
- data/lib/smart_rag/models/section_fts.rb +5 -0
- data/lib/smart_rag/models/section_tag.rb +5 -0
- data/lib/smart_rag/models/source_document.rb +1 -1
- data/lib/smart_rag/models/source_section.rb +1 -1
- data/lib/smart_rag/models/tag.rb +1 -1
- data/lib/smart_rag/models/text_search_config.rb +5 -0
- data/lib/smart_rag/retrieve.rb +72 -1
- data/lib/smart_rag/services/embedding_service.rb +1 -1
- data/lib/smart_rag/services/fulltext_search_service.rb +11 -13
- data/lib/smart_rag/services/hybrid_search_service.rb +15 -11
- data/lib/smart_rag/services/summarization_service.rb +1 -1
- data/lib/smart_rag/services/tag_service.rb +1 -1
- data/lib/smart_rag/version.rb +1 -1
- data/lib/smart_rag.rb +264 -30
- data/patch_language.rb +27 -0
- data/requirements.md +376 -0
- data/source_documents_export.json +11072 -0
- data/todo.md +931 -0
- data/workers/analyze_content.rb +6 -2
- data/workers/get_embedding.rb +1 -1
- metadata +151 -38
data/lib/smart_rag.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "sequel"
|
|
|
7
7
|
require "logger"
|
|
8
8
|
require "digest"
|
|
9
9
|
require "json"
|
|
10
|
+
require "uri"
|
|
10
11
|
|
|
11
12
|
module SmartRAG
|
|
12
13
|
class Error < StandardError; end
|
|
@@ -64,16 +65,105 @@ module SmartRAG
|
|
|
64
65
|
|
|
65
66
|
# Add document to knowledge base
|
|
66
67
|
def add_document(document_path, options = {})
|
|
67
|
-
|
|
68
|
+
document_metadata = symbolize_hash(options[:metadata] || {}).merge(media_type: 'document', schema_version: 1)
|
|
69
|
+
result = @document_processor.create_document(document_path, options.merge(metadata: document_metadata))
|
|
68
70
|
{
|
|
69
71
|
document_id: result[:document].id,
|
|
72
|
+
media_type: 'document',
|
|
70
73
|
section_count: result[:sections].length,
|
|
71
74
|
status: "success",
|
|
75
|
+
metadata: normalize_document_metadata(result[:document].metadata),
|
|
76
|
+
warnings: []
|
|
72
77
|
}
|
|
73
78
|
end
|
|
74
79
|
|
|
80
|
+
def add_media(source, options = {})
|
|
81
|
+
require_relative 'smart_rag/core/media_processor'
|
|
82
|
+
require_relative 'smart_rag/core/media_metadata_extractor'
|
|
83
|
+
requested_type = options[:media_type]
|
|
84
|
+
requested_type = nil if requested_type.to_s == 'auto'
|
|
85
|
+
detection_source = source.to_s.match?(%r{\Ahttps?://}) ? URI.parse(source.to_s).path : source
|
|
86
|
+
detected_type = requested_type || ::SmartRAG::Core::MediaMetadataExtractor.new.detect_media_type(detection_source)
|
|
87
|
+
return add_document(source, options) if detected_type.to_s == 'document'
|
|
88
|
+
|
|
89
|
+
@media_processor ||= build_media_processor
|
|
90
|
+
@media_processor.create(source, options)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_image(source, options = {})
|
|
94
|
+
add_media(source, options.merge(media_type: 'image'))
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_audio(source, options = {})
|
|
98
|
+
add_media(source, options.merge(media_type: 'audio'))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def add_video(source, options = {})
|
|
102
|
+
add_media(source, options.merge(media_type: 'video'))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def enqueue_media(source, options = {})
|
|
106
|
+
job_options = options.dup
|
|
107
|
+
operation = job_options.delete(:operation) || :add_media
|
|
108
|
+
max_attempts = job_options.delete(:max_attempts) || @config.dig(:media, :async, :max_attempts) || 3
|
|
109
|
+
idempotency_key = job_options.delete(:idempotency_key)
|
|
110
|
+
principal = job_options.delete(:principal) || 'system'
|
|
111
|
+
staging_media_object_id = job_options.delete(:staging_media_object_id)
|
|
112
|
+
queued = media_job_queue.enqueue(operation: operation, source: source, options: job_options,
|
|
113
|
+
max_attempts: max_attempts, idempotency_key: idempotency_key,
|
|
114
|
+
principal: principal, staging_media_object_id: staging_media_object_id)
|
|
115
|
+
{ job_id: queued[:id], status: queued[:status], media_type: job_options[:media_type],
|
|
116
|
+
deduplicated: queued[:deduplicated] == true, warnings: [] }
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def stage_media_upload(source)
|
|
120
|
+
store = media_content_store
|
|
121
|
+
return nil unless store
|
|
122
|
+
stored = store.put(source)
|
|
123
|
+
object_id = media_object_registry.register(stored, byte_size: File.size(source))
|
|
124
|
+
stored.merge(media_object_id: object_id)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def media_job(job_id, principal: nil)
|
|
128
|
+
media_job_queue.job(job_id, principal: principal)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def media_jobs(status: nil, limit: 20, offset: 0, principal: nil)
|
|
132
|
+
media_job_queue.list(status: status, limit: limit, offset: offset, principal: principal)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def cancel_media_job(job_id, principal: nil)
|
|
136
|
+
media_job_queue.cancel(job_id, principal: principal)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def retry_media_job(job_id, principal: nil)
|
|
140
|
+
media_job_queue.retry_job(job_id, principal: principal)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def media_job_statistics(principal: nil)
|
|
144
|
+
media_job_queue.statistics(stale_after_seconds: media_async_config.fetch(:stale_after_seconds, 900),
|
|
145
|
+
principal: principal)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def recover_media_jobs
|
|
149
|
+
media_job_queue.recover_stale(timeout_seconds: media_async_config.fetch(:stale_after_seconds, 900))
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def prune_media_jobs
|
|
153
|
+
media_job_queue.prune(retention_seconds: media_async_config.fetch(:retention_seconds, 604_800))
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def garbage_collect_media_objects(limit: 100)
|
|
157
|
+
return { removed_count: 0, object_ids: [], disabled: true } unless media_object_registry
|
|
158
|
+
media_object_registry.garbage_collect(limit: limit)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def run_media_jobs(limit: 1)
|
|
162
|
+
media_job_queue.run(limit: limit)
|
|
163
|
+
end
|
|
164
|
+
|
|
75
165
|
# Remove document from knowledge base
|
|
76
|
-
def remove_document(document_id)
|
|
166
|
+
def remove_document(document_id, principal: nil)
|
|
77
167
|
return { success: false, deleted_sections: 0, deleted_embeddings: 0 } unless document_id.to_s =~ /\A-?\d+\Z/
|
|
78
168
|
|
|
79
169
|
doc_id_i = document_id.to_i
|
|
@@ -81,20 +171,25 @@ module SmartRAG
|
|
|
81
171
|
result = nil
|
|
82
172
|
|
|
83
173
|
@delete_mutex.synchronize do
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
174
|
+
::SmartRAG.db.transaction do
|
|
175
|
+
document_scope = ::SmartRAG::Models::SourceDocument.where(id: doc_id_i)
|
|
176
|
+
document_scope = document_scope.where(principal: principal.to_s) if principal
|
|
177
|
+
doc = document_scope.first
|
|
178
|
+
if doc.nil?
|
|
179
|
+
result = { success: false, deleted_sections: 0, deleted_embeddings: 0 }
|
|
180
|
+
else
|
|
181
|
+
media_object_registry&.detach_document(doc_id_i)
|
|
182
|
+
section_ids = ::SmartRAG::Models::SourceSection.where(document_id: doc_id_i).select_map(:id)
|
|
183
|
+
deleted_embeddings = section_ids.any? ? ::SmartRAG::Models::Embedding.where(source_id: section_ids).delete : 0
|
|
184
|
+
deleted_sections = ::SmartRAG::Models::SourceSection.where(document_id: doc_id_i).delete
|
|
185
|
+
deleted = ::SmartRAG::Models::SourceDocument.where(id: doc_id_i).delete
|
|
186
|
+
|
|
187
|
+
result = {
|
|
188
|
+
success: deleted > 0,
|
|
189
|
+
deleted_sections: deleted_sections,
|
|
190
|
+
deleted_embeddings: deleted_embeddings,
|
|
191
|
+
}
|
|
192
|
+
end
|
|
98
193
|
end
|
|
99
194
|
end
|
|
100
195
|
|
|
@@ -105,10 +200,12 @@ module SmartRAG
|
|
|
105
200
|
end
|
|
106
201
|
|
|
107
202
|
# Get document information
|
|
108
|
-
def get_document(document_id)
|
|
203
|
+
def get_document(document_id, principal: nil)
|
|
109
204
|
return nil unless document_id.to_s =~ /\A-?\d+\Z/
|
|
110
205
|
|
|
111
|
-
|
|
206
|
+
dataset = ::SmartRAG::Models::SourceDocument.where(id: document_id.to_i)
|
|
207
|
+
dataset = dataset.where(principal: principal.to_s) if principal
|
|
208
|
+
document = dataset.first
|
|
112
209
|
return nil unless document
|
|
113
210
|
|
|
114
211
|
{
|
|
@@ -148,6 +245,7 @@ module SmartRAG
|
|
|
148
245
|
per_page = [[per_page, 1].max, 100].min
|
|
149
246
|
|
|
150
247
|
dataset = ::SmartRAG::Models::SourceDocument.dataset
|
|
248
|
+
dataset = dataset.where(principal: options[:principal].to_s) if options[:principal]
|
|
151
249
|
|
|
152
250
|
if options[:search] && !options[:search].empty?
|
|
153
251
|
search_term = "%#{options[:search]}%"
|
|
@@ -246,17 +344,21 @@ module SmartRAG
|
|
|
246
344
|
end
|
|
247
345
|
|
|
248
346
|
search_type = (options[:search_type] || "hybrid").to_s
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
347
|
+
options = scope_search_options(options)
|
|
348
|
+
return empty_search_response(normalized_query, search_type) if options.delete(:principal_scope_empty)
|
|
349
|
+
|
|
350
|
+
response = case search_type
|
|
351
|
+
when "hybrid"
|
|
352
|
+
hybrid_search(normalized_query, options.merge(search_type: :hybrid))
|
|
353
|
+
when "vector"
|
|
354
|
+
vector_search(normalized_query, options.merge(search_type: :vector))
|
|
355
|
+
when "fulltext"
|
|
356
|
+
fulltext_search(normalized_query, options.merge(search_type: :fulltext))
|
|
357
|
+
else
|
|
358
|
+
raise ArgumentError, "Invalid search_type: #{search_type}. Must be 'hybrid', 'vector', or 'fulltext'"
|
|
359
|
+
end
|
|
360
|
+
filter_search_response(response, options[:document_ids]) if options[:principal]
|
|
361
|
+
response
|
|
260
362
|
end
|
|
261
363
|
|
|
262
364
|
# Structured retrieval interface for SmartBrain integration.
|
|
@@ -726,7 +828,8 @@ module SmartRAG
|
|
|
726
828
|
end
|
|
727
829
|
|
|
728
830
|
# Get system statistics
|
|
729
|
-
def statistics
|
|
831
|
+
def statistics(principal: nil)
|
|
832
|
+
return scoped_statistics(principal) if principal
|
|
730
833
|
{
|
|
731
834
|
document_count: ::SmartRAG::Models::SourceDocument.count,
|
|
732
835
|
section_count: ::SmartRAG::Models::SourceSection.count,
|
|
@@ -748,6 +851,135 @@ module SmartRAG
|
|
|
748
851
|
|
|
749
852
|
private
|
|
750
853
|
|
|
854
|
+
def scope_search_options(options)
|
|
855
|
+
return options unless options[:principal]
|
|
856
|
+
allowed_ids = ::SmartRAG::Models::SourceDocument.where(principal: options[:principal].to_s).select_map(:id)
|
|
857
|
+
requested_ids = Array(options[:document_ids]).map(&:to_i)
|
|
858
|
+
allowed_ids &= requested_ids unless requested_ids.empty?
|
|
859
|
+
options.merge(document_ids: allowed_ids, principal_scope_empty: allowed_ids.empty?)
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
def empty_search_response(query, search_type)
|
|
863
|
+
{ query: query, search_type: search_type.to_sym, results: [], total_results: 0,
|
|
864
|
+
metadata: { total_count: 0 } }
|
|
865
|
+
end
|
|
866
|
+
|
|
867
|
+
def filter_search_response(response, allowed_document_ids)
|
|
868
|
+
return response unless response.is_a?(Hash)
|
|
869
|
+
allowed = Array(allowed_document_ids).map(&:to_i)
|
|
870
|
+
key = response.key?(:results) ? :results : 'results'
|
|
871
|
+
results = Array(response[key]).select do |result|
|
|
872
|
+
section = result.is_a?(Hash) ? (result[:section] || result['section'] || result) : result
|
|
873
|
+
document_id = if result.is_a?(Hash)
|
|
874
|
+
result[:document_id] || result['document_id']
|
|
875
|
+
end
|
|
876
|
+
document_id ||= if section.is_a?(Hash)
|
|
877
|
+
section[:document_id] || section['document_id']
|
|
878
|
+
elsif section.respond_to?(:document_id)
|
|
879
|
+
section.document_id
|
|
880
|
+
end
|
|
881
|
+
document_id && allowed.include?(document_id.to_i)
|
|
882
|
+
end
|
|
883
|
+
response[key] = results
|
|
884
|
+
response[:total_results] = results.length if response.key?(:total_results)
|
|
885
|
+
response['total_results'] = results.length if response.key?('total_results')
|
|
886
|
+
if response[:metadata].is_a?(Hash)
|
|
887
|
+
response[:metadata][:total_count] = results.length
|
|
888
|
+
elsif response['metadata'].is_a?(Hash)
|
|
889
|
+
response['metadata']['total_count'] = results.length
|
|
890
|
+
end
|
|
891
|
+
response
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
def scoped_statistics(principal)
|
|
895
|
+
document_ids = ::SmartRAG::Models::SourceDocument.where(principal: principal.to_s).select_map(:id)
|
|
896
|
+
section_ids = ::SmartRAG::Models::SourceSection.where(document_id: document_ids).select_map(:id)
|
|
897
|
+
{
|
|
898
|
+
document_count: document_ids.length,
|
|
899
|
+
section_count: section_ids.length,
|
|
900
|
+
topic_count: ::SmartRAG.db[:research_topic_sections].where(section_id: section_ids).distinct.count(:research_topic_id),
|
|
901
|
+
tag_count: ::SmartRAG.db[:section_tags].where(section_id: section_ids).distinct.count(:tag_id),
|
|
902
|
+
embedding_count: ::SmartRAG::Models::Embedding.where(source_id: section_ids).count
|
|
903
|
+
}
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
def normalize_document_metadata(value)
|
|
907
|
+
parsed = value.is_a?(String) ? JSON.parse(value) : value
|
|
908
|
+
parsed.is_a?(Hash) ? parsed : {}
|
|
909
|
+
rescue JSON::ParserError
|
|
910
|
+
{}
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def symbolize_hash(value)
|
|
914
|
+
return {} unless value.is_a?(Hash)
|
|
915
|
+
|
|
916
|
+
value.each_with_object({}) { |(key, item), result| result[key.to_sym] = item }
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
def media_job_queue
|
|
920
|
+
require_relative 'smart_rag/core/media_job_queue'
|
|
921
|
+
@media_job_queue ||= ::SmartRAG::Core::MediaJobQueue.new(
|
|
922
|
+
db: ::SmartRAG.db,
|
|
923
|
+
heartbeat_interval_seconds: media_async_config.fetch(:heartbeat_interval_seconds, 30),
|
|
924
|
+
handler: lambda do |operation, source, options|
|
|
925
|
+
raise ArgumentError, "unsupported media job operation: #{operation}" unless %i[add_document add_media add_image add_audio add_video].include?(operation)
|
|
926
|
+
options.delete(:delete_source_after)
|
|
927
|
+
public_send(operation, source, options)
|
|
928
|
+
end
|
|
929
|
+
)
|
|
930
|
+
end
|
|
931
|
+
|
|
932
|
+
def media_async_config
|
|
933
|
+
@config.dig(:media, :async) || {}
|
|
934
|
+
end
|
|
935
|
+
|
|
936
|
+
def build_media_processor
|
|
937
|
+
require_relative 'smart_rag/core/media_processor'
|
|
938
|
+
require_relative 'smart_rag/core/media_extractors'
|
|
939
|
+
require_relative 'smart_rag/core/media_safety_policy'
|
|
940
|
+
require_relative 'smart_rag/core/local_content_store'
|
|
941
|
+
require_relative 'smart_rag/core/s3_content_store'
|
|
942
|
+
require_relative 'smart_rag/core/media_object_registry'
|
|
943
|
+
|
|
944
|
+
media_config = @config[:media] || {}
|
|
945
|
+
store_config = media_config[:content_store] || {}
|
|
946
|
+
store = media_content_store
|
|
947
|
+
::SmartRAG::Core::MediaProcessor.new(
|
|
948
|
+
document_processor: @document_processor,
|
|
949
|
+
safety_policy: ::SmartRAG::Core::MediaSafetyPolicy.new(media_config),
|
|
950
|
+
content_store: store,
|
|
951
|
+
object_registry: media_object_registry,
|
|
952
|
+
default_extractors: ::SmartRAG::Core::MediaExtractors::Factory.build(media_config),
|
|
953
|
+
logger: @logger
|
|
954
|
+
)
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
def media_content_store
|
|
958
|
+
return @media_content_store if defined?(@media_content_store)
|
|
959
|
+
require_relative 'smart_rag/core/local_content_store'
|
|
960
|
+
require_relative 'smart_rag/core/s3_content_store'
|
|
961
|
+
store_config = @config.dig(:media, :content_store) || {}
|
|
962
|
+
return @media_content_store = nil unless store_config.fetch(:enabled, false)
|
|
963
|
+
|
|
964
|
+
provider = store_config.fetch(:provider, 'local').to_s
|
|
965
|
+
@media_content_store = case provider
|
|
966
|
+
when 'local'
|
|
967
|
+
::SmartRAG::Core::LocalContentStore.new(root: store_config.fetch(:root))
|
|
968
|
+
when 's3'
|
|
969
|
+
::SmartRAG::Core::S3ContentStore.new(**store_config.reject { |key, _| %i[enabled provider root].include?(key) })
|
|
970
|
+
else
|
|
971
|
+
raise ArgumentError, "unsupported media content store provider: #{provider}"
|
|
972
|
+
end
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
def media_object_registry
|
|
976
|
+
return @media_object_registry if defined?(@media_object_registry)
|
|
977
|
+
store = media_content_store
|
|
978
|
+
return @media_object_registry = nil unless store
|
|
979
|
+
require_relative 'smart_rag/core/media_object_registry'
|
|
980
|
+
@media_object_registry = ::SmartRAG::Core::MediaObjectRegistry.new(db: ::SmartRAG.db, content_store: store)
|
|
981
|
+
end
|
|
982
|
+
|
|
751
983
|
def initialize_db_connection
|
|
752
984
|
db_config = @config[:database]
|
|
753
985
|
|
|
@@ -962,6 +1194,7 @@ module SmartRAG
|
|
|
962
1194
|
error: error_message
|
|
963
1195
|
}
|
|
964
1196
|
},
|
|
1197
|
+
total_results: 0,
|
|
965
1198
|
search_type: :fulltext
|
|
966
1199
|
}
|
|
967
1200
|
end
|
|
@@ -970,6 +1203,7 @@ module SmartRAG
|
|
|
970
1203
|
{
|
|
971
1204
|
query: query,
|
|
972
1205
|
results: [],
|
|
1206
|
+
total_results: 0,
|
|
973
1207
|
metadata: {
|
|
974
1208
|
total_count: 0,
|
|
975
1209
|
execution_time_ms: 0,
|
data/patch_language.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# Quick patch script to fix the language detection issue
|
|
3
|
+
|
|
4
|
+
file_path = '/root/smart_rag/lib/smart_rag/services/hybrid_search_service.rb'
|
|
5
|
+
content = File.read(file_path)
|
|
6
|
+
|
|
7
|
+
# Find and replace the problematic line
|
|
8
|
+
# We want to add debug logging before the language line
|
|
9
|
+
old_text = ' @logger.info "Hybrid search: \'#{query}\', language: #{language}, limit: #{limit}, alpha: #{alpha}"'
|
|
10
|
+
|
|
11
|
+
if content.include?(old_text)
|
|
12
|
+
puts 'Found the text to replace!'
|
|
13
|
+
|
|
14
|
+
# Check if debug log is already there
|
|
15
|
+
debug_text = ' @logger.debug "HybridSearchService: language=#{language} (type: #{language.class}), options[:language]=#{options[:language].inspect}"'
|
|
16
|
+
|
|
17
|
+
if !content.include?(debug_text)
|
|
18
|
+
puts 'Adding debug log...'
|
|
19
|
+
new_content = content.sub(old_text, debug_text + "\n" + old_text)
|
|
20
|
+
File.write(file_path, new_content)
|
|
21
|
+
puts 'Done! Debug log added.'
|
|
22
|
+
else
|
|
23
|
+
puts 'Debug log already exists.'
|
|
24
|
+
end
|
|
25
|
+
else
|
|
26
|
+
puts 'Could not find the target text!'
|
|
27
|
+
end
|