legion-llm 0.8.18 → 0.8.21

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1106f652c69b801af983117b4fc97f6bf547dc4d63f1b1df12eb6f1adb6d51d2
4
- data.tar.gz: '00593f91f0467fd63e5a8867017033da483bf0abc4fcfa26641fe97fd66c3674'
3
+ metadata.gz: 5fdd5beb0c0e3a464cd06848369ec4cf9c07beab7a9c1560cfc0d13e1a6dc721
4
+ data.tar.gz: 2539e7e1a1cebcd9ba363ac66f79c08f9314d7477d482f1d17a7bf6185e1fd0b
5
5
  SHA512:
6
- metadata.gz: 30c59046ad40659fa02f3bde8db8807b3a9c3b718365dc1be5d92ca01c9cb84e593c4bcab5027f12a591ee12c3f21671ae3695573f5e85b3c5e9d8b7b1ebf74b
7
- data.tar.gz: 91368ea195a58f8321cca378febcca27a96685a6a1fe78ac432be88731ee03a4ca050b1782f8d1f24da6045b5f136ea070ee14fd4dbb2eb28e23c8db63f28174
6
+ metadata.gz: 377c8498427bbe07d0dd171ce8fba340683ac8fb0286b781092caa24eb19c038fc9e621115bfcbfe12aee25c1f84aa267b4c289b03cd9424a2beee0d31ccf128
7
+ data.tar.gz: 611ab93b2732f10aaac6015b2fc042af6de8963b23012c73a00a6c6a36477639a61763ace1be0e65eb2f38c815cd0418c9d118a644090324a6de53b98cc2f5a6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Legion LLM Changelog
2
2
 
3
+ ## [0.8.21] - 2026-04-22
4
+
5
+ ### Fixed
6
+ - Tool audit events are now published to `llm.audit` exchange via `Audit.emit_tools` after each tool execution completes. Previously `emit_tools` was defined but never called — the `llm.audit.tools` queue was always empty.
7
+ - Metering events now include `request_type: 'chat'` so the routing key is `metering.chat` instead of `metering.` (empty suffix).
8
+
9
+ ## [0.8.20] - 2026-04-22
10
+
11
+ ### Fixed
12
+ - `tool_trigger_defaults[:tool_limit]` raised from 10 to 25. The v0.8.19 reduction from 50→10 was too aggressive — extension tools (Teams, etc.) are only injected via trigger matching, and a limit of 10 crowded them out when multiple extensions matched.
13
+
14
+ ## [0.8.19] - 2026-04-22
15
+
16
+ ### Fixed
17
+ - `Skills::Base#emit_event` passed a positional Hash to `Legion::Events.emit(**payload)`, causing `ArgumentError` on every skill activation. Now uses keyword splat correctly.
18
+ - `file_edit` client tool crashed with `TypeError: no implicit conversion of nil into String` when the LLM passed nil `old_text`/`new_text`. Now returns an error message to the LLM instead of crashing.
19
+ - `tool_trigger_defaults[:tool_limit]` reduced from 50 to 10 to prevent trigger word matching from injecting dozens of unrelated extension tools on normal user messages.
20
+
3
21
  ## [0.8.18] - 2026-04-22
4
22
 
5
23
  ### Fixed
@@ -37,7 +37,7 @@ module Legion
37
37
  end
38
38
  end
39
39
 
40
- def dispatch_client_tool(ref, **kwargs) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity
40
+ def dispatch_client_tool(ref, **kwargs) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
41
41
  case ref
42
42
  when 'sh'
43
43
  cmd = kwargs[:command] || kwargs[:cmd] || kwargs.values.first.to_s
@@ -55,8 +55,10 @@ module Legion
55
55
  path = kwargs[:path] || kwargs[:file_path]
56
56
  old_text = kwargs[:old_text] || kwargs[:search]
57
57
  new_text = kwargs[:new_text] || kwargs[:replace]
58
+ return 'file_edit error: old_text is required' if old_text.nil? || old_text.empty?
59
+
58
60
  content = ::File.read(path, encoding: 'utf-8')
59
- content.sub!(old_text, new_text)
61
+ content.sub!(old_text, new_text || '')
60
62
  ::File.write(path, content)
61
63
  "Edited #{path}"
62
64
  when 'list_directory'
@@ -179,6 +179,7 @@ module Legion
179
179
  "conversation_id=#{pipeline_response.conversation_id || conversation_id || 'none'} " \
180
180
  "provider=#{routing[:provider] || routing['provider'] || 'unknown'} " \
181
181
  "model=#{routing[:model] || routing['model'] || 'unknown'} " \
182
+ "input_tokens=#{token_value(tokens, :input) || 0} output_tokens=#{token_value(tokens, :output) || 0} " \
182
183
  "tool_calls=#{extract_tool_calls(pipeline_response).size} " \
183
184
  "tool_executions=#{Array(pipeline_response.timeline).count { |event| event[:key].to_s.start_with?('tool:execute:') }} " \
184
185
  "stop_reason=#{pipeline_response.stop&.dig(:reason) || 'unknown'} stream=true"
@@ -204,6 +205,7 @@ module Legion
204
205
  "conversation_id=#{pipeline_response.conversation_id || conversation_id || 'none'} " \
205
206
  "provider=#{routing[:provider] || routing['provider'] || 'unknown'} " \
206
207
  "model=#{routing[:model] || routing['model'] || 'unknown'} " \
208
+ "input_tokens=#{token_value(tokens, :input) || 0} output_tokens=#{token_value(tokens, :output) || 0} " \
207
209
  "tool_calls=#{tool_calls.size} " \
208
210
  "tool_executions=#{Array(pipeline_response.timeline).count { |event| event[:key].to_s.start_with?('tool:execute:') }} " \
209
211
  "stop_reason=#{pipeline_response.stop&.dig(:reason) || 'unknown'} stream=false"
@@ -842,6 +842,33 @@ module Legion
842
842
  result: result_str[0, 4096], result_size: result_str.bytesize,
843
843
  started_at: started_at, finished_at: finished_at, duration_ms: duration_ms
844
844
  )
845
+
846
+ publish_tool_audit(tc_id, tc_name, result_str, is_error, duration_ms, started_at, finished_at)
847
+ end
848
+
849
+ def publish_tool_audit(tc_id, tc_name, result_str, is_error, duration_ms, started_at, finished_at)
850
+ Legion::LLM::Audit.emit_tools(
851
+ request_id: @request.id,
852
+ conversation_id: @request.conversation_id,
853
+ exchange_id: @exchange_id,
854
+ tool_name: tc_name,
855
+ tool_call: {
856
+ id: tc_id,
857
+ name: tc_name,
858
+ status: is_error ? :error : :success,
859
+ duration_ms: duration_ms,
860
+ started_at: started_at,
861
+ finished_at: finished_at
862
+ },
863
+ result: result_str[0, 4096],
864
+ caller: @request.caller,
865
+ classification: @request.classification,
866
+ tracing: @tracing,
867
+ timestamp: finished_at,
868
+ request_type: 'tool'
869
+ )
870
+ rescue StandardError => e
871
+ handle_exception(e, level: :warn, operation: 'llm.pipeline.publish_tool_audit', tool_name: tc_name)
845
872
  end
846
873
 
847
874
  def tool_call_field(tool_call, field)
@@ -1004,6 +1031,7 @@ module Legion
1004
1031
  provider: @resolved_provider,
1005
1032
  model_id: @resolved_model,
1006
1033
  tier: tier,
1034
+ request_type: 'chat',
1007
1035
  input_tokens: input_tokens,
1008
1036
  output_tokens: output_tokens,
1009
1037
  latency_ms: latency_ms
@@ -285,7 +285,7 @@ module Legion
285
285
  def self.tool_trigger_defaults
286
286
  {
287
287
  scan_depth: 10,
288
- tool_limit: 50
288
+ tool_limit: 25
289
289
  }
290
290
  end
291
291
 
@@ -246,7 +246,7 @@ module Legion
246
246
  def emit_event(conv_id, event, **payload)
247
247
  return unless conv_id
248
248
 
249
- Legion::Events.emit(event, { conversation_id: conv_id }.merge(payload))
249
+ Legion::Events.emit(event, conversation_id: conv_id, **payload)
250
250
  end
251
251
 
252
252
  protected
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.8.18'
5
+ VERSION = '0.8.21'
6
6
  end
7
7
  end
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.8.18
4
+ version: 0.8.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity