llm.rb 4.9.0 → 4.11.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/CHANGELOG.md +152 -0
- data/README.md +178 -31
- data/data/anthropic.json +209 -242
- data/data/deepseek.json +15 -15
- data/data/google.json +553 -403
- data/data/openai.json +740 -535
- data/data/xai.json +250 -253
- data/data/zai.json +157 -90
- data/lib/llm/context/deserializer.rb +2 -1
- data/lib/llm/context.rb +58 -2
- data/lib/llm/contract/completion.rb +7 -0
- data/lib/llm/error.rb +4 -0
- data/lib/llm/eventhandler.rb +7 -0
- data/lib/llm/function/registry.rb +106 -0
- data/lib/llm/function/task.rb +39 -0
- data/lib/llm/function.rb +12 -7
- data/lib/llm/mcp/transport/http/event_handler.rb +66 -0
- data/lib/llm/mcp/transport/http.rb +156 -0
- data/lib/llm/mcp/transport/stdio.rb +7 -0
- data/lib/llm/mcp.rb +74 -30
- data/lib/llm/message.rb +9 -2
- data/lib/llm/provider.rb +10 -0
- data/lib/llm/providers/anthropic/response_adapter/completion.rb +6 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +37 -4
- data/lib/llm/providers/anthropic.rb +1 -1
- data/lib/llm/providers/google/response_adapter/completion.rb +12 -5
- data/lib/llm/providers/google/stream_parser.rb +54 -11
- data/lib/llm/providers/google/utils.rb +30 -0
- data/lib/llm/providers/google.rb +2 -0
- data/lib/llm/providers/ollama/response_adapter/completion.rb +6 -0
- data/lib/llm/providers/ollama/stream_parser.rb +10 -4
- data/lib/llm/providers/ollama.rb +1 -1
- data/lib/llm/providers/openai/response_adapter/completion.rb +7 -0
- data/lib/llm/providers/openai/response_adapter/responds.rb +84 -10
- data/lib/llm/providers/openai/responses/stream_parser.rb +63 -4
- data/lib/llm/providers/openai/responses.rb +1 -1
- data/lib/llm/providers/openai/stream_parser.rb +68 -4
- data/lib/llm/providers/openai.rb +1 -1
- data/lib/llm/schema/all_of.rb +31 -0
- data/lib/llm/schema/any_of.rb +31 -0
- data/lib/llm/schema/one_of.rb +31 -0
- data/lib/llm/schema/parser.rb +36 -0
- data/lib/llm/schema.rb +45 -8
- data/lib/llm/stream/queue.rb +51 -0
- data/lib/llm/stream.rb +102 -0
- data/lib/llm/tool.rb +53 -47
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +3 -2
- data/llm.gemspec +2 -2
- metadata +12 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Schema
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Schema::AnyOf LLM::Schema::AnyOf} class represents an
|
|
6
|
+
# anyOf union in a JSON schema. It is a subclass of
|
|
7
|
+
# {LLM::Schema::Leaf LLM::Schema::Leaf}.
|
|
8
|
+
class AnyOf < Leaf
|
|
9
|
+
##
|
|
10
|
+
# Returns an anyOf union for the given types.
|
|
11
|
+
# @return [LLM::Schema::AnyOf]
|
|
12
|
+
def self.[](*types)
|
|
13
|
+
schema = LLM::Schema.new
|
|
14
|
+
new(types.map { LLM::Schema::Utils.resolve(schema, _1) })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @param [Array<LLM::Schema::Leaf>] values
|
|
19
|
+
# The values allowed by the union
|
|
20
|
+
# @return [LLM::Schema::AnyOf]
|
|
21
|
+
def initialize(values)
|
|
22
|
+
@values = values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# @return [Hash]
|
|
27
|
+
def to_h
|
|
28
|
+
super.merge!(anyOf: @values)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Schema
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Schema::OneOf LLM::Schema::OneOf} class represents an
|
|
6
|
+
# oneOf union in a JSON schema. It is a subclass of
|
|
7
|
+
# {LLM::Schema::Leaf LLM::Schema::Leaf}.
|
|
8
|
+
class OneOf < Leaf
|
|
9
|
+
##
|
|
10
|
+
# Returns a oneOf union for the given types.
|
|
11
|
+
# @return [LLM::Schema::OneOf]
|
|
12
|
+
def self.[](*types)
|
|
13
|
+
schema = LLM::Schema.new
|
|
14
|
+
new(types.map { LLM::Schema::Utils.resolve(schema, _1) })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @param [Array<LLM::Schema::Leaf>] values
|
|
19
|
+
# The values allowed by the union
|
|
20
|
+
# @return [LLM::Schema::OneOf]
|
|
21
|
+
def initialize(values)
|
|
22
|
+
@values = values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# @return [Hash]
|
|
27
|
+
def to_h
|
|
28
|
+
super.merge!(oneOf: @values)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/llm/schema/parser.rb
CHANGED
|
@@ -8,6 +8,8 @@ class LLM::Schema
|
|
|
8
8
|
# external JSON schema definitions into the schema objects used
|
|
9
9
|
# throughout llm.rb.
|
|
10
10
|
module Parser
|
|
11
|
+
METADATA_KEYS = %w[description default enum const].freeze
|
|
12
|
+
|
|
11
13
|
##
|
|
12
14
|
# Parses a JSON schema into an {LLM::Schema::Leaf}.
|
|
13
15
|
# @param [Hash] schema
|
|
@@ -27,6 +29,8 @@ class LLM::Schema
|
|
|
27
29
|
when "number" then apply(parse_number(schema), schema)
|
|
28
30
|
when "boolean" then apply(schema().boolean, schema)
|
|
29
31
|
when "null" then apply(schema().null, schema)
|
|
32
|
+
when ::Array then apply(schema().any_of(*schema["type"].map { parse(schema.except("type", *METADATA_KEYS).merge("type" => _1), root) }), schema.except("type"))
|
|
33
|
+
when nil then parse_union(schema, root)
|
|
30
34
|
else raise TypeError, "unsupported schema type #{schema["type"].inspect}"
|
|
31
35
|
end
|
|
32
36
|
end
|
|
@@ -50,6 +54,14 @@ class LLM::Schema
|
|
|
50
54
|
schema().array(items)
|
|
51
55
|
end
|
|
52
56
|
|
|
57
|
+
def parse_union(schema, root)
|
|
58
|
+
return apply(schema().any_of(*schema["anyOf"].map { parse(_1, root) }), schema) if schema.key?("anyOf")
|
|
59
|
+
return apply(schema().one_of(*schema["oneOf"].map { parse(_1, root) }), schema) if schema.key?("oneOf")
|
|
60
|
+
return apply(schema().all_of(*schema["allOf"].map { parse(_1, root) }), schema) if schema.key?("allOf")
|
|
61
|
+
return parse(infer_type(schema), root) if infer_type(schema)
|
|
62
|
+
raise TypeError, "unsupported schema type #{schema["type"].inspect}"
|
|
63
|
+
end
|
|
64
|
+
|
|
53
65
|
def parse_string(schema)
|
|
54
66
|
leaf = schema().string
|
|
55
67
|
leaf.min(schema["minLength"]) if schema.key?("minLength")
|
|
@@ -105,5 +117,29 @@ class LLM::Schema
|
|
|
105
117
|
rescue KeyError
|
|
106
118
|
raise TypeError, "unresolvable schema ref #{ref.inspect}"
|
|
107
119
|
end
|
|
120
|
+
|
|
121
|
+
def infer_type(schema)
|
|
122
|
+
if schema.key?("const")
|
|
123
|
+
schema.merge("type" => type_of(schema["const"]))
|
|
124
|
+
elsif schema.key?("enum")
|
|
125
|
+
type = type_of(schema["enum"].first)
|
|
126
|
+
return unless type && schema["enum"].all? { type_of(_1) == type }
|
|
127
|
+
schema.merge("type" => type)
|
|
128
|
+
elsif schema.key?("default")
|
|
129
|
+
schema.merge("type" => type_of(schema["default"]))
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def type_of(value)
|
|
134
|
+
case value
|
|
135
|
+
when ::Hash then "object"
|
|
136
|
+
when ::Array then "array"
|
|
137
|
+
when ::String then "string"
|
|
138
|
+
when ::Integer then "integer"
|
|
139
|
+
when ::Float then "number"
|
|
140
|
+
when ::TrueClass, ::FalseClass then "boolean"
|
|
141
|
+
when ::NilClass then "null"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
108
144
|
end
|
|
109
145
|
end
|
data/lib/llm/schema.rb
CHANGED
|
@@ -35,6 +35,9 @@ class LLM::Schema
|
|
|
35
35
|
require_relative "schema/leaf"
|
|
36
36
|
require_relative "schema/object"
|
|
37
37
|
require_relative "schema/array"
|
|
38
|
+
require_relative "schema/all_of"
|
|
39
|
+
require_relative "schema/any_of"
|
|
40
|
+
require_relative "schema/one_of"
|
|
38
41
|
require_relative "schema/string"
|
|
39
42
|
require_relative "schema/enum"
|
|
40
43
|
require_relative "schema/number"
|
|
@@ -45,6 +48,23 @@ class LLM::Schema
|
|
|
45
48
|
@__monitor = Monitor.new
|
|
46
49
|
extend LLM::Schema::Parser
|
|
47
50
|
|
|
51
|
+
##
|
|
52
|
+
# @api private
|
|
53
|
+
module Utils
|
|
54
|
+
extend self
|
|
55
|
+
|
|
56
|
+
def resolve(schema, type)
|
|
57
|
+
if LLM::Schema::Leaf === type
|
|
58
|
+
type
|
|
59
|
+
elsif Class === type && type.respond_to?(:object)
|
|
60
|
+
type.object
|
|
61
|
+
else
|
|
62
|
+
target = type.name.split("::").last.downcase
|
|
63
|
+
schema.public_send(target)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
48
68
|
##
|
|
49
69
|
# Configures a monitor for a subclass
|
|
50
70
|
# @return [void]
|
|
@@ -65,14 +85,7 @@ class LLM::Schema
|
|
|
65
85
|
# A hash of options
|
|
66
86
|
def self.property(name, type, description, options = {})
|
|
67
87
|
lock do
|
|
68
|
-
|
|
69
|
-
prop = type
|
|
70
|
-
elsif Class === type && type.respond_to?(:object)
|
|
71
|
-
prop = type.object
|
|
72
|
-
else
|
|
73
|
-
target = type.name.split("::").last.downcase
|
|
74
|
-
prop = schema.public_send(target)
|
|
75
|
-
end
|
|
88
|
+
prop = Utils.resolve(schema, type)
|
|
76
89
|
options = {description:}.merge(options)
|
|
77
90
|
options.each { (_2 == true) ? prop.public_send(_1) : prop.public_send(_1, *_2) }
|
|
78
91
|
object[name] = prop
|
|
@@ -120,6 +133,30 @@ class LLM::Schema
|
|
|
120
133
|
Array.new(*items)
|
|
121
134
|
end
|
|
122
135
|
|
|
136
|
+
##
|
|
137
|
+
# Returns an anyOf union
|
|
138
|
+
# @param [Array<LLM::Schema::Leaf>] values The union values
|
|
139
|
+
# @return [LLM::Schema::AnyOf]
|
|
140
|
+
def any_of(*values)
|
|
141
|
+
AnyOf.new(values)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
##
|
|
145
|
+
# Returns an allOf union
|
|
146
|
+
# @param [Array<LLM::Schema::Leaf>] values The union values
|
|
147
|
+
# @return [LLM::Schema::AllOf]
|
|
148
|
+
def all_of(*values)
|
|
149
|
+
AllOf.new(values)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
##
|
|
153
|
+
# Returns a oneOf union
|
|
154
|
+
# @param [Array<LLM::Schema::Leaf>] values The union values
|
|
155
|
+
# @return [LLM::Schema::OneOf]
|
|
156
|
+
def one_of(*values)
|
|
157
|
+
OneOf.new(values)
|
|
158
|
+
end
|
|
159
|
+
|
|
123
160
|
##
|
|
124
161
|
# Returns a string
|
|
125
162
|
# @return [LLM::Schema::String]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Stream
|
|
4
|
+
##
|
|
5
|
+
# A small queue for collecting streamed tool work. Values can be immediate
|
|
6
|
+
# {LLM::Function::Return} objects or concurrent handles returned by
|
|
7
|
+
# {LLM::Function#spawn}. Calling {#wait(strategy)} resolves queued work and
|
|
8
|
+
# returns an array of {LLM::Function::Return} values.
|
|
9
|
+
class Queue
|
|
10
|
+
##
|
|
11
|
+
# @return [LLM::Stream::Queue]
|
|
12
|
+
def initialize
|
|
13
|
+
@items = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Enqueue a function return or spawned task.
|
|
18
|
+
# @param [LLM::Function::Return, Thread, Async::Task, Fiber] item
|
|
19
|
+
# @return [LLM::Stream::Queue]
|
|
20
|
+
def <<(item)
|
|
21
|
+
@items << item
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Returns true when the queue is empty.
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
def empty?
|
|
29
|
+
@items.empty?
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Waits for queued work to finish and returns function results.
|
|
34
|
+
# @param [Symbol] strategy
|
|
35
|
+
# Controls concurrency strategy:
|
|
36
|
+
# - `:thread`: Use threads
|
|
37
|
+
# - `:task`: Use async tasks (requires async gem)
|
|
38
|
+
# - `:fiber`: Use raw fibers
|
|
39
|
+
# @return [Array<LLM::Function::Return>]
|
|
40
|
+
def wait(strategy)
|
|
41
|
+
returns, tasks = @items.shift(@items.length).partition { LLM::Function::Return === _1 }
|
|
42
|
+
returns.concat case strategy
|
|
43
|
+
when :thread then LLM::Function::ThreadGroup.new(tasks).wait
|
|
44
|
+
when :task then LLM::Function::TaskGroup.new(tasks).wait
|
|
45
|
+
when :fiber then LLM::Function::FiberGroup.new(tasks).wait
|
|
46
|
+
else raise ArgumentError, "Unknown strategy: #{strategy.inspect}. Expected :thread, :task, or :fiber"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
alias_method :value, :wait
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/llm/stream.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Stream LLM::Stream} class provides the callback interface for
|
|
6
|
+
# streamed model output in llm.rb.
|
|
7
|
+
#
|
|
8
|
+
# A stream object can be an instance of {LLM::Stream LLM::Stream}, a
|
|
9
|
+
# subclass that overrides the callbacks it needs, or any other object that
|
|
10
|
+
# implements some or all of the same interface. {#queue} provides a small
|
|
11
|
+
# helper for collecting asynchronous tool work started from a callback, and
|
|
12
|
+
# {#tool_not_found} returns an in-band tool error when a streamed tool
|
|
13
|
+
# cannot be resolved.
|
|
14
|
+
#
|
|
15
|
+
# @note The `on_*` callbacks run inline with the streaming parser. They
|
|
16
|
+
# therefore block streaming progress and should generally return as
|
|
17
|
+
# quickly as possible.
|
|
18
|
+
#
|
|
19
|
+
# The most common callback is {#on_content}, which also maps to {#<<} for
|
|
20
|
+
# compatibility with `StringIO`-style objects. Providers may also call
|
|
21
|
+
# {#on_reasoning_content} and {#on_tool_call} when that data is available.
|
|
22
|
+
class Stream
|
|
23
|
+
require_relative "stream/queue"
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Returns a lazily-initialized queue for tool results or spawned work.
|
|
27
|
+
# @return [LLM::Stream::Queue]
|
|
28
|
+
def queue
|
|
29
|
+
@queue ||= Queue.new
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# Waits for queued tool work to finish and returns function results.
|
|
34
|
+
# @param [Symbol] strategy
|
|
35
|
+
# The concurrency strategy to use
|
|
36
|
+
# @return [Array<LLM::Function::Return>]
|
|
37
|
+
def wait(strategy)
|
|
38
|
+
queue.wait(strategy)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @group Public callbacks
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Called when visible assistant output is streamed.
|
|
45
|
+
# @param [String] content
|
|
46
|
+
# A chunk of assistant-visible text.
|
|
47
|
+
# @return [nil]
|
|
48
|
+
def on_content(content)
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
alias_method :<<, :on_content
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Called when reasoning output is streamed separately from visible content.
|
|
55
|
+
# @param [String] content
|
|
56
|
+
# A chunk of reasoning text.
|
|
57
|
+
# @return [nil]
|
|
58
|
+
def on_reasoning_content(content)
|
|
59
|
+
nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# Called when a streamed tool call has been fully constructed.
|
|
64
|
+
# @note A stream implementation may start tool execution here, for
|
|
65
|
+
# example by pushing `tool.spawn(:thread)`, `tool.spawn(:fiber)`, or
|
|
66
|
+
# `tool.spawn(:task)` onto {#queue}. When a streamed tool cannot be
|
|
67
|
+
# resolved, `error` is passed as an {LLM::Function::Return}. It can be
|
|
68
|
+
# sent back to the model, allowing the tool-call path to recover and the
|
|
69
|
+
# session to continue. Tool resolution depends on
|
|
70
|
+
# {LLM::Function.registry}, which includes {LLM::Tool LLM::Tool}
|
|
71
|
+
# subclasses, including MCP tools, but not functions defined with
|
|
72
|
+
# {LLM.function}.
|
|
73
|
+
# @param [LLM::Function] tool
|
|
74
|
+
# The parsed tool call.
|
|
75
|
+
# @param [LLM::Function::Return, nil] error
|
|
76
|
+
# An in-band tool error for unresolved tool calls.
|
|
77
|
+
# @return [nil]
|
|
78
|
+
def on_tool_call(tool, error)
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# @endgroup
|
|
83
|
+
|
|
84
|
+
# @group Error handlers
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Returns a function return describing a streamed tool that could not
|
|
88
|
+
# be resolved.
|
|
89
|
+
# @note This is mainly useful as a fallback from {#on_tool_call}. It
|
|
90
|
+
# should be uncommon in normal use, since streamed tool callbacks only
|
|
91
|
+
# run for tools already defined in the context.
|
|
92
|
+
# @param [LLM::Function] tool
|
|
93
|
+
# @return [LLM::Function::Return]
|
|
94
|
+
def tool_not_found(tool)
|
|
95
|
+
LLM::Function::Return.new(tool.id, tool.name, {
|
|
96
|
+
error: true, type: LLM::NoSuchToolError.name, message: "tool not found"
|
|
97
|
+
})
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# @endgroup
|
|
101
|
+
end
|
|
102
|
+
end
|
data/lib/llm/tool.rb
CHANGED
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
class LLM::Tool
|
|
21
21
|
require_relative "tool/param"
|
|
22
22
|
extend LLM::Tool::Param
|
|
23
|
+
extend LLM::Function::Registry
|
|
23
24
|
|
|
24
25
|
types = [
|
|
25
|
-
:Leaf, :String, :Enum,
|
|
26
|
+
:Leaf, :String, :Enum,
|
|
27
|
+
:AllOf, :AnyOf, :OneOf,
|
|
26
28
|
:Object, :Integer, :Number,
|
|
27
|
-
:Boolean, :Null
|
|
29
|
+
:Array, :Boolean, :Null
|
|
28
30
|
]
|
|
29
31
|
types.each do |constant|
|
|
30
32
|
const_set constant, LLM::Schema.const_get(constant)
|
|
@@ -38,58 +40,51 @@ class LLM::Tool
|
|
|
38
40
|
# @return [Class<LLM::Tool>]
|
|
39
41
|
# Returns a subclass of LLM::Tool
|
|
40
42
|
def self.mcp(mcp, tool)
|
|
41
|
-
klass = Class.new(LLM::Tool) do
|
|
42
|
-
name tool["name"]
|
|
43
|
-
description tool["description"]
|
|
44
|
-
params { tool["inputSchema"] || {type: "object", properties: {}} }
|
|
45
|
-
|
|
46
|
-
define_singleton_method(:inspect) do
|
|
47
|
-
"<LLM::Tool:0x#{object_id.to_s(16)} name=#{tool["name"]} (mcp)>"
|
|
48
|
-
end
|
|
49
|
-
singleton_class.alias_method :to_s, :inspect
|
|
50
|
-
|
|
51
|
-
define_singleton_method(:mcp?) do
|
|
52
|
-
true
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
define_method(:call) do |**args|
|
|
56
|
-
mcp.call_tool(tool["name"], args)
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
unregister(klass)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
##
|
|
63
|
-
# Returns all subclasses of LLM::Tool
|
|
64
|
-
# @note
|
|
65
|
-
# This method excludes tools who haven't defined a name
|
|
66
|
-
# as well as tools defined via MCP.
|
|
67
|
-
# @return [Array<LLM::Tool>]
|
|
68
|
-
def self.registry
|
|
69
43
|
lock do
|
|
70
|
-
@
|
|
44
|
+
@mcp = true
|
|
45
|
+
klass = Class.new(LLM::Tool) do
|
|
46
|
+
name tool["name"]
|
|
47
|
+
description tool["description"]
|
|
48
|
+
params { tool["inputSchema"] || {type: "object", properties: {}} }
|
|
49
|
+
|
|
50
|
+
define_singleton_method(:inspect) do
|
|
51
|
+
"<LLM::Tool:0x#{object_id.to_s(16)} name=#{tool["name"]} (mcp)>"
|
|
52
|
+
end
|
|
53
|
+
singleton_class.alias_method :to_s, :inspect
|
|
54
|
+
|
|
55
|
+
define_singleton_method(:mcp?) do
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
define_method(:call) do |**args|
|
|
60
|
+
mcp.call_tool(tool["name"], args)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
@mcp = false
|
|
64
|
+
register(klass)
|
|
65
|
+
klass
|
|
66
|
+
ensure
|
|
67
|
+
@mcp = false
|
|
71
68
|
end
|
|
72
69
|
end
|
|
73
|
-
@registry = []
|
|
74
70
|
|
|
75
71
|
##
|
|
76
72
|
# Clear the registry
|
|
77
73
|
# @return [void]
|
|
78
74
|
def self.clear_registry!
|
|
79
75
|
lock do
|
|
80
|
-
@
|
|
81
|
-
|
|
76
|
+
@__registry.each_value { LLM::Function.unregister(_1.function) }
|
|
77
|
+
super
|
|
82
78
|
end
|
|
83
79
|
end
|
|
84
80
|
|
|
85
81
|
##
|
|
86
|
-
#
|
|
82
|
+
# Registers a tool and its function.
|
|
87
83
|
# @param [LLM::Tool] tool
|
|
88
84
|
# @api private
|
|
89
85
|
def self.register(tool)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
end
|
|
86
|
+
super
|
|
87
|
+
LLM::Function.register(tool.function)
|
|
93
88
|
end
|
|
94
89
|
|
|
95
90
|
##
|
|
@@ -98,7 +93,9 @@ class LLM::Tool
|
|
|
98
93
|
# @api private
|
|
99
94
|
def self.unregister(tool)
|
|
100
95
|
lock do
|
|
101
|
-
|
|
96
|
+
LLM::Function.unregister(tool.function)
|
|
97
|
+
super
|
|
98
|
+
tool
|
|
102
99
|
end
|
|
103
100
|
end
|
|
104
101
|
|
|
@@ -110,7 +107,7 @@ class LLM::Tool
|
|
|
110
107
|
LLM.lock(:inherited) do
|
|
111
108
|
tool.instance_eval { @__monitor ||= Monitor.new }
|
|
112
109
|
tool.function.register(tool)
|
|
113
|
-
LLM::Tool.register(tool)
|
|
110
|
+
LLM::Tool.register(tool) unless lock { @mcp }
|
|
114
111
|
end
|
|
115
112
|
end
|
|
116
113
|
|
|
@@ -124,6 +121,22 @@ class LLM::Tool
|
|
|
124
121
|
end
|
|
125
122
|
end
|
|
126
123
|
|
|
124
|
+
##
|
|
125
|
+
# Returns all registered tool classes with definitions.
|
|
126
|
+
# @return [Array<Class<LLM::Tool>>]
|
|
127
|
+
def self.registry
|
|
128
|
+
super.select(&:name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Finds a registered tool by name.
|
|
133
|
+
# @param [String] name
|
|
134
|
+
# @return [Class<LLM::Tool>]
|
|
135
|
+
# @raise [LLM::NoSuchToolError]
|
|
136
|
+
def self.find_by_name!(name)
|
|
137
|
+
find_by_name(name) || raise(LLM::NoSuchToolError, "no such tool #{name.inspect}")
|
|
138
|
+
end
|
|
139
|
+
|
|
127
140
|
##
|
|
128
141
|
# Returns (or sets) the tool description
|
|
129
142
|
# @param [String, nil] desc The tool description
|
|
@@ -152,13 +165,6 @@ class LLM::Tool
|
|
|
152
165
|
end
|
|
153
166
|
end
|
|
154
167
|
|
|
155
|
-
##
|
|
156
|
-
# @api private
|
|
157
|
-
def self.lock(&)
|
|
158
|
-
@__monitor.synchronize(&)
|
|
159
|
-
end
|
|
160
|
-
@__monitor = Monitor.new
|
|
161
|
-
|
|
162
168
|
##
|
|
163
169
|
# Returns true if the tool is an MCP tool
|
|
164
170
|
# @return [Boolean]
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
|
@@ -20,6 +20,7 @@ module LLM
|
|
|
20
20
|
require_relative "llm/mime"
|
|
21
21
|
require_relative "llm/multipart"
|
|
22
22
|
require_relative "llm/file"
|
|
23
|
+
require_relative "llm/stream"
|
|
23
24
|
require_relative "llm/provider"
|
|
24
25
|
require_relative "llm/context"
|
|
25
26
|
require_relative "llm/agent"
|
|
@@ -29,10 +30,11 @@ module LLM
|
|
|
29
30
|
require_relative "llm/eventhandler"
|
|
30
31
|
require_relative "llm/tool"
|
|
31
32
|
require_relative "llm/server_tool"
|
|
33
|
+
require_relative "llm/mcp"
|
|
32
34
|
|
|
33
35
|
##
|
|
34
36
|
# Thread-safe monitors for different contexts
|
|
35
|
-
@monitors = {require: Monitor.new, clients: Monitor.new, inherited: Monitor.new, registry: Monitor.new}
|
|
37
|
+
@monitors = {require: Monitor.new, clients: Monitor.new, inherited: Monitor.new, registry: Monitor.new, mcp: Monitor.new}
|
|
36
38
|
|
|
37
39
|
##
|
|
38
40
|
# Model registry
|
|
@@ -161,7 +163,6 @@ module LLM
|
|
|
161
163
|
# The working directory for the MCP process
|
|
162
164
|
# @return [LLM::MCP]
|
|
163
165
|
def mcp(llm = nil, **)
|
|
164
|
-
lock(:require) { require_relative "llm/mcp" unless defined?(LLM::MCP) }
|
|
165
166
|
LLM::MCP.new(llm, **)
|
|
166
167
|
end
|
|
167
168
|
|
data/llm.gemspec
CHANGED
|
@@ -63,7 +63,7 @@ Gem::Specification.new do |spec|
|
|
|
63
63
|
spec.files = Dir[
|
|
64
64
|
"README.md", "LICENSE",
|
|
65
65
|
"lib/*.rb", "lib/**/*.rb",
|
|
66
|
-
"data/*.json",
|
|
66
|
+
"data/*.json", "CHANGELOG.md",
|
|
67
67
|
"llm.gemspec"
|
|
68
68
|
]
|
|
69
69
|
spec.require_paths = ["lib"]
|
|
@@ -81,4 +81,4 @@ Gem::Specification.new do |spec|
|
|
|
81
81
|
spec.add_development_dependency "net-http-persistent", "~> 4.0"
|
|
82
82
|
spec.add_development_dependency "opentelemetry-sdk", "~> 1.10"
|
|
83
83
|
spec.add_development_dependency "logger", "~> 1.7"
|
|
84
|
-
end
|
|
84
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antar Azri
|
|
@@ -235,6 +235,7 @@ executables: []
|
|
|
235
235
|
extensions: []
|
|
236
236
|
extra_rdoc_files: []
|
|
237
237
|
files:
|
|
238
|
+
- CHANGELOG.md
|
|
238
239
|
- LICENSE
|
|
239
240
|
- README.md
|
|
240
241
|
- data/anthropic.json
|
|
@@ -262,6 +263,8 @@ files:
|
|
|
262
263
|
- lib/llm/function.rb
|
|
263
264
|
- lib/llm/function/array.rb
|
|
264
265
|
- lib/llm/function/fiber_group.rb
|
|
266
|
+
- lib/llm/function/registry.rb
|
|
267
|
+
- lib/llm/function/task.rb
|
|
265
268
|
- lib/llm/function/task_group.rb
|
|
266
269
|
- lib/llm/function/thread_group.rb
|
|
267
270
|
- lib/llm/function/tracing.rb
|
|
@@ -271,6 +274,8 @@ files:
|
|
|
271
274
|
- lib/llm/mcp/error.rb
|
|
272
275
|
- lib/llm/mcp/pipe.rb
|
|
273
276
|
- lib/llm/mcp/rpc.rb
|
|
277
|
+
- lib/llm/mcp/transport/http.rb
|
|
278
|
+
- lib/llm/mcp/transport/http/event_handler.rb
|
|
274
279
|
- lib/llm/mcp/transport/stdio.rb
|
|
275
280
|
- lib/llm/message.rb
|
|
276
281
|
- lib/llm/mime.rb
|
|
@@ -315,6 +320,7 @@ files:
|
|
|
315
320
|
- lib/llm/providers/google/response_adapter/models.rb
|
|
316
321
|
- lib/llm/providers/google/response_adapter/web_search.rb
|
|
317
322
|
- lib/llm/providers/google/stream_parser.rb
|
|
323
|
+
- lib/llm/providers/google/utils.rb
|
|
318
324
|
- lib/llm/providers/llamacpp.rb
|
|
319
325
|
- lib/llm/providers/ollama.rb
|
|
320
326
|
- lib/llm/providers/ollama/error_handler.rb
|
|
@@ -358,6 +364,8 @@ files:
|
|
|
358
364
|
- lib/llm/registry.rb
|
|
359
365
|
- lib/llm/response.rb
|
|
360
366
|
- lib/llm/schema.rb
|
|
367
|
+
- lib/llm/schema/all_of.rb
|
|
368
|
+
- lib/llm/schema/any_of.rb
|
|
361
369
|
- lib/llm/schema/array.rb
|
|
362
370
|
- lib/llm/schema/boolean.rb
|
|
363
371
|
- lib/llm/schema/enum.rb
|
|
@@ -366,11 +374,14 @@ files:
|
|
|
366
374
|
- lib/llm/schema/null.rb
|
|
367
375
|
- lib/llm/schema/number.rb
|
|
368
376
|
- lib/llm/schema/object.rb
|
|
377
|
+
- lib/llm/schema/one_of.rb
|
|
369
378
|
- lib/llm/schema/parser.rb
|
|
370
379
|
- lib/llm/schema/string.rb
|
|
371
380
|
- lib/llm/schema/version.rb
|
|
372
381
|
- lib/llm/server_tool.rb
|
|
373
382
|
- lib/llm/session.rb
|
|
383
|
+
- lib/llm/stream.rb
|
|
384
|
+
- lib/llm/stream/queue.rb
|
|
374
385
|
- lib/llm/tool.rb
|
|
375
386
|
- lib/llm/tool/param.rb
|
|
376
387
|
- lib/llm/tracer.rb
|