llm.rb 11.1.0 → 11.2.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 +84 -1
- data/README.md +27 -4
- data/lib/llm/a2a/transport/http.rb +9 -8
- data/lib/llm/a2a.rb +14 -7
- data/lib/llm/agent.rb +6 -3
- data/lib/llm/context.rb +20 -6
- data/lib/llm/function/array.rb +6 -0
- data/lib/llm/function.rb +26 -0
- data/lib/llm/json_adapter.rb +8 -2
- data/lib/llm/mcp/transport/http.rb +7 -5
- data/lib/llm/mcp.rb +6 -7
- data/lib/llm/provider.rb +1 -18
- data/lib/llm/providers/anthropic/files.rb +6 -6
- data/lib/llm/providers/anthropic/models.rb +1 -1
- data/lib/llm/providers/anthropic.rb +1 -1
- data/lib/llm/providers/bedrock/models.rb +4 -4
- data/lib/llm/providers/bedrock/signature.rb +3 -3
- data/lib/llm/providers/bedrock.rb +1 -1
- data/lib/llm/providers/google/files.rb +5 -5
- data/lib/llm/providers/google/images.rb +1 -1
- data/lib/llm/providers/google/models.rb +1 -1
- data/lib/llm/providers/google.rb +2 -2
- data/lib/llm/providers/ollama/models.rb +1 -1
- data/lib/llm/providers/ollama.rb +2 -2
- data/lib/llm/providers/openai/audio.rb +3 -3
- data/lib/llm/providers/openai/files.rb +5 -5
- data/lib/llm/providers/openai/images.rb +3 -3
- data/lib/llm/providers/openai/models.rb +1 -1
- data/lib/llm/providers/openai/moderations.rb +1 -1
- data/lib/llm/providers/openai/responses.rb +3 -3
- data/lib/llm/providers/openai/vector_stores.rb +11 -11
- data/lib/llm/providers/openai.rb +2 -2
- data/lib/llm/skill.rb +1 -1
- data/lib/llm/tool.rb +21 -0
- data/lib/llm/transport/curb.rb +246 -0
- data/lib/llm/transport/execution.rb +1 -1
- data/lib/llm/transport/http.rb +9 -4
- data/lib/llm/transport/net_http_adapter.rb +61 -0
- data/lib/llm/transport/persistent_http.rb +10 -5
- data/lib/llm/transport/request.rb +121 -0
- data/lib/llm/transport/response/curb.rb +112 -0
- data/lib/llm/transport/response.rb +1 -0
- data/lib/llm/transport/utils.rb +42 -17
- data/lib/llm/transport.rb +17 -45
- data/lib/llm/version.rb +1 -1
- data/llm.gemspec +3 -3
- metadata +8 -4
|
@@ -8,7 +8,7 @@ class LLM::Bedrock
|
|
|
8
8
|
# Signs HTTP requests and headers with AWS Signature V4.
|
|
9
9
|
#
|
|
10
10
|
# Returns the signed headers as a Hash through #to_h, ready to merge
|
|
11
|
-
# into
|
|
11
|
+
# into an {LLM::Transport::Request} or other HTTP client. Everything else is
|
|
12
12
|
# private.
|
|
13
13
|
#
|
|
14
14
|
# Uses only Ruby's stdlib (openssl, digest) with no external deps.
|
|
@@ -89,8 +89,8 @@ class LLM::Bedrock
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
##
|
|
92
|
-
# @param [
|
|
93
|
-
# @return [
|
|
92
|
+
# @param [LLM::Transport::Request] req
|
|
93
|
+
# @return [LLM::Transport::Request]
|
|
94
94
|
def sign!(req)
|
|
95
95
|
to_h.each { |k, v| req[k] = v }
|
|
96
96
|
req
|
|
@@ -217,7 +217,7 @@ module LLM
|
|
|
217
217
|
body = LLM.json.dump(payload)
|
|
218
218
|
path = stream ? "/model/#{model_id}/converse-stream" \
|
|
219
219
|
: "/model/#{model_id}/converse"
|
|
220
|
-
req =
|
|
220
|
+
req = LLM::Transport::Request.post(path, headers)
|
|
221
221
|
transport.set_body_stream(req, StringIO.new(body))
|
|
222
222
|
[req, messages, body]
|
|
223
223
|
end
|
|
@@ -45,7 +45,7 @@ class LLM::Google
|
|
|
45
45
|
# @return [LLM::Response]
|
|
46
46
|
def all(**params)
|
|
47
47
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
48
|
-
req =
|
|
48
|
+
req = LLM::Transport::Request.get("/v1beta/files?#{query}", headers)
|
|
49
49
|
res, span, tracer = execute(request: req, operation: "request")
|
|
50
50
|
res = ResponseAdapter.adapt(res, type: :files)
|
|
51
51
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -64,7 +64,7 @@ class LLM::Google
|
|
|
64
64
|
# @return [LLM::Response]
|
|
65
65
|
def create(file:, **params)
|
|
66
66
|
file = LLM.File(file)
|
|
67
|
-
req =
|
|
67
|
+
req = LLM::Transport::Request.post(request_upload_url(file:), {})
|
|
68
68
|
req["content-length"] = file.bytesize
|
|
69
69
|
req["X-Goog-Upload-Offset"] = 0
|
|
70
70
|
req["X-Goog-Upload-Command"] = "upload, finalize"
|
|
@@ -91,7 +91,7 @@ class LLM::Google
|
|
|
91
91
|
def get(file:, **params)
|
|
92
92
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
93
93
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
94
|
-
req =
|
|
94
|
+
req = LLM::Transport::Request.get("/v1beta/#{file_id}?#{query}", headers)
|
|
95
95
|
res, span, tracer = execute(request: req, operation: "request")
|
|
96
96
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
97
97
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -111,7 +111,7 @@ class LLM::Google
|
|
|
111
111
|
def delete(file:, **params)
|
|
112
112
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
113
113
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
114
|
-
req =
|
|
114
|
+
req = LLM::Transport::Request.delete("/v1beta/#{file_id}?#{query}", headers)
|
|
115
115
|
res, span, tracer = execute(request: req, operation: "request")
|
|
116
116
|
res = LLM::Response.new(res)
|
|
117
117
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -128,7 +128,7 @@ class LLM::Google
|
|
|
128
128
|
private
|
|
129
129
|
|
|
130
130
|
def request_upload_url(file:)
|
|
131
|
-
req =
|
|
131
|
+
req = LLM::Transport::Request.post("/upload/v1beta/files?key=#{key}", headers)
|
|
132
132
|
req["X-Goog-Upload-Protocol"] = "resumable"
|
|
133
133
|
req["X-Goog-Upload-Command"] = "start"
|
|
134
134
|
req["X-Goog-Upload-Header-Content-Length"] = file.bytesize
|
|
@@ -40,7 +40,7 @@ class LLM::Google
|
|
|
40
40
|
# @raise (see LLM::Provider#request)
|
|
41
41
|
# @return [LLM::Response]
|
|
42
42
|
def create(prompt:, n: 1, image_size: nil, aspect_ratio: nil, person_generation: nil, model: "imagen-4.0-generate-001", **params)
|
|
43
|
-
req =
|
|
43
|
+
req = LLM::Transport::Request.post("/v1beta/models/#{model}:predict?key=#{key}", headers)
|
|
44
44
|
body = LLM.json.dump({
|
|
45
45
|
parameters: {
|
|
46
46
|
sampleCount: n,
|
|
@@ -39,7 +39,7 @@ class LLM::Google
|
|
|
39
39
|
# @return [LLM::Response]
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
42
|
-
req =
|
|
42
|
+
req = LLM::Transport::Request.get("/v1beta/models?#{query}", headers)
|
|
43
43
|
res, span, tracer = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
45
45
|
tracer.on_request_finish(operation: "request", res:, span:)
|
data/lib/llm/providers/google.rb
CHANGED
|
@@ -56,7 +56,7 @@ module LLM
|
|
|
56
56
|
def embed(input, model: "gemini-embedding-001", **params)
|
|
57
57
|
model = model.respond_to?(:id) ? model.id : model
|
|
58
58
|
path = ["/v1beta/models/#{model}", "embedContent?key=#{@key}"].join(":")
|
|
59
|
-
req =
|
|
59
|
+
req = LLM::Transport::Request.post(path, headers)
|
|
60
60
|
req.body = LLM.json.dump({content: {parts: [{text: input}]}})
|
|
61
61
|
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
62
62
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
@@ -205,7 +205,7 @@ module LLM
|
|
|
205
205
|
action = stream ? "streamGenerateContent?key=#{@key}&alt=sse" : "generateContent?key=#{@key}"
|
|
206
206
|
model.respond_to?(:id) ? model.id : model
|
|
207
207
|
path = ["/v1beta/models/#{model}", action].join(":")
|
|
208
|
-
req =
|
|
208
|
+
req = LLM::Transport::Request.post(path, headers)
|
|
209
209
|
messages = build_complete_messages(prompt, params, role)
|
|
210
210
|
body = LLM.json.dump({contents: adapt(messages)}.merge!(params))
|
|
211
211
|
transport.set_body_stream(req, StringIO.new(body))
|
|
@@ -40,7 +40,7 @@ class LLM::Ollama
|
|
|
40
40
|
# @return [LLM::Response]
|
|
41
41
|
def all(**params)
|
|
42
42
|
query = URI.encode_www_form(params)
|
|
43
|
-
req =
|
|
43
|
+
req = LLM::Transport::Request.get("/api/tags?#{query}", headers)
|
|
44
44
|
res, span, tracer = execute(request: req, operation: "request")
|
|
45
45
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
46
46
|
tracer.on_request_finish(operation: "request", res:, span:)
|
data/lib/llm/providers/ollama.rb
CHANGED
|
@@ -48,7 +48,7 @@ module LLM
|
|
|
48
48
|
# @return [LLM::Response]
|
|
49
49
|
def embed(input, model: default_model, **params)
|
|
50
50
|
params = {model:}.merge!(params)
|
|
51
|
-
req =
|
|
51
|
+
req = LLM::Transport::Request.post("/v1/embeddings", headers)
|
|
52
52
|
req.body = LLM.json.dump({input:}.merge!(params))
|
|
53
53
|
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
54
54
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
@@ -129,7 +129,7 @@ module LLM
|
|
|
129
129
|
def build_complete_request(prompt, params, role)
|
|
130
130
|
messages = build_complete_messages(prompt, params, role)
|
|
131
131
|
body = LLM.json.dump({messages: [adapt(messages)].flatten}.merge!(params))
|
|
132
|
-
req =
|
|
132
|
+
req = LLM::Transport::Request.post("/api/chat", headers)
|
|
133
133
|
transport.set_body_stream(req, StringIO.new(body))
|
|
134
134
|
req
|
|
135
135
|
end
|
|
@@ -32,7 +32,7 @@ class LLM::OpenAI
|
|
|
32
32
|
# @raise (see LLM::Provider#request)
|
|
33
33
|
# @return [LLM::Response]
|
|
34
34
|
def create_speech(input:, voice: "alloy", model: "gpt-4o-mini-tts", response_format: "mp3", **params)
|
|
35
|
-
req =
|
|
35
|
+
req = LLM::Transport::Request.post(path("/audio/speech"), headers)
|
|
36
36
|
req.body = LLM.json.dump({input:, voice:, model:, response_format:}.merge!(params))
|
|
37
37
|
io = StringIO.new("".b)
|
|
38
38
|
res, span, tracer = execute(request: req, operation: "request") { _1.read_body { |chunk| io << chunk } }
|
|
@@ -55,7 +55,7 @@ class LLM::OpenAI
|
|
|
55
55
|
# @return [LLM::Response]
|
|
56
56
|
def create_transcription(file:, model: "whisper-1", **params)
|
|
57
57
|
multi = LLM::Multipart.new(params.merge!(file: LLM.File(file), model:))
|
|
58
|
-
req =
|
|
58
|
+
req = LLM::Transport::Request.post(path("/audio/transcriptions"), headers)
|
|
59
59
|
req["content-type"] = multi.content_type
|
|
60
60
|
transport.set_body_stream(req, multi.body)
|
|
61
61
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -79,7 +79,7 @@ class LLM::OpenAI
|
|
|
79
79
|
# @return [LLM::Response]
|
|
80
80
|
def create_translation(file:, model: "whisper-1", **params)
|
|
81
81
|
multi = LLM::Multipart.new(params.merge!(file: LLM.File(file), model:))
|
|
82
|
-
req =
|
|
82
|
+
req = LLM::Transport::Request.post(path("/audio/translations"), headers)
|
|
83
83
|
req["content-type"] = multi.content_type
|
|
84
84
|
transport.set_body_stream(req, multi.body)
|
|
85
85
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -40,7 +40,7 @@ class LLM::OpenAI
|
|
|
40
40
|
# @return [LLM::Response]
|
|
41
41
|
def all(**params)
|
|
42
42
|
query = URI.encode_www_form(params)
|
|
43
|
-
req =
|
|
43
|
+
req = LLM::Transport::Request.get(path("/files?#{query}"), headers)
|
|
44
44
|
res, span, tracer = execute(request: req, operation: "request")
|
|
45
45
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
46
46
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -60,7 +60,7 @@ class LLM::OpenAI
|
|
|
60
60
|
# @return [LLM::Response]
|
|
61
61
|
def create(file:, purpose: "assistants", **params)
|
|
62
62
|
multi = LLM::Multipart.new(params.merge!(file: LLM.File(file), purpose:))
|
|
63
|
-
req =
|
|
63
|
+
req = LLM::Transport::Request.post(path("/files"), headers)
|
|
64
64
|
req["content-type"] = multi.content_type
|
|
65
65
|
transport.set_body_stream(req, multi.body)
|
|
66
66
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -83,7 +83,7 @@ class LLM::OpenAI
|
|
|
83
83
|
def get(file:, **params)
|
|
84
84
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
85
85
|
query = URI.encode_www_form(params)
|
|
86
|
-
req =
|
|
86
|
+
req = LLM::Transport::Request.get(path("/files/#{file_id}?#{query}"), headers)
|
|
87
87
|
res, span, tracer = execute(request: req, operation: "request")
|
|
88
88
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
89
89
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -105,7 +105,7 @@ class LLM::OpenAI
|
|
|
105
105
|
def download(file:, **params)
|
|
106
106
|
query = URI.encode_www_form(params)
|
|
107
107
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
108
|
-
req =
|
|
108
|
+
req = LLM::Transport::Request.get(path("/files/#{file_id}/content?#{query}"), headers)
|
|
109
109
|
io = StringIO.new("".b)
|
|
110
110
|
res, span, tracer = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
111
111
|
res = LLM::Response.new(res).tap { _1.define_singleton_method(:file) { io } }
|
|
@@ -125,7 +125,7 @@ class LLM::OpenAI
|
|
|
125
125
|
# @return [LLM::Response]
|
|
126
126
|
def delete(file:)
|
|
127
127
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
128
|
-
req =
|
|
128
|
+
req = LLM::Transport::Request.delete(path("/files/#{file_id}"), headers)
|
|
129
129
|
res, span, tracer = execute(request: req, operation: "request")
|
|
130
130
|
res = LLM::Response.new(res)
|
|
131
131
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -50,7 +50,7 @@ class LLM::OpenAI
|
|
|
50
50
|
# @raise (see LLM::Provider#request)
|
|
51
51
|
# @return [LLM::Response]
|
|
52
52
|
def create(prompt:, model: "dall-e-3", response_format: "b64_json", **params)
|
|
53
|
-
req =
|
|
53
|
+
req = LLM::Transport::Request.post(path("/images/generations"), headers)
|
|
54
54
|
req.body = LLM.json.dump({prompt:, n: 1, model:, response_format:}.merge!(params))
|
|
55
55
|
res, span, tracer = execute(request: req, operation: "request")
|
|
56
56
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
@@ -76,7 +76,7 @@ class LLM::OpenAI
|
|
|
76
76
|
def create_variation(image:, model: "dall-e-2", response_format: "b64_json", **params)
|
|
77
77
|
image = LLM.File(image)
|
|
78
78
|
multi = LLM::Multipart.new(params.merge!(image:, model:, response_format:))
|
|
79
|
-
req =
|
|
79
|
+
req = LLM::Transport::Request.post(path("/images/variations"), headers)
|
|
80
80
|
req["content-type"] = multi.content_type
|
|
81
81
|
transport.set_body_stream(req, multi.body)
|
|
82
82
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -102,7 +102,7 @@ class LLM::OpenAI
|
|
|
102
102
|
def edit(image:, prompt:, model: "dall-e-2", response_format: "b64_json", **params)
|
|
103
103
|
image = LLM.File(image)
|
|
104
104
|
multi = LLM::Multipart.new(params.merge!(image:, prompt:, model:, response_format:))
|
|
105
|
-
req =
|
|
105
|
+
req = LLM::Transport::Request.post(path("/images/edits"), headers)
|
|
106
106
|
req["content-type"] = multi.content_type
|
|
107
107
|
transport.set_body_stream(req, multi.body)
|
|
108
108
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -39,7 +39,7 @@ class LLM::OpenAI
|
|
|
39
39
|
# @return [LLM::Response]
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params)
|
|
42
|
-
req =
|
|
42
|
+
req = LLM::Transport::Request.get(path("/models?#{query}"), headers)
|
|
43
43
|
res, span, tracer = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
45
45
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -47,7 +47,7 @@ class LLM::OpenAI
|
|
|
47
47
|
# @param [String, LLM::Model] model The model to use
|
|
48
48
|
# @return [LLM::Response]
|
|
49
49
|
def create(input:, model: "omni-moderation-latest", **params)
|
|
50
|
-
req =
|
|
50
|
+
req = LLM::Transport::Request.post(path("/moderations"), headers)
|
|
51
51
|
input = RequestAdapter::Moderation.new(input).adapt
|
|
52
52
|
req.body = LLM.json.dump({input:, model:}.merge!(params))
|
|
53
53
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -40,7 +40,7 @@ class LLM::OpenAI
|
|
|
40
40
|
params = [params, adapt_schema(params), adapt_tools(tools)].inject({}, &:merge!).compact
|
|
41
41
|
role, stream = params.delete(:role), params.delete(:stream)
|
|
42
42
|
params[:stream] = true if @provider.streamable?(stream) || stream == true
|
|
43
|
-
req =
|
|
43
|
+
req = LLM::Transport::Request.post(path("/responses"), headers)
|
|
44
44
|
messages = build_complete_messages(prompt, params, role)
|
|
45
45
|
@provider.tracer.set_request_metadata(user_input: extract_user_input(messages, fallback: prompt))
|
|
46
46
|
body = LLM.json.dump({input: [adapt(messages, mode: :response)].flatten}.merge!(params))
|
|
@@ -61,7 +61,7 @@ class LLM::OpenAI
|
|
|
61
61
|
def get(response, **params)
|
|
62
62
|
response_id = response.respond_to?(:id) ? response.id : response
|
|
63
63
|
query = URI.encode_www_form(params)
|
|
64
|
-
req =
|
|
64
|
+
req = LLM::Transport::Request.get(path("/responses/#{response_id}?#{query}"), headers)
|
|
65
65
|
res, span, tracer = execute(request: req, operation: "request")
|
|
66
66
|
res = ResponseAdapter.adapt(res, type: :responds)
|
|
67
67
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -76,7 +76,7 @@ class LLM::OpenAI
|
|
|
76
76
|
# @return [LLM::Object] Response body
|
|
77
77
|
def delete(response)
|
|
78
78
|
response_id = response.respond_to?(:id) ? response.id : response
|
|
79
|
-
req =
|
|
79
|
+
req = LLM::Transport::Request.delete(path("/responses/#{response_id}"), headers)
|
|
80
80
|
res, span, tracer = execute(request: req, operation: "request")
|
|
81
81
|
res = LLM::Response.new(res)
|
|
82
82
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -31,7 +31,7 @@ class LLM::OpenAI
|
|
|
31
31
|
# @return [LLM::Response]
|
|
32
32
|
def all(**params)
|
|
33
33
|
query = URI.encode_www_form(params)
|
|
34
|
-
req =
|
|
34
|
+
req = LLM::Transport::Request.get(path("/vector_stores?#{query}"), headers)
|
|
35
35
|
res, span, tracer = execute(request: req, operation: "request")
|
|
36
36
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
37
37
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -47,7 +47,7 @@ class LLM::OpenAI
|
|
|
47
47
|
# @return [LLM::Response]
|
|
48
48
|
# @see https://platform.openai.com/docs/api-reference/vector_stores/create OpenAI docs
|
|
49
49
|
def create(name:, file_ids: nil, **params)
|
|
50
|
-
req =
|
|
50
|
+
req = LLM::Transport::Request.post(path("/vector_stores"), headers)
|
|
51
51
|
req.body = LLM.json.dump(params.merge({name:, file_ids:}).compact)
|
|
52
52
|
res, span, tracer = execute(request: req, operation: "request")
|
|
53
53
|
res = LLM::Response.new(res)
|
|
@@ -72,7 +72,7 @@ class LLM::OpenAI
|
|
|
72
72
|
# @see https://platform.openai.com/docs/api-reference/vector_stores/retrieve OpenAI docs
|
|
73
73
|
def get(vector:)
|
|
74
74
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
75
|
-
req =
|
|
75
|
+
req = LLM::Transport::Request.get(path("/vector_stores/#{vector_id}"), headers)
|
|
76
76
|
res, span, tracer = execute(request: req, operation: "request")
|
|
77
77
|
res = LLM::Response.new(res)
|
|
78
78
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -89,7 +89,7 @@ class LLM::OpenAI
|
|
|
89
89
|
# @see https://platform.openai.com/docs/api-reference/vector_stores/modify OpenAI docs
|
|
90
90
|
def modify(vector:, name: nil, **params)
|
|
91
91
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
92
|
-
req =
|
|
92
|
+
req = LLM::Transport::Request.post(path("/vector_stores/#{vector_id}"), headers)
|
|
93
93
|
req.body = LLM.json.dump(params.merge({name:}).compact)
|
|
94
94
|
res, span, tracer = execute(request: req, operation: "request")
|
|
95
95
|
res = LLM::Response.new(res)
|
|
@@ -105,7 +105,7 @@ class LLM::OpenAI
|
|
|
105
105
|
# @see https://platform.openai.com/docs/api-reference/vector_stores/delete OpenAI docs
|
|
106
106
|
def delete(vector:)
|
|
107
107
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
108
|
-
req =
|
|
108
|
+
req = LLM::Transport::Request.delete(path("/vector_stores/#{vector_id}"), headers)
|
|
109
109
|
res, span, tracer = execute(request: req, operation: "request")
|
|
110
110
|
res = LLM::Response.new(res)
|
|
111
111
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -122,7 +122,7 @@ class LLM::OpenAI
|
|
|
122
122
|
# @see https://platform.openai.com/docs/api-reference/vector_stores/search OpenAI docs
|
|
123
123
|
def search(vector:, query:, **params)
|
|
124
124
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
125
|
-
req =
|
|
125
|
+
req = LLM::Transport::Request.post(path("/vector_stores/#{vector_id}/search"), headers)
|
|
126
126
|
req.body = LLM.json.dump(params.merge({query:}).compact)
|
|
127
127
|
res, span, tracer = execute(request: req, operation: "retrieval")
|
|
128
128
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
@@ -140,7 +140,7 @@ class LLM::OpenAI
|
|
|
140
140
|
def all_files(vector:, **params)
|
|
141
141
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
142
142
|
query = URI.encode_www_form(params)
|
|
143
|
-
req =
|
|
143
|
+
req = LLM::Transport::Request.get(path("/vector_stores/#{vector_id}/files?#{query}"), headers)
|
|
144
144
|
res, span, tracer = execute(request: req, operation: "request")
|
|
145
145
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
146
146
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -159,7 +159,7 @@ class LLM::OpenAI
|
|
|
159
159
|
def add_file(vector:, file:, attributes: nil, **params)
|
|
160
160
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
161
161
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
162
|
-
req =
|
|
162
|
+
req = LLM::Transport::Request.post(path("/vector_stores/#{vector_id}/files"), headers)
|
|
163
163
|
req.body = LLM.json.dump(params.merge({file_id:, attributes:}).compact)
|
|
164
164
|
res, span, tracer = execute(request: req, operation: "request")
|
|
165
165
|
res = LLM::Response.new(res)
|
|
@@ -190,7 +190,7 @@ class LLM::OpenAI
|
|
|
190
190
|
def update_file(vector:, file:, attributes:, **params)
|
|
191
191
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
192
192
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
193
|
-
req =
|
|
193
|
+
req = LLM::Transport::Request.post(path("/vector_stores/#{vector_id}/files/#{file_id}"), headers)
|
|
194
194
|
req.body = LLM.json.dump(params.merge({attributes:}).compact)
|
|
195
195
|
res, span, tracer = execute(request: req, operation: "request")
|
|
196
196
|
res = LLM::Response.new(res)
|
|
@@ -209,7 +209,7 @@ class LLM::OpenAI
|
|
|
209
209
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
210
210
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
211
211
|
query = URI.encode_www_form(params)
|
|
212
|
-
req =
|
|
212
|
+
req = LLM::Transport::Request.get(path("/vector_stores/#{vector_id}/files/#{file_id}?#{query}"), headers)
|
|
213
213
|
res, span, tracer = execute(request: req, operation: "request")
|
|
214
214
|
res = LLM::Response.new(res)
|
|
215
215
|
tracer.on_request_finish(operation: "request", res:, span:)
|
|
@@ -226,7 +226,7 @@ class LLM::OpenAI
|
|
|
226
226
|
def delete_file(vector:, file:)
|
|
227
227
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
228
228
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
229
|
-
req =
|
|
229
|
+
req = LLM::Transport::Request.delete(path("/vector_stores/#{vector_id}/files/#{file_id}"), headers)
|
|
230
230
|
res, span, tracer = execute(request: req, operation: "request")
|
|
231
231
|
res = LLM::Response.new(res)
|
|
232
232
|
tracer.on_request_finish(operation: "request", res:, span:)
|
data/lib/llm/providers/openai.rb
CHANGED
|
@@ -52,7 +52,7 @@ module LLM
|
|
|
52
52
|
# @raise (see LLM::Provider#request)
|
|
53
53
|
# @return (see LLM::Provider#embed)
|
|
54
54
|
def embed(input, model: "text-embedding-3-small", **params)
|
|
55
|
-
req =
|
|
55
|
+
req = LLM::Transport::Request.post(path("/embeddings"), headers)
|
|
56
56
|
req.body = LLM.json.dump({input:, model:}.merge!(params))
|
|
57
57
|
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
58
58
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
@@ -222,7 +222,7 @@ module LLM
|
|
|
222
222
|
def build_complete_request(prompt, params, role)
|
|
223
223
|
messages = build_complete_messages(prompt, params, role)
|
|
224
224
|
body = LLM.json.dump({messages: adapt(messages, mode: :complete).flatten}.merge!(params))
|
|
225
|
-
req =
|
|
225
|
+
req = LLM::Transport::Request.post(completions_path, headers)
|
|
226
226
|
transport.set_body_stream(req, StringIO.new(body))
|
|
227
227
|
[req, messages]
|
|
228
228
|
end
|
data/lib/llm/skill.rb
CHANGED
|
@@ -159,7 +159,7 @@ module LLM
|
|
|
159
159
|
params[:concurrency] = concurrency if concurrency
|
|
160
160
|
agent = Class.new(LLM::Agent) do
|
|
161
161
|
instructions(instructions)
|
|
162
|
-
tools(inherit_tools ? ctx.params[:tools] : tools)
|
|
162
|
+
tools(inherit_tools ? [*ctx.params[:tools]].reject(&:skill?) : tools)
|
|
163
163
|
tracer(tracer)
|
|
164
164
|
end.new(ctx.llm, params)
|
|
165
165
|
agent.messages.concat(messages_for(ctx))
|
data/lib/llm/tool.rb
CHANGED
|
@@ -202,6 +202,13 @@ class LLM::Tool
|
|
|
202
202
|
false
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
+
##
|
|
206
|
+
# Returns true if the tool is a skill
|
|
207
|
+
# @return [Boolean]
|
|
208
|
+
def self.skill?
|
|
209
|
+
false
|
|
210
|
+
end
|
|
211
|
+
|
|
205
212
|
##
|
|
206
213
|
# Returns a function bound to this tool instance.
|
|
207
214
|
# @return [LLM::Function]
|
|
@@ -216,6 +223,20 @@ class LLM::Tool
|
|
|
216
223
|
self.class.mcp?
|
|
217
224
|
end
|
|
218
225
|
|
|
226
|
+
##
|
|
227
|
+
# Returns true if the tool is an A2A tool
|
|
228
|
+
# @return [Boolean]
|
|
229
|
+
def a2a?
|
|
230
|
+
self.class.a2a?
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
##
|
|
234
|
+
# Returns true if the tool is a skill
|
|
235
|
+
# @return [Boolean]
|
|
236
|
+
def skill?
|
|
237
|
+
self.class.skill?
|
|
238
|
+
end
|
|
239
|
+
|
|
219
240
|
##
|
|
220
241
|
# Called when an in-flight tool run is interrupted.
|
|
221
242
|
# Tools can override this to implement cooperative cleanup.
|