legion-llm 0.5.17 → 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 +4 -4
- data/AGENTS.md +37 -0
- data/CHANGELOG.md +20 -0
- data/lib/legion/llm/helper.rb +132 -0
- data/lib/legion/llm/helpers/llm.rb +3 -50
- data/lib/legion/llm/version.rb +1 -1
- data/lib/legion/llm.rb +1 -2
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac3514ef943a51cc0f3d2457cc4fa976b63cb2999896626ade1d76dc7c4f7804
|
|
4
|
+
data.tar.gz: f71220f0ad03f65436d0e0f6ce0f34a3ecbbb5fc0bda3add29f9dcaa1713b07d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,25 @@
|
|
|
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
|
+
|
|
18
|
+
## [0.5.18] - 2026-03-29
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
- `Legion::LLM::Embeddings` now eagerly required at load time — previously lazy-required only inside `embed_direct`/`embed_batch`, causing `uninitialized constant Legion::LLM::Embeddings` when extensions (e.g. lex-apollo) referenced the constant directly
|
|
22
|
+
|
|
3
23
|
## [0.5.17] - 2026-03-28
|
|
4
24
|
|
|
5
25
|
### Added
|
|
@@ -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
|
-
|
|
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
|
data/lib/legion/llm/version.rb
CHANGED
data/lib/legion/llm.rb
CHANGED
|
@@ -11,6 +11,7 @@ require 'legion/llm/compressor'
|
|
|
11
11
|
require 'legion/llm/quality_checker'
|
|
12
12
|
require 'legion/llm/confidence_score'
|
|
13
13
|
require 'legion/llm/confidence_scorer'
|
|
14
|
+
require 'legion/llm/embeddings'
|
|
14
15
|
require 'legion/llm/escalation_history'
|
|
15
16
|
require 'legion/llm/hooks'
|
|
16
17
|
require 'legion/llm/cache'
|
|
@@ -175,7 +176,6 @@ module Legion
|
|
|
175
176
|
|
|
176
177
|
# Direct embed bypassing gateway
|
|
177
178
|
def embed_direct(text, **)
|
|
178
|
-
require 'legion/llm/embeddings'
|
|
179
179
|
Embeddings.generate(text: text, **)
|
|
180
180
|
end
|
|
181
181
|
|
|
@@ -183,7 +183,6 @@ module Legion
|
|
|
183
183
|
# @param texts [Array<String>] texts to embed
|
|
184
184
|
# @return [Array<Hash>]
|
|
185
185
|
def embed_batch(texts, **)
|
|
186
|
-
require 'legion/llm/embeddings'
|
|
187
186
|
Embeddings.generate_batch(texts: texts, **)
|
|
188
187
|
end
|
|
189
188
|
|
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.
|
|
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
|