llm.rb 4.1.0 → 4.2.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/LICENSE +2 -2
- data/README.md +186 -172
- data/lib/llm/agent.rb +49 -37
- data/lib/llm/bot.rb +57 -28
- data/lib/llm/function/tracing.rb +19 -0
- data/lib/llm/function.rb +16 -3
- data/lib/llm/json_adapter.rb +1 -1
- data/lib/llm/message.rb +7 -0
- data/lib/llm/prompt.rb +85 -0
- data/lib/llm/provider.rb +56 -10
- data/lib/llm/providers/anthropic/error_handler.rb +27 -5
- data/lib/llm/providers/anthropic/files.rb +22 -16
- data/lib/llm/providers/anthropic/models.rb +4 -3
- data/lib/llm/providers/anthropic.rb +6 -5
- data/lib/llm/providers/deepseek.rb +3 -3
- data/lib/llm/providers/gemini/error_handler.rb +34 -12
- data/lib/llm/providers/gemini/files.rb +18 -13
- data/lib/llm/providers/gemini/images.rb +4 -3
- data/lib/llm/providers/gemini/models.rb +4 -3
- data/lib/llm/providers/gemini.rb +9 -7
- data/lib/llm/providers/llamacpp.rb +3 -3
- data/lib/llm/providers/ollama/error_handler.rb +28 -6
- data/lib/llm/providers/ollama/models.rb +4 -3
- data/lib/llm/providers/ollama.rb +9 -7
- data/lib/llm/providers/openai/audio.rb +10 -7
- data/lib/llm/providers/openai/error_handler.rb +41 -14
- data/lib/llm/providers/openai/files.rb +19 -14
- data/lib/llm/providers/openai/images.rb +10 -7
- data/lib/llm/providers/openai/models.rb +4 -3
- data/lib/llm/providers/openai/moderations.rb +4 -3
- data/lib/llm/providers/openai/responses.rb +10 -7
- data/lib/llm/providers/openai/vector_stores.rb +34 -23
- data/lib/llm/providers/openai.rb +9 -7
- data/lib/llm/providers/xai.rb +3 -3
- data/lib/llm/providers/zai.rb +2 -2
- data/lib/llm/schema/object.rb +2 -2
- data/lib/llm/schema.rb +16 -2
- data/lib/llm/server_tool.rb +3 -3
- data/lib/llm/session.rb +3 -0
- data/lib/llm/tracer/logger.rb +192 -0
- data/lib/llm/tracer/null.rb +49 -0
- data/lib/llm/tracer/telemetry.rb +255 -0
- data/lib/llm/tracer.rb +134 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +4 -3
- data/llm.gemspec +4 -1
- metadata +38 -3
- data/lib/llm/builder.rb +0 -79
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
##
|
|
5
|
+
# A no-op tracer that ignores all tracing callbacks.
|
|
6
|
+
class Tracer::Null < Tracer
|
|
7
|
+
##
|
|
8
|
+
# @param (see LLM::Tracer#on_request_start)
|
|
9
|
+
# @return [nil]
|
|
10
|
+
def on_request_start(**)
|
|
11
|
+
nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# @param (see LLM::Tracer#on_request_finish)
|
|
16
|
+
# @return [nil]
|
|
17
|
+
def on_request_finish(**)
|
|
18
|
+
nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# @param (see LLM::Tracer#on_request_error)
|
|
23
|
+
# @return [nil]
|
|
24
|
+
def on_request_error(**)
|
|
25
|
+
nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# @param (see LLM::Tracer#on_tool_start)
|
|
30
|
+
# @return [nil]
|
|
31
|
+
def on_tool_start(**)
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# @param (see LLM::Tracer#on_tool_finish)
|
|
37
|
+
# @return [nil]
|
|
38
|
+
def on_tool_finish(**)
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# @param (see LLM::Tracer#on_tool_error)
|
|
44
|
+
# @return [nil]
|
|
45
|
+
def on_tool_error(**)
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Tracer::Telemetry LLM::Tracer::Telemetry} tracer provides
|
|
6
|
+
# telemetry support through the [opentelemetry-ruby](https://github.com/open-telemetry/opentelemetry-ruby)
|
|
7
|
+
# RubyGem. The gem should be installed separately since this feature is opt-in
|
|
8
|
+
# and disabled by default. This feature exists to support integration with tools
|
|
9
|
+
# like [LangSmith](https://www.langsmith.com).
|
|
10
|
+
#
|
|
11
|
+
# @see https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai Telemetry specs (index)
|
|
12
|
+
# @see https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/openai.md Telemetry specs (OpenAI)
|
|
13
|
+
#
|
|
14
|
+
# @example InMemory export
|
|
15
|
+
# #!/usr/bin/env ruby
|
|
16
|
+
# require "llm"
|
|
17
|
+
# require "pp"
|
|
18
|
+
#
|
|
19
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
20
|
+
# llm.tracer = LLM::Tracer::Telemetry.new(llm)
|
|
21
|
+
#
|
|
22
|
+
# ses = LLM::Session.new(llm)
|
|
23
|
+
# ses.talk "hello"
|
|
24
|
+
# ses.talk "how are you?"
|
|
25
|
+
# ses.tracer.spans.each { |span| pp span }
|
|
26
|
+
#
|
|
27
|
+
# @example OTLP export
|
|
28
|
+
# #!/usr/bin/env ruby
|
|
29
|
+
# require "llm"
|
|
30
|
+
# require "opentelemetry-exporter-otlp"
|
|
31
|
+
#
|
|
32
|
+
# endpoint = "https://api.smith.langchain.com/otel/v1/traces"
|
|
33
|
+
# exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint:)
|
|
34
|
+
#
|
|
35
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
36
|
+
# llm.tracer = LLM::Tracer::Telemetry.new(llm, exporter:)
|
|
37
|
+
#
|
|
38
|
+
# ses = LLM::Session.new(llm)
|
|
39
|
+
# ses.talk "hello"
|
|
40
|
+
# ses.talk "how are you?"
|
|
41
|
+
class Tracer::Telemetry < Tracer
|
|
42
|
+
##
|
|
43
|
+
# param [LLM::Provider] provider
|
|
44
|
+
# An LLM provider
|
|
45
|
+
# @return [LLM::Tracer::Telemetry]
|
|
46
|
+
def initialize(provider, options = {})
|
|
47
|
+
super
|
|
48
|
+
@exporter = options.delete(:exporter)
|
|
49
|
+
setup!
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# @param (see LLM::Tracer#on_request_start)
|
|
54
|
+
def on_request_start(operation:, model: nil)
|
|
55
|
+
case operation
|
|
56
|
+
when "chat" then start_chat(operation:, model:)
|
|
57
|
+
when "retrieval" then start_retrieval(operation:)
|
|
58
|
+
else nil
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# @param (see LLM::Tracer#on_request_finish)
|
|
64
|
+
def on_request_finish(operation:, res:, model: nil, span: nil)
|
|
65
|
+
return nil unless span
|
|
66
|
+
case operation
|
|
67
|
+
when "chat" then finish_chat(operation:, model:, res:, span:)
|
|
68
|
+
when "retrieval" then finish_retrieval(operation:, res:, span:)
|
|
69
|
+
else nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# @param (see LLM::Tracer#on_request_error)
|
|
75
|
+
def on_request_error(ex:, span:)
|
|
76
|
+
return nil unless span
|
|
77
|
+
attributes = {"error.type" => ex.class.to_s}.compact
|
|
78
|
+
attributes.each { span.set_attribute(_1, _2) }
|
|
79
|
+
span.add_event("gen_ai.request.finish")
|
|
80
|
+
span.status = ::OpenTelemetry::Trace::Status.error(ex.message)
|
|
81
|
+
span.tap(&:finish)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# @param (see LLM::Tracer#on_tool_start)
|
|
86
|
+
# @return (see LLM::Tracer#on_tool_start)
|
|
87
|
+
def on_tool_start(id:, name:, arguments:, model:)
|
|
88
|
+
attributes = {
|
|
89
|
+
"gen_ai.operation.name" => "execute_tool",
|
|
90
|
+
"gen_ai.request.model" => model,
|
|
91
|
+
"gen_ai.tool.call.id" => id,
|
|
92
|
+
"gen_ai.tool.name" => name,
|
|
93
|
+
"gen_ai.tool.call.arguments" => LLM.json.dump(arguments),
|
|
94
|
+
"gen_ai.provider.name" => provider_name,
|
|
95
|
+
"server.address" => provider_host,
|
|
96
|
+
"server.port" => provider_port
|
|
97
|
+
}.compact
|
|
98
|
+
span_name = ["execute_tool", name].compact.join(" ")
|
|
99
|
+
span = @tracer.start_span(span_name.empty? ? "gen_ai.tool" : span_name, kind: :client, attributes:)
|
|
100
|
+
span.add_event("gen_ai.tool.start")
|
|
101
|
+
span
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# @param (see LLM::Tracer#on_tool_finish)
|
|
106
|
+
# @return (see LLM::Tracer#on_tool_finish)
|
|
107
|
+
def on_tool_finish(result:, span:)
|
|
108
|
+
return nil unless span
|
|
109
|
+
attributes = {
|
|
110
|
+
"gen_ai.tool.call.id" => result.id,
|
|
111
|
+
"gen_ai.tool.name" => result.name,
|
|
112
|
+
"gen_ai.tool.call.result" => LLM.json.dump(result.value)
|
|
113
|
+
}.compact
|
|
114
|
+
attributes.each { span.set_attribute(_1, _2) }
|
|
115
|
+
span.add_event("gen_ai.tool.finish")
|
|
116
|
+
span.tap(&:finish)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# @param (see LLM::Tracer#on_tool_error)
|
|
121
|
+
# @return (see LLM::Tracer#on_tool_error)
|
|
122
|
+
def on_tool_error(ex:, span:)
|
|
123
|
+
return nil unless span
|
|
124
|
+
attributes = {"error.type" => ex.class.to_s}.compact
|
|
125
|
+
attributes.each { span.set_attribute(_1, _2) }
|
|
126
|
+
span.add_event("gen_ai.tool.finish")
|
|
127
|
+
span.status = ::OpenTelemetry::Trace::Status.error(ex.message)
|
|
128
|
+
span.tap(&:finish)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# @note
|
|
133
|
+
# This method returns an empty array for exporters that
|
|
134
|
+
# do not implement 'finished_spans' such as the OTLP
|
|
135
|
+
# exporter
|
|
136
|
+
# @return [Array<OpenTelemetry::SDK::Trace::SpanData>]
|
|
137
|
+
def spans
|
|
138
|
+
return [] unless @exporter.respond_to?(:finished_spans)
|
|
139
|
+
flush!
|
|
140
|
+
@exporter.finished_spans
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Flushes queued telemetry to the configured exporter.
|
|
145
|
+
# @note
|
|
146
|
+
# Exports are batched in the background by default.
|
|
147
|
+
# Long-lived processes usually do not need to call this method.
|
|
148
|
+
# Short-lived scripts should call {#flush!} before exit to reduce
|
|
149
|
+
# the risk of losing spans that are still buffered.
|
|
150
|
+
# @return (see LLM::Tracer#flush!)
|
|
151
|
+
def flush!
|
|
152
|
+
@tracer_provider.force_flush
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
##
|
|
159
|
+
# @api private
|
|
160
|
+
def setup!
|
|
161
|
+
require "opentelemetry/sdk" unless defined?(OpenTelemetry)
|
|
162
|
+
@exporter ||= OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter.new
|
|
163
|
+
processor = OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(@exporter)
|
|
164
|
+
@tracer_provider = OpenTelemetry::SDK::Trace::TracerProvider.new
|
|
165
|
+
@tracer_provider.add_span_processor(processor)
|
|
166
|
+
@tracer = @tracer_provider.tracer("llm.rb", LLM::VERSION)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
##
|
|
170
|
+
# @param [String] operation
|
|
171
|
+
# @param [LLM::Response] res
|
|
172
|
+
# @api private
|
|
173
|
+
def finish_attributes(operation, res)
|
|
174
|
+
case @provider.class.to_s
|
|
175
|
+
when "LLM::OpenAI" then openai_attributes(operation, res)
|
|
176
|
+
else {}
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
##
|
|
181
|
+
# @param [String] operation
|
|
182
|
+
# @param [LLM::Response] res
|
|
183
|
+
# @api private
|
|
184
|
+
def openai_attributes(operation, res)
|
|
185
|
+
case operation
|
|
186
|
+
when "chat"
|
|
187
|
+
{
|
|
188
|
+
"openai.response.service_tier" => res.service_tier,
|
|
189
|
+
"openai.response.system_fingerprint" => res.system_fingerprint
|
|
190
|
+
}
|
|
191
|
+
when "retrieval"
|
|
192
|
+
{
|
|
193
|
+
"openai.vector_store.search.result_count" => res.size,
|
|
194
|
+
"openai.vector_store.search.has_more" => res.has_more
|
|
195
|
+
}
|
|
196
|
+
else {}
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
##
|
|
201
|
+
# start_*
|
|
202
|
+
|
|
203
|
+
def start_chat(operation:, model:)
|
|
204
|
+
attributes = {
|
|
205
|
+
"gen_ai.operation.name" => operation,
|
|
206
|
+
"gen_ai.request.model" => model,
|
|
207
|
+
"gen_ai.provider.name" => provider_name,
|
|
208
|
+
"server.address" => provider_host,
|
|
209
|
+
"server.port" => provider_port
|
|
210
|
+
}.compact
|
|
211
|
+
span_name = [operation, model].compact.join(" ")
|
|
212
|
+
span = @tracer.start_span(span_name.empty? ? "gen_ai.request" : span_name, kind: :client, attributes:)
|
|
213
|
+
span.add_event("gen_ai.request.start")
|
|
214
|
+
span
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def start_retrieval(operation:)
|
|
218
|
+
attributes = {
|
|
219
|
+
"gen_ai.operation.name" => operation,
|
|
220
|
+
"gen_ai.provider.name" => provider_name,
|
|
221
|
+
"server.address" => provider_host,
|
|
222
|
+
"server.port" => provider_port
|
|
223
|
+
}.compact
|
|
224
|
+
span = @tracer.start_span(operation, kind: :client, attributes:)
|
|
225
|
+
span.add_event("gen_ai.request.start")
|
|
226
|
+
span
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
##
|
|
230
|
+
# finish_*
|
|
231
|
+
|
|
232
|
+
def finish_chat(operation:, model:, res:, span:)
|
|
233
|
+
attributes = {
|
|
234
|
+
"gen_ai.operation.name" => operation,
|
|
235
|
+
"gen_ai.request.model" => model,
|
|
236
|
+
"gen_ai.response.id" => res.id,
|
|
237
|
+
"gen_ai.response.model" => model,
|
|
238
|
+
"gen_ai.usage.input_tokens" => res.usage.input_tokens,
|
|
239
|
+
"gen_ai.usage.output_tokens" => res.usage.output_tokens
|
|
240
|
+
}.merge!(finish_attributes(operation, res)).compact
|
|
241
|
+
attributes.each { span.set_attribute(_1, _2) }
|
|
242
|
+
span.add_event("gen_ai.request.finish")
|
|
243
|
+
span.tap(&:finish)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def finish_retrieval(operation:, res:, span:)
|
|
247
|
+
attributes = {
|
|
248
|
+
"gen_ai.operation.name" => operation
|
|
249
|
+
}.merge!(finish_attributes(operation, res)).compact
|
|
250
|
+
attributes.each { span.set_attribute(_1, _2) }
|
|
251
|
+
span.add_event("gen_ai.request.finish")
|
|
252
|
+
span.tap(&:finish)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
data/lib/llm/tracer.rb
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Tracer LLM::Tracer} is the superclass of all
|
|
6
|
+
# LLM tracers. It can be helpful for implementing instrumentation
|
|
7
|
+
# and hooking into the lifecycle of an LLM request. See
|
|
8
|
+
# {LLM::Tracer::Telemetry LLM::Tracer::Telemetry}, and
|
|
9
|
+
# {LLM::Tracer::Logger LLM::Tracer::Logger} for example
|
|
10
|
+
# tracer implementations.
|
|
11
|
+
class Tracer
|
|
12
|
+
require_relative "tracer/logger"
|
|
13
|
+
require_relative "tracer/telemetry"
|
|
14
|
+
require_relative "tracer/null"
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# @param [LLM::Provider] provider
|
|
18
|
+
# A provider
|
|
19
|
+
# @param [Hash] options
|
|
20
|
+
# A hash of options
|
|
21
|
+
def initialize(provider, options = {})
|
|
22
|
+
@provider = provider
|
|
23
|
+
@options = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Called before an LLM provider request is executed.
|
|
28
|
+
# @param [String] operation
|
|
29
|
+
# @param [String] model
|
|
30
|
+
# @return [void]
|
|
31
|
+
def on_request_start(operation:, model: nil)
|
|
32
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Called after an LLM provider request succeeds.
|
|
37
|
+
# @param [String] operation
|
|
38
|
+
# @param [String] model
|
|
39
|
+
# @param [LLM::Response] res
|
|
40
|
+
# @param [Object, nil] span
|
|
41
|
+
# @return [void]
|
|
42
|
+
def on_request_finish(operation:, res:, model: nil, span: nil)
|
|
43
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Called when an LLM provider request fails.
|
|
48
|
+
# @param [LLM::Error] ex
|
|
49
|
+
# @param [Object, nil] span
|
|
50
|
+
# @return [void]
|
|
51
|
+
def on_request_error(ex:, span:)
|
|
52
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Called before a local tool/function executes.
|
|
57
|
+
# @param [String] id
|
|
58
|
+
# The tool call ID assigned by the model/provider
|
|
59
|
+
# @param [String] name
|
|
60
|
+
# The tool (function) name.
|
|
61
|
+
# @param [Hash] arguments
|
|
62
|
+
# The parsed tool arguments.
|
|
63
|
+
# @param [String] model
|
|
64
|
+
# The model name
|
|
65
|
+
# @return [void]
|
|
66
|
+
def on_tool_start(id:, name:, arguments:, model:)
|
|
67
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Called after a local tool/function succeeds.
|
|
72
|
+
# @param [LLM::Function::Return] result
|
|
73
|
+
# The tool return object.
|
|
74
|
+
# @param [Object, nil] span
|
|
75
|
+
# The span/context object returned by {#on_tool_start}.
|
|
76
|
+
# @return [void]
|
|
77
|
+
def on_tool_finish(result:, span:)
|
|
78
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Called when a local tool/function raises.
|
|
83
|
+
# @param [Exception] ex
|
|
84
|
+
# The raised error.
|
|
85
|
+
# @param [Object, nil] span
|
|
86
|
+
# The span/context object returned by {#on_tool_start}.
|
|
87
|
+
# @return [void]
|
|
88
|
+
def on_tool_error(ex:, span:)
|
|
89
|
+
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# @return [String]
|
|
94
|
+
def inspect
|
|
95
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} @provider=#{@provider.class} @tracer=#{@tracer.inspect}>"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# @return [Array]
|
|
100
|
+
def spans
|
|
101
|
+
[]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Flush the tracer
|
|
106
|
+
# @note
|
|
107
|
+
# This method is only implemented by the {LLM::Tracer::Telemetry} tracer.
|
|
108
|
+
# It is a noop for other tracers.
|
|
109
|
+
# @return [nil]
|
|
110
|
+
def flush!
|
|
111
|
+
nil
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# @return [String]
|
|
118
|
+
def provider_name
|
|
119
|
+
@provider.class.name.split("::").last.downcase
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# @return [String]
|
|
124
|
+
def provider_host
|
|
125
|
+
@provider.instance_variable_get(:@host)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
##
|
|
129
|
+
# @return [String]
|
|
130
|
+
def provider_port
|
|
131
|
+
@provider.instance_variable_get(:@port)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
module LLM
|
|
4
4
|
require "stringio"
|
|
5
5
|
require_relative "llm/json_adapter"
|
|
6
|
+
require_relative "llm/tracer"
|
|
6
7
|
require_relative "llm/error"
|
|
7
8
|
require_relative "llm/contract"
|
|
8
9
|
require_relative "llm/usage"
|
|
9
|
-
require_relative "llm/
|
|
10
|
+
require_relative "llm/prompt"
|
|
10
11
|
require_relative "llm/schema"
|
|
11
12
|
require_relative "llm/object"
|
|
12
13
|
require_relative "llm/version"
|
|
@@ -17,7 +18,7 @@ module LLM
|
|
|
17
18
|
require_relative "llm/multipart"
|
|
18
19
|
require_relative "llm/file"
|
|
19
20
|
require_relative "llm/provider"
|
|
20
|
-
require_relative "llm/
|
|
21
|
+
require_relative "llm/session"
|
|
21
22
|
require_relative "llm/agent"
|
|
22
23
|
require_relative "llm/buffer"
|
|
23
24
|
require_relative "llm/function"
|
|
@@ -35,7 +36,7 @@ module LLM
|
|
|
35
36
|
##
|
|
36
37
|
# Returns the JSON adapter used by the library
|
|
37
38
|
# @return [Class]
|
|
38
|
-
# Returns a class that responds to
|
|
39
|
+
# Returns a class that responds to `dump` and `load`
|
|
39
40
|
def json
|
|
40
41
|
@json ||= JSONAdapter::JSON
|
|
41
42
|
end
|
data/llm.gemspec
CHANGED
|
@@ -21,7 +21,8 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.required_ruby_version = ">= 3.2.0"
|
|
22
22
|
|
|
23
23
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
24
|
-
spec.metadata["source_code_uri"] = "https://github.com/llmrb/llm"
|
|
24
|
+
spec.metadata["source_code_uri"] = "https://github.com/llmrb/llm.rb"
|
|
25
|
+
spec.metadata["documentation_uri"] = "https://0x1eef.github.io/x/llm.rb"
|
|
25
26
|
|
|
26
27
|
spec.files = Dir[
|
|
27
28
|
"README.md", "LICENSE",
|
|
@@ -41,4 +42,6 @@ Gem::Specification.new do |spec|
|
|
|
41
42
|
spec.add_development_dependency "vcr", "~> 6.0"
|
|
42
43
|
spec.add_development_dependency "dotenv", "~> 2.8"
|
|
43
44
|
spec.add_development_dependency "net-http-persistent", "~> 4.0"
|
|
45
|
+
spec.add_development_dependency "opentelemetry-sdk", "~> 1.10"
|
|
46
|
+
spec.add_development_dependency "logger", "~> 1.7"
|
|
44
47
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Antar Azri
|
|
@@ -164,6 +164,34 @@ dependencies:
|
|
|
164
164
|
- - "~>"
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '4.0'
|
|
167
|
+
- !ruby/object:Gem::Dependency
|
|
168
|
+
name: opentelemetry-sdk
|
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
|
170
|
+
requirements:
|
|
171
|
+
- - "~>"
|
|
172
|
+
- !ruby/object:Gem::Version
|
|
173
|
+
version: '1.10'
|
|
174
|
+
type: :development
|
|
175
|
+
prerelease: false
|
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
177
|
+
requirements:
|
|
178
|
+
- - "~>"
|
|
179
|
+
- !ruby/object:Gem::Version
|
|
180
|
+
version: '1.10'
|
|
181
|
+
- !ruby/object:Gem::Dependency
|
|
182
|
+
name: logger
|
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
|
184
|
+
requirements:
|
|
185
|
+
- - "~>"
|
|
186
|
+
- !ruby/object:Gem::Version
|
|
187
|
+
version: '1.7'
|
|
188
|
+
type: :development
|
|
189
|
+
prerelease: false
|
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
+
requirements:
|
|
192
|
+
- - "~>"
|
|
193
|
+
- !ruby/object:Gem::Version
|
|
194
|
+
version: '1.7'
|
|
167
195
|
description: llm.rb is a zero-dependency Ruby toolkit for Large Language Models that
|
|
168
196
|
includes OpenAI, Gemini, Anthropic, xAI (grok), zAI, DeepSeek, Ollama, and LlamaCpp.
|
|
169
197
|
The toolkit includes full support for chat, streaming, tool calling, audio, images,
|
|
@@ -181,7 +209,6 @@ files:
|
|
|
181
209
|
- lib/llm/agent.rb
|
|
182
210
|
- lib/llm/bot.rb
|
|
183
211
|
- lib/llm/buffer.rb
|
|
184
|
-
- lib/llm/builder.rb
|
|
185
212
|
- lib/llm/client.rb
|
|
186
213
|
- lib/llm/contract.rb
|
|
187
214
|
- lib/llm/contract/completion.rb
|
|
@@ -192,6 +219,7 @@ files:
|
|
|
192
219
|
- lib/llm/eventstream/parser.rb
|
|
193
220
|
- lib/llm/file.rb
|
|
194
221
|
- lib/llm/function.rb
|
|
222
|
+
- lib/llm/function/tracing.rb
|
|
195
223
|
- lib/llm/json_adapter.rb
|
|
196
224
|
- lib/llm/message.rb
|
|
197
225
|
- lib/llm/mime.rb
|
|
@@ -200,6 +228,7 @@ files:
|
|
|
200
228
|
- lib/llm/object.rb
|
|
201
229
|
- lib/llm/object/builder.rb
|
|
202
230
|
- lib/llm/object/kernel.rb
|
|
231
|
+
- lib/llm/prompt.rb
|
|
203
232
|
- lib/llm/provider.rb
|
|
204
233
|
- lib/llm/providers/anthropic.rb
|
|
205
234
|
- lib/llm/providers/anthropic/error_handler.rb
|
|
@@ -283,8 +312,13 @@ files:
|
|
|
283
312
|
- lib/llm/schema/string.rb
|
|
284
313
|
- lib/llm/schema/version.rb
|
|
285
314
|
- lib/llm/server_tool.rb
|
|
315
|
+
- lib/llm/session.rb
|
|
286
316
|
- lib/llm/tool.rb
|
|
287
317
|
- lib/llm/tool/param.rb
|
|
318
|
+
- lib/llm/tracer.rb
|
|
319
|
+
- lib/llm/tracer/logger.rb
|
|
320
|
+
- lib/llm/tracer/null.rb
|
|
321
|
+
- lib/llm/tracer/telemetry.rb
|
|
288
322
|
- lib/llm/usage.rb
|
|
289
323
|
- lib/llm/utils.rb
|
|
290
324
|
- lib/llm/version.rb
|
|
@@ -294,7 +328,8 @@ licenses:
|
|
|
294
328
|
- 0BSD
|
|
295
329
|
metadata:
|
|
296
330
|
homepage_uri: https://github.com/llmrb/llm
|
|
297
|
-
source_code_uri: https://github.com/llmrb/llm
|
|
331
|
+
source_code_uri: https://github.com/llmrb/llm.rb
|
|
332
|
+
documentation_uri: https://0x1eef.github.io/x/llm.rb
|
|
298
333
|
rdoc_options: []
|
|
299
334
|
require_paths:
|
|
300
335
|
- lib
|
data/lib/llm/builder.rb
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
##
|
|
4
|
-
# The {LLM::Builder LLM::Builder} class can build a collection
|
|
5
|
-
# of messages that can be sent in a single request.
|
|
6
|
-
#
|
|
7
|
-
# @note
|
|
8
|
-
# This API is not meant to be used directly.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# llm = LLM.openai(key: ENV["KEY"])
|
|
12
|
-
# bot = LLM::Bot.new(llm)
|
|
13
|
-
# prompt = bot.build_prompt do
|
|
14
|
-
# it.system "Your task is to assist the user"
|
|
15
|
-
# it.user "Hello. Can you assist me?"
|
|
16
|
-
# end
|
|
17
|
-
# res = bot.chat(prompt)
|
|
18
|
-
class LLM::Builder
|
|
19
|
-
##
|
|
20
|
-
# @param [Proc] evaluator
|
|
21
|
-
# The evaluator
|
|
22
|
-
def initialize(provider, &evaluator)
|
|
23
|
-
@provider = provider
|
|
24
|
-
@buffer = []
|
|
25
|
-
@evaluator = evaluator
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
##
|
|
29
|
-
# @return [void]
|
|
30
|
-
def call
|
|
31
|
-
@evaluator.call(self)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
##
|
|
35
|
-
# @param [String] content
|
|
36
|
-
# The message
|
|
37
|
-
# @param [Symbol] role
|
|
38
|
-
# The role (eg user, system)
|
|
39
|
-
# @return [void]
|
|
40
|
-
def chat(content, role: @provider.user_role)
|
|
41
|
-
role = case role.to_sym
|
|
42
|
-
when :system then @provider.system_role
|
|
43
|
-
when :user then @provider.user_role
|
|
44
|
-
when :developer then @provider.developer_role
|
|
45
|
-
else role
|
|
46
|
-
end
|
|
47
|
-
@buffer << LLM::Message.new(role, content)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
##
|
|
51
|
-
# @param [String] content
|
|
52
|
-
# The message content
|
|
53
|
-
# @return [void]
|
|
54
|
-
def user(content)
|
|
55
|
-
chat(content, role: @provider.user_role)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
##
|
|
59
|
-
# @param [String] content
|
|
60
|
-
# The message content
|
|
61
|
-
# @return [void]
|
|
62
|
-
def system(content)
|
|
63
|
-
chat(content, role: @provider.system_role)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
##
|
|
67
|
-
# @param [String] content
|
|
68
|
-
# The message content
|
|
69
|
-
# @return [void]
|
|
70
|
-
def developer(content)
|
|
71
|
-
chat(content, role: @provider.developer_role)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
##
|
|
75
|
-
# @return [Array]
|
|
76
|
-
def to_a
|
|
77
|
-
@buffer.dup
|
|
78
|
-
end
|
|
79
|
-
end
|