lex-llm 0.7.3 → 0.8.0
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/CHANGELOG.md +106 -0
- data/RULES.md +97 -0
- data/lib/legion/extensions/llm/auto_registration.rb +4 -13
- data/lib/legion/extensions/llm/canonical/chunk.rb +106 -92
- data/lib/legion/extensions/llm/canonical/content_block.rb +66 -75
- data/lib/legion/extensions/llm/canonical/message.rb +52 -67
- data/lib/legion/extensions/llm/canonical/params.rb +59 -33
- data/lib/legion/extensions/llm/canonical/request.rb +67 -54
- data/lib/legion/extensions/llm/canonical/response.rb +61 -76
- data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +99 -51
- data/lib/legion/extensions/llm/canonical/tool_call.rb +42 -51
- data/lib/legion/extensions/llm/canonical/tool_definition.rb +56 -46
- data/lib/legion/extensions/llm/canonical/tool_schema.rb +15 -22
- data/lib/legion/extensions/llm/canonical/usage.rb +47 -40
- data/lib/legion/extensions/llm/canonical.rb +6 -5
- data/lib/legion/extensions/llm/configuration.rb +40 -10
- data/lib/legion/extensions/llm/connection.rb +8 -30
- data/lib/legion/extensions/llm/credential_sources.rb +32 -49
- data/lib/legion/extensions/llm/discovery/actor.rb +92 -0
- data/lib/legion/extensions/llm/discovery/pipeline.rb +604 -0
- data/lib/legion/extensions/llm/error.rb +0 -14
- data/lib/legion/extensions/llm/fleet/contract_error.rb +15 -0
- data/lib/legion/extensions/llm/fleet/envelope_validation.rb +7 -6
- data/lib/legion/extensions/llm/fleet/fleet_envelope.rb +66 -0
- data/lib/legion/extensions/llm/fleet/protocol.rb +18 -5
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +58 -153
- data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -150
- data/lib/legion/extensions/llm/inventory/errors.rb +0 -1
- data/lib/legion/extensions/llm/inventory/evidence.rb +1 -1
- data/lib/legion/extensions/llm/inventory/identity.rb +55 -39
- data/lib/legion/extensions/llm/inventory/probe_token.rb +9 -12
- data/lib/legion/extensions/llm/inventory/publisher.rb +15 -53
- data/lib/legion/extensions/llm/inventory/records.rb +82 -99
- data/lib/legion/extensions/llm/inventory/registry.rb +54 -46
- data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
- data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +249 -0
- data/lib/legion/extensions/llm/inventory/weight_schema.rb +146 -0
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +172 -182
- data/lib/legion/extensions/llm/provider.rb +192 -315
- data/lib/legion/extensions/llm/provider_contract.rb +25 -8
- data/lib/legion/extensions/llm/provider_settings.rb +5 -26
- data/lib/legion/extensions/llm/responses/thinking_extractor.rb +8 -1
- data/lib/legion/extensions/llm/responses/tool_arguments.rb +34 -0
- data/lib/legion/extensions/llm/routing/provider_outcome.rb +25 -0
- data/lib/legion/extensions/llm/routing/records.rb +34 -17
- data/lib/legion/extensions/llm/stream_accumulator.rb +186 -270
- data/lib/legion/extensions/llm/streaming.rb +50 -35
- data/lib/legion/extensions/llm/taxonomies.rb +29 -17
- data/lib/legion/extensions/llm/transport/fleet_lane.rb +8 -10
- data/lib/legion/extensions/llm/transport/messages/fleet_error.rb +3 -2
- data/lib/legion/extensions/llm/transport/messages/fleet_request.rb +6 -8
- data/lib/legion/extensions/llm/transport/messages/fleet_response.rb +10 -9
- data/lib/legion/extensions/llm/utils.rb +23 -5
- data/lib/legion/extensions/llm/version.rb +1 -1
- data/lib/legion/extensions/llm.rb +10 -98
- data/spec/legion/extensions/llm/auto_registration_spec.rb +4 -9
- data/spec/legion/extensions/llm/canonical/chunk_spec.rb +66 -252
- data/spec/legion/extensions/llm/canonical/content_block_spec.rb +52 -197
- data/spec/legion/extensions/llm/canonical/message_spec.rb +89 -204
- data/spec/legion/extensions/llm/canonical/params_spec.rb +57 -136
- data/spec/legion/extensions/llm/canonical/request_spec.rb +81 -143
- data/spec/legion/extensions/llm/canonical/response_spec.rb +68 -204
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +68 -148
- data/spec/legion/extensions/llm/canonical/tool_call_spec.rb +59 -162
- data/spec/legion/extensions/llm/canonical/tool_definition_spec.rb +55 -191
- data/spec/legion/extensions/llm/canonical/tool_schema_spec.rb +26 -67
- data/spec/legion/extensions/llm/canonical/usage_spec.rb +46 -155
- data/spec/legion/extensions/llm/configuration_spec.rb +31 -5
- data/spec/legion/extensions/llm/conformance/canonical_type_examples.rb +106 -0
- data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
- data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +130 -0
- data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +507 -0
- data/spec/legion/extensions/llm/conformance/ssot_provider_examples.rb +11 -10
- data/spec/legion/extensions/llm/credential_sources_spec.rb +12 -13
- data/spec/legion/extensions/llm/error_spec.rb +2 -12
- data/spec/legion/extensions/llm/fleet/exact_offering_spec.rb +59 -45
- data/spec/legion/extensions/llm/fleet/provider_responder_spec.rb +178 -67
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +101 -62
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +118 -123
- data/spec/legion/extensions/llm/inventory/boot_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/identity_spec.rb +127 -110
- data/spec/legion/extensions/llm/inventory/probe_token_spec.rb +4 -4
- data/spec/legion/extensions/llm/inventory/publisher_spec.rb +8 -61
- data/spec/legion/extensions/llm/inventory/records_spec.rb +129 -41
- data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +55 -4
- data/spec/legion/extensions/llm/inventory/registry_replacement_spec.rb +5 -4
- data/spec/legion/extensions/llm/inventory/snapshot_spec.rb +11 -5
- data/spec/legion/extensions/llm/inventory/weight_reconciler_spec.rb +312 -0
- data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +141 -0
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +105 -68
- data/spec/legion/extensions/llm/provider/open_ai_compatible_tool_calls_array_spec.rb +7 -31
- data/spec/legion/extensions/llm/provider_contract_spec.rb +10 -15
- data/spec/legion/extensions/llm/provider_spec.rb +98 -78
- data/spec/legion/extensions/llm/routing/records_spec.rb +38 -5
- data/spec/legion/extensions/llm/stream_accumulator_spec.rb +174 -144
- data/spec/legion/extensions/llm/streaming_spec.rb +27 -0
- data/spec/legion/extensions/llm/taxonomies_spec.rb +43 -16
- data/spec/legion/extensions/llm/transport/fleet_lane_spec.rb +1 -1
- data/spec/legion/extensions/llm/utils_spec.rb +26 -7
- data/spec/legion/extensions/llm_base_contract_spec.rb +55 -90
- data/spec/legion/extensions/llm_extension_spec.rb +5 -5
- data/spec/support/fake_llm_provider.rb +45 -39
- data/spec/support/fake_ssot_harness.rb +7 -2
- data/spec/support/ssot_registry_helpers.rb +3 -2
- metadata +15 -54
- data/lib/legion/extensions/llm/agent.rb +0 -366
- data/lib/legion/extensions/llm/aliases.json +0 -436
- data/lib/legion/extensions/llm/aliases.rb +0 -67
- data/lib/legion/extensions/llm/attachment.rb +0 -229
- data/lib/legion/extensions/llm/chat.rb +0 -354
- data/lib/legion/extensions/llm/chunk.rb +0 -10
- data/lib/legion/extensions/llm/content.rb +0 -81
- data/lib/legion/extensions/llm/context.rb +0 -33
- data/lib/legion/extensions/llm/embedding.rb +0 -33
- data/lib/legion/extensions/llm/image.rb +0 -109
- data/lib/legion/extensions/llm/inventory/capabilities.rb +0 -40
- data/lib/legion/extensions/llm/inventory/scoped_refresher.rb +0 -311
- data/lib/legion/extensions/llm/message.rb +0 -118
- data/lib/legion/extensions/llm/mime_type.rb +0 -75
- data/lib/legion/extensions/llm/model/info.rb +0 -286
- data/lib/legion/extensions/llm/model/modalities.rb +0 -26
- data/lib/legion/extensions/llm/model/pricing.rb +0 -52
- data/lib/legion/extensions/llm/model/pricing_category.rb +0 -50
- data/lib/legion/extensions/llm/model/pricing_tier.rb +0 -37
- data/lib/legion/extensions/llm/model.rb +0 -11
- data/lib/legion/extensions/llm/models.json +0 -57313
- data/lib/legion/extensions/llm/models.rb +0 -530
- data/lib/legion/extensions/llm/models_schema.json +0 -168
- data/lib/legion/extensions/llm/moderation.rb +0 -60
- data/lib/legion/extensions/llm/registry_event_builder.rb +0 -141
- data/lib/legion/extensions/llm/registry_publisher.rb +0 -107
- data/lib/legion/extensions/llm/responses/chat_response.rb +0 -43
- data/lib/legion/extensions/llm/responses/embedding_response.rb +0 -38
- data/lib/legion/extensions/llm/responses/stream_chunk.rb +0 -43
- data/lib/legion/extensions/llm/routing/lane_key.rb +0 -66
- data/lib/legion/extensions/llm/routing/model_offering.rb +0 -241
- data/lib/legion/extensions/llm/routing/offering_registry.rb +0 -101
- data/lib/legion/extensions/llm/routing/registry_event.rb +0 -167
- data/lib/legion/extensions/llm/thinking.rb +0 -53
- data/lib/legion/extensions/llm/tokens.rb +0 -51
- data/lib/legion/extensions/llm/tool_call.rb +0 -34
- data/lib/legion/extensions/llm/transcription.rb +0 -39
- data/lib/legion/extensions/llm/transport/messages/registry_event.rb +0 -44
- data/spec/legion/extensions/llm/agent_spec.rb +0 -179
- data/spec/legion/extensions/llm/attachment_spec.rb +0 -25
- data/spec/legion/extensions/llm/conformance/fixtures/ssot_identity_vectors.json +0 -84
- data/spec/legion/extensions/llm/context_spec.rb +0 -127
- data/spec/legion/extensions/llm/inventory/capabilities_spec.rb +0 -43
- data/spec/legion/extensions/llm/inventory/scoped_refresher_spec.rb +0 -337
- data/spec/legion/extensions/llm/message_spec.rb +0 -64
- data/spec/legion/extensions/llm/model/info_spec.rb +0 -222
- data/spec/legion/extensions/llm/models_spec.rb +0 -104
- data/spec/legion/extensions/llm/registry_event_builder_spec.rb +0 -68
- data/spec/legion/extensions/llm/registry_publisher_spec.rb +0 -22
- data/spec/legion/extensions/llm/responses/response_objects_spec.rb +0 -75
- data/spec/legion/extensions/llm/routing/model_offering_spec.rb +0 -281
- data/spec/legion/extensions/llm/routing/offering_registry_spec.rb +0 -50
- data/spec/legion/extensions/llm/routing/registry_event_spec.rb +0 -120
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
return unless defined?(Legion::Transport::Message)
|
|
4
|
-
|
|
5
|
-
require_relative '../exchanges/llm_registry'
|
|
6
|
-
|
|
7
|
-
module Legion
|
|
8
|
-
module Extensions
|
|
9
|
-
module Llm
|
|
10
|
-
module Transport
|
|
11
|
-
module Messages
|
|
12
|
-
# Publishes lex-llm RegistryEvent envelopes to the shared llm.registry exchange.
|
|
13
|
-
# Accepts a `provider_family` for constructing the app_id and routing key.
|
|
14
|
-
class RegistryEvent < ::Legion::Transport::Message
|
|
15
|
-
def initialize(event:, provider_family: nil, **options)
|
|
16
|
-
@provider_family = provider_family
|
|
17
|
-
super(**event.to_h.merge(options))
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def exchange
|
|
21
|
-
Exchanges::LlmRegistry
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def routing_key
|
|
25
|
-
@options[:routing_key] || "llm.registry.#{@options.fetch(:event_type)}"
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def type
|
|
29
|
-
'llm.registry.event'
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def app_id
|
|
33
|
-
@options[:app_id] || "lex-llm-#{@provider_family || 'unknown'}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def persistent # rubocop:disable Naming/PredicateMethod
|
|
37
|
-
false
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe Legion::Extensions::Llm::Agent do
|
|
6
|
-
include_context 'with configured Legion::Extensions::Llm'
|
|
7
|
-
include_context 'with fake llm provider'
|
|
8
|
-
|
|
9
|
-
it 'builds a configured plain chat via .chat with runtime inputs' do
|
|
10
|
-
tool_class = Class.new(Legion::Extensions::Llm::Tool) do
|
|
11
|
-
def name = 'echo_tool'
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
15
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
16
|
-
inputs :display_name
|
|
17
|
-
instructions { "Hello #{display_name}" }
|
|
18
|
-
tools { [tool_class.new] }
|
|
19
|
-
params { { max_tokens: 12 } }
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
chat = agent_class.chat(display_name: 'Ava')
|
|
23
|
-
|
|
24
|
-
expect(chat.messages.first.role).to eq(:system)
|
|
25
|
-
expect(chat.messages.first.content).to eq('Hello Ava')
|
|
26
|
-
expect(chat.tools.keys).to include(:echo_tool)
|
|
27
|
-
expect(chat.params).to eq(max_tokens: 12)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it 'exposes Legion::Extensions::Llm::Chat as chat in execution context for .chat' do
|
|
31
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
32
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
33
|
-
instructions { chat.class.name }
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
chat = agent_class.chat
|
|
37
|
-
expect(chat.messages.first.content).to eq('Legion::Extensions::Llm::Chat')
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
it 'raises when instructions default prompt is missing' do
|
|
41
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
42
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
43
|
-
instructions
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
expect { agent_class.chat }.to raise_error(Legion::Extensions::Llm::PromptNotFoundError, /Prompt file not found/)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
it 'supports inline schema DSL via schema do ... end' do
|
|
50
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
51
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
52
|
-
schema do
|
|
53
|
-
string :verdict, enum: %w[pass revise]
|
|
54
|
-
string :feedback
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
chat = agent_class.chat
|
|
59
|
-
|
|
60
|
-
expect(chat.schema).to include(name: 'Schema', strict: true, schema: include(type: 'object'))
|
|
61
|
-
expect(chat.schema.dig(:schema, :properties)).to include(
|
|
62
|
-
verdict: include(type: 'string'),
|
|
63
|
-
feedback: include(type: 'string')
|
|
64
|
-
)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
it 'supports runtime-evaluated schema blocks that return a schema value' do
|
|
68
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
69
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
70
|
-
inputs :strict
|
|
71
|
-
|
|
72
|
-
schema do
|
|
73
|
-
if strict
|
|
74
|
-
{
|
|
75
|
-
type: 'object',
|
|
76
|
-
properties: { answer: { type: 'string' } },
|
|
77
|
-
required: ['answer'],
|
|
78
|
-
additionalProperties: false
|
|
79
|
-
}
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
strict_chat = agent_class.chat(strict: true)
|
|
85
|
-
loose_chat = agent_class.chat(strict: false)
|
|
86
|
-
|
|
87
|
-
expect(strict_chat.schema).to include(name: 'response', strict: true, schema: include(type: 'object'))
|
|
88
|
-
expect(loose_chat.schema).to be_nil
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
it 'can ask using a registered provider' do
|
|
92
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
93
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
94
|
-
instructions 'Answer questions clearly.'
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
stub_const('SpecChatAgent', agent_class)
|
|
98
|
-
|
|
99
|
-
response = SpecChatAgent.new.ask('hello')
|
|
100
|
-
expect(response.content).to include('fake response to hello')
|
|
101
|
-
expect(response.role).to eq(:assistant)
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
it 'delegates add_message to the underlying chat interface' do
|
|
105
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
106
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
agent = agent_class.new
|
|
110
|
-
message = agent.add_message(role: :user, content: 'Hello')
|
|
111
|
-
|
|
112
|
-
expect(message.role).to eq(:user)
|
|
113
|
-
expect(message.content).to eq('Hello')
|
|
114
|
-
expect(agent.chat.messages.last).to eq(message)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it 'exposes messages like Legion::Extensions::Llm::Chat' do
|
|
118
|
-
agent_class = Class.new(Legion::Extensions::Llm::Agent) do
|
|
119
|
-
model 'fake-chat-model', provider: :fake_llm, assume_model_exists: true
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
agent = agent_class.new
|
|
123
|
-
agent.add_message(role: :user, content: 'First')
|
|
124
|
-
|
|
125
|
-
expect(agent.messages).to eq(agent.chat.messages)
|
|
126
|
-
expect(agent.messages.last.content).to eq('First')
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
it 'delegates callback hooks to the underlying chat' do
|
|
130
|
-
fake_chat = Class.new do
|
|
131
|
-
attr_reader :events
|
|
132
|
-
|
|
133
|
-
def initialize
|
|
134
|
-
@events = []
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def on_new_message(&)
|
|
138
|
-
@events << :new_message
|
|
139
|
-
self
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def on_end_message(&)
|
|
143
|
-
@events << :end_message
|
|
144
|
-
self
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def on_tool_call(&)
|
|
148
|
-
@events << :tool_call
|
|
149
|
-
self
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
def on_tool_result(&)
|
|
153
|
-
@events << :tool_result
|
|
154
|
-
self
|
|
155
|
-
end
|
|
156
|
-
end.new
|
|
157
|
-
|
|
158
|
-
agent = Class.new(described_class).new(chat: fake_chat)
|
|
159
|
-
|
|
160
|
-
expect(agent.on_new_message { :ok }).to eq(fake_chat)
|
|
161
|
-
expect(agent.on_end_message { :ok }).to eq(fake_chat)
|
|
162
|
-
expect(agent.on_tool_call { :ok }).to eq(fake_chat)
|
|
163
|
-
expect(agent.on_tool_result { :ok }).to eq(fake_chat)
|
|
164
|
-
expect(fake_chat.events).to eq(%i[new_message end_message tool_call tool_result])
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
it 'supports Enumerable by delegating each to chat' do
|
|
168
|
-
fake_chat = Class.new do
|
|
169
|
-
def each(&)
|
|
170
|
-
return enum_for(:each) unless block_given?
|
|
171
|
-
|
|
172
|
-
%w[first second].each(&)
|
|
173
|
-
end
|
|
174
|
-
end.new
|
|
175
|
-
|
|
176
|
-
agent = Class.new(described_class).new(chat: fake_chat)
|
|
177
|
-
expect(agent.map(&:upcase)).to eq(%w[FIRST SECOND])
|
|
178
|
-
end
|
|
179
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
require 'open3'
|
|
5
|
-
require 'rbconfig'
|
|
6
|
-
|
|
7
|
-
RSpec.describe Legion::Extensions::Llm::Attachment do
|
|
8
|
-
it 'supports path attachments from the public API' do
|
|
9
|
-
script = <<~'RUBY'
|
|
10
|
-
require 'legion/extensions/llm'
|
|
11
|
-
|
|
12
|
-
content = Legion::Extensions::Llm::Content.new('What is in this file?', 'spec/fixtures/ruby.txt')
|
|
13
|
-
attachment = content.attachments.first
|
|
14
|
-
puts "#{attachment.filename},#{attachment.mime_type}"
|
|
15
|
-
RUBY
|
|
16
|
-
|
|
17
|
-
stdout, stderr, status = Open3.capture3(
|
|
18
|
-
RbConfig.ruby, '-Ilib', '-e', script,
|
|
19
|
-
chdir: File.expand_path('../../../..', __dir__)
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
expect(status.success?).to be(true), stderr
|
|
23
|
-
expect(stdout.strip).to eq('ruby.txt,text/plain')
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"description": "Binding SSOT v3 identity input/byte/digest fixtures. Recompute every field and digest; do not merely check a prefix. No Ruby object hash, hash-iteration order, or delimiter splitting is used anywhere.",
|
|
3
|
-
"algorithm": {
|
|
4
|
-
"frame": "binary bytes: decimal(UTF-8 byte length) + ASCII 0x3a colon + exact UTF-8 bytes",
|
|
5
|
-
"offering_id": "off:v1: + lowercase_hex(SHA256(frame(provider_family) + frame(instance_id) + frame(provider_native_key)))",
|
|
6
|
-
"lane_id": "lane:v1: + lowercase_hex(SHA256(ASCII(\"lane-v1\\u0000\") + frame(provider_family) + frame(instance_id) + frame(operation) + frame(model) + frame(offering_id)))",
|
|
7
|
-
"lane_domain_prefix_hex": "6c616e652d763100",
|
|
8
|
-
"notes": "Tier is never an identity input. provider_native_key, model, and offering_id preserve declared case."
|
|
9
|
-
},
|
|
10
|
-
"vectors": [
|
|
11
|
-
{
|
|
12
|
-
"provider_family": "vllm",
|
|
13
|
-
"instance_id": "h200",
|
|
14
|
-
"provider_native_key": "gemma4",
|
|
15
|
-
"model": "gemma4",
|
|
16
|
-
"operation": "chat",
|
|
17
|
-
"framed_hex": {
|
|
18
|
-
"provider_family": "343a766c6c6d",
|
|
19
|
-
"instance_id": "343a68323030",
|
|
20
|
-
"provider_native_key": "363a67656d6d6134",
|
|
21
|
-
"operation": "343a63686174",
|
|
22
|
-
"model": "363a67656d6d6134",
|
|
23
|
-
"offering_id": "37313a6f66663a76313a63393636373732643766386634323863323461373762653936663934653131316261313035326266303933303938663034313362353632633130353964636431"
|
|
24
|
-
},
|
|
25
|
-
"offering_hash_input_hex": "343a766c6c6d343a68323030363a67656d6d6134",
|
|
26
|
-
"expected_offering_id": "off:v1:c966772d7f8f428c24a77be96f94e111ba1052bf093098f0413b562c1059dcd1",
|
|
27
|
-
"lane_hash_input_hex": "6c616e652d763100343a766c6c6d343a68323030343a63686174363a67656d6d613437313a6f66663a76313a63393636373732643766386634323863323461373762653936663934653131316261313035326266303933303938663034313362353632633130353964636431",
|
|
28
|
-
"expected_lane_id": "lane:v1:430444bb58975ab56329c7c3a6c483a1b9cb49d1c089a56f3c93c6aa27d5f9b3"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"provider_family": "vllm",
|
|
32
|
-
"instance_id": "helios1",
|
|
33
|
-
"provider_native_key": "gemma4",
|
|
34
|
-
"model": "gemma4",
|
|
35
|
-
"operation": "chat",
|
|
36
|
-
"framed_hex": {
|
|
37
|
-
"provider_family": "343a766c6c6d",
|
|
38
|
-
"instance_id": "373a68656c696f7331",
|
|
39
|
-
"provider_native_key": "363a67656d6d6134",
|
|
40
|
-
"operation": "343a63686174",
|
|
41
|
-
"model": "363a67656d6d6134",
|
|
42
|
-
"offering_id": "37313a6f66663a76313a66336333316366313830313739363436343163386166656463656261316136366135326661626630313463666632623365316438383165333639376339656334"
|
|
43
|
-
},
|
|
44
|
-
"offering_hash_input_hex": "343a766c6c6d373a68656c696f7331363a67656d6d6134",
|
|
45
|
-
"expected_offering_id": "off:v1:f3c31cf18017964641c8afedceba1a66a52fabf014cff2b3e1d881e3697c9ec4",
|
|
46
|
-
"lane_hash_input_hex": "6c616e652d763100343a766c6c6d373a68656c696f7331343a63686174363a67656d6d613437313a6f66663a76313a66336333316366313830313739363436343163386166656463656261316136366135326661626630313463666632623365316438383165333639376339656334",
|
|
47
|
-
"expected_lane_id": "lane:v1:e4a99f7884ea1e9c36b0a0e62da5038832857f0e4b69234133faa81b0e30e880"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"provider_family": "azure_foundry",
|
|
51
|
-
"instance_id": "prod:eastus",
|
|
52
|
-
"provider_native_key": "deploy:gpt-4o",
|
|
53
|
-
"model": "gpt-4o",
|
|
54
|
-
"operation": "chat",
|
|
55
|
-
"framed_hex": {
|
|
56
|
-
"provider_family": "31333a617a7572655f666f756e647279",
|
|
57
|
-
"instance_id": "31313a70726f643a656173747573",
|
|
58
|
-
"provider_native_key": "31333a6465706c6f793a6770742d346f",
|
|
59
|
-
"operation": "343a63686174",
|
|
60
|
-
"model": "363a6770742d346f",
|
|
61
|
-
"offering_id": "37313a6f66663a76313a37646431346639386164346266656335316666623136396561323064633137616564316562653033613363396664666365383966383638343534633035646137"
|
|
62
|
-
},
|
|
63
|
-
"offering_hash_input_hex": "31333a617a7572655f666f756e64727931313a70726f643a65617374757331333a6465706c6f793a6770742d346f",
|
|
64
|
-
"expected_offering_id": "off:v1:7dd14f98ad4bfec51ffb169ea20dc17aed1ebe03a3c9fdfce89f868454c05da7",
|
|
65
|
-
"lane_hash_input_hex": "6c616e652d76310031333a617a7572655f666f756e64727931313a70726f643a656173747573343a63686174363a6770742d346f37313a6f66663a76313a37646431346639386164346266656335316666623136396561323064633137616564316562653033613363396664666365383966383638343534633035646137",
|
|
66
|
-
"expected_lane_id": "lane:v1:2334bc87506ae6312a4f64b53798d723d07279294ee6a3181e23db298cf910aa"
|
|
67
|
-
}
|
|
68
|
-
],
|
|
69
|
-
"tier_invariance": {
|
|
70
|
-
"note": "The vllm+h200+gemma4+chat vector reproduces identical offering_id/lane_id under every tier; tier is not framed.",
|
|
71
|
-
"base_vector_index": 0,
|
|
72
|
-
"tiers": [
|
|
73
|
-
"local",
|
|
74
|
-
"frontier"
|
|
75
|
-
]
|
|
76
|
-
},
|
|
77
|
-
"independence": {
|
|
78
|
-
"note": "vectors[0] and vectors[1] share provider_family+model but differ by instance_id, proving vllm+h200+gemma4 and vllm+helios1+gemma4 are independent identities.",
|
|
79
|
-
"vector_indexes": [
|
|
80
|
-
0,
|
|
81
|
-
1
|
|
82
|
-
]
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe Legion::Extensions::Llm::Context do
|
|
6
|
-
include_context 'with configured Legion::Extensions::Llm'
|
|
7
|
-
include_context 'with fake llm provider'
|
|
8
|
-
|
|
9
|
-
describe '#initialize' do
|
|
10
|
-
it 'creates a copy of the global configuration' do
|
|
11
|
-
# Get current config values
|
|
12
|
-
original_model = Legion::Extensions::Llm.config.default_model
|
|
13
|
-
original_log_regexp_timeout = Legion::Extensions::Llm.config.log_regexp_timeout
|
|
14
|
-
|
|
15
|
-
# Create context with modified config
|
|
16
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
17
|
-
config.default_model = 'modified-model'
|
|
18
|
-
config.fake_llm_api_key = 'modified-key'
|
|
19
|
-
config.log_regexp_timeout = 5.0
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Verify global config is unchanged
|
|
23
|
-
expect(Legion::Extensions::Llm.config.default_model).to eq(original_model)
|
|
24
|
-
expect(Legion::Extensions::Llm.config.log_regexp_timeout).to eq(original_log_regexp_timeout)
|
|
25
|
-
|
|
26
|
-
# Verify context has modified config
|
|
27
|
-
expect(context.config.default_model).to eq('modified-model')
|
|
28
|
-
expect(context.config.fake_llm_api_key).to eq('modified-key')
|
|
29
|
-
expect(context.config.log_regexp_timeout).to eq(5.0)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
it 'preserves log_regexp_timeout when Regexp timeout is unavailable' do
|
|
33
|
-
allow(Regexp).to receive(:respond_to?).and_call_original
|
|
34
|
-
allow(Regexp).to receive(:respond_to?).with(:timeout).and_return(false)
|
|
35
|
-
allow(Legion::Extensions::Llm.logger).to receive(:warn)
|
|
36
|
-
|
|
37
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
38
|
-
config.log_regexp_timeout = 5.0
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
expect(context.config.log_regexp_timeout).to eq(5.0)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
describe 'context chat operations' do
|
|
46
|
-
it 'creates a chat with context-specific configuration' do
|
|
47
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
48
|
-
config.default_model = 'fake-chat-model'
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
chat = context.chat(provider: :fake_llm, assume_model_exists: true)
|
|
52
|
-
expect(chat.model.id).to eq('fake-chat-model')
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
it 'allows specifying a model when creating the chat' do
|
|
56
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
57
|
-
config.default_model = 'fake-chat-model'
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
chat = context.chat(model: 'other-fake-chat-model', provider: :fake_llm, assume_model_exists: true)
|
|
61
|
-
expect(chat.model.id).to eq('other-fake-chat-model')
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
describe 'context embed operations' do
|
|
66
|
-
it 'respects context-specific embedding model' do
|
|
67
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
68
|
-
config.default_embedding_model = 'fake-embed'
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
embedding = context.embed('Test embedding', provider: :fake_llm, assume_model_exists: true)
|
|
72
|
-
expect(embedding.model).to eq('fake-embed')
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
it 'allows specifying a model at embed time' do
|
|
76
|
-
context = Legion::Extensions::Llm.context do |config|
|
|
77
|
-
config.default_embedding_model = 'fake-embed'
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
embedding = context.embed('Test embedding', model: 'override-embed', provider: :fake_llm,
|
|
81
|
-
assume_model_exists: true)
|
|
82
|
-
expect(embedding.model).to eq('override-embed')
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
describe 'multiple independent contexts' do
|
|
87
|
-
it 'allows multiple contexts with different configurations' do
|
|
88
|
-
context1 = Legion::Extensions::Llm.context do |config|
|
|
89
|
-
config.default_model = 'fake-chat-1'
|
|
90
|
-
config.log_regexp_timeout = 5.0
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
context2 = Legion::Extensions::Llm.context do |config|
|
|
94
|
-
config.default_model = 'fake-chat-2'
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
chat1 = context1.chat(provider: :fake_llm, assume_model_exists: true)
|
|
98
|
-
chat2 = context2.chat(provider: :fake_llm, assume_model_exists: true)
|
|
99
|
-
|
|
100
|
-
expect(chat1.model.id).to eq('fake-chat-1')
|
|
101
|
-
expect(context1.config.log_regexp_timeout).to eq(5.0)
|
|
102
|
-
|
|
103
|
-
expect(chat2.model.id).to eq('fake-chat-2')
|
|
104
|
-
expected_timeout = Regexp.respond_to?(:timeout) ? (Regexp.timeout || 1.0) : nil
|
|
105
|
-
expect(context2.config.log_regexp_timeout).to eq(expected_timeout)
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
it 'ensures changes in one context do not affect another' do
|
|
109
|
-
context1 = Legion::Extensions::Llm.context do |config|
|
|
110
|
-
config.fake_llm_api_key = 'key1'
|
|
111
|
-
config.default_model = 'model1'
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
context2 = Legion::Extensions::Llm.context do |config|
|
|
115
|
-
config.fake_llm_api_key = 'key2'
|
|
116
|
-
config.default_model = 'model2'
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Modify context1 after creation
|
|
120
|
-
context1.config.fake_llm_api_key = 'modified-key1'
|
|
121
|
-
|
|
122
|
-
# Context2 should be unaffected
|
|
123
|
-
expect(context2.config.fake_llm_api_key).to eq('key2')
|
|
124
|
-
expect(context2.config.default_model).to eq('model2')
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'spec_helper'
|
|
4
|
-
|
|
5
|
-
RSpec.describe Legion::Extensions::Llm::Inventory::Capabilities do
|
|
6
|
-
describe '.normalize' do
|
|
7
|
-
it 'returns symbols for raw capability names' do
|
|
8
|
-
expect(described_class.normalize(%i[tools streaming])).to contain_exactly(:tools, :streaming)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
it 'collapses provider aliases into the canonical vocabulary' do
|
|
12
|
-
expect(described_class.normalize([:function_calling])).to include(:tools)
|
|
13
|
-
expect(described_class.normalize([:tool_use])).to include(:tools)
|
|
14
|
-
expect(described_class.normalize([:stream])).to include(:streaming)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it 'returns an empty array for nil/empty input' do
|
|
18
|
-
expect(described_class.normalize(nil)).to eq([])
|
|
19
|
-
expect(described_class.normalize([])).to eq([])
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
it 'is the same vocabulary as Legion::Extensions::Llm::Capabilities' do
|
|
23
|
-
expect(described_class::ALIASES).to equal(Legion::Extensions::Llm::Capabilities::ALIASES)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
describe '.merge' do
|
|
28
|
-
it 'unions multiple capability sets' do
|
|
29
|
-
result = described_class.merge([:tools], %i[streaming tools])
|
|
30
|
-
expect(result).to contain_exactly(:tools, :streaming)
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
describe '.include_all?' do
|
|
35
|
-
it 'returns true when required is a subset of available' do
|
|
36
|
-
expect(described_class.include_all?(%i[tools streaming], [:tools])).to be(true)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
it 'returns false when required is missing from available' do
|
|
40
|
-
expect(described_class.include_all?([:streaming], [:tools])).to be(false)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|