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 +4 -4
- data/AGENTS.md +37 -0
- data/CHANGELOG.md +15 -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
- 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,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
|
-
|
|
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
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
|