lex-llm 0.7.6 → 0.8.4
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 +139 -0
- data/RULES.md +97 -0
- data/lib/legion/extensions/llm/auto_registration.rb +4 -13
- data/lib/legion/extensions/llm/canonical/chunk.rb +107 -94
- data/lib/legion/extensions/llm/canonical/content_block.rb +65 -75
- data/lib/legion/extensions/llm/canonical/message.rb +53 -69
- data/lib/legion/extensions/llm/canonical/params.rb +58 -34
- data/lib/legion/extensions/llm/canonical/request.rb +68 -56
- data/lib/legion/extensions/llm/canonical/response.rb +62 -78
- data/lib/legion/extensions/llm/canonical/strict.rb +105 -0
- data/lib/legion/extensions/llm/canonical/thinking.rb +36 -84
- data/lib/legion/extensions/llm/canonical/thinking_config.rb +149 -0
- data/lib/legion/extensions/llm/canonical/tool_call.rb +43 -53
- 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 +7 -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 -156
- data/lib/legion/extensions/llm/fleet/token_validator.rb +15 -21
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +107 -154
- 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 +36 -100
- data/lib/legion/extensions/llm/inventory/registry.rb +54 -48
- data/lib/legion/extensions/llm/inventory/snapshot.rb +7 -21
- data/lib/legion/extensions/llm/inventory/weight_reconciler.rb +13 -6
- data/lib/legion/extensions/llm/inventory/weight_schema.rb +47 -8
- data/lib/legion/extensions/llm/provider/open_ai_compatible.rb +178 -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 +48 -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 +14 -26
- 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 +8 -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 +55 -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/config_spec.rb +222 -0
- data/spec/legion/extensions/llm/canonical/thinking_spec.rb +23 -171
- 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/client_translator_examples.rb +1 -2
- data/spec/legion/extensions/llm/conformance/conformance.rb +10 -2
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_fleet_round_trip.json +1 -1
- data/spec/legion/extensions/llm/conformance/fixtures/canonical_thinking_request.json +2 -2
- data/spec/legion/extensions/llm/conformance/provider_translator_examples.rb +1 -1
- data/spec/legion/extensions/llm/conformance/ssot_contract_conformance_spec.rb +128 -0
- data/spec/legion/extensions/llm/conformance/ssot_contract_examples.rb +505 -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 +173 -95
- data/spec/legion/extensions/llm/fleet/token_validator_spec.rb +7 -2
- data/spec/legion/extensions/llm/fleet/worker_execution_spec.rb +93 -74
- data/spec/legion/extensions/llm/fleet_messages_spec.rb +119 -125
- data/spec/legion/extensions/llm/gemspec_spec.rb +1 -2
- 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 +82 -47
- data/spec/legion/extensions/llm/inventory/registry_activation_spec.rb +49 -16
- 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 +7 -3
- data/spec/legion/extensions/llm/inventory/weight_schema_spec.rb +60 -10
- data/spec/legion/extensions/llm/provider/open_ai_compatible_spec.rb +128 -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 +97 -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 -43
- 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
- metadata +13 -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 -310
- 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 -340
- 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,60 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
# Identify potentially harmful content in text.
|
|
7
|
-
# https://platform.openai.com/docs/guides/moderation
|
|
8
|
-
class Moderation
|
|
9
|
-
attr_reader :id, :model, :results
|
|
10
|
-
|
|
11
|
-
def initialize(id:, model:, results:)
|
|
12
|
-
@id = id
|
|
13
|
-
@model = model
|
|
14
|
-
@results = results
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.moderate(input,
|
|
18
|
-
model: nil,
|
|
19
|
-
provider: nil,
|
|
20
|
-
assume_model_exists: false,
|
|
21
|
-
context: nil)
|
|
22
|
-
config = context&.config || Legion::Extensions::Llm.config
|
|
23
|
-
model ||= config.default_moderation_model
|
|
24
|
-
model, provider_instance = Models.resolve(model, provider: provider, assume_exists: assume_model_exists,
|
|
25
|
-
config: config)
|
|
26
|
-
model_id = model.id
|
|
27
|
-
|
|
28
|
-
provider_instance.moderate(input, model: model_id)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Convenience method to get content from moderation result
|
|
32
|
-
def content
|
|
33
|
-
results
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Check if any content was flagged
|
|
37
|
-
def flagged?
|
|
38
|
-
results.any? { |result| result['flagged'] }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Get all flagged categories across all results
|
|
42
|
-
def flagged_categories
|
|
43
|
-
results.flat_map do |result|
|
|
44
|
-
result['categories']&.select { |_category, flagged| flagged }&.keys || []
|
|
45
|
-
end.uniq
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Get category scores for the first result (most common case)
|
|
49
|
-
def category_scores
|
|
50
|
-
results.first&.dig('category_scores') || {}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Get categories for the first result (most common case)
|
|
54
|
-
def categories
|
|
55
|
-
results.first&.dig('categories') || {}
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
# Builds sanitized lex-llm registry envelopes for provider state.
|
|
7
|
-
# Parameterized by `provider_family` so each lex-llm-* gem can reuse this
|
|
8
|
-
# class without defining its own copy.
|
|
9
|
-
class RegistryEventBuilder
|
|
10
|
-
include Legion::Logging::Helper
|
|
11
|
-
|
|
12
|
-
attr_reader :provider_family
|
|
13
|
-
|
|
14
|
-
def initialize(provider_family:)
|
|
15
|
-
@provider_family = provider_family.to_s.downcase.to_sym
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def readiness(readiness)
|
|
19
|
-
registry_event_class.public_send(
|
|
20
|
-
readiness[:ready] ? :available : :unavailable,
|
|
21
|
-
provider_offering(readiness),
|
|
22
|
-
runtime: runtime_metadata,
|
|
23
|
-
health: readiness_health(readiness),
|
|
24
|
-
metadata: readiness_metadata(readiness)
|
|
25
|
-
)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def model_available(model, readiness:)
|
|
29
|
-
registry_event_class.available(
|
|
30
|
-
model_offering(model),
|
|
31
|
-
runtime: runtime_metadata,
|
|
32
|
-
health: model_health(readiness),
|
|
33
|
-
metadata: model_metadata(model)
|
|
34
|
-
)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
def provider_offering(readiness)
|
|
40
|
-
{
|
|
41
|
-
provider_family: provider_family,
|
|
42
|
-
provider_instance: provider_instance,
|
|
43
|
-
transport: :http,
|
|
44
|
-
model: 'provider-readiness',
|
|
45
|
-
usage_type: :inference,
|
|
46
|
-
capabilities: [],
|
|
47
|
-
health: readiness_health(readiness),
|
|
48
|
-
metadata: { lex: extension_sym, provider_readiness: true }
|
|
49
|
-
}
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def model_offering(model)
|
|
53
|
-
{
|
|
54
|
-
provider_family: provider_family,
|
|
55
|
-
provider_instance: provider_instance,
|
|
56
|
-
transport: :http,
|
|
57
|
-
model: model.id,
|
|
58
|
-
usage_type: usage_type_for(model),
|
|
59
|
-
capabilities: Array(model.capabilities).map(&:to_sym),
|
|
60
|
-
limits: model_limits(model),
|
|
61
|
-
metadata: { lex: extension_sym, model_name: model.name }.compact
|
|
62
|
-
}
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def readiness_health(readiness)
|
|
66
|
-
source = readiness[:health]
|
|
67
|
-
health = {
|
|
68
|
-
ready: readiness[:ready] == true,
|
|
69
|
-
status: readiness[:ready] ? :available : :unavailable,
|
|
70
|
-
checked: !source.is_a?(Hash) || source[:checked] != false
|
|
71
|
-
}
|
|
72
|
-
add_readiness_error(health, source)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def add_readiness_error(health, source)
|
|
76
|
-
error = source.is_a?(Hash) ? source : {}
|
|
77
|
-
error_class = error[:error] || error['error']
|
|
78
|
-
error_message = error[:message] || error['message']
|
|
79
|
-
health[:error_class] = error_class if error_class
|
|
80
|
-
health[:error] = error_message if error_message
|
|
81
|
-
health
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def model_health(readiness)
|
|
85
|
-
ready = readiness.fetch(:ready, true) == true
|
|
86
|
-
{ ready:, status: ready ? :available : :degraded }
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def readiness_metadata(readiness)
|
|
90
|
-
{
|
|
91
|
-
extension: extension_sym,
|
|
92
|
-
provider: provider_family,
|
|
93
|
-
configured: readiness[:configured] == true,
|
|
94
|
-
live: readiness[:live] == true
|
|
95
|
-
}
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def model_metadata(model)
|
|
99
|
-
{ extension: extension_sym, provider: provider_family, model_type: model_type_for(model) }
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def runtime_metadata
|
|
103
|
-
{ node: provider_instance }
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def model_limits(model)
|
|
107
|
-
limits = {}
|
|
108
|
-
limits[:context_window] = model.context_window if model.respond_to?(:context_window)
|
|
109
|
-
limits[:max_output_tokens] = model.max_output_tokens if model.respond_to?(:max_output_tokens)
|
|
110
|
-
limits.compact
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def usage_type_for(model)
|
|
114
|
-
model_type_for(model) == 'embedding' ? :embedding : :inference
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def model_type_for(model)
|
|
118
|
-
model.respond_to?(:type) ? model.type : 'chat'
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def extension_sym
|
|
122
|
-
:"llm_#{provider_family}"
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def provider_instance
|
|
126
|
-
configured_node = (::Legion::Settings.dig(:node, :canonical_name) if defined?(::Legion::Settings))
|
|
127
|
-
value = configured_node.to_s.strip
|
|
128
|
-
value.empty? ? provider_family : value.to_sym
|
|
129
|
-
rescue StandardError => e
|
|
130
|
-
handle_exception(e, level: :warn, handled: true,
|
|
131
|
-
operation: "#{provider_family}.registry.provider_instance")
|
|
132
|
-
provider_family
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def registry_event_class
|
|
136
|
-
::Legion::Extensions::Llm::Routing::RegistryEvent
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'concurrent'
|
|
4
|
-
|
|
5
|
-
module Legion
|
|
6
|
-
module Extensions
|
|
7
|
-
module Llm
|
|
8
|
-
# Best-effort publisher for LLM provider availability events.
|
|
9
|
-
# Parameterized by `provider_family` so each lex-llm-* gem can reuse this
|
|
10
|
-
# class without defining its own copy.
|
|
11
|
-
class RegistryPublisher
|
|
12
|
-
include Legion::Logging::Helper
|
|
13
|
-
|
|
14
|
-
ASYNC_THREAD_POOL = Concurrent::FixedThreadPool.new(1, fallback_policy: :caller_runs)
|
|
15
|
-
|
|
16
|
-
attr_reader :provider_family
|
|
17
|
-
|
|
18
|
-
def initialize(provider_family:, builder: nil)
|
|
19
|
-
@provider_family = provider_family.to_s.downcase.to_sym
|
|
20
|
-
@builder = builder || RegistryEventBuilder.new(provider_family: @provider_family)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def app_id
|
|
24
|
-
"lex-llm-#{provider_family}"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def publish_readiness_async(readiness)
|
|
28
|
-
log.debug { "publishing readiness event to llm.registry for #{provider_family}" }
|
|
29
|
-
schedule { publish_event(@builder.readiness(readiness)) }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def publish_models_async(models, readiness:)
|
|
33
|
-
log.debug { "publishing #{Array(models).size} model event(s) to llm.registry for #{provider_family}" }
|
|
34
|
-
schedule do
|
|
35
|
-
Array(models).each do |model|
|
|
36
|
-
publish_event(@builder.model_available(model, readiness:))
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
def schedule(&)
|
|
44
|
-
return false unless publishing_available?
|
|
45
|
-
|
|
46
|
-
ASYNC_THREAD_POOL.post do
|
|
47
|
-
yield
|
|
48
|
-
rescue StandardError => e
|
|
49
|
-
handle_exception(e, level: :warn, handled: true,
|
|
50
|
-
operation: "#{provider_family}.registry.schedule_thread")
|
|
51
|
-
end
|
|
52
|
-
rescue StandardError => e
|
|
53
|
-
handle_exception(e, level: :warn, handled: true,
|
|
54
|
-
operation: "#{provider_family}.registry.schedule")
|
|
55
|
-
false
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def publish_event(event)
|
|
59
|
-
return false unless publishing_available?
|
|
60
|
-
|
|
61
|
-
message_class.new(event:, provider_family: provider_family, app_id: app_id).publish(spool: false)
|
|
62
|
-
rescue StandardError => e
|
|
63
|
-
handle_exception(e, level: :warn, handled: true,
|
|
64
|
-
operation: "#{provider_family}.registry.publish_event")
|
|
65
|
-
false
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def publishing_available?
|
|
69
|
-
return false unless registry_event_available?
|
|
70
|
-
return false unless transport_message_available?
|
|
71
|
-
return true unless defined?(::Legion::Transport::Connection)
|
|
72
|
-
return true unless ::Legion::Transport::Connection.respond_to?(:session_open?)
|
|
73
|
-
|
|
74
|
-
::Legion::Transport::Connection.session_open?
|
|
75
|
-
rescue StandardError => e
|
|
76
|
-
handle_exception(e, level: :warn, handled: true,
|
|
77
|
-
operation: "#{provider_family}.registry.publishing_available?")
|
|
78
|
-
false
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def registry_event_available?
|
|
82
|
-
defined?(::Legion::Extensions::Llm::Routing::RegistryEvent)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def transport_message_available?
|
|
86
|
-
return true if message_class_defined?
|
|
87
|
-
return false unless defined?(::Legion::Transport::Message) && defined?(::Legion::Transport::Exchange)
|
|
88
|
-
|
|
89
|
-
require 'legion/extensions/llm/transport/messages/registry_event'
|
|
90
|
-
message_class_defined?
|
|
91
|
-
rescue LoadError => e
|
|
92
|
-
handle_exception(e, level: :warn, handled: true,
|
|
93
|
-
operation: "#{provider_family}.registry.transport_load")
|
|
94
|
-
false
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def message_class_defined?
|
|
98
|
-
defined?(::Legion::Extensions::Llm::Transport::Messages::RegistryEvent)
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def message_class
|
|
102
|
-
::Legion::Extensions::Llm::Transport::Messages::RegistryEvent
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Responses
|
|
7
|
-
# Normalized non-streaming chat provider response.
|
|
8
|
-
class ChatResponse
|
|
9
|
-
attr_reader :content, :thinking, :metadata, :model, :tool_calls, :tokens, :raw, :internal_metadata
|
|
10
|
-
|
|
11
|
-
def initialize(content:, thinking: nil, metadata: {}, model: nil, tool_calls: nil, tokens: nil, raw: nil) # rubocop:disable Metrics/ParameterLists
|
|
12
|
-
extraction = ThinkingExtractor.extract(content, metadata: metadata)
|
|
13
|
-
|
|
14
|
-
@content = extraction.content
|
|
15
|
-
@thinking = thinking || extraction.thinking
|
|
16
|
-
@metadata = extraction.metadata
|
|
17
|
-
@internal_metadata = metadata.to_h
|
|
18
|
-
@model = model
|
|
19
|
-
@tool_calls = tool_calls
|
|
20
|
-
@tokens = tokens
|
|
21
|
-
@raw = raw
|
|
22
|
-
|
|
23
|
-
freeze
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def to_h
|
|
27
|
-
{
|
|
28
|
-
content: content,
|
|
29
|
-
metadata: metadata,
|
|
30
|
-
model: model,
|
|
31
|
-
tool_calls: tool_calls,
|
|
32
|
-
tokens: tokens
|
|
33
|
-
}.compact
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def to_internal_h
|
|
37
|
-
to_h.merge(thinking: thinking, metadata: internal_metadata, raw: raw).compact
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Responses
|
|
7
|
-
# Normalized embedding provider response.
|
|
8
|
-
class EmbeddingResponse
|
|
9
|
-
attr_reader :vectors, :model, :tokens, :metadata, :raw
|
|
10
|
-
|
|
11
|
-
def initialize(vectors:, model:, tokens: nil, metadata: {}, raw: nil)
|
|
12
|
-
@vectors = vectors
|
|
13
|
-
@model = model
|
|
14
|
-
@tokens = tokens
|
|
15
|
-
@metadata = ThinkingExtractor.extract(nil, metadata: metadata).metadata
|
|
16
|
-
@internal_metadata = metadata.to_h
|
|
17
|
-
@raw = raw
|
|
18
|
-
|
|
19
|
-
freeze
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def to_h
|
|
23
|
-
{
|
|
24
|
-
vectors: vectors,
|
|
25
|
-
model: model,
|
|
26
|
-
tokens: tokens,
|
|
27
|
-
metadata: metadata
|
|
28
|
-
}.compact
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def to_internal_h
|
|
32
|
-
to_h.merge(metadata: @internal_metadata, raw: raw).compact
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Responses
|
|
7
|
-
# Normalized streaming provider response chunk.
|
|
8
|
-
class StreamChunk
|
|
9
|
-
attr_reader :content, :thinking, :metadata, :model, :tool_calls, :tokens, :raw, :internal_metadata
|
|
10
|
-
|
|
11
|
-
def initialize(content: nil, thinking: nil, metadata: {}, model: nil, tool_calls: nil, tokens: nil, raw: nil) # rubocop:disable Metrics/ParameterLists
|
|
12
|
-
extraction = ThinkingExtractor.extract(content, metadata: metadata)
|
|
13
|
-
|
|
14
|
-
@content = extraction.content
|
|
15
|
-
@thinking = thinking || extraction.thinking
|
|
16
|
-
@metadata = extraction.metadata
|
|
17
|
-
@internal_metadata = metadata.to_h
|
|
18
|
-
@model = model
|
|
19
|
-
@tool_calls = tool_calls
|
|
20
|
-
@tokens = tokens
|
|
21
|
-
@raw = raw
|
|
22
|
-
|
|
23
|
-
freeze
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def to_h
|
|
27
|
-
{
|
|
28
|
-
content: content,
|
|
29
|
-
metadata: metadata,
|
|
30
|
-
model: model,
|
|
31
|
-
tool_calls: tool_calls,
|
|
32
|
-
tokens: tokens
|
|
33
|
-
}.compact
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def to_internal_h
|
|
37
|
-
to_h.merge(thinking: thinking, metadata: internal_metadata, raw: raw).compact
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Legion
|
|
4
|
-
module Extensions
|
|
5
|
-
module Llm
|
|
6
|
-
module Routing
|
|
7
|
-
# DEPRECATED (Phase 4 removal): retained only for protocol-v2 fleet
|
|
8
|
-
# compatibility. The new canonical execution identity comes solely from
|
|
9
|
-
# Inventory::Identity (off:v1:/lane:v1:); do not reinterpret an old
|
|
10
|
-
# five-part lane as a new lane:v1: identity.
|
|
11
|
-
# Builds stable fleet lane keys from provider-neutral model offerings.
|
|
12
|
-
module LaneKey
|
|
13
|
-
module_function
|
|
14
|
-
|
|
15
|
-
def for(offering, prefix: 'llm.fleet', include_context: true, include_fingerprint: false)
|
|
16
|
-
parts = [prefix, lane_kind(offering), model_slug(lane_model(offering))]
|
|
17
|
-
parts << "ctx#{offering.context_window}" if include_context && offering.inference? && offering.context_window
|
|
18
|
-
parts.push('elig', eligibility_fingerprint(offering)) if include_fingerprint
|
|
19
|
-
parts.join('.')
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def lane_model(offering)
|
|
23
|
-
return offering.canonical_model_alias if offering.respond_to?(:canonical_model_alias) &&
|
|
24
|
-
offering.canonical_model_alias.to_s != ''
|
|
25
|
-
|
|
26
|
-
offering.model
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def lane_kind(offering)
|
|
30
|
-
offering.embedding? ? 'embed' : 'inference'
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def model_slug(model)
|
|
34
|
-
model.to_s.downcase.gsub(/[^a-z0-9]+/, '-').gsub(/\A-+|-+\z/, '')
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def eligibility_fingerprint(offering)
|
|
38
|
-
canonical = {
|
|
39
|
-
usage_type: offering.usage_type,
|
|
40
|
-
capabilities: offering.capabilities.sort,
|
|
41
|
-
context_window: offering.context_window,
|
|
42
|
-
max_output_tokens: offering.max_output_tokens,
|
|
43
|
-
policy_tags: offering.policy_tags.sort,
|
|
44
|
-
metadata: fingerprint_metadata(offering.metadata)
|
|
45
|
-
}
|
|
46
|
-
Digest::SHA1.hexdigest(Legion::JSON.generate(canonical))[0, 10]
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def fingerprint_metadata(metadata)
|
|
50
|
-
metadata.fetch(:eligibility, {})
|
|
51
|
-
.to_h
|
|
52
|
-
.transform_keys(&:to_sym)
|
|
53
|
-
.reject { |key, _| sensitive_fingerprint_key?(key) }
|
|
54
|
-
.sort
|
|
55
|
-
.to_h
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def sensitive_fingerprint_key?(key)
|
|
59
|
-
%i[credential credentials endpoint endpoint_url identity path prompt reply_to secret secrets token
|
|
60
|
-
url].include?(key)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|