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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +242 -1
  3. data/LICENSE +92 -17
  4. data/README.md +204 -623
  5. data/data/anthropic.json +433 -249
  6. data/data/bedrock.json +2097 -1055
  7. data/data/deepinfra.json +993 -0
  8. data/data/deepseek.json +53 -28
  9. data/data/google.json +389 -771
  10. data/data/openai.json +1053 -771
  11. data/data/xai.json +133 -292
  12. data/data/zai.json +249 -141
  13. data/lib/llm/active_record/acts_as_agent.rb +3 -41
  14. data/lib/llm/active_record/acts_as_llm.rb +18 -0
  15. data/lib/llm/active_record.rb +3 -3
  16. data/lib/llm/context.rb +9 -5
  17. data/lib/llm/contract/completion.rb +2 -2
  18. data/lib/llm/provider.rb +2 -2
  19. data/lib/llm/providers/deepinfra/audio.rb +66 -0
  20. data/lib/llm/providers/deepinfra/images.rb +90 -0
  21. data/lib/llm/providers/deepinfra/response_adapter.rb +36 -0
  22. data/lib/llm/providers/deepinfra.rb +100 -0
  23. data/lib/llm/providers/deepseek/images.rb +109 -0
  24. data/lib/llm/providers/deepseek/request_adapter.rb +32 -0
  25. data/lib/llm/providers/deepseek/response_adapter/image.rb +9 -0
  26. data/lib/llm/providers/deepseek/response_adapter.rb +29 -0
  27. data/lib/llm/providers/deepseek.rb +4 -2
  28. data/lib/llm/providers/google/request_adapter.rb +22 -5
  29. data/lib/llm/providers/google.rb +4 -4
  30. data/lib/llm/providers/openai/audio.rb +6 -2
  31. data/lib/llm/providers/openai/images.rb +9 -50
  32. data/lib/llm/providers/openai/request_adapter/respond.rb +38 -4
  33. data/lib/llm/providers/openai/response_adapter/audio.rb +5 -1
  34. data/lib/llm/providers/openai/response_adapter/completion.rb +1 -1
  35. data/lib/llm/providers/openai/response_adapter/image.rb +0 -4
  36. data/lib/llm/providers/openai/responses.rb +1 -0
  37. data/lib/llm/providers/openai/stream_parser.rb +5 -6
  38. data/lib/llm/providers/openai.rb +2 -2
  39. data/lib/llm/providers/xai/images.rb +49 -26
  40. data/lib/llm/providers/xai.rb +2 -2
  41. data/lib/llm/response.rb +10 -0
  42. data/lib/llm/schema/leaf.rb +7 -1
  43. data/lib/llm/schema/renderer.rb +121 -0
  44. data/lib/llm/schema.rb +30 -0
  45. data/lib/llm/sequel/agent.rb +2 -43
  46. data/lib/llm/sequel/plugin.rb +25 -7
  47. data/lib/llm/tracer/telemetry.rb +4 -6
  48. data/lib/llm/tracer.rb +9 -21
  49. data/lib/llm/transport/execution.rb +16 -1
  50. data/lib/llm/transport/net_http_adapter.rb +1 -1
  51. data/lib/llm/uridata.rb +16 -0
  52. data/lib/llm/version.rb +1 -1
  53. data/lib/llm.rb +9 -0
  54. data/llm.gemspec +5 -18
  55. data/resources/deepdive.md +798 -264
  56. metadata +15 -18
  57. data/lib/llm/tracer/langsmith.rb +0 -144
@@ -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 the model class and
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) if 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
@@ -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
@@ -30,8 +30,7 @@ module LLM
30
30
  # require "llm"
31
31
  # require "opentelemetry-exporter-otlp"
32
32
  #
33
- # endpoint = "https://api.smith.langchain.com/otel/v1/traces"
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 { "langsmith.metadata.#{_1}" })
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["langsmith.metadata.chunks"] = chunks_json if chunks_json
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 (e.g. langsmith.metadata.chunks).
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 (emitted as langsmith.metadata.*) from llm.rb or caller.
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 (e.g. langsmith.metadata.*).
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; {LLM::Tracer::Langsmith} merges
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 (e.g. langsmith.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
- # as langsmith.metadata.*. Cleared after use. Used by apps to attach
174
- # routing/intent that is only known after the response.
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 (e.g. Langsmith) override to
197
- # return fiber-local inputs; default returns {}.
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*} then LLM::Object.from(LLM.json.load(res.body))
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::HTTP::GenericRequest.new(method, path, nil, headers)
24
+ else ::Net::HTTPGenericRequest.new(method, path, nil, headers)
25
25
  end
26
26
  if req.body
27
27
  http_req.body = req.body
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "11.3.1"
4
+ VERSION = "12.0.0"
5
5
  end
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 = ["Robert (0x1eef)", "Antar Azri", "Rodrigo Serrano"]
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 = <<~DESC
13
- llm.rb is Ruby's capable AI runtime.
14
-
15
- It runs on Ruby's standard library by default. loads optional pieces
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/"