llm.rb 11.3.1 → 12.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/CHANGELOG.md +354 -2
- data/LICENSE +93 -17
- data/README.md +226 -616
- data/data/anthropic.json +322 -426
- data/data/bedrock.json +2634 -1144
- data/data/deepinfra.json +1513 -0
- data/data/deepseek.json +57 -28
- data/data/google.json +411 -771
- data/data/openai.json +1104 -771
- data/data/xai.json +141 -292
- data/data/zai.json +263 -141
- data/lib/llm/active_record/acts_as_agent.rb +3 -41
- data/lib/llm/active_record/acts_as_llm.rb +18 -0
- data/lib/llm/active_record.rb +3 -3
- data/lib/llm/agent.rb +25 -1
- data/lib/llm/context.rb +17 -5
- data/lib/llm/contract/completion.rb +2 -2
- data/lib/llm/json_adapter.rb +29 -3
- data/lib/llm/provider.rb +3 -3
- data/lib/llm/providers/deepinfra/audio.rb +66 -0
- data/lib/llm/providers/deepinfra/images.rb +90 -0
- data/lib/llm/providers/deepinfra/response_adapter.rb +36 -0
- data/lib/llm/providers/deepinfra.rb +100 -0
- data/lib/llm/providers/deepseek/images.rb +109 -0
- data/lib/llm/providers/deepseek/request_adapter.rb +32 -0
- data/lib/llm/providers/deepseek/response_adapter/image.rb +9 -0
- data/lib/llm/providers/deepseek/response_adapter.rb +29 -0
- data/lib/llm/providers/deepseek.rb +4 -2
- data/lib/llm/providers/google/request_adapter.rb +22 -5
- data/lib/llm/providers/google.rb +4 -4
- data/lib/llm/providers/llamacpp.rb +5 -5
- data/lib/llm/providers/openai/audio.rb +6 -2
- data/lib/llm/providers/openai/images.rb +9 -50
- data/lib/llm/providers/openai/request_adapter/respond.rb +38 -4
- data/lib/llm/providers/openai/response_adapter/audio.rb +5 -1
- data/lib/llm/providers/openai/response_adapter/completion.rb +1 -1
- data/lib/llm/providers/openai/response_adapter/image.rb +0 -4
- data/lib/llm/providers/openai/responses.rb +1 -0
- data/lib/llm/providers/openai/stream_parser.rb +5 -6
- data/lib/llm/providers/openai.rb +2 -2
- data/lib/llm/providers/xai/images.rb +49 -26
- data/lib/llm/providers/xai.rb +2 -2
- data/lib/llm/repl/input.rb +64 -0
- data/lib/llm/repl/status.rb +30 -0
- data/lib/llm/repl/stream.rb +46 -0
- data/lib/llm/repl/transcript.rb +61 -0
- data/lib/llm/repl/window.rb +107 -0
- data/lib/llm/repl.rb +78 -0
- data/lib/llm/response.rb +10 -0
- data/lib/llm/schema/leaf.rb +7 -1
- data/lib/llm/schema/renderer.rb +121 -0
- data/lib/llm/schema.rb +30 -0
- data/lib/llm/sequel/agent.rb +2 -43
- data/lib/llm/sequel/plugin.rb +25 -7
- data/lib/llm/tools/chdir.rb +23 -0
- data/lib/llm/tools/git.rb +41 -0
- data/lib/llm/tools/mkdir.rb +32 -0
- data/lib/llm/tools/pwd.rb +20 -0
- data/lib/llm/tools/read_file.rb +40 -0
- data/lib/llm/tools/rg.rb +46 -0
- data/lib/llm/tools/shell.rb +48 -0
- data/lib/llm/tools/swap_text.rb +25 -0
- data/lib/llm/tools/write_file.rb +24 -0
- data/lib/llm/tools.rb +5 -0
- data/lib/llm/tracer/telemetry.rb +4 -6
- data/lib/llm/tracer.rb +9 -21
- data/lib/llm/transport/execution.rb +16 -1
- data/lib/llm/transport/net_http_adapter.rb +1 -1
- data/lib/llm/uridata.rb +16 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +9 -0
- data/llm.gemspec +5 -18
- data/resources/deepdive.md +829 -263
- metadata +31 -18
- data/lib/llm/tracer/langsmith.rb +0 -144
|
@@ -17,46 +17,6 @@ module LLM::ActiveRecord
|
|
|
17
17
|
# class and forwarded to an internal agent subclass.
|
|
18
18
|
module ActsAsAgent
|
|
19
19
|
module ClassMethods
|
|
20
|
-
def model(model = nil, &block)
|
|
21
|
-
return agent.model if model.nil? && !block
|
|
22
|
-
agent.model(model, &block)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def tools(*tools, &block)
|
|
26
|
-
return agent.tools if tools.empty? && !block
|
|
27
|
-
agent.tools(*tools, &block)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def skills(*skills, &block)
|
|
31
|
-
return agent.skills if skills.empty? && !block
|
|
32
|
-
agent.skills(*skills, &block)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def schema(schema = nil, &block)
|
|
36
|
-
return agent.schema if schema.nil? && !block
|
|
37
|
-
agent.schema(schema, &block)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def instructions(instructions = nil)
|
|
41
|
-
return agent.instructions if instructions.nil?
|
|
42
|
-
agent.instructions(instructions)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def concurrency(concurrency = nil)
|
|
46
|
-
return agent.concurrency if concurrency.nil?
|
|
47
|
-
agent.concurrency(concurrency)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def confirm(*tool_names, &block)
|
|
51
|
-
return agent.confirm if tool_names.empty? && !block
|
|
52
|
-
agent.confirm(*tool_names, &block)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def tracer(tracer = nil, &block)
|
|
56
|
-
return agent.tracer if tracer.nil? && !block
|
|
57
|
-
agent.tracer(tracer, &block)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
20
|
def agent
|
|
61
21
|
@agent ||= Class.new(LLM::Agent)
|
|
62
22
|
end
|
|
@@ -92,13 +52,15 @@ module LLM::ActiveRecord
|
|
|
92
52
|
# Evaluated in the model class after the wrapper is installed, so agent
|
|
93
53
|
# DSL methods such as `model`, `tools`, `schema`, `instructions`, and
|
|
94
54
|
# `concurrency` can be configured inline.
|
|
55
|
+
# @yieldparam [LLM::Agent] agent
|
|
56
|
+
# Yields an instance of {LLM::Agent LLM::Agent}.
|
|
95
57
|
# @return [void]
|
|
96
58
|
def acts_as_agent(options = EMPTY_HASH, &block)
|
|
97
59
|
options = DEFAULTS.merge(options)
|
|
98
60
|
class_attribute :llm_plugin_options, instance_accessor: false, default: DEFAULTS unless respond_to?(:llm_plugin_options)
|
|
99
61
|
self.llm_plugin_options = options.freeze
|
|
100
62
|
extend Hooks
|
|
101
|
-
class_exec(&block)
|
|
63
|
+
block_given? ? class_exec(agent, &block) : nil
|
|
102
64
|
end
|
|
103
65
|
|
|
104
66
|
module InstanceMethods
|
|
@@ -204,6 +204,24 @@ module LLM::ActiveRecord
|
|
|
204
204
|
|
|
205
205
|
private
|
|
206
206
|
|
|
207
|
+
##
|
|
208
|
+
# @return [LLM::Provider]
|
|
209
|
+
def set_provider
|
|
210
|
+
raise NotImplementedError, "implement the set_provider callback"
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
##
|
|
214
|
+
# @return [Hash]
|
|
215
|
+
def set_context
|
|
216
|
+
EMPTY_HASH.dup
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
##
|
|
220
|
+
# @return [LLM::Tracer]
|
|
221
|
+
def set_tracer
|
|
222
|
+
nil
|
|
223
|
+
end
|
|
224
|
+
|
|
207
225
|
##
|
|
208
226
|
# @return [LLM::Context]
|
|
209
227
|
def ctx
|
data/lib/llm/active_record.rb
CHANGED
data/lib/llm/agent.rb
CHANGED
|
@@ -61,7 +61,11 @@ module LLM
|
|
|
61
61
|
# Returns the current tools when no argument is provided
|
|
62
62
|
def self.tools(*tools, &block)
|
|
63
63
|
return @tools || [] if tools.empty? && !block
|
|
64
|
-
|
|
64
|
+
if tools.size == 1 and tools.grep(Symbol).any?
|
|
65
|
+
@tools = tools.first
|
|
66
|
+
else
|
|
67
|
+
@tools = block || tools.flatten
|
|
68
|
+
end
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
##
|
|
@@ -334,6 +338,15 @@ module LLM
|
|
|
334
338
|
@tracer || @ctx.tracer
|
|
335
339
|
end
|
|
336
340
|
|
|
341
|
+
##
|
|
342
|
+
# @param [LLM::Tracer, nil] other
|
|
343
|
+
# A tracer, or nil.
|
|
344
|
+
# @return [void]
|
|
345
|
+
def tracer=(other)
|
|
346
|
+
@ctx.tracer = other
|
|
347
|
+
@tracer = other
|
|
348
|
+
end
|
|
349
|
+
|
|
337
350
|
##
|
|
338
351
|
# @return [LLM::Stream, #<<, nil]
|
|
339
352
|
# Returns a stream object, or nil
|
|
@@ -375,6 +388,17 @@ module LLM
|
|
|
375
388
|
@ctx.context_window
|
|
376
389
|
end
|
|
377
390
|
|
|
391
|
+
##
|
|
392
|
+
# Start a minimalist repl that can interact
|
|
393
|
+
# with the agent and its current state. This
|
|
394
|
+
# method requires the 'curses' gem to be installed
|
|
395
|
+
# and available to require.
|
|
396
|
+
# @return [void]
|
|
397
|
+
def repl
|
|
398
|
+
require_relative "repl" unless defined?(::LLM::Repl)
|
|
399
|
+
LLM::Repl.new(self).start
|
|
400
|
+
end
|
|
401
|
+
|
|
378
402
|
##
|
|
379
403
|
# @see LLM::Context#params
|
|
380
404
|
# @return [Hash]
|
data/lib/llm/context.rb
CHANGED
|
@@ -75,19 +75,22 @@ module LLM
|
|
|
75
75
|
# The parameters to maintain throughout the conversation.
|
|
76
76
|
# Any parameter the provider supports can be included and
|
|
77
77
|
# not only those listed here.
|
|
78
|
-
# @option params [Symbol] :mode
|
|
78
|
+
# @option params [Symbol] :mode
|
|
79
|
+
# Defaults to `:responses` for OpenAI, otherwise it defaults
|
|
80
|
+
# to `:completions`.
|
|
79
81
|
# @option params [String] :model Defaults to the provider's default model
|
|
80
82
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
81
83
|
# @option params [Array<String>, nil] :skills Defaults to nil
|
|
82
84
|
def initialize(llm, params = {})
|
|
83
85
|
@llm = llm
|
|
84
|
-
@mode = params.delete(:mode) || :completions
|
|
86
|
+
@mode = params.delete(:mode) || (llm.name == :openai ? :responses : :completions)
|
|
85
87
|
@compactor = params.delete(:compactor)
|
|
86
88
|
@guard = params.delete(:guard)
|
|
87
89
|
@transformer = params.delete(:transformer)
|
|
88
90
|
tools = [*params.delete(:tools), *load_skills(params.delete(:skills))]
|
|
89
91
|
@params = {model: llm.default_model, schema: nil}.compact.merge!(params)
|
|
90
92
|
@params[:tools] = tools unless tools.empty?
|
|
93
|
+
@params[:store] ||= false if @mode == :responses
|
|
91
94
|
@messages = LLM::Buffer.new(llm)
|
|
92
95
|
end
|
|
93
96
|
|
|
@@ -199,7 +202,7 @@ module LLM
|
|
|
199
202
|
role = params[:role] || @llm.user_role
|
|
200
203
|
role = @llm.tool_role if params[:role].nil? && [*prompt].grep(LLM::Function::Return).any?
|
|
201
204
|
@messages.concat LLM::Prompt === prompt ? prompt.to_a : [LLM::Message.new(role, prompt)]
|
|
202
|
-
@messages.concat [res.choices[-1]]
|
|
205
|
+
@messages.concat [res.choices[-1]].compact
|
|
203
206
|
res
|
|
204
207
|
end
|
|
205
208
|
|
|
@@ -418,6 +421,14 @@ module LLM
|
|
|
418
421
|
@llm.tracer
|
|
419
422
|
end
|
|
420
423
|
|
|
424
|
+
##
|
|
425
|
+
# @param [LLM::Tracer, nil] other
|
|
426
|
+
# A tracer, or nil.
|
|
427
|
+
# @return [void]
|
|
428
|
+
def tracer=(other)
|
|
429
|
+
@llm.tracer = nil
|
|
430
|
+
end
|
|
431
|
+
|
|
421
432
|
##
|
|
422
433
|
# @return [LLM::Stream, #<<, nil]
|
|
423
434
|
# Returns a stream object, or nil
|
|
@@ -548,7 +559,8 @@ module LLM
|
|
|
548
559
|
prompt, params = transform(prompt, params)
|
|
549
560
|
bind!(params[:stream], params[:model], params[:tools])
|
|
550
561
|
res_id = params[:store] == false ? nil : @messages.find(&:assistant?)&.response&.response_id
|
|
551
|
-
|
|
562
|
+
input = res_id ? [] : @messages.to_a
|
|
563
|
+
params = params.merge(previous_response_id: res_id, input:).compact
|
|
552
564
|
[prompt, params, @llm.responses.create(prompt, params)]
|
|
553
565
|
end
|
|
554
566
|
|
|
@@ -597,7 +609,7 @@ module LLM
|
|
|
597
609
|
[*message.extra.tool_calls].each do |tool|
|
|
598
610
|
next if returns.any? { _1.id == tool[:id] }
|
|
599
611
|
attrs = {cancelled: true, reason: "function call cancelled"}
|
|
600
|
-
cancelled << LLM::Function::Return.new(tool
|
|
612
|
+
cancelled << LLM::Function::Return.new(tool[:id], tool[:name], attrs)
|
|
601
613
|
end
|
|
602
614
|
messages << LLM::Message.new(@llm.tool_role, cancelled) unless cancelled.empty?
|
|
603
615
|
end
|
|
@@ -98,10 +98,10 @@ module LLM::Contract
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
##
|
|
101
|
-
# @return [
|
|
101
|
+
# @return [LLM::Object]
|
|
102
102
|
# Returns the LLM response after parsing it as JSON
|
|
103
103
|
def content!
|
|
104
|
-
LLM.json.load(content)
|
|
104
|
+
LLM::Object.from LLM.json.load(content)
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
##
|
data/lib/llm/json_adapter.rb
CHANGED
|
@@ -38,11 +38,11 @@ module LLM
|
|
|
38
38
|
def self.dump(obj, state = nil, **options)
|
|
39
39
|
require "json" unless defined?(::JSON)
|
|
40
40
|
if ::JSON::State === state
|
|
41
|
-
::JSON.generate(obj, state)
|
|
41
|
+
::JSON.generate(normalize(obj), state)
|
|
42
42
|
elsif state
|
|
43
|
-
::JSON.dump(obj, state, **options)
|
|
43
|
+
::JSON.dump(normalize(obj), state, **options)
|
|
44
44
|
else
|
|
45
|
-
::JSON.dump(obj, **options)
|
|
45
|
+
::JSON.dump(normalize(obj), **options)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -59,6 +59,32 @@ module LLM
|
|
|
59
59
|
require "json" unless defined?(::JSON)
|
|
60
60
|
[::JSON::ParserError]
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# JSON 3.0 compat
|
|
65
|
+
# Walks `obj` and encodes every string that is
|
|
66
|
+
# found into a UTF-8 compatible string.
|
|
67
|
+
def self.normalize(obj)
|
|
68
|
+
case obj
|
|
69
|
+
when String then normalize_string(obj)
|
|
70
|
+
when Array then obj.map { normalize(_1) }
|
|
71
|
+
when Hash then obj.map { [_1, normalize(_2)] }.to_h
|
|
72
|
+
when LLM::Object then obj.map { [_1, normalize(_2)] }.to_h
|
|
73
|
+
else obj
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private_class_method :normalize
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# JSON 3.0 compat
|
|
80
|
+
# Normalizes a string as a UTF-8 encoded string
|
|
81
|
+
# that's compatible with the JSON spec.
|
|
82
|
+
def self.normalize_string(str)
|
|
83
|
+
return str if str.encoding == Encoding::UTF_8
|
|
84
|
+
str = (+str).force_encoding("UTF-8")
|
|
85
|
+
str.valid_encoding? ? str : str.scrub
|
|
86
|
+
end
|
|
87
|
+
private_class_method :normalize_string
|
|
62
88
|
end
|
|
63
89
|
|
|
64
90
|
##
|
data/lib/llm/provider.rb
CHANGED
|
@@ -286,7 +286,7 @@ class LLM::Provider
|
|
|
286
286
|
# A tracer
|
|
287
287
|
# @return [void]
|
|
288
288
|
def tracer=(tracer)
|
|
289
|
-
@tracer = tracer
|
|
289
|
+
@tracer = tracer || LLM::Tracer::Null.new(self)
|
|
290
290
|
end
|
|
291
291
|
|
|
292
292
|
##
|
|
@@ -348,8 +348,8 @@ class LLM::Provider
|
|
|
348
348
|
|
|
349
349
|
private
|
|
350
350
|
|
|
351
|
-
def path(suffix)
|
|
352
|
-
return suffix if @base_path.empty?
|
|
351
|
+
def path(suffix, base_path: true)
|
|
352
|
+
return suffix if !base_path || @base_path.empty?
|
|
353
353
|
"#{@base_path}#{suffix}"
|
|
354
354
|
end
|
|
355
355
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::DeepInfra
|
|
4
|
+
class Audio
|
|
5
|
+
##
|
|
6
|
+
# @param [LLM::Provider] provider
|
|
7
|
+
# A provider
|
|
8
|
+
# @return [LLM::DeepInfra::Audio]
|
|
9
|
+
def initialize(provider)
|
|
10
|
+
@provider = provider
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# @param [String] input
|
|
15
|
+
# A string of text
|
|
16
|
+
# @param [String] model
|
|
17
|
+
# A text-to-speech model.
|
|
18
|
+
# Defaults to hexgrad/Kokoro-82M.
|
|
19
|
+
# @param [Hash] params
|
|
20
|
+
# Any other model-specific parameters
|
|
21
|
+
# @return [LLM::Response]
|
|
22
|
+
def create_speech(input:, model: "hexgrad/Kokoro-82M", **params)
|
|
23
|
+
path = path("/v1/inference/#{model}", base_path: false)
|
|
24
|
+
req = LLM::Transport::Request.post(path, headers)
|
|
25
|
+
req.body = JSON.dump(params.merge(text: input))
|
|
26
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
27
|
+
res = ResponseAdapter.adapt LLM::Response.new(res), type: :audio
|
|
28
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
29
|
+
res
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @see https://deepinfra.com/models/automatic-speech-recognition speech-to-text models
|
|
34
|
+
# @see https://docs.deepinfra.com/apis/speech API docs
|
|
35
|
+
# @param [String, LLM::File] file
|
|
36
|
+
# An audio file
|
|
37
|
+
# @param [String] model
|
|
38
|
+
# A speech-to-text model.
|
|
39
|
+
# @param [Hash] params
|
|
40
|
+
# Any other model-specific parameters
|
|
41
|
+
# @return [LLM::Response]
|
|
42
|
+
def create_transcription(file:, model: "openai/whisper-large-v3", **params)
|
|
43
|
+
path = path("/v1/inference/#{model}", base_path: false)
|
|
44
|
+
multi = LLM::Multipart.new(params.merge!(audio: LLM.File(file)))
|
|
45
|
+
req = LLM::Transport::Request.post(path, headers)
|
|
46
|
+
req["content-type"] = multi.content_type
|
|
47
|
+
transport.set_body_stream(req, multi.body)
|
|
48
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
49
|
+
res = LLM::Response.new(res)
|
|
50
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
51
|
+
res
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
##
|
|
55
|
+
# @raise [NotImplementedError]
|
|
56
|
+
def create_translation(...)
|
|
57
|
+
raise NotImplementedError
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
[:path, :headers, :execute, :transport].each do |m|
|
|
63
|
+
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::DeepInfra
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::DeepInfra::Images LLM::DeepInfra::Images} class provides an
|
|
6
|
+
# interface for [DeepInfra's images API](https://docs.deepinfra.com/apis/image-generation).
|
|
7
|
+
# DeepInfra returns base64-encoded image data.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# #!/usr/bin/env ruby
|
|
11
|
+
# require "llm"
|
|
12
|
+
#
|
|
13
|
+
# llm = LLM.deepinfra(key: ENV["KEY"])
|
|
14
|
+
# res = llm.images.create prompt: "A dog on a rocket to the moon"
|
|
15
|
+
# IO.copy_stream res.images[0], "rocket.png"
|
|
16
|
+
class Images
|
|
17
|
+
##
|
|
18
|
+
# @param [LLM::Provider] provider
|
|
19
|
+
# @return [LLM::DeepInfra::Images]
|
|
20
|
+
def initialize(provider)
|
|
21
|
+
@provider = provider
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# @see https://deepinfra.com/models/text-to-image DeepInfra Image Models
|
|
26
|
+
# @param [String] prompt
|
|
27
|
+
# A prompt
|
|
28
|
+
# @param [String] model
|
|
29
|
+
# A text-to-image model.
|
|
30
|
+
# Defaults to the black-forest-labs/FLUX-2-klein-4b.
|
|
31
|
+
# @param [String] size
|
|
32
|
+
# Image size (eg 1024x1024)
|
|
33
|
+
# @param [Integer] n
|
|
34
|
+
# The number of images to default
|
|
35
|
+
# @param [String] response_format
|
|
36
|
+
# No other options other than the default are supported.
|
|
37
|
+
# @param [String] quality
|
|
38
|
+
# Exists for compat. Noop.
|
|
39
|
+
# @param [String] style
|
|
40
|
+
# Exists for compat. Noop.
|
|
41
|
+
# @return [LLM::Response<LLM::OpenAI::ResponseAdapter::Image>]
|
|
42
|
+
# Returns a response
|
|
43
|
+
def create(prompt:, model: "black-forest-labs/FLUX-2-klein-4b", size: "1024x1024", n: 1, response_format: "b64_json", quality: nil, style: nil)
|
|
44
|
+
req = LLM::Transport::Request.post(path("/images/generations"), headers)
|
|
45
|
+
params = {prompt:, model:, size:, n:, response_format:, quality:, style:}.compact
|
|
46
|
+
req.body = LLM.json.dump(params)
|
|
47
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
48
|
+
res = LLM::OpenAI::ResponseAdapter.adapt(res, type: :image)
|
|
49
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
50
|
+
res
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# @see https://deepinfra.com/models/text-to-image DeepInfra Image Models
|
|
55
|
+
# @param [String, LLM::File, File] image
|
|
56
|
+
# The image to edit.
|
|
57
|
+
# @param [String] prompt
|
|
58
|
+
# A text description of the desired edits.
|
|
59
|
+
# @param [String] model
|
|
60
|
+
# The model to use.
|
|
61
|
+
# @param [String] size
|
|
62
|
+
# Image size (eg 1024x1024)
|
|
63
|
+
# @param [Integer] n
|
|
64
|
+
# The number of images to generate.
|
|
65
|
+
# @param [String] response_format
|
|
66
|
+
# DeepInfra currently supports b64_json.
|
|
67
|
+
# @param [Hash] params
|
|
68
|
+
# Other parameters supported by DeepInfra, such as :mask or :user.
|
|
69
|
+
# @return [LLM::Response<LLM::OpenAI::ResponseAdapter::Image>]
|
|
70
|
+
# Returns a response
|
|
71
|
+
def edit(image:, prompt:, model: "black-forest-labs/FLUX-2-klein-4b", size: "1024x1024", n: 1, response_format: "b64_json", **params)
|
|
72
|
+
params = params.merge!(image: LLM.File(image), prompt:, model:, size:, n:, response_format:)
|
|
73
|
+
params[:mask] = LLM.File(params[:mask]) if params[:mask]
|
|
74
|
+
multi = LLM::Multipart.new(params)
|
|
75
|
+
req = LLM::Transport::Request.post(path("/images/edits"), headers)
|
|
76
|
+
req["content-type"] = multi.content_type
|
|
77
|
+
transport.set_body_stream(req, multi.body)
|
|
78
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
79
|
+
res = LLM::OpenAI::ResponseAdapter.adapt(res, type: :image)
|
|
80
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
81
|
+
res
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
[:path, :headers, :execute, :transport].each do |m|
|
|
87
|
+
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::DeepInfra
|
|
4
|
+
##
|
|
5
|
+
# @private
|
|
6
|
+
module ResponseAdapter
|
|
7
|
+
module Audio
|
|
8
|
+
##
|
|
9
|
+
# @return [LLM::URIData]
|
|
10
|
+
def audio
|
|
11
|
+
@audio ||= LLM::URIData.parse(super)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @param [LLM::Response, Net::HTTPResponse] res
|
|
19
|
+
# @param [Symbol] type
|
|
20
|
+
# @return [LLM::Response]
|
|
21
|
+
def adapt(res, type:)
|
|
22
|
+
response = (LLM::Response === res) ? res : LLM::Response.new(res)
|
|
23
|
+
adapter = select(type)
|
|
24
|
+
response.extend(adapter)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# @api private
|
|
29
|
+
def select(type)
|
|
30
|
+
case type
|
|
31
|
+
when :audio then Audio
|
|
32
|
+
else LLM::OpenAI::ResponseAdapter.select(type)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "openai" unless defined?(LLM::OpenAI)
|
|
4
|
+
|
|
5
|
+
module LLM
|
|
6
|
+
##
|
|
7
|
+
# The DeepInfra class implements a provider for
|
|
8
|
+
# [DeepInfra](https://deepinfra.com)
|
|
9
|
+
# through its OpenAI-compatible API.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# #!/usr/bin/env ruby
|
|
13
|
+
# require "llm"
|
|
14
|
+
#
|
|
15
|
+
# llm = LLM.deepinfra(key: ENV["KEY"])
|
|
16
|
+
# ctx = LLM::Context.new(llm)
|
|
17
|
+
# ctx.talk "Hello"
|
|
18
|
+
# ctx.messages.select(&:assistant?).each { print "[#{_1.role}]", _1.content, "\n" }
|
|
19
|
+
class DeepInfra < OpenAI
|
|
20
|
+
HOST = "api.deepinfra.com"
|
|
21
|
+
BASE_PATH = "/v1/openai"
|
|
22
|
+
require_relative "deepinfra/images"
|
|
23
|
+
require_relative "deepinfra/audio"
|
|
24
|
+
require_relative "deepinfra/response_adapter"
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @param key (see LLM::Provider#initialize)
|
|
28
|
+
# @param host (see LLM::Provider#initialize)
|
|
29
|
+
# @param base_path (see LLM::Provider#initialize)
|
|
30
|
+
# @return [LLM::DeepInfra]
|
|
31
|
+
def initialize(host: HOST, base_path: BASE_PATH, **)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# @return [Symbol]
|
|
37
|
+
# Returns the provider's name
|
|
38
|
+
def name
|
|
39
|
+
:deepinfra
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Provides an interface to DeepInfra's OpenAI-compatible image API.
|
|
44
|
+
# @see https://deepinfra.com/models/text-to-image DeepInfra image models
|
|
45
|
+
# @return [LLM::DeepInfra::Images]
|
|
46
|
+
def images
|
|
47
|
+
LLM::DeepInfra::Images.new(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Provides an embedding.
|
|
52
|
+
# @see https://deepinfra.com/BAAI/bge-m3 BAAI/bge-m3
|
|
53
|
+
# @param input (see LLM::Provider#embed)
|
|
54
|
+
# @param model (see LLM::Provider#embed)
|
|
55
|
+
# @param params (see LLM::Provider#embed)
|
|
56
|
+
# @raise (see LLM::Provider#request)
|
|
57
|
+
# @return (see LLM::Provider#embed)
|
|
58
|
+
def embed(input, model: "BAAI/bge-m3", **params)
|
|
59
|
+
super
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# @raise [NotImplementedError]
|
|
64
|
+
def responses
|
|
65
|
+
raise NotImplementedError
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
##
|
|
69
|
+
# @return [LLM::DeepInfra::Audio]
|
|
70
|
+
def audio
|
|
71
|
+
LLM::DeepInfra::Audio.new(self)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# @raise [NotImplementedError]
|
|
76
|
+
def files
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# @raise [NotImplementedError]
|
|
82
|
+
def moderations
|
|
83
|
+
raise NotImplementedError
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# @raise [NotImplementedError]
|
|
88
|
+
def vector_stores
|
|
89
|
+
raise NotImplementedError
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# Returns the default model for chat completions
|
|
94
|
+
# @see https://deepinfra.com/models/zai-org/GLM-5.2 zai-org/GLM-5.2
|
|
95
|
+
# @return [String]
|
|
96
|
+
def default_model
|
|
97
|
+
"zai-org/GLM-5.2"
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|