legion-llm 0.5.18 → 0.5.19

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: 9e522e0d580a9f76eb134b4bec7e360aba72d831c4a997da2a56e3fbf5e4af2f
4
- data.tar.gz: a27d63184aab2c0f65739a153d3a0943612787c723d2ab3305ac4649b050b75a
3
+ metadata.gz: ac3514ef943a51cc0f3d2457cc4fa976b63cb2999896626ade1d76dc7c4f7804
4
+ data.tar.gz: f71220f0ad03f65436d0e0f6ce0f34a3ecbbb5fc0bda3add29f9dcaa1713b07d
5
5
  SHA512:
6
- metadata.gz: '09fd95274c21c1963a3249ab8a0ad312702a545a493448ccca89fab82314b8148a3c0ef5530f01d82e01eb893d4ad5cee5ea461dc0c01a7a977f71d336fdcfd1'
7
- data.tar.gz: 23f322666410c4a603c05b35f077f4fbe80a9266942a8d7483a658351cdb6e678c3aec69acad4eac14b2ca0e87619fbf0eed73ae1a19c8bcf85dd51656dd687d
6
+ metadata.gz: 7112f4a875c4a49f01d2bf30249c44f85e9427722009c7474a3bb303f4f082010e117650bcacddd1ee2a0cd930d45ad043b6374f8050b31046d195975dcdf222
7
+ data.tar.gz: f01fd5cfc55684040cc9c312154811a117fb568576f136c9e87fb83c178c89f9ea6eceaf025e7c4f678e182be5d0a76eb7f656ce6b8b552eeb577f3fbe1e737a
data/AGENTS.md ADDED
@@ -0,0 +1,37 @@
1
+ # legion-llm Agent Notes
2
+
3
+ ## Scope
4
+
5
+ `legion-llm` provides provider configuration, chat/embed/structured interfaces, dynamic routing, escalation, quality checks, and pipeline execution for Legion.
6
+
7
+ ## Fast Start
8
+
9
+ ```bash
10
+ bundle install
11
+ bundle exec rspec
12
+ bundle exec rubocop
13
+ ```
14
+
15
+ ## Primary Entry Points
16
+
17
+ - `lib/legion/llm.rb`
18
+ - `lib/legion/llm/providers.rb`
19
+ - `lib/legion/llm/router/`
20
+ - `lib/legion/llm/pipeline/`
21
+ - `lib/legion/llm/structured_output.rb`
22
+ - `lib/legion/llm/embeddings.rb`
23
+ - `lib/legion/llm/fleet/`
24
+
25
+ ## Guardrails
26
+
27
+ - Keep typed error behavior and retry semantics stable (`ProviderDown`, `RateLimitError`, `EscalationExhausted`, etc.).
28
+ - Routing and escalation must remain deterministic given the same inputs/settings.
29
+ - Preserve pipeline feature-flag behavior; avoid forcing pipeline-only code paths.
30
+ - Keep provider credentials resolved through settings secret resolution flow; never hardcode secrets.
31
+ - Maintain compatibility with direct methods (`chat_direct`, `embed_direct`, `structured_direct`) and daemon-aware flows.
32
+ - Health tracker and rule scoring are contract-sensitive; changes require spec updates.
33
+
34
+ ## Validation
35
+
36
+ - Run targeted specs for modified router/pipeline/provider code.
37
+ - Before handoff, run full `bundle exec rspec` and `bundle exec rubocop`.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Legion LLM Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - `Legion::LLM::Helper` module at `lib/legion/llm/helper.rb` — canonical helper following cache/transport pattern
7
+ - Layered defaults: `llm_default_model`, `llm_default_provider`, `llm_default_intent` (LEX-overridable)
8
+ - `llm_embed_batch` — batch embedding convenience
9
+ - `llm_structured` — structured JSON output convenience
10
+ - `llm_ask` — daemon-first single-shot convenience
11
+ - `llm_connected?` / `llm_can_embed?` / `llm_routing_enabled?` — status helpers
12
+ - `llm_cost_estimate` / `llm_cost_summary` / `llm_budget_remaining` — cost and budget helpers
13
+ - Layered model/provider/intent defaults applied to `llm_chat` and `llm_session`
14
+
15
+ ### Changed
16
+ - `lib/legion/llm/helpers/llm.rb` is now a backward-compat shim that includes `Legion::LLM::Helper`
17
+
3
18
  ## [0.5.18] - 2026-03-29
4
19
 
5
20
  ### Fixed
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module LLM
5
+ module Helper
6
+ # --- Layered Defaults ---
7
+ # Override in your LEX to set extension-specific defaults.
8
+ # Resolution chain: per-call kwarg -> LEX override -> Settings -> nil (auto-detect)
9
+
10
+ def llm_default_model
11
+ return nil unless defined?(Legion::Settings)
12
+
13
+ Legion::Settings.dig(:llm, :default_model)
14
+ rescue StandardError
15
+ nil
16
+ end
17
+
18
+ def llm_default_provider
19
+ return nil unless defined?(Legion::Settings)
20
+
21
+ Legion::Settings.dig(:llm, :default_provider)
22
+ rescue StandardError
23
+ nil
24
+ end
25
+
26
+ def llm_default_intent
27
+ return nil unless defined?(Legion::Settings)
28
+
29
+ Legion::Settings.dig(:llm, :routing, :default_intent)
30
+ rescue StandardError
31
+ nil
32
+ end
33
+
34
+ # --- Core Operations ---
35
+
36
+ def llm_chat(message, model: nil, provider: nil, intent: nil, tier: nil, tools: [], # rubocop:disable Metrics/ParameterLists
37
+ instructions: nil, compress: 0, escalate: nil, max_escalations: nil,
38
+ quality_check: nil, caller: nil, use_default_intent: false)
39
+ effective_model = model || llm_default_model
40
+ effective_provider = provider || llm_default_provider
41
+ effective_intent = intent || (use_default_intent ? llm_default_intent : nil)
42
+
43
+ if compress.positive?
44
+ message = Legion::LLM::Compressor.compress(message, level: compress)
45
+ instructions = Legion::LLM::Compressor.compress(instructions, level: compress) if instructions
46
+ end
47
+
48
+ if escalate
49
+ return Legion::LLM.chat(model: effective_model, provider: effective_provider,
50
+ intent: effective_intent, tier: tier,
51
+ escalate: true, max_escalations: max_escalations,
52
+ quality_check: quality_check, message: message, caller: caller)
53
+ end
54
+
55
+ chat = Legion::LLM.chat(model: effective_model, provider: effective_provider,
56
+ intent: effective_intent, tier: tier,
57
+ escalate: false, caller: caller)
58
+ chat.with_instructions(instructions) if instructions
59
+ chat.with_tools(*tools) unless tools.empty?
60
+ chat.ask(message)
61
+ end
62
+
63
+ def llm_embed(text, **)
64
+ Legion::LLM.embed(text, **)
65
+ end
66
+
67
+ def llm_embed_batch(texts, **)
68
+ Legion::LLM.embed_batch(texts, **)
69
+ end
70
+
71
+ def llm_session(model: nil, provider: nil, intent: nil, tier: nil, caller: nil, use_default_intent: false)
72
+ effective_model = model || llm_default_model
73
+ effective_provider = provider || llm_default_provider
74
+ effective_intent = intent || (use_default_intent ? llm_default_intent : nil)
75
+
76
+ Legion::LLM.chat(model: effective_model, provider: effective_provider,
77
+ intent: effective_intent, tier: tier,
78
+ escalate: false, caller: caller)
79
+ end
80
+
81
+ def llm_structured(messages:, schema:, **)
82
+ Legion::LLM.structured(messages: messages, schema: schema, **)
83
+ end
84
+
85
+ def llm_ask(message:, **)
86
+ Legion::LLM.ask(message: message, **)
87
+ end
88
+
89
+ # --- Status ---
90
+
91
+ def llm_connected?
92
+ defined?(Legion::LLM) && Legion::LLM.started?
93
+ rescue StandardError
94
+ false
95
+ end
96
+
97
+ def llm_can_embed?
98
+ llm_connected? && Legion::LLM.can_embed?
99
+ rescue StandardError
100
+ false
101
+ end
102
+
103
+ def llm_routing_enabled?
104
+ llm_connected? && Legion::LLM::Router.routing_enabled?
105
+ rescue StandardError
106
+ false
107
+ end
108
+
109
+ # --- Cost / Budget ---
110
+
111
+ def llm_cost_estimate(model: nil, input_tokens: 0, output_tokens: 0)
112
+ model ||= llm_default_model
113
+ Legion::LLM::CostEstimator.estimate(model_id: model, input_tokens: input_tokens,
114
+ output_tokens: output_tokens)
115
+ rescue StandardError
116
+ 0.0
117
+ end
118
+
119
+ def llm_cost_summary(since: nil)
120
+ Legion::LLM::CostTracker.summary(since: since)
121
+ rescue StandardError
122
+ { total_cost_usd: 0.0, total_requests: 0, total_input_tokens: 0, total_output_tokens: 0, by_model: {} }
123
+ end
124
+
125
+ def llm_budget_remaining
126
+ Legion::LLM::Hooks::BudgetGuard.remaining
127
+ rescue StandardError
128
+ Float::INFINITY
129
+ end
130
+ end
131
+ end
132
+ end
@@ -1,59 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'legion/llm/helper'
4
+
3
5
  module Legion
4
6
  module Extensions
5
7
  module Helpers
6
8
  module LLM
7
- # Quick chat from any extension runner
8
- # @param message [String] the prompt
9
- # @param model [String] optional model override
10
- # @param provider [Symbol] optional provider override
11
- # @param intent [Hash, nil] routing intent (capability, privacy, etc.)
12
- # @param tier [Symbol, nil] explicit tier override
13
- # @param tools [Array<Class>] optional RubyLLM::Tool subclasses
14
- # @param instructions [String] optional system instructions
15
- # @param escalate [Boolean, nil] enable model escalation on low-quality responses
16
- # @param max_escalations [Integer, nil] max escalation attempts
17
- # @param quality_check [Proc, nil] callable that returns true if response is acceptable
18
- # @return [RubyLLM::Message] the assistant response
19
- def llm_chat(message, model: nil, provider: nil, intent: nil, tier: nil, tools: [], instructions: nil, # rubocop:disable Metrics/ParameterLists
20
- compress: 0, escalate: nil, max_escalations: nil, quality_check: nil, caller: nil)
21
- if compress.positive?
22
- message = Legion::LLM::Compressor.compress(message, level: compress)
23
- instructions = Legion::LLM::Compressor.compress(instructions, level: compress) if instructions
24
- end
25
-
26
- # When escalation is active, chat() handles ask() internally via message: kwarg
27
- if escalate
28
- return Legion::LLM.chat(model: model, provider: provider, intent: intent, tier: tier,
29
- escalate: true, max_escalations: max_escalations,
30
- quality_check: quality_check, message: message, caller: caller)
31
- end
32
-
33
- chat = Legion::LLM.chat(model: model, provider: provider, intent: intent, tier: tier,
34
- escalate: false, caller: caller)
35
- chat.with_instructions(instructions) if instructions
36
- chat.with_tools(*tools) unless tools.empty?
37
- chat.ask(message)
38
- end
39
-
40
- # Quick embed from any extension runner
41
- # @param text [String, Array<String>] text to embed
42
- # @param model [String] optional model override
43
- # @return [RubyLLM::Embedding]
44
- def llm_embed(text, model: nil)
45
- Legion::LLM.embed(text, model: model)
46
- end
47
-
48
- # Get a raw chat object for multi-turn conversations
49
- # @param model [String] optional model override
50
- # @param provider [Symbol] optional provider override
51
- # @param intent [Hash, nil] routing intent (capability, privacy, etc.)
52
- # @param tier [Symbol, nil] explicit tier override
53
- # @return [RubyLLM::Chat]
54
- def llm_session(model: nil, provider: nil, intent: nil, tier: nil, caller: nil)
55
- Legion::LLM.chat(model: model, provider: provider, intent: intent, tier: tier, escalate: false, caller: caller)
56
- end
9
+ include Legion::LLM::Helper
57
10
  end
58
11
  end
59
12
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.5.18'
5
+ VERSION = '0.5.19'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legion-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.18
4
+ version: 0.5.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -179,6 +179,7 @@ files:
179
179
  - ".github/workflows/ci.yml"
180
180
  - ".gitignore"
181
181
  - ".rubocop.yml"
182
+ - AGENTS.md
182
183
  - CHANGELOG.md
183
184
  - CLAUDE.md
184
185
  - CODEOWNERS
@@ -230,6 +231,7 @@ files:
230
231
  - lib/legion/llm/fleet/dispatcher.rb
231
232
  - lib/legion/llm/fleet/handler.rb
232
233
  - lib/legion/llm/fleet/reply_dispatcher.rb
234
+ - lib/legion/llm/helper.rb
233
235
  - lib/legion/llm/helpers/llm.rb
234
236
  - lib/legion/llm/hooks.rb
235
237
  - lib/legion/llm/hooks/budget_guard.rb