ask-instrumentation 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: dcb0532585201d8c5d321aa173794fc0774b34e634b0b65ca0dcee15f2819649
4
+ data.tar.gz: a56ac64e426a4a01c27983cb4e705eba13ccb4a71b075ace4a2595cf4ca63229
5
+ SHA512:
6
+ metadata.gz: 5aa9ae01411b237e901041d55f604283dea028e2262a5f2c818317d2e57e490a0f065f9462a5efee089b6b1e900960ac9569adc553022356d5821eea8d9d0d72
7
+ data.tar.gz: 69d831ccbbd59ee50374ede18ef77a073fb856b1089e8676ba18cf284bb853718fcf34d92b4c5e0f672d93f3ef20d536e1f4b3d1678834f4de354ca90251a214
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0] - Unreleased
4
+
5
+ ### Added
6
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Kaka Ruto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # ask-instrumentation
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/ask-instrumentation.svg)](https://badge.fury.io/rb/ask-instrumentation)
4
+ [![CI](https://github.com/ask-rb/ask-instrumentation/actions/workflows/ci.yml/badge.svg)](https://github.com/ask-rb/ask-instrumentation/actions/workflows/ci.yml)
5
+
6
+ LLM observability for the ask-rb ecosystem. Emits `ActiveSupport::Notifications`
7
+ events for chat completions, embeddings, tool calls, and image generation.
8
+
9
+ Works with **any** LLM provider — not tied to a specific one. Subscribe to events
10
+ for cost tracking, logging, analytics, or alerting.
11
+
12
+ ## Installation
13
+
14
+ ```ruby
15
+ gem "ask-instrumentation"
16
+ ```
17
+
18
+ Or install it yourself:
19
+
20
+ ```bash
21
+ gem install ask-instrumentation
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ```ruby
27
+ require "ask/instrumentation"
28
+
29
+ # Subscribe to all ask events
30
+ Ask::Instrumentation.subscribe do |event|
31
+ puts "#{event.name}: #{event.duration}ms"
32
+ end
33
+
34
+ # Instrument a chat completion
35
+ Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
36
+ # your LLM call here
37
+ end
38
+
39
+ # Wrap with metadata context
40
+ Ask::Instrumentation.with_metadata(user_id: 42, session_id: "abc") do
41
+ Ask::Instrumentation.instrument("chat.ask", { provider: "openai", model: "gpt-4" }) do
42
+ # ...
43
+ end
44
+ end
45
+ ```
46
+
47
+ ## Events
48
+
49
+ | Event | Payload | Description |
50
+ |---|---|---|
51
+ | `chat.ask` | provider, model, input_tokens, output_tokens, duration | Chat completion |
52
+ | `chat.stream.ask` | provider, model, input_tokens, output_tokens, duration | Streaming chat |
53
+ | `tool.ask` | provider, tool_name, tool_args, duration | Tool call |
54
+ | `embedding.ask` | provider, model, input_tokens, duration | Embedding |
55
+ | `image.ask` | provider, model, size, duration | Image generation |
56
+
57
+ ## Examples
58
+
59
+ ### Cost Tracking
60
+
61
+ Subscribe to chat events and sum model costs:
62
+
63
+ ```ruby
64
+ require "ask/instrumentation"
65
+
66
+ COST_PER_TOKEN = {
67
+ "gpt-4" => { input: 0.03 / 1000, output: 0.06 / 1000 },
68
+ "gpt-3.5-turbo" => { input: 0.001 / 1000, output: 0.002 / 1000 },
69
+ "claude-3-opus" => { input: 0.015 / 1000, output: 0.075 / 1000 }
70
+ }.freeze
71
+
72
+ total_cost = 0.0
73
+
74
+ Ask::Instrumentation.subscribe(/chat\.ask/) do |event|
75
+ payload = event.payload
76
+ model = payload[:model]
77
+ pricing = COST_PER_TOKEN[model]
78
+ next unless pricing
79
+
80
+ cost = (payload[:input_tokens].to_i * pricing[:input]) +
81
+ (payload[:output_tokens].to_i * pricing[:output])
82
+ total_cost += cost
83
+
84
+ puts "[COST] #{model}: $%.6f (total: $%.4f)" % [cost, total_cost]
85
+ end
86
+
87
+ # Later, in your application code:
88
+ Ask::Instrumentation.with_metadata(session_id: "sess_123") do
89
+ Ask::Instrumentation.instrument("chat.ask",
90
+ provider: "openai",
91
+ model: "gpt-4",
92
+ input_tokens: 150,
93
+ output_tokens: 50
94
+ ) do
95
+ # actual LLM API call
96
+ end
97
+ end
98
+ ```
99
+
100
+ ### Request Logging
101
+
102
+ Log all LLM events to a file with structured data:
103
+
104
+ ```ruby
105
+ require "ask/instrumentation"
106
+ require "logger"
107
+
108
+ logger = Logger.new("log/llm.log")
109
+
110
+ Ask::Instrumentation.subscribe do |event|
111
+ logger.info({
112
+ event: event.name,
113
+ duration: event.duration.round(2),
114
+ **event.payload
115
+ }.to_json)
116
+ end
117
+ ```
118
+
119
+ ### Usage Analytics with Metadata
120
+
121
+ Track per-user and per-session usage:
122
+
123
+ ```ruby
124
+ require "ask/instrumentation"
125
+
126
+ # In your application (e.g., a Rails controller):
127
+ class ChatController < ApplicationController
128
+ def create
129
+ Ask::Instrumentation.with_metadata(
130
+ user_id: current_user.id,
131
+ session_id: request.session.id,
132
+ request_id: request.request_id
133
+ ) do
134
+ # All events emitted here automatically include user/session context
135
+ response = llm_client.chat(params[:message])
136
+ render json: response
137
+ end
138
+ end
139
+ end
140
+
141
+ # In a monitoring background worker:
142
+ Ask::Instrumentation.subscribe(/chat\.ask/) do |event|
143
+ payload = event.payload
144
+ UsageReport.increment(
145
+ user_id: payload[:user_id],
146
+ model: payload[:model],
147
+ tokens_in: payload[:input_tokens],
148
+ tokens_out: payload[:output_tokens]
149
+ )
150
+ end
151
+ ```
152
+
153
+ ### Integration with ask-llm-providers
154
+
155
+ When using the ask-rb ecosystem, providers emit events automatically:
156
+
157
+ ```ruby
158
+ require "ask/provider"
159
+
160
+ # Events are emitted automatically — no manual instrumentation needed.
161
+ provider = Ask::Provider.for(:openai)
162
+ provider.complete(prompt: "Hello!") # emits chat.ask
163
+
164
+ # Subscribe to track everything:
165
+ Ask::Instrumentation.subscribe do |event|
166
+ puts "[#{event.name}] #{event.payload[:model]} (#{event.duration}ms)"
167
+ end
168
+ ```
169
+
170
+ ### With Your Own Provider
171
+
172
+ You can emit events from any custom provider:
173
+
174
+ ```ruby
175
+ class MyCustomProvider
176
+ def complete(prompt)
177
+ Ask::Instrumentation.instrument("chat.ask",
178
+ provider: "my_custom",
179
+ model: "my-model-v1",
180
+ input_tokens: prompt.length / 4
181
+ ) do
182
+ response = call_api(prompt)
183
+ # enrich payload after the call by returning a hash from the block?
184
+ # No — use with_metadata or include everything upfront.
185
+ response
186
+ end
187
+ end
188
+ end
189
+ ```
190
+
191
+ ## API
192
+
193
+ ### `.subscribe(pattern = /\.ask$/, &block)`
194
+
195
+ Subscribe to ask events. Accepts an optional pattern (defaults to all `.ask` events).
196
+
197
+ ```ruby
198
+ # All ask events
199
+ Ask::Instrumentation.subscribe { |event| ... }
200
+
201
+ # Only chat events
202
+ Ask::Instrumentation.subscribe(/chat\.ask/) { |event| ... }
203
+ ```
204
+
205
+ ### `.unsubscribe(subscriber_or_name)`
206
+
207
+ Remove a subscriber by passing the object returned from `subscribe` or a string/regexp.
208
+
209
+ ```ruby
210
+ subscriber = Ask::Instrumentation.subscribe { |e| ... }
211
+ Ask::Instrumentation.unsubscribe(subscriber)
212
+ ```
213
+
214
+ ### `.instrument(name, payload = {}, &block)`
215
+
216
+ Emit an event. The block is instrumented and its return value is passed through.
217
+ Metadata from `with_metadata` is automatically merged into the payload.
218
+
219
+ ```ruby
220
+ result = Ask::Instrumentation.instrument("chat.ask",
221
+ provider: "openai",
222
+ model: "gpt-4"
223
+ ) do
224
+ llm_call
225
+ end
226
+ ```
227
+
228
+ ### `.with_metadata(hash, &block)`
229
+
230
+ Set thread-local metadata that is merged into all events emitted inside the block.
231
+ Nested blocks merge inner metadata into outer, with inner values taking precedence.
232
+
233
+ ```ruby
234
+ Ask::Instrumentation.with_metadata(user_id: 42) do
235
+ Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
236
+ # event payload includes user_id: 42
237
+ end
238
+ end
239
+ ```
240
+
241
+ ### `.current_metadata`
242
+
243
+ Return the current thread's metadata hash (empty hash if no metadata is set).
244
+
245
+ ```ruby
246
+ Ask::Instrumentation.current_metadata # => {}
247
+ ```
248
+
249
+ ## Thread Safety
250
+
251
+ All metadata is stored in `Thread.current`, making it safe to use in concurrent
252
+ environments. Each thread has its own metadata context:
253
+
254
+ ```ruby
255
+ Ask::Instrumentation.with_metadata(thread: "main") do
256
+ Thread.new do
257
+ # This thread has its own metadata context
258
+ Ask::Instrumentation.current_metadata # => {}
259
+ Ask::Instrumentation.with_metadata(thread: "worker") do
260
+ # ...
261
+ end
262
+ end.join
263
+ end
264
+ ```
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ git clone https://github.com/ask-rb/ask-instrumentation.git
270
+ cd ask-instrumentation
271
+ bundle install
272
+ bundle exec rake test
273
+ ```
274
+
275
+ ## License
276
+
277
+ MIT
@@ -0,0 +1,13 @@
1
+ module Ask
2
+ module Instrumentation
3
+ # Placeholder for Chat-specific instrumentation helpers.
4
+ #
5
+ # This module will be loaded automatically when
6
+ # +Ask::Instrumentation::Chat+ is referenced (via +autoload+).
7
+ #
8
+ # Future versions may include helper methods for instrumenting
9
+ # streaming chat completions.
10
+ module Chat
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ module Ask
2
+ module Instrumentation
3
+ # Placeholder for Embedding-specific instrumentation helpers.
4
+ #
5
+ # This module will be loaded automatically when
6
+ # +Ask::Instrumentation::Embedding+ is referenced (via +autoload+).
7
+ module Embedding
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Ask
2
+ module Instrumentation
3
+ # Placeholder for Tool-specific instrumentation helpers.
4
+ #
5
+ # This module will be loaded automatically when
6
+ # +Ask::Instrumentation::Tool+ is referenced (via +autoload+).
7
+ module Tool
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Ask
2
+ module Instrumentation
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,163 @@
1
+ require "active_support"
2
+ require "active_support/notifications"
3
+ require_relative "instrumentation/version"
4
+
5
+ module Ask
6
+ # Instrumentation for LLM observability in the ask-rb ecosystem.
7
+ #
8
+ # Provides a thin wrapper around +ActiveSupport::Notifications+ for emitting
9
+ # and subscribing to LLM events such as chat completions, embeddings, tool
10
+ # calls, and image generation.
11
+ #
12
+ # Events follow the +{operation}.ask+ naming convention:
13
+ #
14
+ # chat.ask # Chat completion
15
+ # chat.stream.ask # Streaming chat
16
+ # tool.ask # Tool execution
17
+ # embedding.ask # Embedding generation
18
+ # image.ask # Image generation
19
+ #
20
+ # Each event payload includes provider-agnostic keys such as +provider+,
21
+ # +model+, +duration+, and provider-specific metrics (+input_tokens+,
22
+ # +output_tokens+, etc.).
23
+ #
24
+ # == Usage
25
+ #
26
+ # require "ask/instrumentation"
27
+ #
28
+ # # Subscribe to all ask events
29
+ # Ask::Instrumentation.subscribe do |event|
30
+ # puts "#{event.name}: #{event.duration}ms"
31
+ # end
32
+ #
33
+ # # Subscribe with a callable subscriber
34
+ # Ask::Instrumentation.subscribe(MySubscriber.new)
35
+ #
36
+ # # Instrument a chat completion
37
+ # Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
38
+ # # your LLM call here
39
+ # end
40
+ #
41
+ # # Wrap with metadata context
42
+ # Ask::Instrumentation.with_metadata(user_id: 42, session_id: "abc") do
43
+ # Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
44
+ # # ...
45
+ # end
46
+ # end
47
+ module Instrumentation
48
+ autoload :Chat, "ask/instrumentation/chat"
49
+ autoload :Embedding, "ask/instrumentation/embedding"
50
+ autoload :Tool, "ask/instrumentation/tool"
51
+
52
+ class << self
53
+ # Subscribe to ask instrumentation events.
54
+ #
55
+ # Accepts a callable subscriber (an object that responds to +#call+)
56
+ # and/or a block. When a pattern is given, only events matching the
57
+ # pattern are delivered to the subscriber.
58
+ #
59
+ # @overload subscribe(pattern = /\.ask$/, subscriber)
60
+ # @param pattern [Regexp, String] Optional event name pattern
61
+ # @param subscriber [#call] An object that responds to +#call(event)+
62
+ # @return [ActiveSupport::Notifications::Fanout::Subscriber]
63
+ #
64
+ # @overload subscribe(pattern = /\.ask$/, &block)
65
+ # @param pattern [Regexp, String] Optional event name pattern
66
+ # @param block [Proc] The callback invoked on each matching event
67
+ # @return [ActiveSupport::Notifications::Fanout::Subscriber]
68
+ #
69
+ # @example Subscribe with a block
70
+ # Ask::Instrumentation.subscribe { |event| puts event.name }
71
+ #
72
+ # @example Subscribe with a callable object
73
+ # Ask::Instrumentation.subscribe(MySubscriber.new)
74
+ #
75
+ # @example Subscribe with a pattern and callable
76
+ # Ask::Instrumentation.subscribe(/chat\.ask/, MySubscriber.new)
77
+ def subscribe(pattern = /\.ask$/, subscriber = nil, &block)
78
+ ActiveSupport::Notifications.subscribe(pattern, subscriber || block)
79
+ end
80
+
81
+ # Unsubscribe from events.
82
+ #
83
+ # Delegates to +ActiveSupport::Notifications.unsubscribe+. Accepts either
84
+ # the subscriber object returned from {subscribe} or a string/regexp
85
+ # pattern.
86
+ #
87
+ # @param subscriber_or_name [ActiveSupport::Notifications::Fanout::Subscriber, String, Regexp]
88
+ # The subscriber to remove
89
+ # @return [void]
90
+ #
91
+ # @example
92
+ # subscriber = Ask::Instrumentation.subscribe { |e| puts e.name }
93
+ # Ask::Instrumentation.unsubscribe(subscriber)
94
+ def unsubscribe(subscriber_or_name)
95
+ ActiveSupport::Notifications.unsubscribe(subscriber_or_name)
96
+ end
97
+
98
+ # Instrument a block of code, wrapping it in an +ActiveSupport::Notifications+ event.
99
+ #
100
+ # The event name should follow the +{operation}.ask+ convention.
101
+ # The payload is automatically merged with the current thread metadata
102
+ # (set via {with_metadata}) before being emitted.
103
+ #
104
+ # @param name [String] The event name (e.g. +"chat.ask"+)
105
+ # @param payload [Hash] The event payload
106
+ # @yield The block to instrument
107
+ # @return [Object] The return value of the block
108
+ #
109
+ # @example
110
+ # Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
111
+ # # LLM call
112
+ # end
113
+ def instrument(name, payload = {})
114
+ merged = current_metadata.merge(payload)
115
+ ActiveSupport::Notifications.instrument(name, merged) { yield if block_given? }
116
+ end
117
+
118
+ # Execute a block with thread-local metadata that gets merged into all
119
+ # events emitted within the block.
120
+ #
121
+ # Nested calls merge the inner metadata into the outer, with the inner
122
+ # values taking precedence for duplicate keys. The outer metadata is
123
+ # restored after the inner block completes.
124
+ #
125
+ # Metadata is useful for attaching context such as +user_id+,
126
+ # +session_id+, +request_id+, or +trace_id+ to every event.
127
+ #
128
+ # @param metadata [Hash] The metadata hash to attach
129
+ # @yield The block to execute with the metadata context
130
+ # @return [Object] The return value of the block
131
+ #
132
+ # @example
133
+ # Ask::Instrumentation.with_metadata(user_id: 42) do
134
+ # Ask::Instrumentation.instrument("chat.ask", provider: "openai", model: "gpt-4") do
135
+ # # ... event payload will include user_id: 42
136
+ # end
137
+ # end
138
+ def with_metadata(metadata = {})
139
+ previous = Thread.current[:ask_instrumentation_metadata]
140
+ Thread.current[:ask_instrumentation_metadata] = (previous || {}).merge(metadata)
141
+ yield
142
+ ensure
143
+ Thread.current[:ask_instrumentation_metadata] = previous
144
+ end
145
+
146
+ # Return the current thread's metadata hash.
147
+ #
148
+ # This is the metadata hash that will be merged into any event payload
149
+ # emitted via {instrument} in the current thread.
150
+ #
151
+ # @return [Hash] The current metadata (empty hash if none set)
152
+ #
153
+ # @example
154
+ # Ask::Instrumentation.current_metadata # => {}
155
+ # Ask::Instrumentation.with_metadata(user_id: 42) do
156
+ # Ask::Instrumentation.current_metadata # => { user_id: 42 }
157
+ # end
158
+ def current_metadata
159
+ Thread.current[:ask_instrumentation_metadata] || {}
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1 @@
1
+ require_relative "ask/instrumentation"
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ask-instrumentation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kaka Ruto
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.25'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.25'
40
+ - !ruby/object:Gem::Dependency
41
+ name: mocha
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '3.1'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.1'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '13.0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '13.0'
68
+ description: Emits ActiveSupport::Notifications events for chat completions, embeddings,
69
+ tool calls, and image generation. Works with any LLM provider. Subscribe to events
70
+ for cost tracking, logging, analytics, or alerting.
71
+ email:
72
+ - kaka@myrrlabs.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - CHANGELOG.md
78
+ - LICENSE
79
+ - README.md
80
+ - lib/ask-instrumentation.rb
81
+ - lib/ask/instrumentation.rb
82
+ - lib/ask/instrumentation/chat.rb
83
+ - lib/ask/instrumentation/embedding.rb
84
+ - lib/ask/instrumentation/tool.rb
85
+ - lib/ask/instrumentation/version.rb
86
+ homepage: https://github.com/ask-rb/ask-instrumentation
87
+ licenses:
88
+ - MIT
89
+ metadata:
90
+ homepage_uri: https://github.com/ask-rb/ask-instrumentation
91
+ source_code_uri: https://github.com/ask-rb/ask-instrumentation
92
+ changelog_uri: https://github.com/ask-rb/ask-instrumentation/blob/master/CHANGELOG.md
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '3.2'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubygems_version: 4.0.3
108
+ specification_version: 4
109
+ summary: LLM observability for the ask-rb ecosystem
110
+ test_files: []