llm.rb 11.3.1 → 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 +242 -1
- data/LICENSE +92 -17
- data/README.md +204 -623
- 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/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/context.rb +9 -5
- data/lib/llm/contract/completion.rb +2 -2
- data/lib/llm/provider.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 +22 -5
- data/lib/llm/providers/google.rb +4 -4
- 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/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/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 +798 -264
- metadata +15 -18
- data/lib/llm/tracer/langsmith.rb +0 -144
data/lib/llm/sequel/agent.rb
CHANGED
|
@@ -7,8 +7,7 @@ module LLM::Sequel
|
|
|
7
7
|
# This wrapper reuses the same record-backed runtime surface as
|
|
8
8
|
# {LLM::Sequel::Plugin}, but builds an {LLM::Agent LLM::Agent} instead of an
|
|
9
9
|
# {LLM::Context LLM::Context}. Agent defaults such as model, tools, schema,
|
|
10
|
-
# instructions, and concurrency are configured on
|
|
11
|
-
# forwarded to an internal agent subclass.
|
|
10
|
+
# instructions, and concurrency are configured on an internal agent subclass.
|
|
12
11
|
module Agent
|
|
13
12
|
require_relative "plugin"
|
|
14
13
|
EMPTY_HASH = LLM::Sequel::Plugin::EMPTY_HASH
|
|
@@ -25,7 +24,7 @@ module LLM::Sequel
|
|
|
25
24
|
options = DEFAULTS.merge(options)
|
|
26
25
|
model.db.extension :pg_json if %i[json jsonb].include?(options[:format])
|
|
27
26
|
model.instance_variable_set(:@llm_agent_options, options.freeze)
|
|
28
|
-
model.instance_exec(&block)
|
|
27
|
+
block_given? ? model.instance_exec(model.agent, &block) : nil
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
module ClassMethods
|
|
@@ -33,46 +32,6 @@ module LLM::Sequel
|
|
|
33
32
|
@llm_agent_options || Agent::DEFAULTS
|
|
34
33
|
end
|
|
35
34
|
|
|
36
|
-
def model(model = nil, &block)
|
|
37
|
-
return agent.model if model.nil? && !block
|
|
38
|
-
agent.model(model, &block)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def tools(*tools, &block)
|
|
42
|
-
return agent.tools if tools.empty? && !block
|
|
43
|
-
agent.tools(*tools, &block)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def skills(*skills, &block)
|
|
47
|
-
return agent.skills if skills.empty? && !block
|
|
48
|
-
agent.skills(*skills, &block)
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def schema(schema = nil, &block)
|
|
52
|
-
return agent.schema if schema.nil? && !block
|
|
53
|
-
agent.schema(schema, &block)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def instructions(instructions = nil)
|
|
57
|
-
return agent.instructions if instructions.nil?
|
|
58
|
-
agent.instructions(instructions)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def concurrency(concurrency = nil)
|
|
62
|
-
return agent.concurrency if concurrency.nil?
|
|
63
|
-
agent.concurrency(concurrency)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def confirm(*tool_names, &block)
|
|
67
|
-
return agent.confirm if tool_names.empty? && !block
|
|
68
|
-
agent.confirm(*tool_names, &block)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def tracer(tracer = nil, &block)
|
|
72
|
-
return agent.tracer if tracer.nil? && !block
|
|
73
|
-
agent.tracer(tracer, &block)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
35
|
def agent
|
|
77
36
|
@agent ||= Class.new(LLM::Agent)
|
|
78
37
|
end
|
data/lib/llm/sequel/plugin.rb
CHANGED
|
@@ -16,6 +16,13 @@ module LLM::Sequel
|
|
|
16
16
|
# JSON typecasting for the model. `provider:`, `context:`, and `tracer:`
|
|
17
17
|
# can also be configured as symbols that are called on the model.
|
|
18
18
|
module Plugin
|
|
19
|
+
DEFAULTS = {
|
|
20
|
+
data_column: :data,
|
|
21
|
+
format: :string,
|
|
22
|
+
provider: :set_provider,
|
|
23
|
+
context: :set_context,
|
|
24
|
+
tracer: :set_tracer
|
|
25
|
+
}.freeze
|
|
19
26
|
EMPTY_HASH = {}.freeze
|
|
20
27
|
|
|
21
28
|
##
|
|
@@ -94,13 +101,6 @@ module LLM::Sequel
|
|
|
94
101
|
end
|
|
95
102
|
end
|
|
96
103
|
end
|
|
97
|
-
DEFAULTS = {
|
|
98
|
-
data_column: :data,
|
|
99
|
-
format: :string,
|
|
100
|
-
tracer: nil,
|
|
101
|
-
provider: nil,
|
|
102
|
-
context: EMPTY_HASH
|
|
103
|
-
}.freeze
|
|
104
104
|
|
|
105
105
|
##
|
|
106
106
|
# Called by Sequel when the plugin is first applied to a model class.
|
|
@@ -304,6 +304,24 @@ module LLM::Sequel
|
|
|
304
304
|
|
|
305
305
|
private
|
|
306
306
|
|
|
307
|
+
##
|
|
308
|
+
# @return [LLM::Provider]
|
|
309
|
+
def set_provider
|
|
310
|
+
raise NotImplementedError, "implement the set_provider callback"
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
##
|
|
314
|
+
# @return [Hash]
|
|
315
|
+
def set_context
|
|
316
|
+
Plugin::EMPTY_HASH.dup
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
##
|
|
320
|
+
# @return [LLM::Tracer]
|
|
321
|
+
def set_tracer
|
|
322
|
+
nil
|
|
323
|
+
end
|
|
324
|
+
|
|
307
325
|
##
|
|
308
326
|
# @return [LLM::Context]
|
|
309
327
|
def ctx
|
data/lib/llm/tracer/telemetry.rb
CHANGED
|
@@ -30,8 +30,7 @@ module LLM
|
|
|
30
30
|
# require "llm"
|
|
31
31
|
# require "opentelemetry-exporter-otlp"
|
|
32
32
|
#
|
|
33
|
-
#
|
|
34
|
-
# exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint:)
|
|
33
|
+
# exporter = OpenTelemetry::Exporter::OTLP::Exporter.new
|
|
35
34
|
#
|
|
36
35
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
37
36
|
# llm.tracer = LLM::Tracer::Telemetry.new(llm, exporter:)
|
|
@@ -59,7 +58,6 @@ module LLM
|
|
|
59
58
|
# @return [self]
|
|
60
59
|
def start_trace(trace_group_id: nil, name: "llm", attributes: {}, metadata: nil)
|
|
61
60
|
return self if trace_group_id.to_s.empty?
|
|
62
|
-
|
|
63
61
|
span_context = span_context_from_trace_group_id(trace_group_id.to_s)
|
|
64
62
|
parent_ctx = ::OpenTelemetry::Trace.context_with_span(
|
|
65
63
|
::OpenTelemetry::Trace.non_recording_span(span_context)
|
|
@@ -316,7 +314,7 @@ module LLM
|
|
|
316
314
|
set_span_attributes(span, consume_extra_outputs.merge(outputs || {}))
|
|
317
315
|
finish_metadata = consume_finish_metadata_proc(res)
|
|
318
316
|
metadata = (metadata || {}).merge(finish_metadata || {})
|
|
319
|
-
set_span_attributes(span, metadata.transform_keys { "
|
|
317
|
+
set_span_attributes(span, metadata.transform_keys { "llm.metadata.#{_1}" })
|
|
320
318
|
span.add_event("gen_ai.request.finish")
|
|
321
319
|
span.tap(&:finish)
|
|
322
320
|
end
|
|
@@ -326,7 +324,7 @@ module LLM
|
|
|
326
324
|
"gen_ai.operation.name" => operation
|
|
327
325
|
}.merge!(finish_attributes(operation, res)).compact
|
|
328
326
|
chunks_json = retrieval_chunks_json(res)
|
|
329
|
-
attributes["
|
|
327
|
+
attributes["llm.metadata.chunks"] = chunks_json if chunks_json
|
|
330
328
|
attributes.each { span.set_attribute(_1, _2) }
|
|
331
329
|
span.add_event("gen_ai.request.finish")
|
|
332
330
|
span.tap(&:finish)
|
|
@@ -334,7 +332,7 @@ module LLM
|
|
|
334
332
|
|
|
335
333
|
##
|
|
336
334
|
# @api private
|
|
337
|
-
# Serialize retrieval response chunks for span attributes
|
|
335
|
+
# Serialize retrieval response chunks for span attributes.
|
|
338
336
|
# Returns a JSON string or nil when res has no data.
|
|
339
337
|
def consume_finish_metadata_proc(res)
|
|
340
338
|
key = LLM::Tracer::FINISH_METADATA_PROC_KEY
|
data/lib/llm/tracer.rb
CHANGED
|
@@ -11,7 +11,6 @@ module LLM
|
|
|
11
11
|
class Tracer
|
|
12
12
|
require_relative "tracer/logger"
|
|
13
13
|
require_relative "tracer/telemetry"
|
|
14
|
-
require_relative "tracer/langsmith"
|
|
15
14
|
require_relative "tracer/null"
|
|
16
15
|
|
|
17
16
|
##
|
|
@@ -45,7 +44,7 @@ module LLM
|
|
|
45
44
|
# @param [Object, nil] span
|
|
46
45
|
# @param [String] model
|
|
47
46
|
# @param [Hash, nil] outputs Optional span attributes (e.g. gen_ai.output.messages) from llm.rb or caller.
|
|
48
|
-
# @param [Hash, nil] metadata Optional metadata
|
|
47
|
+
# @param [Hash, nil] metadata Optional metadata from llm.rb or caller.
|
|
49
48
|
# @return [void]
|
|
50
49
|
def on_request_finish(operation:, res:, model: nil, span: nil, outputs: nil, metadata: nil)
|
|
51
50
|
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
@@ -110,8 +109,7 @@ module LLM
|
|
|
110
109
|
# @param [Hash] attributes
|
|
111
110
|
# OpenTelemetry attributes to set on the root span.
|
|
112
111
|
# @param [Hash, nil] metadata
|
|
113
|
-
# Optional. Trace-level metadata merged into the trace
|
|
114
|
-
# Only used by tracers that support it (e.g. {LLM::Tracer::Langsmith}).
|
|
112
|
+
# Optional. Trace-level metadata merged into the trace by tracers that support it.
|
|
115
113
|
# @return [self]
|
|
116
114
|
def start_trace(trace_group_id: nil, name: "llm", attributes: {}, metadata: nil)
|
|
117
115
|
self
|
|
@@ -150,11 +148,10 @@ module LLM
|
|
|
150
148
|
##
|
|
151
149
|
# Merges extra attributes for the current trace/span. Used by applications
|
|
152
150
|
# (e.g. chatbot) to add metadata, span inputs, or span outputs to the next
|
|
153
|
-
# span or to the trace. No-op by default
|
|
154
|
-
# into fiber-local storage and emits them as langsmith/GenAI attributes.
|
|
151
|
+
# span or to the trace. No-op by default.
|
|
155
152
|
#
|
|
156
153
|
# @param [Hash, nil] metadata
|
|
157
|
-
# Key-value pairs merged into trace/span metadata
|
|
154
|
+
# Key-value pairs merged into trace/span metadata.
|
|
158
155
|
# @param [Hash, nil] inputs
|
|
159
156
|
# Key-value pairs set on the next span at start (e.g. gen_ai.input.messages).
|
|
160
157
|
# Consumed when the span is created.
|
|
@@ -169,9 +166,9 @@ module LLM
|
|
|
169
166
|
##
|
|
170
167
|
# Optional: set a proc to supply metadata when the next chat span finishes.
|
|
171
168
|
# The proc is called with the response (res) and should return a Hash of
|
|
172
|
-
# metadata (e.g. { intent: "...", confidence: 1.0 }) to merge onto the span
|
|
173
|
-
#
|
|
174
|
-
#
|
|
169
|
+
# metadata (e.g. { intent: "...", confidence: 1.0 }) to merge onto the span.
|
|
170
|
+
# Cleared after use. Used by apps to attach routing/intent that is only
|
|
171
|
+
# known after the response.
|
|
175
172
|
#
|
|
176
173
|
# @param [Proc, nil] proc (res) -> Hash or nil
|
|
177
174
|
# @return [self]
|
|
@@ -182,19 +179,10 @@ module LLM
|
|
|
182
179
|
|
|
183
180
|
FINISH_METADATA_PROC_KEY = :"llm.tracer.finish_metadata_proc"
|
|
184
181
|
|
|
185
|
-
##
|
|
186
|
-
# Returns the current extra bag (metadata, inputs, outputs) for the current
|
|
187
|
-
# thread/trace. Used by subclasses; default returns empty hashes.
|
|
188
|
-
#
|
|
189
|
-
# @return [Hash] { metadata: {}, inputs: {}, outputs: {} }
|
|
190
|
-
def current_extra
|
|
191
|
-
{}
|
|
192
|
-
end
|
|
193
|
-
|
|
194
182
|
##
|
|
195
183
|
# Returns and clears extra inputs for the next span. Called by the telemetry
|
|
196
|
-
# tracer when starting a span. Subclasses
|
|
197
|
-
#
|
|
184
|
+
# tracer when starting a span. Subclasses can override to return stored
|
|
185
|
+
# inputs; default returns {}.
|
|
198
186
|
#
|
|
199
187
|
# @return [Hash] Attribute key => value to set on the span at start
|
|
200
188
|
def consume_extra_inputs
|
|
@@ -59,9 +59,24 @@ class LLM::Transport
|
|
|
59
59
|
# @return [LLM::Object, String]
|
|
60
60
|
def parse_response(res)
|
|
61
61
|
case res["content-type"]
|
|
62
|
-
when %r{\Aapplication/json\s*}
|
|
62
|
+
when %r{\Aapplication/json\s*}
|
|
63
|
+
body = read_body(res.body)
|
|
64
|
+
LLM::Object.from(LLM.json.load(body))
|
|
63
65
|
else res.body
|
|
64
66
|
end
|
|
65
67
|
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# @param [#class] body
|
|
71
|
+
# @return [String]
|
|
72
|
+
def read_body(body)
|
|
73
|
+
case body.class.to_s
|
|
74
|
+
when "Net::ReadAdapter"
|
|
75
|
+
str = +""
|
|
76
|
+
body.read_body { str << _1 }
|
|
77
|
+
str
|
|
78
|
+
else body
|
|
79
|
+
end
|
|
80
|
+
end
|
|
66
81
|
end
|
|
67
82
|
end
|
|
@@ -21,7 +21,7 @@ class LLM::Transport
|
|
|
21
21
|
when :put then ::Net::HTTP::Put.new(path, headers)
|
|
22
22
|
when :patch then ::Net::HTTP::Patch.new(path, headers)
|
|
23
23
|
when :delete then ::Net::HTTP::Delete.new(path, headers)
|
|
24
|
-
else ::Net::
|
|
24
|
+
else ::Net::HTTPGenericRequest.new(method, path, nil, headers)
|
|
25
25
|
end
|
|
26
26
|
if req.body
|
|
27
27
|
http_req.body = req.body
|
data/lib/llm/uridata.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
class URIData < Struct.new(:content_type, :encoding_type, :encoded, :decoded)
|
|
5
|
+
##
|
|
6
|
+
# @param [String] str
|
|
7
|
+
# A string
|
|
8
|
+
# @return [URIData]
|
|
9
|
+
def self.parse(str)
|
|
10
|
+
_, data = str.split(":")
|
|
11
|
+
content_type, data = data.split(";")
|
|
12
|
+
encoding_type, data = data.split(",")
|
|
13
|
+
URIData.new(content_type, encoding_type, data, StringIO.new(data.unpack1("m0")))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
|
@@ -37,6 +37,7 @@ module LLM
|
|
|
37
37
|
require_relative "llm/server_tool"
|
|
38
38
|
require_relative "llm/mcp"
|
|
39
39
|
require_relative "llm/a2a"
|
|
40
|
+
require_relative "llm/uridata"
|
|
40
41
|
|
|
41
42
|
##
|
|
42
43
|
# Thread-safe monitors for different contexts
|
|
@@ -154,6 +155,14 @@ module LLM
|
|
|
154
155
|
LLM::OpenAI.new(**)
|
|
155
156
|
end
|
|
156
157
|
|
|
158
|
+
##
|
|
159
|
+
# @param key (see LLM::Provider#initialize)
|
|
160
|
+
# @return (see LLM::DeepInfra#initialize)
|
|
161
|
+
def deepinfra(**)
|
|
162
|
+
lock(:require) { require_relative "llm/providers/deepinfra" unless defined?(LLM::DeepInfra) }
|
|
163
|
+
LLM::DeepInfra.new(**)
|
|
164
|
+
end
|
|
165
|
+
|
|
157
166
|
##
|
|
158
167
|
# @param (see LLM::Bedrock#initialize)
|
|
159
168
|
# @return (see LLM::Bedrock#initialize)
|
data/llm.gemspec
CHANGED
|
@@ -5,27 +5,14 @@ require_relative "lib/llm/version"
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "llm.rb"
|
|
7
7
|
spec.version = LLM::VERSION
|
|
8
|
-
spec.authors = ["
|
|
8
|
+
spec.authors = ["bsdrobert", "Antar Azri", "Rodrigo Serrano"]
|
|
9
9
|
spec.email = ["robert@r.uby.dev"]
|
|
10
10
|
|
|
11
11
|
spec.summary = "Ruby's capable AI runtime"
|
|
12
|
-
spec.description =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
only when needed, and offers a single runtime for providers, agents,
|
|
17
|
-
tools, skills, MCP, A2A (Agent2Agent), RAG (vector stores & embeddings),
|
|
18
|
-
streaming, files, and persisted state. As a bonus, llm.rb is also available
|
|
19
|
-
for mruby.
|
|
20
|
-
|
|
21
|
-
It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
|
|
22
|
-
Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. It
|
|
23
|
-
also includes built-in ActiveRecord and Sequel support, plus concurrent
|
|
24
|
-
tool execution through threads, tasks (via async gem), fibers, ractors,
|
|
25
|
-
and fork (via xchan.rb gem).
|
|
26
|
-
DESC
|
|
27
|
-
|
|
28
|
-
spec.license = "0BSD"
|
|
12
|
+
spec.description = "llm.rb is not a library, framework or toolkit but " \
|
|
13
|
+
"an advanced runtime for building highly capable AI " \
|
|
14
|
+
"applications on CRuby."
|
|
15
|
+
spec.license = "BUSL-1.1"
|
|
29
16
|
spec.required_ruby_version = ">= 3.3.0"
|
|
30
17
|
|
|
31
18
|
spec.homepage = "https://r.uby.dev/llm/"
|