lex-llm 0.4.8 → 0.4.9
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 +7 -0
- data/lib/legion/extensions/llm/attachment.rb +3 -3
- data/lib/legion/extensions/llm/configuration.rb +3 -1
- data/lib/legion/extensions/llm/connection.rb +42 -12
- data/lib/legion/extensions/llm/credential_sources.rb +19 -6
- data/lib/legion/extensions/llm/fleet/provider_responder.rb +9 -1
- data/lib/legion/extensions/llm/fleet/publish_safety.rb +2 -0
- data/lib/legion/extensions/llm/fleet/settings.rb +8 -2
- data/lib/legion/extensions/llm/fleet/token_validator.rb +5 -0
- data/lib/legion/extensions/llm/fleet/worker_execution.rb +6 -1
- data/lib/legion/extensions/llm/models.rb +4 -4
- data/lib/legion/extensions/llm/provider.rb +66 -3
- data/lib/legion/extensions/llm/stream_accumulator.rb +3 -5
- data/lib/legion/extensions/llm/streaming.rb +8 -4
- data/lib/legion/extensions/llm/tool.rb +4 -2
- data/lib/legion/extensions/llm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2b7839d1fcb47e176aa62970f3ec40d04b9e25095418285d3cf6a5d492ccb8d
|
|
4
|
+
data.tar.gz: f444cd5054325a007f05749d6d4a2ca0933bf1f5b7004b2585daaa97745eb337
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7412bc0234b379941ae045fa826f267d97b1577dba15ee7268957d62c999808e27392d9acf65d0bf464c31aa7365e3912642a4b0041eb92294f108c4203d8f38
|
|
7
|
+
data.tar.gz: a90e99a7c61f6fda2ffc4d2e4fd6b2fcba6f69bf75d5da526cacef550c345aadd157ea1ee6efeae59e3f69160a8e6ad92237a31997e9ef259c69dc29a6db01e6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.9 - 2026-05-13
|
|
4
|
+
|
|
5
|
+
- Route provider, tool, streaming, model, attachment, connection, credential, and fleet diagnostics through `Legion::Logging::Helper`.
|
|
6
|
+
- Replace temporary provider and stream probes with helper-backed debug logs that preserve model, tool, parameter, and header-key context without stdout or fatal-level noise.
|
|
7
|
+
- Add handled debug exception logging around provider discovery, credential probes, and fleet cleanup fallbacks.
|
|
8
|
+
- Fix provider request debug logging when callers pass tools as a hash.
|
|
9
|
+
|
|
3
10
|
## 0.4.8 - 2026-05-11
|
|
4
11
|
|
|
5
12
|
- Set `remote_invocable?` to false — this extension does not need remote AMQP topology (exchanges, queues, DLX).
|
|
@@ -8,6 +8,8 @@ module Legion
|
|
|
8
8
|
module Llm
|
|
9
9
|
# A class representing a file attachment.
|
|
10
10
|
class Attachment
|
|
11
|
+
include Legion::Logging::Helper
|
|
12
|
+
|
|
11
13
|
attr_reader :source, :filename, :mime_type
|
|
12
14
|
|
|
13
15
|
def initialize(source, filename: nil)
|
|
@@ -50,9 +52,7 @@ module Legion
|
|
|
50
52
|
elsif io_like?
|
|
51
53
|
load_content_from_io
|
|
52
54
|
else
|
|
53
|
-
|
|
54
|
-
"Source is neither a URL, path, ActiveStorage, nor IO-like: #{@source.class}"
|
|
55
|
-
)
|
|
55
|
+
log.warn { "Source is neither a URL, path, ActiveStorage, nor IO-like: #{@source.class}" }
|
|
56
56
|
nil
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -5,6 +5,8 @@ module Legion
|
|
|
5
5
|
module Llm
|
|
6
6
|
# Global configuration for Legion::Extensions::Llm
|
|
7
7
|
class Configuration
|
|
8
|
+
include Legion::Logging::Helper
|
|
9
|
+
|
|
8
10
|
class << self
|
|
9
11
|
# Declare a single configuration option.
|
|
10
12
|
def option(key, default = nil)
|
|
@@ -68,7 +70,7 @@ module Legion
|
|
|
68
70
|
elsif Regexp.respond_to?(:timeout)
|
|
69
71
|
@log_regexp_timeout = value
|
|
70
72
|
else
|
|
71
|
-
|
|
73
|
+
log.warn { "log_regexp_timeout is not supported on Ruby #{RUBY_VERSION}" }
|
|
72
74
|
@log_regexp_timeout = value
|
|
73
75
|
end
|
|
74
76
|
end
|
|
@@ -5,18 +5,34 @@ module Legion
|
|
|
5
5
|
module Llm
|
|
6
6
|
# Connection class for managing API connections to various providers.
|
|
7
7
|
class Connection
|
|
8
|
+
include Legion::Logging::Helper
|
|
9
|
+
|
|
8
10
|
attr_reader :provider, :connection, :config
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
class << self
|
|
13
|
+
include Legion::Logging::Helper
|
|
14
|
+
|
|
15
|
+
def basic(&)
|
|
16
|
+
logger = faraday_logger
|
|
17
|
+
Faraday.new do |f|
|
|
18
|
+
f.response :logger,
|
|
19
|
+
logger,
|
|
20
|
+
bodies: false,
|
|
21
|
+
errors: true,
|
|
22
|
+
headers: false,
|
|
23
|
+
log_level: :debug
|
|
24
|
+
f.response :raise_error
|
|
25
|
+
yield f if block_given?
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def faraday_logger
|
|
32
|
+
config = Legion::Extensions::Llm.config
|
|
33
|
+
return config.logger if config.respond_to?(:logger) && config.logger
|
|
34
|
+
|
|
35
|
+
log
|
|
20
36
|
end
|
|
21
37
|
end
|
|
22
38
|
|
|
@@ -59,9 +75,10 @@ module Legion
|
|
|
59
75
|
end
|
|
60
76
|
|
|
61
77
|
def setup_logging(faraday)
|
|
78
|
+
logger = faraday_logger
|
|
62
79
|
faraday.response :logger,
|
|
63
|
-
|
|
64
|
-
bodies:
|
|
80
|
+
logger,
|
|
81
|
+
bodies: debug_logger?(logger),
|
|
65
82
|
errors: true,
|
|
66
83
|
headers: false,
|
|
67
84
|
log_level: :debug do |logger|
|
|
@@ -70,6 +87,19 @@ module Legion
|
|
|
70
87
|
end
|
|
71
88
|
end
|
|
72
89
|
|
|
90
|
+
def faraday_logger
|
|
91
|
+
return config.logger if config.respond_to?(:logger) && config.logger
|
|
92
|
+
|
|
93
|
+
log
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def debug_logger?(logger)
|
|
97
|
+
return logger.debug? if logger.respond_to?(:debug?)
|
|
98
|
+
return logger.level.to_i <= Logger::DEBUG if logger.respond_to?(:level)
|
|
99
|
+
|
|
100
|
+
false
|
|
101
|
+
end
|
|
102
|
+
|
|
73
103
|
def logging_regexp(pattern)
|
|
74
104
|
return Regexp.new(pattern) if @config.log_regexp_timeout.nil? || !Regexp.respond_to?(:timeout)
|
|
75
105
|
|
|
@@ -11,6 +11,9 @@ module Legion
|
|
|
11
11
|
# network probes). All methods are pure readers — the calling provider
|
|
12
12
|
# decides what to do with the result.
|
|
13
13
|
module CredentialSources
|
|
14
|
+
include Legion::Logging::Helper
|
|
15
|
+
extend Legion::Logging::Helper
|
|
16
|
+
|
|
14
17
|
CLAUDE_SETTINGS = File.expand_path('~/.claude/settings.json')
|
|
15
18
|
CLAUDE_PROJECT = File.join(Dir.pwd, '.claude', 'settings.json')
|
|
16
19
|
CODEX_AUTH = File.expand_path('~/.codex/auth.json')
|
|
@@ -79,7 +82,9 @@ module Legion
|
|
|
79
82
|
return nil unless defined?(::Legion::Settings)
|
|
80
83
|
|
|
81
84
|
::Legion::Settings.dig(*path)
|
|
82
|
-
rescue StandardError
|
|
85
|
+
rescue StandardError => e
|
|
86
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.setting',
|
|
87
|
+
path: path.map(&:to_s))
|
|
83
88
|
nil
|
|
84
89
|
end
|
|
85
90
|
|
|
@@ -104,7 +109,9 @@ module Legion
|
|
|
104
109
|
end
|
|
105
110
|
end
|
|
106
111
|
true
|
|
107
|
-
rescue StandardError
|
|
112
|
+
rescue StandardError => e
|
|
113
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.socket_open',
|
|
114
|
+
host:, port:)
|
|
108
115
|
false
|
|
109
116
|
ensure
|
|
110
117
|
sock&.close
|
|
@@ -120,7 +127,9 @@ module Legion
|
|
|
120
127
|
end
|
|
121
128
|
response = conn.get(path)
|
|
122
129
|
response.status >= 200 && response.status < 300
|
|
123
|
-
rescue StandardError
|
|
130
|
+
rescue StandardError => e
|
|
131
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.http_ok',
|
|
132
|
+
path:)
|
|
124
133
|
false
|
|
125
134
|
ensure
|
|
126
135
|
conn&.close if conn.respond_to?(:close)
|
|
@@ -168,7 +177,8 @@ module Legion
|
|
|
168
177
|
|
|
169
178
|
normalized = host.delete_prefix('[').delete_suffix(']')
|
|
170
179
|
%w[localhost 127.0.0.1 ::1].include?(normalized)
|
|
171
|
-
rescue URI::InvalidURIError
|
|
180
|
+
rescue URI::InvalidURIError => e
|
|
181
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.localhost')
|
|
172
182
|
false
|
|
173
183
|
end
|
|
174
184
|
|
|
@@ -199,7 +209,9 @@ module Legion
|
|
|
199
209
|
else
|
|
200
210
|
::JSON.parse(raw, symbolize_names: true)
|
|
201
211
|
end
|
|
202
|
-
rescue StandardError
|
|
212
|
+
rescue StandardError => e
|
|
213
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.read_json',
|
|
214
|
+
path:)
|
|
203
215
|
{}
|
|
204
216
|
end
|
|
205
217
|
|
|
@@ -220,7 +232,8 @@ module Legion
|
|
|
220
232
|
return true if exp.nil?
|
|
221
233
|
|
|
222
234
|
exp.to_i > Time.now.to_i
|
|
223
|
-
rescue StandardError
|
|
235
|
+
rescue StandardError => e
|
|
236
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.credential_sources.token_valid')
|
|
224
237
|
true
|
|
225
238
|
end
|
|
226
239
|
|
|
@@ -22,6 +22,9 @@ module Legion
|
|
|
22
22
|
module Fleet
|
|
23
23
|
# Shared implementation for provider-owned fleet responder runners.
|
|
24
24
|
module ProviderResponder
|
|
25
|
+
include Legion::Logging::Helper
|
|
26
|
+
extend Legion::Logging::Helper
|
|
27
|
+
|
|
25
28
|
class ConfigurationError < StandardError; end
|
|
26
29
|
|
|
27
30
|
REQUIRED_FIELDS = %i[
|
|
@@ -71,6 +74,8 @@ module Legion
|
|
|
71
74
|
ack(delivery || properties)
|
|
72
75
|
response
|
|
73
76
|
rescue StandardError => e
|
|
77
|
+
handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.provider_responder.call',
|
|
78
|
+
provider_family:)
|
|
74
79
|
safe_publish_error(envelope, e) if defined?(envelope) && envelope
|
|
75
80
|
reject(delivery || properties, requeue: requeue_error?(e))
|
|
76
81
|
raise
|
|
@@ -165,7 +170,10 @@ module Legion
|
|
|
165
170
|
|
|
166
171
|
def safe_publish_error(envelope, error)
|
|
167
172
|
publish_error(envelope, error)
|
|
168
|
-
rescue StandardError
|
|
173
|
+
rescue StandardError => e
|
|
174
|
+
handle_exception(e, level: :debug, handled: true,
|
|
175
|
+
operation: 'llm.fleet.provider_responder.safe_publish_error',
|
|
176
|
+
error_class: error.class.name)
|
|
169
177
|
nil
|
|
170
178
|
end
|
|
171
179
|
|
|
@@ -6,6 +6,9 @@ module Legion
|
|
|
6
6
|
module Fleet
|
|
7
7
|
# Reads fleet settings from Legion::Settings when available, falling back to lex-llm defaults.
|
|
8
8
|
module Settings
|
|
9
|
+
include Legion::Logging::Helper
|
|
10
|
+
extend Legion::Logging::Helper
|
|
11
|
+
|
|
9
12
|
module_function
|
|
10
13
|
|
|
11
14
|
def value(*path, default:)
|
|
@@ -29,7 +32,8 @@ module Legion
|
|
|
29
32
|
llm = safe_fetch(::Legion::Settings, :llm)
|
|
30
33
|
configured << llm if llm.respond_to?(:key?)
|
|
31
34
|
configured
|
|
32
|
-
rescue StandardError
|
|
35
|
+
rescue StandardError => e
|
|
36
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.fleet.settings.configured')
|
|
33
37
|
[]
|
|
34
38
|
end
|
|
35
39
|
|
|
@@ -49,7 +53,9 @@ module Legion
|
|
|
49
53
|
|
|
50
54
|
def safe_fetch(source, key)
|
|
51
55
|
source[key] || source[key.to_s]
|
|
52
|
-
rescue StandardError
|
|
56
|
+
rescue StandardError => e
|
|
57
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.fleet.settings.safe_fetch',
|
|
58
|
+
key: key.to_s)
|
|
53
59
|
nil
|
|
54
60
|
end
|
|
55
61
|
|
|
@@ -12,6 +12,9 @@ module Legion
|
|
|
12
12
|
module Fleet
|
|
13
13
|
# Verifies responder-side fleet JWTs and prevents replay on provider nodes.
|
|
14
14
|
module TokenValidator
|
|
15
|
+
include Legion::Logging::Helper
|
|
16
|
+
extend Legion::Logging::Helper
|
|
17
|
+
|
|
15
18
|
@seen_jtis = Concurrent::Map.new
|
|
16
19
|
@replay_mutex = Mutex.new
|
|
17
20
|
|
|
@@ -35,6 +38,7 @@ module Legion
|
|
|
35
38
|
rescue TokenError
|
|
36
39
|
raise
|
|
37
40
|
rescue StandardError => e
|
|
41
|
+
handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.token_validator.validate')
|
|
38
42
|
raise TokenError, "fleet token verification failed: #{e.message}"
|
|
39
43
|
end
|
|
40
44
|
|
|
@@ -167,6 +171,7 @@ module Legion
|
|
|
167
171
|
rescue TokenError
|
|
168
172
|
raise
|
|
169
173
|
rescue StandardError => e
|
|
174
|
+
handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.token_validator.signing_key')
|
|
170
175
|
raise TokenError, "no signing key available: #{e.message}"
|
|
171
176
|
end
|
|
172
177
|
|
|
@@ -11,6 +11,9 @@ module Legion
|
|
|
11
11
|
module Fleet
|
|
12
12
|
# Applies responder-side policy and dispatches a fleet request to a local lex-llm provider.
|
|
13
13
|
module WorkerExecution
|
|
14
|
+
include Legion::Logging::Helper
|
|
15
|
+
extend Legion::Logging::Helper
|
|
16
|
+
|
|
14
17
|
class PolicyError < StandardError; end
|
|
15
18
|
|
|
16
19
|
@idempotency_keys = Concurrent::Map.new
|
|
@@ -29,10 +32,12 @@ module Legion
|
|
|
29
32
|
TokenValidator.mark_replay!(claims[:jti]) if claims.is_a?(Hash)
|
|
30
33
|
response
|
|
31
34
|
rescue TokenError => e
|
|
35
|
+
handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.worker_execution.identity')
|
|
32
36
|
release_idempotency!(idempotency_key) if idempotency_key
|
|
33
37
|
release_replay!(claims)
|
|
34
38
|
raise PolicyError, e.message
|
|
35
|
-
rescue StandardError
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
handle_exception(e, level: :warn, handled: false, operation: 'llm.fleet.worker_execution.call')
|
|
36
41
|
release_idempotency!(idempotency_key) if idempotency_key
|
|
37
42
|
release_replay!(claims)
|
|
38
43
|
raise
|
|
@@ -169,7 +169,7 @@ module Legion
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def fetch_models_dev_models(existing_models) # rubocop:disable Metrics/PerceivedComplexity
|
|
172
|
-
|
|
172
|
+
log.info 'Fetching models from models.dev API...'
|
|
173
173
|
|
|
174
174
|
connection = Connection.basic do |f|
|
|
175
175
|
f.request :json
|
|
@@ -202,11 +202,11 @@ module Legion
|
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
def log_provider_fetch(provider_fetch)
|
|
205
|
-
|
|
205
|
+
log.info(
|
|
206
206
|
"Fetching models from providers: #{provider_fetch[:configured_names].join(', ')}"
|
|
207
207
|
)
|
|
208
208
|
provider_fetch[:failed].each do |failure|
|
|
209
|
-
|
|
209
|
+
log.warn(
|
|
210
210
|
"Failed to fetch #{failure[:name]} models (#{failure[:error].class}: #{failure[:error].message}). " \
|
|
211
211
|
'Keeping existing.'
|
|
212
212
|
)
|
|
@@ -216,7 +216,7 @@ module Legion
|
|
|
216
216
|
def log_models_dev_fetch(models_dev_fetch)
|
|
217
217
|
return if models_dev_fetch[:fetched]
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
log.warn('Using cached models.dev data due to fetch failure.')
|
|
220
220
|
end
|
|
221
221
|
|
|
222
222
|
def merge_with_existing(existing_models, provider_fetch, models_dev_fetch)
|
|
@@ -75,6 +75,19 @@ module Legion
|
|
|
75
75
|
def complete(messages, tools:, temperature:, model:, params: {}, headers: {}, schema: nil, thinking: nil,
|
|
76
76
|
tool_prefs: nil, &)
|
|
77
77
|
normalized_temperature = maybe_normalize_temperature(temperature, model)
|
|
78
|
+
log_provider_request(
|
|
79
|
+
messages: messages,
|
|
80
|
+
tools: tools,
|
|
81
|
+
temperature: temperature,
|
|
82
|
+
normalized_temperature: normalized_temperature,
|
|
83
|
+
model: model,
|
|
84
|
+
params: params,
|
|
85
|
+
headers: headers,
|
|
86
|
+
schema: schema,
|
|
87
|
+
thinking: thinking,
|
|
88
|
+
tool_prefs: tool_prefs,
|
|
89
|
+
streaming: block_given?
|
|
90
|
+
)
|
|
78
91
|
|
|
79
92
|
payload = Utils.deep_merge(
|
|
80
93
|
render_payload(
|
|
@@ -330,7 +343,8 @@ module Legion
|
|
|
330
343
|
uri = URI.parse(url)
|
|
331
344
|
Socket.tcp(uri.host, uri.port, connect_timeout: 1).close
|
|
332
345
|
true
|
|
333
|
-
rescue StandardError
|
|
346
|
+
rescue StandardError => e
|
|
347
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.provider.url_reachable', url:)
|
|
334
348
|
false
|
|
335
349
|
end
|
|
336
350
|
|
|
@@ -352,7 +366,8 @@ module Legion
|
|
|
352
366
|
return nil unless defined?(Legion::Cache)
|
|
353
367
|
|
|
354
368
|
cache_local_instance? ? local_cache_get(key) : cache_get(key)
|
|
355
|
-
rescue StandardError
|
|
369
|
+
rescue StandardError => e
|
|
370
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.provider.model_cache_get', key:)
|
|
356
371
|
nil
|
|
357
372
|
end
|
|
358
373
|
|
|
@@ -368,7 +383,8 @@ module Legion
|
|
|
368
383
|
return yield unless defined?(Legion::Cache)
|
|
369
384
|
|
|
370
385
|
cache_local_instance? ? local_cache_fetch(key, ttl: ttl, &) : cache_fetch(key, ttl: ttl, &)
|
|
371
|
-
rescue StandardError
|
|
386
|
+
rescue StandardError => e
|
|
387
|
+
handle_exception(e, level: :debug, handled: true, operation: 'llm.provider.model_cache_fetch', key:)
|
|
372
388
|
yield
|
|
373
389
|
end
|
|
374
390
|
|
|
@@ -593,6 +609,53 @@ module Legion
|
|
|
593
609
|
temperature
|
|
594
610
|
end
|
|
595
611
|
|
|
612
|
+
def log_provider_request(context)
|
|
613
|
+
log.debug do
|
|
614
|
+
"Preparing provider completion: provider=#{slug} model=#{debug_model_id(context[:model])} " \
|
|
615
|
+
"streaming=#{context[:streaming]} messages=#{Array(context[:messages]).size} " \
|
|
616
|
+
"tools=#{debug_tool_names(context[:tools]).inspect} " \
|
|
617
|
+
"temperature=#{context[:temperature].inspect} " \
|
|
618
|
+
"normalized_temperature=#{context[:normalized_temperature].inspect} " \
|
|
619
|
+
"param_keys=#{debug_hash_keys(context[:params]).inspect} " \
|
|
620
|
+
"header_keys=#{debug_hash_keys(context[:headers]).inspect} " \
|
|
621
|
+
"schema=#{debug_value_summary(context[:schema])} " \
|
|
622
|
+
"thinking=#{debug_value_summary(context[:thinking])} " \
|
|
623
|
+
"tool_prefs=#{debug_value_summary(context[:tool_prefs])}"
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
627
|
+
def debug_model_id(model)
|
|
628
|
+
return model.id if model.respond_to?(:id)
|
|
629
|
+
|
|
630
|
+
model
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
def debug_tool_names(tools)
|
|
634
|
+
tool_definitions = tools.is_a?(Hash) ? tools.values : Array(tools)
|
|
635
|
+
|
|
636
|
+
tool_definitions.filter_map do |tool|
|
|
637
|
+
if tool.respond_to?(:name)
|
|
638
|
+
tool.name
|
|
639
|
+
elsif tool.is_a?(Hash)
|
|
640
|
+
tool[:name] || tool['name']
|
|
641
|
+
else
|
|
642
|
+
tool.class.name
|
|
643
|
+
end
|
|
644
|
+
end
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
def debug_hash_keys(value)
|
|
648
|
+
value.respond_to?(:keys) ? value.keys.map(&:to_s).sort : []
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def debug_value_summary(value)
|
|
652
|
+
return 'nil' if value.nil?
|
|
653
|
+
return "#{value.class}(keys=#{debug_hash_keys(value).inspect})" if value.respond_to?(:keys)
|
|
654
|
+
return "#{value.class}(size=#{value.size})" if value.respond_to?(:size)
|
|
655
|
+
|
|
656
|
+
value.class.name
|
|
657
|
+
end
|
|
658
|
+
|
|
596
659
|
def endpoint_methods
|
|
597
660
|
{
|
|
598
661
|
completion: :completion_url,
|
|
@@ -25,7 +25,7 @@ module Legion
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def add(chunk)
|
|
28
|
-
|
|
28
|
+
log.debug { chunk.inspect } if Legion::Extensions::Llm.config.log_stream_debug
|
|
29
29
|
@model_id ||= chunk.model_id
|
|
30
30
|
|
|
31
31
|
@last_content_delta = +''
|
|
@@ -33,7 +33,7 @@ module Legion
|
|
|
33
33
|
handle_chunk_content(chunk)
|
|
34
34
|
append_thinking_from_chunk(chunk)
|
|
35
35
|
count_tokens chunk
|
|
36
|
-
|
|
36
|
+
log.debug { inspect } if Legion::Extensions::Llm.config.log_stream_debug
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def filtered_chunk(chunk) # rubocop:disable Metrics/PerceivedComplexity
|
|
@@ -101,9 +101,7 @@ module Legion
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
def accumulate_tool_calls(new_tool_calls)
|
|
104
|
-
if Legion::Extensions::Llm.config.log_stream_debug
|
|
105
|
-
Legion::Extensions::Llm.logger.debug { "Accumulating tool calls: #{new_tool_calls}" }
|
|
106
|
-
end
|
|
104
|
+
log.debug { "Accumulating tool calls: #{new_tool_calls}" } if Legion::Extensions::Llm.config.log_stream_debug
|
|
107
105
|
new_tool_calls.each_value do |tool_call|
|
|
108
106
|
if tool_call.id
|
|
109
107
|
start_tool_call(tool_call)
|
|
@@ -5,6 +5,9 @@ module Legion
|
|
|
5
5
|
module Llm
|
|
6
6
|
# Handles streaming responses from AI providers.
|
|
7
7
|
module Streaming
|
|
8
|
+
include Legion::Logging::Helper
|
|
9
|
+
extend Legion::Logging::Helper
|
|
10
|
+
|
|
8
11
|
module_function
|
|
9
12
|
|
|
10
13
|
def stream_response(connection, payload, additional_headers = {}, &block)
|
|
@@ -13,6 +16,9 @@ module Legion
|
|
|
13
16
|
response = connection.post stream_url, payload do |req|
|
|
14
17
|
req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
|
|
15
18
|
on_chunk = build_stream_callback(accumulator, block)
|
|
19
|
+
if Legion::Extensions::Llm.config.log_stream_debug
|
|
20
|
+
log.debug { "Stream callback prepared: #{on_chunk.inspect}" }
|
|
21
|
+
end
|
|
16
22
|
if faraday_1?
|
|
17
23
|
req.options[:on_data] = handle_stream(&on_chunk)
|
|
18
24
|
else
|
|
@@ -21,7 +27,7 @@ module Legion
|
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
message = accumulator.to_message(response)
|
|
24
|
-
|
|
30
|
+
log.debug { "Stream completed: #{message.content}" }
|
|
25
31
|
message
|
|
26
32
|
end
|
|
27
33
|
|
|
@@ -57,9 +63,7 @@ module Legion
|
|
|
57
63
|
end
|
|
58
64
|
|
|
59
65
|
def process_stream_chunk(chunk, parser, env, &)
|
|
60
|
-
if Legion::Extensions::Llm.config.log_stream_debug
|
|
61
|
-
Legion::Extensions::Llm.logger.debug { "Received chunk: #{chunk}" }
|
|
62
|
-
end
|
|
66
|
+
log.debug { "Received chunk: #{chunk}" } if Legion::Extensions::Llm.config.log_stream_debug
|
|
63
67
|
|
|
64
68
|
if error_chunk?(chunk)
|
|
65
69
|
handle_error_chunk(chunk, env)
|
|
@@ -19,6 +19,8 @@ module Legion
|
|
|
19
19
|
|
|
20
20
|
# Base class for creating tools that AI models can use
|
|
21
21
|
class Tool
|
|
22
|
+
include Legion::Logging::Helper
|
|
23
|
+
|
|
22
24
|
# Stops conversation continuation after tool execution
|
|
23
25
|
class Halt
|
|
24
26
|
attr_reader :content
|
|
@@ -105,9 +107,9 @@ module Legion
|
|
|
105
107
|
validation_error = validate_keyword_arguments(normalized_args)
|
|
106
108
|
return { error: "Invalid tool arguments: #{validation_error}" } if validation_error
|
|
107
109
|
|
|
108
|
-
|
|
110
|
+
log.debug { "Tool #{name} called with: #{normalized_args.inspect}" }
|
|
109
111
|
result = execute(**normalized_args)
|
|
110
|
-
|
|
112
|
+
log.debug { "Tool #{name} returned: #{result.inspect}" }
|
|
111
113
|
result
|
|
112
114
|
end
|
|
113
115
|
|