legionio 1.5.15 → 1.5.18

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: d66368a89ce7ef86f5c62277fe403c840caf722f8e6b840000251775ce74e4a4
4
- data.tar.gz: 8a44567875935c668bf2884e7fac6e0831f0dad9f1860f5e2bde28eedb0b7df5
3
+ metadata.gz: fa969cacd00cbd7fcd3234d1d7b46e720da00a4193b4faa967d0f4e9b662b4e4
4
+ data.tar.gz: 2afe90f9456b0c5abf07f7180d562dd84ac7c901e81aea1b841288f3aae929d6
5
5
  SHA512:
6
- metadata.gz: 7e45f6aa1d7838ed5c5ddac267310ea8163f27f1e4c8efd190720da148add55ff9f50e958c8096691d6ce3a613064322f11eba64d8fc0eae79bea26fc1ee1a9c
7
- data.tar.gz: ce24287b1dd968ef9e33e053c358b7e182b92685c6696b0074fef7ab3d0447449452f9959a3086148a73b606fefc643444546ad34e596b8c8ea5039b4bf65c17
6
+ metadata.gz: bf23f83209770f3e9b7c66bd59ccae187f24c136b94059b5bc54a58e9e80188280a94f01ce557eab19eb1a903bcd9e1a9d69cf7a46077ab89297f934ca1b5244
7
+ data.tar.gz: cf041438573f2eb546929ef68e35c527b52af9f552e129fd79b6c94f75862361eeee9d63d4f1fc2b26fecbd70078cc97614c6e928c7fa45cb317b3de858c4eea
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Legion Changelog
2
2
 
3
+ ## [1.5.18] - 2026-03-25
4
+
5
+ ### Added
6
+ - `scope:` parameter on `Helpers::Knowledge` (`ingest_knowledge` and `query_knowledge`)
7
+ - Scope routing: `:local` -> `Apollo::Local`, `:global` -> `Apollo`, `:all` -> both with local-first dedup
8
+ - Default query scope configurable via `Settings[:apollo][:local][:default_query_scope]`
9
+ - `setup_apollo` now starts `Apollo::Local` when available
10
+
11
+ ## [1.5.17] - 2026-03-25
12
+
13
+ ### Added
14
+ - `Helpers::Knowledge` — universal `ingest_knowledge` and `query_knowledge` mixin for all extensions; included automatically in `Extensions::Core`
15
+ - Automatic file extraction via `Legion::Data::Extract` when a file path is passed to `ingest_knowledge`
16
+ - Graceful degradation when `Legion::Apollo` or `Legion::Data::Extract` are not available
17
+ - `setup_apollo` in `Service` boot sequence (between LLM and GAIA); wires `Legion::Apollo.start` with `LoadError`/`StandardError` rescue
18
+ - `:apollo` added to `Readiness::COMPONENTS` between `:llm` and `:gaia`
19
+ - `legion-apollo >= 0.2.1` dependency in gemspec
20
+ - `Helpers::LLM#llm_embed` in LegionIO now forwards all keyword arguments (`provider:`, `dimensions:`, etc.) via anonymous `**` forwarding
21
+
3
22
  ## [1.5.15] - 2026-03-25
4
23
 
5
24
  ### Removed
data/Gemfile CHANGED
@@ -11,6 +11,7 @@ gem 'legion-logging', path: '../legion-logging' if File.exist?(File.expand_path(
11
11
  gem 'legion-mcp', path: '../legion-mcp' if File.exist?(File.expand_path('../legion-mcp', __dir__))
12
12
  gem 'legion-settings', path: '../legion-settings' if File.exist?(File.expand_path('../legion-settings', __dir__))
13
13
 
14
+ gem 'legion-apollo', path: '../legion-apollo' if File.exist?(File.expand_path('../legion-apollo', __dir__))
14
15
  gem 'lex-agentic-memory', path: '../extensions-agentic/lex-agentic-memory' if File.exist?(File.expand_path('../extensions-agentic/lex-agentic-memory', __dir__))
15
16
  gem 'lex-llm-gateway', path: '../extensions-core/lex-llm-gateway' if File.exist?(File.expand_path('../extensions-core/lex-llm-gateway', __dir__))
16
17
  gem 'lex-microsoft_teams', path: '../extensions/lex-microsoft_teams' if File.exist?(File.expand_path('../extensions/lex-microsoft_teams', __dir__))
data/legionio.gemspec CHANGED
@@ -60,6 +60,7 @@ Gem::Specification.new do |spec|
60
60
  spec.add_dependency 'legion-settings', '>= 1.3.19'
61
61
  spec.add_dependency 'legion-transport', '>= 1.4.0'
62
62
 
63
+ spec.add_dependency 'legion-apollo', '>= 0.2.1'
63
64
  spec.add_dependency 'legion-gaia', '>= 0.9.24'
64
65
  spec.add_dependency 'legion-llm', '>= 0.5.8'
65
66
  spec.add_dependency 'legion-tty', '>= 0.4.35'
@@ -21,6 +21,18 @@ rescue LoadError => e
21
21
  Legion::Logging.debug "Extensions::Core: legion-llm helpers not available: #{e.message}" if defined?(Legion::Logging)
22
22
  end
23
23
 
24
+ begin
25
+ require_relative 'helpers/llm'
26
+ rescue LoadError => e
27
+ Legion::Logging.debug "Extensions::Core: local llm helper not available: #{e.message}" if defined?(Legion::Logging)
28
+ end
29
+
30
+ begin
31
+ require_relative 'helpers/knowledge'
32
+ rescue LoadError => e
33
+ Legion::Logging.debug "Extensions::Core: knowledge helper not available: #{e.message}" if defined?(Legion::Logging)
34
+ end
35
+
24
36
  require_relative 'actors/base'
25
37
  require_relative 'actors/every'
26
38
  require_relative 'actors/loop'
@@ -35,6 +47,7 @@ module Legion
35
47
  module Core
36
48
  include Legion::Extensions::Helpers::Transport
37
49
  include Legion::Extensions::Helpers::Lex
50
+ include Legion::Extensions::Helpers::Knowledge if defined?(Legion::Extensions::Helpers::Knowledge)
38
51
 
39
52
  include Legion::Extensions::Builder::Runners
40
53
  include Legion::Extensions::Builder::Helpers
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module Knowledge
7
+ def ingest_knowledge(content_or_path, type: :auto, tags: [], scope: :global, **opts)
8
+ target = resolve_ingest_target(scope)
9
+ return { success: false, error: :apollo_not_available } unless target
10
+
11
+ text, metadata = extract_if_needed(content_or_path, type: type)
12
+ return { success: false, error: :extraction_failed, detail: metadata } unless text
13
+
14
+ extraction_tags = metadata_to_tags(metadata) if metadata
15
+ all_tags = Array(tags) + Array(extraction_tags)
16
+
17
+ target.ingest(
18
+ content: text,
19
+ tags: all_tags,
20
+ source_channel: opts[:source_channel] || derive_lex_name,
21
+ **opts.except(:source_channel)
22
+ )
23
+ end
24
+
25
+ def query_knowledge(text:, limit: 5, scope: nil, **)
26
+ scope ||= default_query_scope
27
+
28
+ case scope.to_sym
29
+ when :local then query_local(text: text, limit: limit, **)
30
+ when :global then query_global(text: text, limit: limit, **)
31
+ else query_all(text: text, limit: limit, **)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def resolve_ingest_target(scope)
38
+ case scope.to_sym
39
+ when :local
40
+ local_available? ? Legion::Apollo::Local : nil
41
+ else
42
+ global_available? ? Legion::Apollo : nil
43
+ end
44
+ end
45
+
46
+ def query_local(text:, limit:, **)
47
+ unless local_available?
48
+ Legion::Logging.debug 'query_knowledge(:local) called but Apollo::Local is not available' if defined?(Legion::Logging)
49
+ return { success: false, error: :apollo_not_available }
50
+ end
51
+
52
+ Legion::Apollo::Local.query(text: text, limit: limit, **)
53
+ end
54
+
55
+ def query_global(text:, limit:, **)
56
+ unless global_available?
57
+ Legion::Logging.debug 'query_knowledge(:global) called but Apollo is not available' if defined?(Legion::Logging)
58
+ return { success: false, error: :apollo_not_available }
59
+ end
60
+
61
+ Legion::Apollo.query(text: text, limit: limit, **)
62
+ end
63
+
64
+ def query_all(text:, limit:, **)
65
+ local_results = local_available? ? Array((Legion::Apollo::Local.query(text: text, limit: limit, **) || {})[:results]) : []
66
+ global_results = global_available? ? Array((Legion::Apollo.query(text: text, limit: limit, **) || {})[:results]) : []
67
+
68
+ return { success: false, error: :apollo_not_available } if local_results.empty? && global_results.empty? && !local_available? && !global_available?
69
+
70
+ merged = merge_results(local_results, global_results)
71
+ { success: true, results: merged.first(limit), count: [merged.size, limit].min, mode: :all }
72
+ end
73
+
74
+ def merge_results(local_results, global_results)
75
+ seen = {}
76
+ merged = []
77
+
78
+ local_results.each do |r|
79
+ key = r[:content_hash] || r[:content]
80
+ seen[key] = true
81
+ merged << r
82
+ end
83
+
84
+ global_results.each do |r|
85
+ key = r[:content_hash] || r[:content]
86
+ merged << r unless seen[key]
87
+ end
88
+
89
+ merged
90
+ end
91
+
92
+ def global_available?
93
+ defined?(Legion::Apollo) && Legion::Apollo.started?
94
+ end
95
+
96
+ def local_available?
97
+ defined?(Legion::Apollo::Local) && Legion::Apollo::Local.started?
98
+ end
99
+
100
+ def default_query_scope
101
+ return :all unless defined?(Legion::Settings)
102
+
103
+ scope = Legion::Settings.dig(:apollo, :local, :default_query_scope)
104
+ scope ? scope.to_sym : :all
105
+ rescue StandardError
106
+ :all
107
+ end
108
+
109
+ def extract_if_needed(content_or_path, type:)
110
+ return extract_file(content_or_path, type: type) if content_or_path.is_a?(String) && File.exist?(content_or_path)
111
+ return extract_file(content_or_path, type: type) if content_or_path.respond_to?(:read)
112
+
113
+ [content_or_path.to_s, nil]
114
+ end
115
+
116
+ def extract_file(source, type:)
117
+ return [source.to_s, nil] unless defined?(Legion::Data::Extract)
118
+
119
+ result = Legion::Data::Extract.extract(source, type: type)
120
+ if result[:text]
121
+ [result[:text], result[:metadata]]
122
+ else
123
+ [nil, result]
124
+ end
125
+ end
126
+
127
+ def metadata_to_tags(metadata)
128
+ tags = []
129
+ tags << metadata[:type].to_s if metadata[:type]
130
+ tags << "pages:#{metadata[:pages]}" if metadata[:pages]
131
+ tags
132
+ end
133
+
134
+ def derive_lex_name
135
+ parts = self.class.name&.split('::')
136
+ parts && parts[2] ? parts[2].downcase : 'unknown'
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Helpers
6
+ module LLM
7
+ # Quick embed from any extension runner, forwarding all keyword arguments.
8
+ # Supports provider:, dimensions:, and any future parameters.
9
+ # @param text [String, Array<String>] text to embed
10
+ # @param kwargs [Hash] forwarded to Legion::LLM.embed (model:, provider:, dimensions:, etc.)
11
+ # @return [Hash] embedding result with :vector, :dimensions, :model, :provider
12
+ def llm_embed(text, **)
13
+ Legion::LLM.embed(text, **)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Legion
4
4
  module Readiness
5
- COMPONENTS = %i[settings crypt transport cache data rbac llm gaia extensions api].freeze
5
+ COMPONENTS = %i[settings crypt transport cache data rbac llm apollo gaia extensions api].freeze
6
6
  DRAIN_TIMEOUT = 5
7
7
 
8
8
  class << self
@@ -98,6 +98,15 @@ module Legion
98
98
  end
99
99
  end
100
100
 
101
+ begin
102
+ setup_apollo
103
+ Legion::Readiness.mark_ready(:apollo)
104
+ rescue LoadError
105
+ Legion::Logging.info 'Legion::Apollo gem is not installed, starting without Apollo'
106
+ rescue StandardError => e
107
+ Legion::Logging.warn "Legion::Apollo failed to load: #{e.message}"
108
+ end
109
+
101
110
  if gaia
102
111
  begin
103
112
  setup_gaia
@@ -317,6 +326,18 @@ module Legion
317
326
  Legion::Logging.warn "Legion::Gaia failed to load: #{e.message}"
318
327
  end
319
328
 
329
+ def setup_apollo
330
+ Legion::Logging.info 'Setting up Legion::Apollo'
331
+ require 'legion/apollo'
332
+ Legion::Apollo.start
333
+ Legion::Apollo::Local.start if defined?(Legion::Apollo::Local)
334
+ Legion::Logging.info 'Legion::Apollo started'
335
+ rescue LoadError
336
+ Legion::Logging.info 'Legion::Apollo gem is not installed, starting without Apollo'
337
+ rescue StandardError => e
338
+ Legion::Logging.warn "Legion::Apollo failed to load: #{e.message}"
339
+ end
340
+
320
341
  def setup_transport
321
342
  Legion::Logging.info 'Setting up Legion::Transport'
322
343
  require 'legion/transport'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.5.15'
4
+ VERSION = '1.5.18'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.15
4
+ version: 1.5.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -317,6 +317,20 @@ dependencies:
317
317
  - - ">="
318
318
  - !ruby/object:Gem::Version
319
319
  version: 1.4.0
320
+ - !ruby/object:Gem::Dependency
321
+ name: legion-apollo
322
+ requirement: !ruby/object:Gem::Requirement
323
+ requirements:
324
+ - - ">="
325
+ - !ruby/object:Gem::Version
326
+ version: 0.2.1
327
+ type: :runtime
328
+ prerelease: false
329
+ version_requirements: !ruby/object:Gem::Requirement
330
+ requirements:
331
+ - - ">="
332
+ - !ruby/object:Gem::Version
333
+ version: 0.2.1
320
334
  - !ruby/object:Gem::Dependency
321
335
  name: legion-gaia
322
336
  requirement: !ruby/object:Gem::Requirement
@@ -746,7 +760,9 @@ files:
746
760
  - lib/legion/extensions/helpers/cache.rb
747
761
  - lib/legion/extensions/helpers/core.rb
748
762
  - lib/legion/extensions/helpers/data.rb
763
+ - lib/legion/extensions/helpers/knowledge.rb
749
764
  - lib/legion/extensions/helpers/lex.rb
765
+ - lib/legion/extensions/helpers/llm.rb
750
766
  - lib/legion/extensions/helpers/logger.rb
751
767
  - lib/legion/extensions/helpers/segments.rb
752
768
  - lib/legion/extensions/helpers/task.rb