llm.rb 11.3.0 → 12.0.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 +270 -1
- data/LICENSE +92 -17
- data/README.md +209 -647
- data/data/anthropic.json +433 -249
- data/data/bedrock.json +2097 -1055
- data/data/deepinfra.json +993 -0
- data/data/deepseek.json +53 -28
- data/data/google.json +389 -771
- data/data/openai.json +1053 -771
- data/data/xai.json +133 -292
- data/data/zai.json +249 -141
- data/lib/llm/a2a.rb +2 -2
- data/lib/llm/active_record/acts_as_agent.rb +10 -41
- data/lib/llm/active_record/acts_as_llm.rb +25 -0
- data/lib/llm/active_record.rb +3 -3
- data/lib/llm/agent.rb +1 -1
- data/lib/llm/context.rb +9 -5
- data/lib/llm/contract/completion.rb +2 -2
- data/lib/llm/cost.rb +1 -1
- data/lib/llm/function/fiber_group.rb +2 -2
- data/lib/llm/function/task_group.rb +2 -2
- data/lib/llm/function/thread_group.rb +3 -3
- data/lib/llm/pipe.rb +1 -1
- data/lib/llm/provider.rb +2 -2
- data/lib/llm/providers/anthropic/request_adapter.rb +1 -1
- data/lib/llm/providers/bedrock/request_adapter/completion.rb +5 -5
- data/lib/llm/providers/bedrock/request_adapter.rb +3 -3
- data/lib/llm/providers/bedrock/response_adapter/completion.rb +2 -2
- data/lib/llm/providers/bedrock/response_adapter.rb +2 -2
- 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 +23 -6
- data/lib/llm/providers/google.rb +4 -4
- data/lib/llm/providers/ollama/request_adapter.rb +1 -1
- 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/request_adapter.rb +1 -1
- 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/registry.rb +2 -2
- data/lib/llm/response.rb +11 -1
- data/lib/llm/schema/leaf.rb +7 -1
- data/lib/llm/schema/object.rb +1 -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/stream.rb +1 -1
- data/lib/llm/tool.rb +2 -2
- 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/http.rb +2 -2
- data/lib/llm/transport/net_http_adapter.rb +1 -1
- data/lib/llm/transport/persistent_http.rb +1 -1
- data/lib/llm/transport/response/http.rb +1 -1
- data/lib/llm/uridata.rb +16 -0
- data/lib/llm/utils.rb +1 -1
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +20 -8
- data/llm.gemspec +14 -26
- data/resources/deepdive.md +966 -0
- metadata +26 -29
- data/lib/llm/tracer/langsmith.rb +0 -144
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# @!parse
|
|
4
|
+
# module ::ActiveRecord
|
|
5
|
+
# class Base
|
|
6
|
+
# end
|
|
7
|
+
# end
|
|
8
|
+
|
|
3
9
|
module LLM::ActiveRecord
|
|
4
10
|
##
|
|
5
11
|
# ActiveRecord integration for persisting {LLM::Agent LLM::Agent} state.
|
|
@@ -11,46 +17,6 @@ module LLM::ActiveRecord
|
|
|
11
17
|
# class and forwarded to an internal agent subclass.
|
|
12
18
|
module ActsAsAgent
|
|
13
19
|
module ClassMethods
|
|
14
|
-
def model(model = nil, &block)
|
|
15
|
-
return agent.model if model.nil? && !block
|
|
16
|
-
agent.model(model, &block)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def tools(*tools, &block)
|
|
20
|
-
return agent.tools if tools.empty? && !block
|
|
21
|
-
agent.tools(*tools, &block)
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def skills(*skills, &block)
|
|
25
|
-
return agent.skills if skills.empty? && !block
|
|
26
|
-
agent.skills(*skills, &block)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def schema(schema = nil, &block)
|
|
30
|
-
return agent.schema if schema.nil? && !block
|
|
31
|
-
agent.schema(schema, &block)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def instructions(instructions = nil)
|
|
35
|
-
return agent.instructions if instructions.nil?
|
|
36
|
-
agent.instructions(instructions)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def concurrency(concurrency = nil)
|
|
40
|
-
return agent.concurrency if concurrency.nil?
|
|
41
|
-
agent.concurrency(concurrency)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def confirm(*tool_names, &block)
|
|
45
|
-
return agent.confirm if tool_names.empty? && !block
|
|
46
|
-
agent.confirm(*tool_names, &block)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def tracer(tracer = nil, &block)
|
|
50
|
-
return agent.tracer if tracer.nil? && !block
|
|
51
|
-
agent.tracer(tracer, &block)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
20
|
def agent
|
|
55
21
|
@agent ||= Class.new(LLM::Agent)
|
|
56
22
|
end
|
|
@@ -86,13 +52,15 @@ module LLM::ActiveRecord
|
|
|
86
52
|
# Evaluated in the model class after the wrapper is installed, so agent
|
|
87
53
|
# DSL methods such as `model`, `tools`, `schema`, `instructions`, and
|
|
88
54
|
# `concurrency` can be configured inline.
|
|
55
|
+
# @yieldparam [LLM::Agent] agent
|
|
56
|
+
# Yields an instance of {LLM::Agent LLM::Agent}.
|
|
89
57
|
# @return [void]
|
|
90
58
|
def acts_as_agent(options = EMPTY_HASH, &block)
|
|
91
59
|
options = DEFAULTS.merge(options)
|
|
92
60
|
class_attribute :llm_plugin_options, instance_accessor: false, default: DEFAULTS unless respond_to?(:llm_plugin_options)
|
|
93
61
|
self.llm_plugin_options = options.freeze
|
|
94
62
|
extend Hooks
|
|
95
|
-
class_exec(&block)
|
|
63
|
+
block_given? ? class_exec(agent, &block) : nil
|
|
96
64
|
end
|
|
97
65
|
|
|
98
66
|
module InstanceMethods
|
|
@@ -134,4 +102,5 @@ module LLM::ActiveRecord
|
|
|
134
102
|
end
|
|
135
103
|
end
|
|
136
104
|
|
|
105
|
+
# @!parse ::ActiveRecord::Base.extend(LLM::ActiveRecord::ActsAsAgent)
|
|
137
106
|
::ActiveRecord::Base.extend(LLM::ActiveRecord::ActsAsAgent)
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# @!parse
|
|
4
|
+
# module ::ActiveRecord
|
|
5
|
+
# class Base
|
|
6
|
+
# end
|
|
7
|
+
# end
|
|
8
|
+
|
|
3
9
|
module LLM::ActiveRecord
|
|
4
10
|
##
|
|
5
11
|
# ActiveRecord integration for persisting {LLM::Context LLM::Context} state.
|
|
@@ -198,6 +204,24 @@ module LLM::ActiveRecord
|
|
|
198
204
|
|
|
199
205
|
private
|
|
200
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
|
+
|
|
201
225
|
##
|
|
202
226
|
# @return [LLM::Context]
|
|
203
227
|
def ctx
|
|
@@ -222,4 +246,5 @@ module LLM::ActiveRecord
|
|
|
222
246
|
end
|
|
223
247
|
end
|
|
224
248
|
|
|
249
|
+
# @!parse ::ActiveRecord::Base.extend(LLM::ActiveRecord::ActsAsLLM)
|
|
225
250
|
::ActiveRecord::Base.extend(LLM::ActiveRecord::ActsAsLLM)
|
data/lib/llm/active_record.rb
CHANGED
data/lib/llm/agent.rb
CHANGED
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
|
|
|
@@ -548,7 +551,8 @@ module LLM
|
|
|
548
551
|
prompt, params = transform(prompt, params)
|
|
549
552
|
bind!(params[:stream], params[:model], params[:tools])
|
|
550
553
|
res_id = params[:store] == false ? nil : @messages.find(&:assistant?)&.response&.response_id
|
|
551
|
-
|
|
554
|
+
input = res_id ? [] : @messages.to_a
|
|
555
|
+
params = params.merge(previous_response_id: res_id, input:).compact
|
|
552
556
|
[prompt, params, @llm.responses.create(prompt, params)]
|
|
553
557
|
end
|
|
554
558
|
|
|
@@ -597,7 +601,7 @@ module LLM
|
|
|
597
601
|
[*message.extra.tool_calls].each do |tool|
|
|
598
602
|
next if returns.any? { _1.id == tool[:id] }
|
|
599
603
|
attrs = {cancelled: true, reason: "function call cancelled"}
|
|
600
|
-
cancelled << LLM::Function::Return.new(tool
|
|
604
|
+
cancelled << LLM::Function::Return.new(tool[:id], tool[:name], attrs)
|
|
601
605
|
end
|
|
602
606
|
messages << LLM::Message.new(@llm.tool_role, cancelled) unless cancelled.empty?
|
|
603
607
|
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/cost.rb
CHANGED
|
@@ -37,7 +37,7 @@ class LLM::Cost < Struct.new(
|
|
|
37
37
|
)
|
|
38
38
|
##
|
|
39
39
|
# Build a cost breakdown from token usage and model pricing.
|
|
40
|
-
# @param [LLM::Context]
|
|
40
|
+
# @param [LLM::Context] ctx
|
|
41
41
|
# Context used to resolve provider, model, and token usage
|
|
42
42
|
# @return [LLM::Cost]
|
|
43
43
|
def self.from(ctx)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class LLM::Function
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Function::FiberGroup} class wraps an array of
|
|
6
|
-
#
|
|
6
|
+
# `Fiber` objects that are running {LLM::Function} calls
|
|
7
7
|
# concurrently using scheduler-backed fibers.
|
|
8
8
|
#
|
|
9
9
|
# This class provides the same interface as {LLM::Function::ThreadGroup}
|
|
@@ -26,7 +26,7 @@ class LLM::Function
|
|
|
26
26
|
# of fiber objects.
|
|
27
27
|
#
|
|
28
28
|
# @param [Array<Fiber>] fibers
|
|
29
|
-
# An array of fibers, each running an
|
|
29
|
+
# An array of fibers, each running an `LLM::Function#spawn_fiber` call.
|
|
30
30
|
#
|
|
31
31
|
# @return [LLM::Function::FiberGroup]
|
|
32
32
|
# Returns a new fiber group.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class LLM::Function
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Function::TaskGroup} class wraps an array of
|
|
6
|
-
#
|
|
6
|
+
# `Async::Task` objects that are running {LLM::Function} calls
|
|
7
7
|
# concurrently using the async gem.
|
|
8
8
|
#
|
|
9
9
|
# This class provides the same interface as {LLM::Function::ThreadGroup}
|
|
@@ -27,7 +27,7 @@ class LLM::Function
|
|
|
27
27
|
# of async task objects.
|
|
28
28
|
#
|
|
29
29
|
# @param [Array<Async::Task>] tasks
|
|
30
|
-
# An array of async tasks, each running an
|
|
30
|
+
# An array of async tasks, each running an `LLM::Function#spawn_async` call.
|
|
31
31
|
#
|
|
32
32
|
# @return [LLM::Function::TaskGroup]
|
|
33
33
|
# Returns a new task group.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
class LLM::Function
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Function::ThreadGroup} class wraps an array of
|
|
6
|
-
#
|
|
6
|
+
# `Thread` objects that are running {LLM::Function} calls
|
|
7
7
|
# concurrently. It provides a single {#wait} method that
|
|
8
8
|
# collects the {LLM::Function::Return} values from those
|
|
9
9
|
# threads.
|
|
@@ -27,11 +27,11 @@ class LLM::Function
|
|
|
27
27
|
class ThreadGroup
|
|
28
28
|
##
|
|
29
29
|
# Creates a new {LLM::Function::ThreadGroup} from an array
|
|
30
|
-
# of
|
|
30
|
+
# of `Thread` objects.
|
|
31
31
|
#
|
|
32
32
|
# @param [Array<Thread>] threads
|
|
33
33
|
# An array of threads, each running an {LLM::Function#spawn}
|
|
34
|
-
# call. The thread's
|
|
34
|
+
# call. The thread's `Thread#value` will be an
|
|
35
35
|
# {LLM::Function::Return}.
|
|
36
36
|
#
|
|
37
37
|
# @return [LLM::Function::ThreadGroup]
|
data/lib/llm/pipe.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module LLM
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Pipe LLM::Pipe} class wraps a pair of IO objects created by
|
|
6
|
-
#
|
|
6
|
+
# `IO.pipe`. It is used by llm.rb internals to manage process and stream
|
|
7
7
|
# communication through one small interface.
|
|
8
8
|
class Pipe
|
|
9
9
|
##
|
data/lib/llm/provider.rb
CHANGED
|
@@ -5,11 +5,11 @@ module LLM::Bedrock::RequestAdapter
|
|
|
5
5
|
# Adapts a single message to Bedrock Converse content blocks.
|
|
6
6
|
#
|
|
7
7
|
# Bedrock Converse content blocks include:
|
|
8
|
-
# - {text: "..."}
|
|
9
|
-
# - {image: {format: "png", source: {bytes: "..."}}}
|
|
10
|
-
# - {document: {format: "pdf", name: "...", source: {bytes: "..."}}}
|
|
11
|
-
# - {toolUse: {toolUseId: "...", name: "...", input:
|
|
12
|
-
# - {toolResult: {toolUseId: "...", content: [{text: "..."}]}}
|
|
8
|
+
# - { text: "..." }
|
|
9
|
+
# - { image: { format: "png", source: { bytes: "..." } } }
|
|
10
|
+
# - { document: { format: "pdf", name: "...", source: { bytes: "..." } } }
|
|
11
|
+
# - { toolUse: { toolUseId: "...", name: "...", input: ... } }
|
|
12
|
+
# - {toolResult: {toolUseId: "...", content: [{ text: "..." }]}}
|
|
13
13
|
#
|
|
14
14
|
# @api private
|
|
15
15
|
class Completion
|
|
@@ -5,10 +5,10 @@ class LLM::Bedrock
|
|
|
5
5
|
# Adapts llm.rb internal message format to Bedrock Converse API format.
|
|
6
6
|
#
|
|
7
7
|
# Bedrock Converse uses:
|
|
8
|
-
# - system: [{text: "..."}] (top-level, separate from messages)
|
|
9
|
-
# - messages: [{role: "user"|"assistant", content: [
|
|
8
|
+
# - system: `[ { text: "..." } ]` (top-level, separate from messages)
|
|
9
|
+
# - messages: `[ { role: "user"|"assistant", content: [ ... ] } ]`
|
|
10
10
|
# - Content blocks: text, image, document, toolUse, toolResult
|
|
11
|
-
# - toolConfig: {tools: [{toolSpec: {name:, description:, inputSchema: {json: ...}}}]}
|
|
11
|
+
# - toolConfig: `{ tools: [ { toolSpec: { name:, description:, inputSchema: { json: ... } } } ] }`
|
|
12
12
|
#
|
|
13
13
|
# @api private
|
|
14
14
|
module RequestAdapter
|
|
@@ -8,9 +8,9 @@ module LLM::Bedrock::ResponseAdapter
|
|
|
8
8
|
# {
|
|
9
9
|
# "output" => {"message" => {
|
|
10
10
|
# "role" => "assistant",
|
|
11
|
-
# "content" => [{"text" => "..."}, {"toolUse" =>
|
|
11
|
+
# "content" => `[ { "text" => "..." }, { "toolUse" => ... } ]`
|
|
12
12
|
# }},
|
|
13
|
-
# "usage" => {"inputTokens" => N, "outputTokens" => N}
|
|
13
|
+
# "usage" => `{ "inputTokens" => N, "outputTokens" => N }`,
|
|
14
14
|
# "modelId" => "anthropic.claude-sonnet-4-20250514-v1:0",
|
|
15
15
|
# "stopReason" => "end_turn"
|
|
16
16
|
# }
|
|
@@ -6,8 +6,8 @@ class LLM::Bedrock
|
|
|
6
6
|
#
|
|
7
7
|
# Bedrock Converse returns:
|
|
8
8
|
# {
|
|
9
|
-
# output: {message: {role: "assistant", content: [
|
|
10
|
-
# usage: {inputTokens: N, outputTokens: N}
|
|
9
|
+
# output: `{ message: { role: "assistant", content: [ ... ] } }`,
|
|
10
|
+
# usage: `{ inputTokens: N, outputTokens: N }`,
|
|
11
11
|
# modelId: "anthropic.claude-...",
|
|
12
12
|
# stopReason: "end_turn" | "tool_use" | "max_tokens" | ...
|
|
13
13
|
# }
|
|
@@ -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
|