ruby_llm 2.0.0 → 2.1.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/README.md +65 -8
- data/lib/generators/ruby_llm/generator_helpers.rb +68 -6
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +1 -0
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +36 -3
- data/lib/generators/ruby_llm/provider/scaffold.rb +32 -22
- data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +1 -5
- data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +1 -1
- data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +1 -1
- data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +1 -1
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +78 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +6 -163
- data/lib/ruby_llm/accounting/usage.rb +86 -49
- data/lib/ruby_llm/active_record/acts_as.rb +3 -0
- data/lib/ruby_llm/active_record/attachment_helpers.rb +50 -25
- data/lib/ruby_llm/active_record/batch.rb +1 -1
- data/lib/ruby_llm/active_record/chat_methods.rb +136 -40
- data/lib/ruby_llm/active_record/mcp_credential.rb +41 -0
- data/lib/ruby_llm/active_record/message_methods.rb +32 -7
- data/lib/ruby_llm/active_record/model.rb +1 -1
- data/lib/ruby_llm/active_record/provider_file.rb +64 -0
- data/lib/ruby_llm/active_record/record.rb +19 -0
- data/lib/ruby_llm/active_record/tool_call.rb +1 -1
- data/lib/ruby_llm/active_record/usage.rb +53 -3
- data/lib/ruby_llm/agent.rb +198 -33
- data/lib/ruby_llm/aliases.json +66 -8
- data/lib/ruby_llm/attachment.rb +54 -10
- data/lib/ruby_llm/batch.rb +26 -3
- data/lib/ruby_llm/chat/tool_concurrency.rb +47 -23
- data/lib/ruby_llm/chat/tool_search.rb +26 -0
- data/lib/ruby_llm/chat.rb +488 -82
- data/lib/ruby_llm/choice.rb +38 -0
- data/lib/ruby_llm/configuration.rb +36 -11
- data/lib/ruby_llm/context.rb +45 -32
- data/lib/ruby_llm/cost.rb +42 -1
- data/lib/ruby_llm/embedding.rb +9 -2
- data/lib/ruby_llm/error.rb +34 -2
- data/lib/ruby_llm/evaluation/assertions.rb +40 -0
- data/lib/ruby_llm/evaluation/case.rb +52 -0
- data/lib/ruby_llm/evaluation/dataset.rb +65 -0
- data/lib/ruby_llm/evaluation/evaluator.rb +126 -0
- data/lib/ruby_llm/evaluation/evidence.rb +125 -0
- data/lib/ruby_llm/evaluation/minitest.rb +26 -0
- data/lib/ruby_llm/evaluation/report.rb +103 -0
- data/lib/ruby_llm/evaluation/result.rb +75 -0
- data/lib/ruby_llm/evaluation/reviewer.rb +20 -0
- data/lib/ruby_llm/evaluation/rspec.rb +23 -0
- data/lib/ruby_llm/evaluation/runner.rb +93 -0
- data/lib/ruby_llm/evaluation/trial.rb +101 -0
- data/lib/ruby_llm/evaluation.rb +289 -0
- data/lib/ruby_llm/image.rb +12 -4
- data/lib/ruby_llm/judge/builder.rb +51 -0
- data/lib/ruby_llm/judge/data.rb +43 -0
- data/lib/ruby_llm/judge/question.rb +101 -0
- data/lib/ruby_llm/judge.rb +179 -0
- data/lib/ruby_llm/judgment.rb +125 -0
- data/lib/ruby_llm/mcp/apps.rb +25 -0
- data/lib/ruby_llm/mcp/client.rb +213 -0
- data/lib/ruby_llm/mcp/collection.rb +56 -0
- data/lib/ruby_llm/mcp/content.rb +44 -0
- data/lib/ruby_llm/mcp/error.rb +27 -0
- data/lib/ruby_llm/mcp/http.rb +379 -0
- data/lib/ruby_llm/mcp/input_request.rb +118 -0
- data/lib/ruby_llm/mcp/input_required_error.rb +29 -0
- data/lib/ruby_llm/mcp/listener.rb +168 -0
- data/lib/ruby_llm/mcp/oauth/key.rb +78 -0
- data/lib/ruby_llm/mcp/oauth/memory_store.rb +28 -0
- data/lib/ruby_llm/mcp/oauth/proofs.rb +39 -0
- data/lib/ruby_llm/mcp/oauth.rb +653 -0
- data/lib/ruby_llm/mcp/param_headers.rb +83 -0
- data/lib/ruby_llm/mcp/prompt.rb +71 -0
- data/lib/ruby_llm/mcp/resource.rb +84 -0
- data/lib/ruby_llm/mcp/resource_template.rb +99 -0
- data/lib/ruby_llm/mcp/result.rb +76 -0
- data/lib/ruby_llm/mcp/session_expired.rb +10 -0
- data/lib/ruby_llm/mcp/stdio.rb +250 -0
- data/lib/ruby_llm/mcp/task.rb +151 -0
- data/lib/ruby_llm/mcp/tool.rb +130 -0
- data/lib/ruby_llm/mcp.rb +1101 -0
- data/lib/ruby_llm/message.rb +41 -6
- data/lib/ruby_llm/model.rb +2 -1
- data/lib/ruby_llm/models/aliases.rb +5 -2
- data/lib/ruby_llm/models/lookup.rb +80 -0
- data/lib/ruby_llm/models/registry.rb +2 -2
- data/lib/ruby_llm/models/schema.rb +1 -1
- data/lib/ruby_llm/models.json +23082 -10158
- data/lib/ruby_llm/models.rb +20 -69
- data/lib/ruby_llm/moderation.rb +7 -2
- data/lib/ruby_llm/ocr.rb +7 -2
- data/lib/ruby_llm/open_telemetry/attributes.rb +111 -0
- data/lib/ruby_llm/open_telemetry.rb +82 -0
- data/lib/ruby_llm/probability.rb +30 -0
- data/lib/ruby_llm/progress.rb +44 -0
- data/lib/ruby_llm/prompt/context.rb +55 -0
- data/lib/ruby_llm/prompt.rb +1 -1
- data/lib/ruby_llm/protocol/binary_streaming.rb +1 -1
- data/lib/ruby_llm/protocol/request_shapes/analysis.rb +190 -0
- data/lib/ruby_llm/protocol/request_shapes.rb +183 -0
- data/lib/ruby_llm/protocol/stored_uploads.rb +62 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +9 -25
- data/lib/ruby_llm/protocol/streaming.rb +38 -31
- data/lib/ruby_llm/protocol.rb +192 -40
- data/lib/ruby_llm/protocols/anthropic/chat.rb +39 -32
- data/lib/ruby_llm/protocols/anthropic/media.rb +6 -2
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -1
- data/lib/ruby_llm/protocols/anthropic/request_shapes.rb +70 -0
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +23 -9
- data/lib/ruby_llm/protocols/anthropic/tools.rb +43 -0
- data/lib/ruby_llm/protocols/anthropic.rb +20 -1
- data/lib/ruby_llm/protocols/bedrock/async_videos.rb +9 -17
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +8 -9
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +1 -17
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +5 -7
- data/lib/ruby_llm/protocols/chat_completions/media.rb +34 -8
- data/lib/ruby_llm/protocols/chat_completions/request_shapes.rb +121 -0
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +3 -0
- data/lib/ruby_llm/protocols/chat_completions.rb +1 -0
- data/lib/ruby_llm/protocols/cohere/batch_requests.rb +0 -5
- data/lib/ruby_llm/protocols/cohere/batches.rb +3 -7
- data/lib/ruby_llm/protocols/cohere/chat.rb +8 -0
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -4
- data/lib/ruby_llm/protocols/cohere/media.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/ocr.rb +1 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +1 -0
- data/lib/ruby_llm/protocols/cohere/tools.rb +5 -1
- data/lib/ruby_llm/protocols/cohere.rb +1 -0
- data/lib/ruby_llm/protocols/converse/chat.rb +22 -131
- data/lib/ruby_llm/protocols/converse/media.rb +16 -2
- data/lib/ruby_llm/protocols/converse/reasoning.rb +194 -0
- data/lib/ruby_llm/protocols/converse/request_shapes.rb +99 -0
- data/lib/ruby_llm/protocols/converse/streaming.rb +2 -2
- data/lib/ruby_llm/protocols/converse/thinking_stream.rb +2 -1
- data/lib/ruby_llm/protocols/converse.rb +3 -5
- data/lib/ruby_llm/protocols/deepseek/files.rb +2 -9
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +1 -7
- data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +11 -17
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +1 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +50 -21
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +2 -2
- data/lib/ruby_llm/protocols/gemini/files.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/images.rb +5 -1
- data/lib/ruby_llm/protocols/gemini/media.rb +15 -3
- data/lib/ruby_llm/protocols/gemini/request_shapes.rb +111 -0
- data/lib/ruby_llm/protocols/gemini/streaming.rb +2 -1
- data/lib/ruby_llm/protocols/gemini/tools.rb +17 -11
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -0
- data/lib/ruby_llm/protocols/gemini/videos.rb +7 -2
- data/lib/ruby_llm/protocols/gemini.rb +1 -0
- data/lib/ruby_llm/protocols/gpustack/videos.rb +7 -9
- data/lib/ruby_llm/protocols/interactions/chat.rb +46 -18
- data/lib/ruby_llm/protocols/interactions/content.rb +9 -2
- data/lib/ruby_llm/protocols/interactions/request_shapes.rb +62 -0
- data/lib/ruby_llm/protocols/interactions/streaming.rb +15 -0
- data/lib/ruby_llm/protocols/interactions/tools.rb +28 -4
- data/lib/ruby_llm/protocols/interactions/transcription.rb +1 -11
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +2 -11
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +2 -2
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +2 -6
- data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +2 -8
- data/lib/ruby_llm/protocols/invoke_model.rb +1 -4
- data/lib/ruby_llm/protocols/mistral/content.rb +13 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +11 -1
- data/lib/ruby_llm/protocols/mistral/conversations/images.rb +5 -1
- data/lib/ruby_llm/protocols/mistral/conversations/request_shapes.rb +45 -0
- data/lib/ruby_llm/protocols/mistral/conversations.rb +1 -0
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +11 -1
- data/lib/ruby_llm/protocols/openai/decisions.rb +135 -0
- data/lib/ruby_llm/protocols/openai/files.rb +0 -7
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -8
- data/lib/ruby_llm/protocols/openrouter/responses.rb +5 -1
- data/lib/ruby_llm/protocols/perplexity/agent.rb +116 -0
- data/lib/ruby_llm/protocols/perplexity/router.rb +2 -43
- data/lib/ruby_llm/protocols/responses/chat.rb +56 -22
- data/lib/ruby_llm/protocols/responses/media.rb +11 -7
- data/lib/ruby_llm/protocols/responses/request_shapes.rb +70 -0
- data/lib/ruby_llm/protocols/responses/streaming.rb +23 -7
- data/lib/ruby_llm/protocols/responses/token_counting.rb +2 -2
- data/lib/ruby_llm/protocols/responses/tools.rb +27 -0
- data/lib/ruby_llm/protocols/responses.rb +1 -0
- data/lib/ruby_llm/protocols/system_one/judgments.rb +42 -0
- data/lib/ruby_llm/protocols/system_one/models.rb +27 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +55 -0
- data/lib/ruby_llm/protocols/system_one.rb +27 -0
- data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +2 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +34 -6
- data/lib/ruby_llm/protocols/vertexai/ranking.rb +9 -30
- data/lib/ruby_llm/protocols/vertexai/research.rb +9 -41
- data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +2 -5
- data/lib/ruby_llm/provider.rb +87 -15
- data/lib/ruby_llm/providers/anthropic.rb +23 -0
- data/lib/ruby_llm/providers/azure/chat.rb +4 -0
- data/lib/ruby_llm/providers/azure/images.rb +2 -2
- data/lib/ruby_llm/providers/azure/media.rb +8 -3
- data/lib/ruby_llm/providers/azure/videos.rb +7 -2
- data/lib/ruby_llm/providers/azure.rb +24 -4
- data/lib/ruby_llm/providers/bedrock/auth.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -3
- data/lib/ruby_llm/providers/bedrock/mantle.rb +5 -9
- data/lib/ruby_llm/providers/bedrock.rb +38 -6
- data/lib/ruby_llm/providers/deepseek/chat.rb +6 -1
- data/lib/ruby_llm/providers/deepseek/responses.rb +6 -0
- data/lib/ruby_llm/providers/deepseek.rb +4 -0
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -5
- data/lib/ruby_llm/providers/hetzner/chat.rb +39 -0
- data/lib/ruby_llm/providers/hetzner.rb +41 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +16 -12
- data/lib/ruby_llm/providers/mistral/media.rb +8 -4
- data/lib/ruby_llm/providers/mistral/ocr.rb +1 -5
- data/lib/ruby_llm/providers/ollama/judgments.rb +26 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -2
- data/lib/ruby_llm/providers/ollama/models.rb +2 -0
- data/lib/ruby_llm/providers/ollama.rb +11 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +3 -1
- data/lib/ruby_llm/providers/openai.rb +14 -0
- data/lib/ruby_llm/providers/openrouter/chat.rb +20 -24
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +2 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -1
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -1
- data/lib/ruby_llm/providers/openrouter/videos.rb +10 -5
- data/lib/ruby_llm/providers/openrouter.rb +4 -0
- data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
- data/lib/ruby_llm/providers/perplexity/media.rb +5 -11
- data/lib/ruby_llm/providers/perplexity/models.rb +8 -4
- data/lib/ruby_llm/providers/perplexity.rb +11 -0
- data/lib/ruby_llm/providers/typesafe.rb +32 -0
- data/lib/ruby_llm/providers/vertexai/credentials.rb +63 -0
- data/lib/ruby_llm/providers/vertexai/gemini.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +1 -0
- data/lib/ruby_llm/providers/vertexai.rb +27 -28
- data/lib/ruby_llm/providers/xai/chat.rb +6 -0
- data/lib/ruby_llm/providers/xai/images.rb +9 -10
- data/lib/ruby_llm/providers/xai/responses.rb +10 -8
- data/lib/ruby_llm/providers/xai/videos.rb +11 -9
- data/lib/ruby_llm/providers/xai.rb +4 -0
- data/lib/ruby_llm/railtie.rb +11 -0
- data/lib/ruby_llm/request_shape/part.rb +117 -0
- data/lib/ruby_llm/request_shape/problem.rb +63 -0
- data/lib/ruby_llm/request_shape/tool_round.rb +64 -0
- data/lib/ruby_llm/request_shape/turn.rb +54 -0
- data/lib/ruby_llm/request_shape.rb +160 -0
- data/lib/ruby_llm/rerank.rb +9 -2
- data/lib/ruby_llm/research_job.rb +21 -5
- data/lib/ruby_llm/score.rb +42 -0
- data/lib/ruby_llm/search_results.rb +2 -2
- data/lib/ruby_llm/server_tool_call.rb +10 -2
- data/lib/ruby_llm/speech.rb +8 -1
- data/lib/ruby_llm/support/cancellation.rb +39 -0
- data/lib/ruby_llm/support/inspectable.rb +3 -2
- data/lib/ruby_llm/support/instrumentation.rb +38 -1
- data/lib/ruby_llm/support/process_cache.rb +60 -0
- data/lib/ruby_llm/support/progress_reporter.rb +33 -0
- data/lib/ruby_llm/thinking.rb +2 -1
- data/lib/ruby_llm/tokens.rb +57 -14
- data/lib/ruby_llm/tool.rb +50 -3
- data/lib/ruby_llm/tool_call.rb +17 -2
- data/lib/ruby_llm/transcription.rb +10 -3
- data/lib/ruby_llm/transport/connection.rb +127 -44
- data/lib/ruby_llm/transport/error_middleware.rb +90 -24
- data/lib/ruby_llm/transport/event_stream_parser.rb +121 -0
- data/lib/ruby_llm/transport/json_response.rb +33 -0
- data/lib/ruby_llm/transport/signing_middleware.rb +29 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +5 -3
- data/lib/ruby_llm/video_job.rb +48 -5
- data/lib/ruby_llm.rb +106 -5
- data/lib/tasks/ruby_llm/evaluations.rb +43 -0
- data/lib/tasks/ruby_llm.rake +6 -9
- data/skills/rubyllm/SKILL.md +33 -12
- metadata +95 -37
- data/lib/generators/ruby_llm/upgrade/legacy_content_sql.rb +0 -34
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +0 -341
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/journal.rb +0 -171
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/verification.rb +0 -197
- data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +0 -174
- data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +0 -468
- data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +0 -101
- data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +0 -247
- data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +0 -662
- data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +0 -251
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +0 -13
- data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +0 -359
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class MCP
|
|
5
|
+
# A tool call that an MCP server runs in the background, as the Tasks
|
|
6
|
+
# extension describes. Declare MCP.extension :tasks, and a server may
|
|
7
|
+
# answer a long call with a task instead of a result.
|
|
8
|
+
#
|
|
9
|
+
# A chat never waits for a task. It pauses the tool call, and
|
|
10
|
+
# Chat#pending_tasks returns the tasks it waits on. #refresh checks on
|
|
11
|
+
# a task once; Chat#complete checks on every one again and resumes the
|
|
12
|
+
# chat once they are done:
|
|
13
|
+
#
|
|
14
|
+
# task = chat.pending_tasks.first
|
|
15
|
+
# task.refresh
|
|
16
|
+
# task.status # => :working
|
|
17
|
+
# task.status_message # => "Rendering page 3 of 12"
|
|
18
|
+
# task.poll_interval # => 5.0
|
|
19
|
+
#
|
|
20
|
+
# MCP#call waits for the task of a tool you call directly.
|
|
21
|
+
class Task
|
|
22
|
+
include Support::Inspectable
|
|
23
|
+
|
|
24
|
+
DONE = %i[completed failed cancelled].freeze
|
|
25
|
+
private_constant :DONE
|
|
26
|
+
|
|
27
|
+
# The task's id on its server.
|
|
28
|
+
attr_reader :id
|
|
29
|
+
|
|
30
|
+
# The ToolCall paused on the task, when it came from a chat.
|
|
31
|
+
attr_reader :tool_call
|
|
32
|
+
|
|
33
|
+
attr_reader :data, :answered # :nodoc:
|
|
34
|
+
|
|
35
|
+
def self.load(mcp, state, tool_call: nil) # :nodoc:
|
|
36
|
+
new(mcp, state['task'], answered: state['answered'], tool_call:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize(mcp, data, answered: nil, tool_call: nil) # :nodoc:
|
|
40
|
+
@mcp = mcp
|
|
41
|
+
@data = data
|
|
42
|
+
@id = data['taskId']
|
|
43
|
+
@answered = Array(answered)
|
|
44
|
+
@tool_call = tool_call
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The task's state: +:working+, +:input_required+, +:completed+,
|
|
48
|
+
# +:failed+, or +:cancelled+.
|
|
49
|
+
def status
|
|
50
|
+
data['status'].to_sym
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# What the server says about the task's state, such as how far it
|
|
54
|
+
# got, or +nil+.
|
|
55
|
+
def status_message
|
|
56
|
+
data['statusMessage']
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# How many seconds the server asks you to wait before checking on the
|
|
60
|
+
# task again, or +nil+.
|
|
61
|
+
def poll_interval
|
|
62
|
+
data['pollIntervalMs'] / 1000.0 if data['pollIntervalMs']
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# When the server may forget the task, as a Time, or +nil+ when it
|
|
66
|
+
# keeps the task for good.
|
|
67
|
+
def expires_at
|
|
68
|
+
Time.iso8601(data['createdAt']) + (data['ttlMs'] / 1000.0) if data['ttlMs'] && data['createdAt']
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns +true+ once the task completed, failed, or was cancelled.
|
|
72
|
+
def done?
|
|
73
|
+
DONE.include?(status)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Returns +true+ when the task finished with a #result.
|
|
77
|
+
def completed?
|
|
78
|
+
status == :completed
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns +true+ when the task failed with an error.
|
|
82
|
+
def failed?
|
|
83
|
+
status == :failed
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns +true+ when the task was cancelled before it finished.
|
|
87
|
+
def cancelled?
|
|
88
|
+
status == :cancelled
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the MCP::Result once the task #completed?, and +nil+ until
|
|
92
|
+
# then. Raises MCP::Error when the task failed or was cancelled.
|
|
93
|
+
def result
|
|
94
|
+
raise error if failed? || cancelled?
|
|
95
|
+
|
|
96
|
+
Result.new(data['result']) if completed?
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Checks on the task once and returns +self+, which now tells whether
|
|
100
|
+
# the task is #done? and how soon to check again. Does nothing once
|
|
101
|
+
# the task is #done?.
|
|
102
|
+
def refresh
|
|
103
|
+
@data = connected.poll_task(id) unless done?
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Checks on the task until it is #done?, sleeping #poll_interval
|
|
108
|
+
# seconds in between, and returns +self+. Answers the server's
|
|
109
|
+
# requests for input with MCP.before_input_request callbacks.
|
|
110
|
+
# +timeout+ defaults to the server's MCP.timeout, and the task is
|
|
111
|
+
# cancelled when it runs out or the chat is cancelled.
|
|
112
|
+
#
|
|
113
|
+
# Raises MCP::Error when the task fails or the time runs out, and
|
|
114
|
+
# MCP::InputRequiredError when no callback answers a request.
|
|
115
|
+
def wait(timeout: nil, interval: nil)
|
|
116
|
+
connected.await_task(self, timeout:, interval:)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Asks the server to cancel the task, and returns +self+. The server
|
|
120
|
+
# may still finish it, so check with #refresh.
|
|
121
|
+
def cancel
|
|
122
|
+
connected.cancel_task(id) unless done?
|
|
123
|
+
self
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def error # :nodoc:
|
|
127
|
+
details = data['error'] || {}
|
|
128
|
+
Error.new(details['message'] || status_message || "Task #{id} was #{status}",
|
|
129
|
+
code: details['code'], data: details['data'])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def record_answers(keys) # :nodoc:
|
|
133
|
+
@answered |= keys
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def to_h # :nodoc:
|
|
137
|
+
{ 'task' => data, 'answered' => (answered unless answered.empty?) }.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private
|
|
141
|
+
|
|
142
|
+
def connected
|
|
143
|
+
@mcp or raise ConfigurationError, "Connect the MCP that runs task #{id} with with_mcp to reach it"
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def inspect_attributes
|
|
147
|
+
{ id:, status:, status_message: }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class MCP
|
|
5
|
+
# A tool offered by an MCP server. It works anywhere a Tool does: a
|
|
6
|
+
# chat renders its name, description, and schema for the model, and
|
|
7
|
+
# calling it calls the server.
|
|
8
|
+
#
|
|
9
|
+
# tool = linear.tools.first
|
|
10
|
+
# tool.read_only? # => true
|
|
11
|
+
# chat.with_tools(tool)
|
|
12
|
+
#
|
|
13
|
+
# The behavior predicates read the server's annotations. They are hints
|
|
14
|
+
# from the server, so trust them only as far as you trust the server.
|
|
15
|
+
class Tool < RubyLLM::Tool
|
|
16
|
+
include Support::Inspectable
|
|
17
|
+
|
|
18
|
+
# The name the model calls this tool by.
|
|
19
|
+
attr_reader :name
|
|
20
|
+
|
|
21
|
+
# The description the model sees.
|
|
22
|
+
attr_reader :description
|
|
23
|
+
|
|
24
|
+
# The JSON Schema for the arguments the model provides.
|
|
25
|
+
attr_reader :parameters_schema
|
|
26
|
+
|
|
27
|
+
# The tool's name on the server.
|
|
28
|
+
attr_reader :server_name
|
|
29
|
+
|
|
30
|
+
# The tool's +_meta+ as the server sent it: a Hash with String keys,
|
|
31
|
+
# empty when there is none. Extensions keep their own vocabulary
|
|
32
|
+
# there.
|
|
33
|
+
attr_reader :meta
|
|
34
|
+
|
|
35
|
+
# The URI of the tool's UI, the +ui://+ resource an MCP App renders
|
|
36
|
+
# next to the tool's results, or +nil+ for a tool without one. Read
|
|
37
|
+
# it with MCP#resource.
|
|
38
|
+
attr_reader :ui_uri
|
|
39
|
+
|
|
40
|
+
# Who may call the tool, as Symbols: +:model+ when chats offer it to
|
|
41
|
+
# the model, +:app+ when a UI from the same server may call it. Tools
|
|
42
|
+
# say nothing about it unless they belong to an MCP App, which makes
|
|
43
|
+
# them <tt>[:model, :app]</tt>.
|
|
44
|
+
attr_reader :visibility
|
|
45
|
+
|
|
46
|
+
attr_reader :fixed_arguments, :wrap # :nodoc:
|
|
47
|
+
|
|
48
|
+
def initialize(mcp, definition, prefix: nil, as: nil, description: nil, fixed_arguments: {}, wrap: nil) # :nodoc:
|
|
49
|
+
super()
|
|
50
|
+
@mcp = mcp
|
|
51
|
+
@server_name = definition['name']
|
|
52
|
+
@name = (as || [prefix, server_name].compact.join('_')).to_s
|
|
53
|
+
@description = description || definition['description']
|
|
54
|
+
@fixed_arguments = fixed_arguments.transform_keys(&:to_sym)
|
|
55
|
+
@wrap = wrap
|
|
56
|
+
@annotations = definition['annotations'] || {}
|
|
57
|
+
@meta = definition['_meta'] || {}
|
|
58
|
+
@ui_uri = Apps.uri(meta)
|
|
59
|
+
@visibility = Apps.visibility(meta)
|
|
60
|
+
@parameters_schema = model_schema(definition['inputSchema'] || {})
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns whether the server says the tool only reads.
|
|
64
|
+
def read_only?
|
|
65
|
+
@annotations['readOnlyHint'] == true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns whether the tool may destroy or overwrite data. Tools that
|
|
69
|
+
# are not read-only count as destructive unless the server says
|
|
70
|
+
# otherwise.
|
|
71
|
+
def destructive?
|
|
72
|
+
!read_only? && @annotations['destructiveHint'] != false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Returns whether calling the tool again with the same arguments has
|
|
76
|
+
# no further effect.
|
|
77
|
+
def idempotent?
|
|
78
|
+
@annotations['idempotentHint'] == true
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns whether the tool reaches beyond the server, such as the web.
|
|
82
|
+
def open_world?
|
|
83
|
+
@annotations['openWorldHint'] != false
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns whether the tool pauses for approval, as declared with
|
|
87
|
+
# MCP.requires_approval.
|
|
88
|
+
def requires_approval?
|
|
89
|
+
@mcp.requires_approval?(self)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Calls the tool on the server and returns its MCP::Result, what the
|
|
93
|
+
# +wrap:+ method made of it, or <tt>{ error: }</tt> when the tool
|
|
94
|
+
# failed. A chat sends the model the result's content. When the
|
|
95
|
+
# server runs the call as a task, returns the MCP::Task without
|
|
96
|
+
# waiting, and a chat pauses the tool call until the task is done.
|
|
97
|
+
# Raises MCP::InputRequiredError when the server needs input that no
|
|
98
|
+
# MCP.before_input_request callback gave.
|
|
99
|
+
def call(**arguments)
|
|
100
|
+
@mcp.run(self, arguments.except(:tool_call))
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Resumes a call that paused on input requests, now answered, or on a
|
|
104
|
+
# task, which it checks on once.
|
|
105
|
+
def resume(input, arguments) # :nodoc:
|
|
106
|
+
@mcp.run(self, arguments, input:)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the MCP::Task a call paused on, from its saved state.
|
|
110
|
+
def task(state, tool_call: nil) # :nodoc:
|
|
111
|
+
Task.load(@mcp, state, tool_call:)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def model_schema(schema)
|
|
117
|
+
schema = SchemaDefinition.new(schema:).json_schema
|
|
118
|
+
return schema if fixed_arguments.empty? || !schema.key?('properties')
|
|
119
|
+
|
|
120
|
+
hidden = fixed_arguments.keys.map(&:to_s)
|
|
121
|
+
schema.merge('properties' => schema['properties'].except(*hidden),
|
|
122
|
+
'required' => Array(schema['required']) - hidden)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def inspect_attributes
|
|
126
|
+
{ name:, from: (server_name unless server_name == name), read_only: read_only? || nil }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|