diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ca6a79..4580ac5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## [0.6.15] - 2026-05-07 + +### Fixed +- Knowledge ingest now sends Apollo chunk provenance through `context:` while retaining metadata compatibility, so source file, heading, section path, chunk index, and token count persist with document chunks. +- Batch embedding now falls back to per-chunk `Legion::LLM.embed` when `embed_batch` is unavailable. +- Retired corpus files now emit explicit observation entries tagged `retired` instead of using an Apollo-unknown `document_retired` content type. +- Retrieval and synthesis failures are logged through helper-based exception handling, and synthesis returns `nil` instead of presenting error strings as answers. +- Monitor-only installs now enable the maintenance actor without requiring `corpus_path`. +- Quality reports count Apollo query access logs with the `query` action recorded by lex-apollo. + ## [0.6.14] - 2026-05-06 ### Changed diff --git a/lib/legion/extensions/knowledge/actors/maintenance_runner.rb b/lib/legion/extensions/knowledge/actors/maintenance_runner.rb index 4c598e2..a6a9f80 100644 --- a/lib/legion/extensions/knowledge/actors/maintenance_runner.rb +++ b/lib/legion/extensions/knowledge/actors/maintenance_runner.rb @@ -21,16 +21,20 @@ def time end def enabled? # rubocop:disable Legion/Extension/ActorEnabledSideEffects - return false unless corpus_path && !corpus_path.empty? + return true if corpus_path && !corpus_path.empty? - true + Runners::Monitor.resolve_monitors.any? rescue StandardError => e handle_exception(e, level: :warn, operation: 'knowledge.maintenance_runner.enabled') false end def args - { path: corpus_path } + path = corpus_path + return { path: path } if path && !path.empty? + + monitors = Runners::Monitor.resolve_monitors + monitors.any? ? { path: monitors.first[:path] } : { path: nil } end private diff --git a/lib/legion/extensions/knowledge/runners/ingest.rb b/lib/legion/extensions/knowledge/runners/ingest.rb index c4777d8..9c98d68 100644 --- a/lib/legion/extensions/knowledge/runners/ingest.rb +++ b/lib/legion/extensions/knowledge/runners/ingest.rb @@ -240,7 +240,7 @@ def build_exists_map(chunks) private_class_method :build_exists_map def llm_embed_available? - defined?(Legion::LLM) && Legion::LLM.respond_to?(:embed_batch) + defined?(Legion::LLM) && (Legion::LLM.respond_to?(:embed_batch) || Legion::LLM.respond_to?(:embed)) end private_class_method :llm_embed_available? @@ -250,9 +250,19 @@ def paired_without_embed(chunks, exists_map) private_class_method :paired_without_embed def build_embed_map(needs_embed) - results = Legion::LLM.embed_batch(needs_embed.map { |c| c[:content] }) # rubocop:disable Legion/HelperMigration/DirectLlm - results.each_with_object({}) do |r, h| - h[needs_embed[r[:index]][:content_hash]] = r[:vector] unless r[:error] + if Legion::LLM.respond_to?(:embed_batch) + results = Legion::LLM.embed_batch(needs_embed.map { |c| c[:content] }) # rubocop:disable Legion/HelperMigration/DirectLlm + results.each_with_object({}) do |r, h| + h[needs_embed[r[:index]][:content_hash]] = r[:vector] unless r[:error] + end + else + needs_embed.each_with_object({}) do |chunk, h| + result = Legion::LLM.embed(chunk[:content]) # rubocop:disable Legion/HelperMigration/DirectLlm + vector = result.is_a?(Hash) ? result[:vector] : result + h[chunk[:content_hash]] = vector if vector.is_a?(Array) && vector.any? + rescue StandardError => e + log.warn(e.message) + end end rescue StandardError => e handle_exception(e, level: :warn, operation: 'knowledge.ingest.build_embed_map') @@ -328,10 +338,10 @@ def retire_file(file_path:) return unless Legion::Apollo.respond_to?(:ingest) && Legion::Apollo.started? Legion::Apollo.ingest( # rubocop:disable Legion/HelperMigration/DirectKnowledge - content: file_path, - content_type: 'document_retired', + content: "Retired document: #{file_path}", + content_type: 'observation', tags: [file_path, 'retired', 'document_chunk'].uniq, - metadata: { source_file: file_path, retired: true } + context: { source_file: file_path, retired: true } ) rescue StandardError => e handle_exception(e, level: :warn, operation: 'knowledge.ingest.retire_file', file_path: file_path) diff --git a/lib/legion/extensions/knowledge/runners/maintenance.rb b/lib/legion/extensions/knowledge/runners/maintenance.rb index 4c8a154..abfbcea 100644 --- a/lib/legion/extensions/knowledge/runners/maintenance.rb +++ b/lib/legion/extensions/knowledge/runners/maintenance.rb @@ -296,7 +296,7 @@ def quality_summary def query_count return 0 unless Helpers::ApolloModels.access_log_available? - Helpers::ApolloModels.access_log.where(action: 'knowledge_query').count + Helpers::ApolloModels.access_log.where(action: 'query').count rescue StandardError => e handle_exception(e, level: :warn, operation: 'knowledge.maintenance.query_count') 0 diff --git a/lib/legion/extensions/knowledge/runners/query.rb b/lib/legion/extensions/knowledge/runners/query.rb index 3df5626..f248a9e 100644 --- a/lib/legion/extensions/knowledge/runners/query.rb +++ b/lib/legion/extensions/knowledge/runners/query.rb @@ -181,11 +181,14 @@ def synthesize_answer(question, chunks) "Context:\n#{context_text}\n\nQuestion: #{question}\n\nAnswer:" end - result = llm_chat(message: prompt, caller: { extension: 'lex-knowledge' }) - result.is_a?(Hash) ? result[:content] : result + result = Legion::LLM.chat( # rubocop:disable Legion/HelperMigration/DirectLlm + message: prompt, + caller: { extension: 'lex-knowledge' } + ) + result.is_a?(Hash) ? result[:content] : result.content rescue StandardError => e handle_exception(e, level: :warn, operation: 'knowledge.query.synthesize_answer') - "Error generating answer: #{e.message}" + nil end private_class_method :synthesize_answer diff --git a/lib/legion/extensions/knowledge/version.rb b/lib/legion/extensions/knowledge/version.rb index 6c55127..e38c2de 100644 --- a/lib/legion/extensions/knowledge/version.rb +++ b/lib/legion/extensions/knowledge/version.rb @@ -3,7 +3,7 @@ module Legion module Extensions module Knowledge - VERSION = '0.6.14' + VERSION = '0.6.15' end end end