legion-llm 0.6.15 → 0.6.16

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: 89101e37722ff5c0c96015b465b3231567b5636c796b49f94b4537fb2e9d0c9d
4
- data.tar.gz: 383eb42f22009930320e9a952e165f1e0a8cb95530c4c2444e70fb30d2f804fe
3
+ metadata.gz: 0fe6246015e9967dec5bbe428ab2cb5869b4a359c4a5b40c0a8206d0fe7b516d
4
+ data.tar.gz: f803de963b1887d064506187c3d7c8fa00b437613f5456bcbf87adae1d53a3a4
5
5
  SHA512:
6
- metadata.gz: 501e4e9051fcaa678c85f47841bdbb2a97b4de433ce8a8be5767f11f4c2a1ccbebcedaa0a9962db82c92c49d209cf4dc71b9027cccc91ec5f8f458b537ee8437
7
- data.tar.gz: f822fa3df241dc19568b70e02198a86404baecc4ab1238ef45f5ac7d6079821e7b52db924ac45db42d23df5f70c15285d576d85e581efd609f79f49fa41471bc
6
+ metadata.gz: e7f65ec573b55b840b7b07b9a9c83c7aa189696b162936e63ad65ddf508be3240eb056fc70d3bbbad532eb2f3db841c4facd3efb1df30e9bf816b4f37d8a33e0
7
+ data.tar.gz: 6a823d728ab547b3ccab050d7a01e9825cb3764446eecb2f32b71ce1c7ff73f7da7f167e9198b9a45f4941b7ed8113c3aac3f1ca95c0a76b46a5b502dd713147
data/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.6.16] - 2026-04-03
6
+
7
+ ### Fixed
8
+ - MCP tool adapter now correctly deserializes `MCP::Tool::Response` objects instead of returning raw `#<Response:0x...>` strings
9
+ - Removed `is_a?(Class)` guard in executor `inject_ruby_llm_tools` that silently dropped MCP adapter instances
10
+ - Added `cached_mcp_tools` to inference route with lazy caching (only persists when tools are available)
11
+
5
12
  ## [0.6.15] - 2026-04-03
6
13
 
7
14
  ### Changed
@@ -675,7 +675,7 @@ module Legion
675
675
 
676
676
  def inject_ruby_llm_tools(session)
677
677
  (@request.tools || []).each do |tool|
678
- session.with_tool(tool) if tool.is_a?(Class)
678
+ session.with_tool(tool)
679
679
  end
680
680
 
681
681
  ToolRegistry.tools.each { |tool| session.with_tool(tool) } if defined?(ToolRegistry)
@@ -35,13 +35,7 @@ module Legion
35
35
  def execute(**args)
36
36
  log.info("[llm][tools] adapter.execute name=#{@tool_name} arguments=#{summarize_payload(args)}")
37
37
  result = @mcp_tool_class.call(**args)
38
- content = if result.is_a?(Hash) && result[:content]
39
- result[:content].map { |c| c[:text] || c['text'] }.compact.join("\n")
40
- elsif result.is_a?(String)
41
- result
42
- else
43
- result.to_s
44
- end
38
+ content = extract_content(result)
45
39
  log.info("[llm][tools] adapter.result name=#{@tool_name} output=#{summarize_payload(content)}")
46
40
  content
47
41
  rescue StandardError => e
@@ -51,6 +45,21 @@ module Legion
51
45
 
52
46
  private
53
47
 
48
+ def extract_content(result)
49
+ # MCP::Tool::Response — has .content array of {type: 'text', text: '...'}
50
+ if result.respond_to?(:content) && result.content.is_a?(Array)
51
+ result.content.filter_map { |c| c[:text] || c['text'] || c.to_s }.join("\n")
52
+ elsif result.is_a?(Hash) && result[:content].is_a?(Array)
53
+ result[:content].filter_map { |c| c[:text] || c['text'] }.join("\n")
54
+ elsif result.is_a?(Hash)
55
+ Legion::JSON.dump(result)
56
+ elsif result.is_a?(String)
57
+ result
58
+ else
59
+ result.to_s
60
+ end
61
+ end
62
+
54
63
  def summarize_payload(payload)
55
64
  payload.to_s[0, 200].inspect
56
65
  end
@@ -214,6 +214,34 @@ module Legion
214
214
  end
215
215
  # rubocop:enable Metrics/BlockLength
216
216
 
217
+ define_method(:cached_mcp_tools) do
218
+ @@llm_cached_mcp_tools ||= begin # rubocop:disable Style/ClassVars
219
+ all = []
220
+ if defined?(Legion::MCP) && Legion::MCP.respond_to?(:server)
221
+ Legion::MCP.server
222
+ if defined?(Legion::MCP::Server) && Legion::MCP::Server.respond_to?(:tool_registry)
223
+ require 'legion/llm/pipeline/mcp_tool_adapter' unless defined?(Legion::LLM::Pipeline::McpToolAdapter)
224
+ Legion::MCP::Server.tool_registry.each do |tc|
225
+ all << Legion::LLM::Pipeline::McpToolAdapter.new(tc)
226
+ rescue StandardError
227
+ nil
228
+ end
229
+ end
230
+ end
231
+ always_names = %w[legion_do legion_get_status legion_run_task legion_describe_runner
232
+ legion_list_extensions legion_get_extension legion_list_tasks
233
+ legion_get_task legion_get_task_logs legion_query_knowledge
234
+ legion_knowledge_health legion_knowledge_context legion_list_workers
235
+ legion_show_worker legion_mesh_status legion_list_peers
236
+ legion_tools legion_search_sessions]
237
+ {
238
+ always: all.select { |t| always_names.include?(t.name) }.freeze,
239
+ deferred: all.reject { |t| always_names.include?(t.name) }.freeze,
240
+ all: all.freeze
241
+ }.freeze
242
+ end
243
+ end
244
+
217
245
  define_method(:extract_tool_calls) do |pipeline_response|
218
246
  tools_data = pipeline_response.tools
219
247
  return [] unless tools_data.is_a?(Array) && !tools_data.empty?
@@ -477,6 +505,17 @@ module Legion
477
505
  build_client_tool_class(ts[:name].to_s, ts[:description].to_s, ts[:parameters] || ts[:input_schema])
478
506
  end
479
507
 
508
+ # Inject daemon MCP tools alongside client tools
509
+ all_tools = tool_declarations.dup
510
+ begin
511
+ mcp_tools = cached_mcp_tools
512
+ mcp_to_inject = Array(requested_tools).empty? ? mcp_tools[:always] : mcp_tools[:all]
513
+ all_tools.concat(mcp_to_inject) if mcp_to_inject&.any?
514
+ log.unknown "[llm][api] inference tools client=#{tool_declarations.size} mcp=#{mcp_to_inject&.size || 0} total=#{all_tools.size}"
515
+ rescue StandardError => e
516
+ handle_exception(e, level: :warn, operation: 'llm.routes.mcp_tool_injection', request_id: request_id)
517
+ end
518
+
480
519
  streaming = body[:stream] == true && request.preferred_type.to_s.include?('text/event-stream')
481
520
  normalized_caller = caller_context.respond_to?(:transform_keys) ? caller_context.transform_keys(&:to_sym) : {}
482
521
  safe_caller_fields = normalized_caller.slice(:context, :session_id, :trace_id)
@@ -502,7 +541,7 @@ module Legion
502
541
  messages: messages,
503
542
  system: body[:system],
504
543
  routing: { provider: provider, model: model },
505
- tools: tool_declarations,
544
+ tools: all_tools,
506
545
  caller: effective_caller,
507
546
  conversation_id: conversation_id,
508
547
  metadata: { requested_tools: requested_tools },
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Legion
4
4
  module LLM
5
- VERSION = '0.6.15'
5
+ VERSION = '0.6.16'
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.6.15
4
+ version: 0.6.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity