llm.rb 4.3.1 → 4.4.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/README.md +1 -1
- data/lib/llm/provider.rb +2 -1
- data/lib/llm/tracer/telemetry.rb +66 -3
- data/lib/llm/tracer.rb +25 -0
- data/lib/llm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 357446a155ea5c66f1f1de5e2172f021bfa339c1006bae3f35a18b1c1ad173a7
|
|
4
|
+
data.tar.gz: 4726be4c9133aa0da37771c6a16ba1eab27771acee6826a259f624a199cf8088
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b97ee9fc6594633d4176d21651a9625e8cd7c55d7d66d9e3a8a0bf5314df957447e7b4af431f57cd5e9c47408ceefc08babbb868af05d9ef7b887e543c6914a8
|
|
7
|
+
data.tar.gz: 5881e618855cf3c9830fcc5edad571b2cb015a514ab99fcd4134e09402fda6ff51e7d034d50e96988739747a0dc5520b40c4938b9f1a0ed9af328dde22a48c99
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"><img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc"></a>
|
|
6
6
|
<a href="https://opensource.org/license/0bsd"><img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License"></a>
|
|
7
|
-
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.
|
|
7
|
+
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.4.0-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
data/lib/llm/provider.rb
CHANGED
|
@@ -39,6 +39,7 @@ class LLM::Provider
|
|
|
39
39
|
@client = persistent ? persistent_client : transient_client
|
|
40
40
|
@tracer = LLM::Tracer::Null.new(self)
|
|
41
41
|
@base_uri = URI("#{ssl ? "https" : "http"}://#{host}:#{port}/")
|
|
42
|
+
@headers = {"User-Agent" => "llm.rb v#{LLM::VERSION}"}
|
|
42
43
|
end
|
|
43
44
|
|
|
44
45
|
##
|
|
@@ -195,7 +196,7 @@ class LLM::Provider
|
|
|
195
196
|
# @return [LLM::Provider]
|
|
196
197
|
# Returns self
|
|
197
198
|
def with(headers:)
|
|
198
|
-
tap {
|
|
199
|
+
tap { @headers.merge!(headers) }
|
|
199
200
|
end
|
|
200
201
|
|
|
201
202
|
##
|
data/lib/llm/tracer/telemetry.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
3
5
|
module LLM
|
|
4
6
|
##
|
|
5
7
|
# The {LLM::Tracer::Telemetry LLM::Tracer::Telemetry} tracer provides
|
|
@@ -46,9 +48,46 @@ module LLM
|
|
|
46
48
|
def initialize(provider, options = {})
|
|
47
49
|
super
|
|
48
50
|
@exporter = options.delete(:exporter)
|
|
51
|
+
@root_span = nil
|
|
52
|
+
@root_context = nil
|
|
49
53
|
setup!
|
|
50
54
|
end
|
|
51
55
|
|
|
56
|
+
##
|
|
57
|
+
# When +trace_group_id+ is provided, it is converted to an OpenTelemetry
|
|
58
|
+
# trace_id (via a deterministic 16-byte hash) so all spans until {#stop_trace}
|
|
59
|
+
# share that trace_id and appear as one trace in OTLP/Langfuse.
|
|
60
|
+
#
|
|
61
|
+
# @param (see LLM::Tracer#start_trace)
|
|
62
|
+
# @return [self]
|
|
63
|
+
def start_trace(trace_group_id: nil, name: "llm", attributes: {})
|
|
64
|
+
return self if trace_group_id.to_s.empty?
|
|
65
|
+
|
|
66
|
+
span_context = span_context_from_trace_group_id(trace_group_id.to_s)
|
|
67
|
+
parent_ctx = ::OpenTelemetry::Trace.context_with_span(
|
|
68
|
+
::OpenTelemetry::Trace.non_recording_span(span_context)
|
|
69
|
+
)
|
|
70
|
+
attrs = attributes.compact
|
|
71
|
+
attrs["llm.trace_group_id"] = trace_group_id.to_s
|
|
72
|
+
@root_span = @tracer.start_span(
|
|
73
|
+
name,
|
|
74
|
+
kind: :server,
|
|
75
|
+
attributes: attrs,
|
|
76
|
+
with_parent: parent_ctx
|
|
77
|
+
)
|
|
78
|
+
@root_context = ::OpenTelemetry::Trace.context_with_span(@root_span)
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# @return [self]
|
|
84
|
+
def stop_trace
|
|
85
|
+
@root_span&.finish
|
|
86
|
+
@root_span = nil
|
|
87
|
+
@root_context = nil
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
52
91
|
##
|
|
53
92
|
# @param (see LLM::Tracer#on_request_start)
|
|
54
93
|
def on_request_start(operation:, model: nil)
|
|
@@ -96,7 +135,7 @@ module LLM
|
|
|
96
135
|
"server.port" => provider_port
|
|
97
136
|
}.compact
|
|
98
137
|
span_name = ["execute_tool", name].compact.join(" ")
|
|
99
|
-
span =
|
|
138
|
+
span = create_span(span_name.empty? ? "gen_ai.tool" : span_name, attributes:)
|
|
100
139
|
span.add_event("gen_ai.tool.start")
|
|
101
140
|
span
|
|
102
141
|
end
|
|
@@ -155,6 +194,30 @@ module LLM
|
|
|
155
194
|
|
|
156
195
|
private
|
|
157
196
|
|
|
197
|
+
##
|
|
198
|
+
# @api private
|
|
199
|
+
def create_span(name, kind: :client, attributes: {})
|
|
200
|
+
opts = {kind:, attributes:}
|
|
201
|
+
opts[:with_parent] = @root_context if @root_context
|
|
202
|
+
@tracer.start_span(name, **opts)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
##
|
|
206
|
+
# Converts a string trace_group_id to an OpenTelemetry SpanContext so all
|
|
207
|
+
# spans created with this context share the same trace_id.
|
|
208
|
+
# @api private
|
|
209
|
+
def span_context_from_trace_group_id(trace_group_id)
|
|
210
|
+
trace_id = Digest::MD5.digest(trace_group_id)
|
|
211
|
+
trace_id = ::OpenTelemetry::Trace.generate_trace_id if trace_id == ::OpenTelemetry::Trace::INVALID_TRACE_ID
|
|
212
|
+
span_id = Digest::SHA256.digest(trace_group_id)[0, 8]
|
|
213
|
+
span_id = ::OpenTelemetry::Trace.generate_span_id if span_id == ::OpenTelemetry::Trace::INVALID_SPAN_ID
|
|
214
|
+
::OpenTelemetry::Trace::SpanContext.new(
|
|
215
|
+
trace_id:,
|
|
216
|
+
span_id:,
|
|
217
|
+
trace_flags: ::OpenTelemetry::Trace::TraceFlags::SAMPLED
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
|
|
158
221
|
##
|
|
159
222
|
# @api private
|
|
160
223
|
def setup!
|
|
@@ -209,7 +272,7 @@ module LLM
|
|
|
209
272
|
"server.port" => provider_port
|
|
210
273
|
}.compact
|
|
211
274
|
span_name = [operation, model].compact.join(" ")
|
|
212
|
-
span =
|
|
275
|
+
span = create_span(span_name.empty? ? "gen_ai.request" : span_name, attributes:)
|
|
213
276
|
span.add_event("gen_ai.request.start")
|
|
214
277
|
span
|
|
215
278
|
end
|
|
@@ -221,7 +284,7 @@ module LLM
|
|
|
221
284
|
"server.address" => provider_host,
|
|
222
285
|
"server.port" => provider_port
|
|
223
286
|
}.compact
|
|
224
|
-
span =
|
|
287
|
+
span = create_span(operation, attributes:)
|
|
225
288
|
span.add_event("gen_ai.request.start")
|
|
226
289
|
span
|
|
227
290
|
end
|
data/lib/llm/tracer.rb
CHANGED
|
@@ -89,6 +89,31 @@ module LLM
|
|
|
89
89
|
raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
|
|
90
90
|
end
|
|
91
91
|
|
|
92
|
+
##
|
|
93
|
+
# Opens a trace group so subsequent LLM spans share the same OpenTelemetry
|
|
94
|
+
# trace_id (and appear as one trace in backends like Langfuse).
|
|
95
|
+
# When +trace_group_id+ is a string, it is used to derive the trace_id.
|
|
96
|
+
#
|
|
97
|
+
# @param [String, nil] trace_group_id
|
|
98
|
+
# Optional. When present, converted to a 16-byte trace_id so all spans
|
|
99
|
+
# created until {#stop_trace} are grouped in one trace.
|
|
100
|
+
# @param [String] name
|
|
101
|
+
# Name for the root span (e.g. "chatbot.turn").
|
|
102
|
+
# @param [Hash] attributes
|
|
103
|
+
# OpenTelemetry attributes to set on the root span.
|
|
104
|
+
# @return [self]
|
|
105
|
+
def start_trace(trace_group_id: nil, name: "llm", attributes: {})
|
|
106
|
+
self
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Finishes the trace group started by {#start_trace}. Safe to call even if
|
|
111
|
+
# no trace is active.
|
|
112
|
+
# @return [self]
|
|
113
|
+
def stop_trace
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
92
117
|
##
|
|
93
118
|
# @return [String]
|
|
94
119
|
def inspect
|
data/lib/llm/version.rb
CHANGED