llm.rb 4.14.0 → 4.16.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 +83 -0
- data/README.md +93 -28
- data/data/anthropic.json +218 -198
- data/data/deepseek.json +1 -1
- data/data/google.json +481 -429
- data/data/openai.json +742 -704
- data/data/xai.json +277 -277
- data/data/zai.json +160 -126
- data/lib/llm/active_record/acts_as_llm.rb +238 -0
- data/lib/llm/active_record.rb +3 -0
- data/lib/llm/context.rb +15 -10
- data/lib/llm/eventstream/parser.rb +40 -8
- data/lib/llm/provider.rb +16 -1
- data/lib/llm/providers/anthropic/stream_parser.rb +6 -3
- data/lib/llm/providers/google/stream_parser.rb +6 -3
- data/lib/llm/providers/ollama/stream_parser.rb +3 -2
- data/lib/llm/providers/openai/audio.rb +4 -4
- data/lib/llm/providers/openai/files.rb +6 -6
- data/lib/llm/providers/openai/images.rb +4 -4
- data/lib/llm/providers/openai/models.rb +2 -2
- data/lib/llm/providers/openai/moderations.rb +2 -2
- data/lib/llm/providers/openai/responses/stream_parser.rb +216 -91
- data/lib/llm/providers/openai/responses.rb +4 -4
- data/lib/llm/providers/openai/stream_parser.rb +111 -57
- data/lib/llm/providers/openai/vector_stores.rb +12 -12
- data/lib/llm/providers/openai.rb +4 -4
- data/lib/llm/response.rb +12 -4
- data/lib/llm/sequel/plugin.rb +252 -0
- data/lib/llm/stream/queue.rb +2 -2
- data/lib/llm/stream.rb +2 -2
- data/lib/llm/version.rb +1 -1
- data/lib/sequel/plugins/llm.rb +8 -0
- metadata +5 -1
data/lib/llm/context.rb
CHANGED
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module LLM
|
|
4
4
|
##
|
|
5
|
-
# {LLM::Context LLM::Context}
|
|
6
|
-
#
|
|
7
|
-
# and cost tracking. It evolves over time as the system runs.
|
|
5
|
+
# {LLM::Context LLM::Context} is the stateful execution boundary in
|
|
6
|
+
# llm.rb.
|
|
8
7
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
8
|
+
# It holds the evolving runtime state for an LLM workflow:
|
|
9
|
+
# conversation history, tool calls and returns, schema and streaming
|
|
10
|
+
# configuration, accumulated usage, and request ownership for
|
|
11
|
+
# interruption.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
13
|
+
# This is broader than prompt context alone. A context is the object
|
|
14
|
+
# that lets one-off prompts, streaming turns, tool execution,
|
|
15
|
+
# persistence, retries, and serialized long-lived workflows all run
|
|
16
|
+
# through the same model.
|
|
17
|
+
#
|
|
18
|
+
# A context can drive the chat completions API that all providers
|
|
19
|
+
# support or the Responses API on providers that expose it.
|
|
15
20
|
#
|
|
16
21
|
# @example
|
|
17
22
|
# #!/usr/bin/env ruby
|
|
@@ -272,13 +277,13 @@ module LLM
|
|
|
272
277
|
##
|
|
273
278
|
# @return [Hash]
|
|
274
279
|
def to_h
|
|
275
|
-
{model:, messages:}
|
|
280
|
+
{schema_version: 1, model:, messages:}
|
|
276
281
|
end
|
|
277
282
|
|
|
278
283
|
##
|
|
279
284
|
# @return [String]
|
|
280
285
|
def to_json(...)
|
|
281
|
-
|
|
286
|
+
to_h.to_json(...)
|
|
282
287
|
end
|
|
283
288
|
|
|
284
289
|
##
|
|
@@ -5,6 +5,7 @@ module LLM::EventStream
|
|
|
5
5
|
# @private
|
|
6
6
|
class Parser
|
|
7
7
|
COMPACT_THRESHOLD = 4096
|
|
8
|
+
Visitor = Struct.new(:target, :on_data, :on_event, :on_id, :on_retry, :on_chunk)
|
|
8
9
|
|
|
9
10
|
##
|
|
10
11
|
# @return [LLM::EventStream::Parser]
|
|
@@ -20,7 +21,12 @@ module LLM::EventStream
|
|
|
20
21
|
# @param [#on_data] visitor
|
|
21
22
|
# @return [void]
|
|
22
23
|
def register(visitor)
|
|
23
|
-
@visitors <<
|
|
24
|
+
@visitors << Visitor.new(
|
|
25
|
+
visitor,
|
|
26
|
+
visitor.respond_to?(:on_data), visitor.respond_to?(:on_event),
|
|
27
|
+
visitor.respond_to?(:on_id), visitor.respond_to?(:on_retry),
|
|
28
|
+
visitor.respond_to?(:on_chunk)
|
|
29
|
+
)
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
##
|
|
@@ -58,12 +64,16 @@ module LLM::EventStream
|
|
|
58
64
|
|
|
59
65
|
private
|
|
60
66
|
|
|
61
|
-
def
|
|
62
|
-
field, value = Event.parse(chunk)
|
|
67
|
+
def parse_event!(chunk, field, value)
|
|
63
68
|
dispatch_visitors(field, value, chunk)
|
|
64
69
|
dispatch_callbacks(field, value, chunk)
|
|
65
70
|
end
|
|
66
71
|
|
|
72
|
+
def parse!(chunk)
|
|
73
|
+
field, value = Event.parse(chunk)
|
|
74
|
+
parse_event!(chunk, field, value)
|
|
75
|
+
end
|
|
76
|
+
|
|
67
77
|
def dispatch_visitors(field, value, chunk)
|
|
68
78
|
@visitors.each { dispatch_visitor(_1, field, value, chunk) }
|
|
69
79
|
end
|
|
@@ -76,11 +86,33 @@ module LLM::EventStream
|
|
|
76
86
|
end
|
|
77
87
|
|
|
78
88
|
def dispatch_visitor(visitor, field, value, chunk)
|
|
79
|
-
|
|
80
|
-
if
|
|
81
|
-
visitor.
|
|
82
|
-
|
|
83
|
-
visitor.on_chunk
|
|
89
|
+
target = visitor.target
|
|
90
|
+
if field == "data"
|
|
91
|
+
if visitor.on_data
|
|
92
|
+
target.on_data(value, chunk)
|
|
93
|
+
elsif visitor.on_chunk
|
|
94
|
+
target.on_chunk(nil, chunk)
|
|
95
|
+
end
|
|
96
|
+
elsif field == "event"
|
|
97
|
+
if visitor.on_event
|
|
98
|
+
target.on_event(value, chunk)
|
|
99
|
+
elsif visitor.on_chunk
|
|
100
|
+
target.on_chunk(nil, chunk)
|
|
101
|
+
end
|
|
102
|
+
elsif field == "id"
|
|
103
|
+
if visitor.on_id
|
|
104
|
+
target.on_id(value, chunk)
|
|
105
|
+
elsif visitor.on_chunk
|
|
106
|
+
target.on_chunk(nil, chunk)
|
|
107
|
+
end
|
|
108
|
+
elsif field == "retry"
|
|
109
|
+
if visitor.on_retry
|
|
110
|
+
target.on_retry(value, chunk)
|
|
111
|
+
elsif visitor.on_chunk
|
|
112
|
+
target.on_chunk(nil, chunk)
|
|
113
|
+
end
|
|
114
|
+
elsif visitor.on_chunk
|
|
115
|
+
target.on_chunk(nil, chunk)
|
|
84
116
|
end
|
|
85
117
|
end
|
|
86
118
|
|
data/lib/llm/provider.rb
CHANGED
|
@@ -22,15 +22,18 @@ class LLM::Provider
|
|
|
22
22
|
# The number of seconds to wait for a response
|
|
23
23
|
# @param [Boolean] ssl
|
|
24
24
|
# Whether to use SSL for the connection
|
|
25
|
+
# @param [String] base_path
|
|
26
|
+
# Optional base path prefix for HTTP API routes.
|
|
25
27
|
# @param [Boolean] persistent
|
|
26
28
|
# Whether to use a persistent connection.
|
|
27
29
|
# Requires the net-http-persistent gem.
|
|
28
|
-
def initialize(key:, host:, port: 443, timeout: 60, ssl: true, persistent: false)
|
|
30
|
+
def initialize(key:, host:, port: 443, timeout: 60, ssl: true, base_path: "", persistent: false)
|
|
29
31
|
@key = key
|
|
30
32
|
@host = host
|
|
31
33
|
@port = port
|
|
32
34
|
@timeout = timeout
|
|
33
35
|
@ssl = ssl
|
|
36
|
+
@base_path = normalize_base_path(base_path)
|
|
34
37
|
@base_uri = URI("#{ssl ? "https" : "http"}://#{host}:#{port}/")
|
|
35
38
|
@headers = {"User-Agent" => "llm.rb v#{LLM::VERSION}"}
|
|
36
39
|
@transport = Transport::HTTP.new(host:, port:, timeout:, ssl:, persistent:)
|
|
@@ -330,6 +333,18 @@ class LLM::Provider
|
|
|
330
333
|
|
|
331
334
|
private
|
|
332
335
|
|
|
336
|
+
def path(suffix)
|
|
337
|
+
return suffix if @base_path.empty?
|
|
338
|
+
"#{@base_path}#{suffix}"
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def normalize_base_path(path)
|
|
342
|
+
path = path.to_s.strip
|
|
343
|
+
return "" if path.empty? || path == "/"
|
|
344
|
+
path = "/#{path}" unless path.start_with?("/")
|
|
345
|
+
path.sub(%r{/+\z}, "")
|
|
346
|
+
end
|
|
347
|
+
|
|
333
348
|
attr_reader :base_uri, :host, :port, :timeout, :ssl, :transport
|
|
334
349
|
|
|
335
350
|
##
|
|
@@ -16,6 +16,9 @@ class LLM::Anthropic
|
|
|
16
16
|
def initialize(stream)
|
|
17
17
|
@body = {"role" => "assistant", "content" => []}
|
|
18
18
|
@stream = stream
|
|
19
|
+
@can_emit_content = stream.respond_to?(:on_content)
|
|
20
|
+
@can_emit_tool_call = stream.respond_to?(:on_tool_call)
|
|
21
|
+
@can_push_content = stream.respond_to?(:<<)
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
##
|
|
@@ -88,15 +91,15 @@ class LLM::Anthropic
|
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
def emit_content(value)
|
|
91
|
-
if @
|
|
94
|
+
if @can_emit_content
|
|
92
95
|
@stream.on_content(value)
|
|
93
|
-
elsif @
|
|
96
|
+
elsif @can_push_content
|
|
94
97
|
@stream << value
|
|
95
98
|
end
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
def emit_tool(tool)
|
|
99
|
-
return unless @
|
|
102
|
+
return unless @can_emit_tool_call
|
|
100
103
|
function, error = resolve_tool(tool)
|
|
101
104
|
@stream.on_tool_call(function, error)
|
|
102
105
|
end
|
|
@@ -17,6 +17,9 @@ class LLM::Google
|
|
|
17
17
|
@body = {"candidates" => []}
|
|
18
18
|
@stream = stream
|
|
19
19
|
@emits = {tools: []}
|
|
20
|
+
@can_emit_content = stream.respond_to?(:on_content)
|
|
21
|
+
@can_emit_tool_call = stream.respond_to?(:on_tool_call)
|
|
22
|
+
@can_push_content = stream.respond_to?(:<<)
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
##
|
|
@@ -126,15 +129,15 @@ class LLM::Google
|
|
|
126
129
|
end
|
|
127
130
|
|
|
128
131
|
def emit_content(value)
|
|
129
|
-
if @
|
|
132
|
+
if @can_emit_content
|
|
130
133
|
@stream.on_content(value)
|
|
131
|
-
elsif @
|
|
134
|
+
elsif @can_push_content
|
|
132
135
|
@stream << value
|
|
133
136
|
end
|
|
134
137
|
end
|
|
135
138
|
|
|
136
139
|
def emit_tool(pindex, cindex, part)
|
|
137
|
-
return unless @
|
|
140
|
+
return unless @can_emit_tool_call
|
|
138
141
|
return unless complete_tool?(part)
|
|
139
142
|
key = [cindex, pindex]
|
|
140
143
|
return if @emits[:tools].include?(key)
|
|
@@ -14,6 +14,7 @@ class LLM::Ollama
|
|
|
14
14
|
def initialize(stream)
|
|
15
15
|
@body = {}
|
|
16
16
|
@stream = stream
|
|
17
|
+
@can_push_content = stream.respond_to?(:<<)
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
##
|
|
@@ -36,10 +37,10 @@ class LLM::Ollama
|
|
|
36
37
|
if key == "message"
|
|
37
38
|
if @body[key]
|
|
38
39
|
@body[key]["content"] << value["content"]
|
|
39
|
-
@stream << value["content"] if @
|
|
40
|
+
@stream << value["content"] if @can_push_content
|
|
40
41
|
else
|
|
41
42
|
@body[key] = value
|
|
42
|
-
@stream << value["content"] if @
|
|
43
|
+
@stream << value["content"] if @can_push_content
|
|
43
44
|
end
|
|
44
45
|
else
|
|
45
46
|
@body[key] = value
|
|
@@ -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 = Net::HTTP::Post.new("/
|
|
35
|
+
req = Net::HTTP::Post.new(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 = Net::HTTP::Post.new("/
|
|
58
|
+
req = Net::HTTP::Post.new(path("/audio/transcriptions"), headers)
|
|
59
59
|
req["content-type"] = multi.content_type
|
|
60
60
|
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 = Net::HTTP::Post.new("/
|
|
82
|
+
req = Net::HTTP::Post.new(path("/audio/translations"), headers)
|
|
83
83
|
req["content-type"] = multi.content_type
|
|
84
84
|
set_body_stream(req, multi.body)
|
|
85
85
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -90,7 +90,7 @@ class LLM::OpenAI
|
|
|
90
90
|
|
|
91
91
|
private
|
|
92
92
|
|
|
93
|
-
[:headers, :execute, :set_body_stream].each do |m|
|
|
93
|
+
[:path, :headers, :execute, :set_body_stream].each do |m|
|
|
94
94
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -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 = Net::HTTP::Get.new("/
|
|
43
|
+
req = Net::HTTP::Get.new(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 = Net::HTTP::Post.new("/
|
|
63
|
+
req = Net::HTTP::Post.new(path("/files"), headers)
|
|
64
64
|
req["content-type"] = multi.content_type
|
|
65
65
|
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 = Net::HTTP::Get.new("/
|
|
86
|
+
req = Net::HTTP::Get.new(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 = Net::HTTP::Get.new("/
|
|
108
|
+
req = Net::HTTP::Get.new(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 = Net::HTTP::Delete.new("/
|
|
128
|
+
req = Net::HTTP::Delete.new(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:)
|
|
@@ -134,7 +134,7 @@ class LLM::OpenAI
|
|
|
134
134
|
|
|
135
135
|
private
|
|
136
136
|
|
|
137
|
-
[:headers, :execute, :set_body_stream].each do |m|
|
|
137
|
+
[:path, :headers, :execute, :set_body_stream].each do |m|
|
|
138
138
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
139
139
|
end
|
|
140
140
|
end
|
|
@@ -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 = Net::HTTP::Post.new("/
|
|
53
|
+
req = Net::HTTP::Post.new(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 = Net::HTTP::Post.new("/
|
|
79
|
+
req = Net::HTTP::Post.new(path("/images/variations"), headers)
|
|
80
80
|
req["content-type"] = multi.content_type
|
|
81
81
|
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 = Net::HTTP::Post.new("/
|
|
105
|
+
req = Net::HTTP::Post.new(path("/images/edits"), headers)
|
|
106
106
|
req["content-type"] = multi.content_type
|
|
107
107
|
set_body_stream(req, multi.body)
|
|
108
108
|
res, span, tracer = execute(request: req, operation: "request")
|
|
@@ -113,7 +113,7 @@ class LLM::OpenAI
|
|
|
113
113
|
|
|
114
114
|
private
|
|
115
115
|
|
|
116
|
-
[:headers, :execute, :set_body_stream].each do |m|
|
|
116
|
+
[:path, :headers, :execute, :set_body_stream].each do |m|
|
|
117
117
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
118
118
|
end
|
|
119
119
|
end
|
|
@@ -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 = Net::HTTP::Get.new("/
|
|
42
|
+
req = Net::HTTP::Get.new(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:)
|
|
@@ -48,7 +48,7 @@ class LLM::OpenAI
|
|
|
48
48
|
|
|
49
49
|
private
|
|
50
50
|
|
|
51
|
-
[:headers, :execute, :set_body_stream].each do |m|
|
|
51
|
+
[:path, :headers, :execute, :set_body_stream].each do |m|
|
|
52
52
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
53
53
|
end
|
|
54
54
|
end
|
|
@@ -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 = Net::HTTP::Post.new("/
|
|
50
|
+
req = Net::HTTP::Post.new(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")
|
|
@@ -58,7 +58,7 @@ class LLM::OpenAI
|
|
|
58
58
|
|
|
59
59
|
private
|
|
60
60
|
|
|
61
|
-
[:headers, :execute].each do |m|
|
|
61
|
+
[:path, :headers, :execute].each do |m|
|
|
62
62
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
63
63
|
end
|
|
64
64
|
end
|