durable-llm 0.1.5 → 0.1.6

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.
@@ -83,6 +83,16 @@ module Durable
83
83
  end
84
84
  end
85
85
 
86
+ # Returns a list of all available provider names as strings.
87
+ #
88
+ # Alias for providers that returns strings instead of symbols, useful for
89
+ # display purposes in error messages and documentation.
90
+ #
91
+ # @return [Array<String>] Array of provider names
92
+ def self.available_providers
93
+ providers.map(&:to_s).sort
94
+ end
95
+
86
96
  # Returns a flat list of all model IDs across all providers.
87
97
  #
88
98
  # This method aggregates model IDs from all available providers by calling
@@ -0,0 +1,185 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module provides helper methods for extracting and formatting responses from
4
+ # LLM API calls. It offers convenient methods to work with response objects from
5
+ # different providers, abstracting away the complexity of response structure variations.
6
+
7
+ module Durable
8
+ module Llm
9
+ # Helper methods for working with LLM responses
10
+ #
11
+ # This module provides convenience methods for extracting content, messages,
12
+ # and metadata from LLM response objects. It handles the common patterns of
13
+ # response processing across different providers.
14
+ #
15
+ # @example Using response helpers
16
+ # response = client.chat(messages: [...])
17
+ # content = ResponseHelpers.extract_content(response)
18
+ # tokens = ResponseHelpers.token_usage(response)
19
+ module ResponseHelpers
20
+ module_function
21
+
22
+ # Extracts the text content from a completion response
23
+ #
24
+ # @param response [Object] The API response object
25
+ # @return [String, nil] The extracted content or nil if not found
26
+ # @example Extract content from response
27
+ # response = client.completion(messages: [...])
28
+ # text = ResponseHelpers.extract_content(response)
29
+ # puts text
30
+ def extract_content(response)
31
+ return nil unless response
32
+ return nil unless response.respond_to?(:choices)
33
+ return nil if response.choices.empty?
34
+
35
+ choice = response.choices.first
36
+ return nil unless choice.respond_to?(:message)
37
+
38
+ message = choice.message
39
+ return nil unless message.respond_to?(:content)
40
+
41
+ message.content
42
+ end
43
+
44
+ # Extracts all choice contents from a response
45
+ #
46
+ # @param response [Object] The API response object
47
+ # @return [Array<String>] Array of content strings from all choices
48
+ # @example Get all alternatives
49
+ # response = client.completion(messages: [...], n: 3)
50
+ # alternatives = ResponseHelpers.all_contents(response)
51
+ def all_contents(response)
52
+ return [] unless response&.respond_to?(:choices)
53
+
54
+ response.choices.map do |choice|
55
+ next unless choice.respond_to?(:message)
56
+
57
+ message = choice.message
58
+ message.content if message.respond_to?(:content)
59
+ end.compact
60
+ end
61
+
62
+ # Extracts token usage information from a response
63
+ #
64
+ # @param response [Object] The API response object
65
+ # @return [Hash, nil] Hash with :prompt_tokens, :completion_tokens, :total_tokens
66
+ # @example Get token usage
67
+ # response = client.completion(messages: [...])
68
+ # usage = ResponseHelpers.token_usage(response)
69
+ # puts "Used #{usage[:total_tokens]} tokens"
70
+ def token_usage(response)
71
+ return nil unless response&.respond_to?(:usage)
72
+
73
+ usage = response.usage
74
+ return nil unless usage
75
+
76
+ {
77
+ prompt_tokens: usage.prompt_tokens,
78
+ completion_tokens: usage.completion_tokens,
79
+ total_tokens: usage.total_tokens
80
+ }
81
+ end
82
+
83
+ # Extracts the finish reason from a response
84
+ #
85
+ # @param response [Object] The API response object
86
+ # @return [String, nil] The finish reason (e.g., 'stop', 'length', 'content_filter')
87
+ # @example Check why completion finished
88
+ # response = client.completion(messages: [...])
89
+ # reason = ResponseHelpers.finish_reason(response)
90
+ # puts "Finished because: #{reason}"
91
+ def finish_reason(response)
92
+ return nil unless response&.respond_to?(:choices)
93
+ return nil if response.choices.empty?
94
+
95
+ choice = response.choices.first
96
+ choice.finish_reason if choice.respond_to?(:finish_reason)
97
+ end
98
+
99
+ # Checks if a response was truncated due to length
100
+ #
101
+ # @param response [Object] The API response object
102
+ # @return [Boolean] True if response was truncated
103
+ # @example Check if truncated
104
+ # response = client.completion(messages: [...])
105
+ # if ResponseHelpers.truncated?(response)
106
+ # puts "Response was cut off. Consider increasing max_tokens."
107
+ # end
108
+ def truncated?(response)
109
+ finish_reason(response) == 'length'
110
+ end
111
+
112
+ # Formats a response as a simple hash with common fields
113
+ #
114
+ # @param response [Object] The API response object
115
+ # @return [Hash] Simplified response hash
116
+ # @example Format response
117
+ # response = client.completion(messages: [...])
118
+ # simple = ResponseHelpers.to_hash(response)
119
+ # # => { content: "...", tokens: {...}, finish_reason: "stop" }
120
+ def to_hash(response)
121
+ {
122
+ content: extract_content(response),
123
+ tokens: token_usage(response),
124
+ finish_reason: finish_reason(response),
125
+ all_contents: all_contents(response)
126
+ }
127
+ end
128
+
129
+ # Extracts model information from response
130
+ #
131
+ # @param response [Object] The API response object
132
+ # @return [String, nil] The model used for the completion
133
+ # @example Get model name
134
+ # response = client.completion(messages: [...])
135
+ # model = ResponseHelpers.model_used(response)
136
+ # puts "Model: #{model}"
137
+ def model_used(response)
138
+ return nil unless response&.respond_to?(:model)
139
+
140
+ response.model
141
+ end
142
+
143
+ # Calculates the cost of a response (approximate)
144
+ #
145
+ # This is a rough estimate based on common pricing. For accurate costs,
146
+ # consult your provider's pricing page.
147
+ #
148
+ # @param response [Object] The API response object
149
+ # @param model [String, nil] Optional model name for pricing lookup
150
+ # @return [Float, nil] Estimated cost in USD
151
+ # @example Estimate cost
152
+ # response = client.completion(messages: [...])
153
+ # cost = ResponseHelpers.estimate_cost(response)
154
+ # puts "Estimated cost: $#{cost}"
155
+ def estimate_cost(response, model = nil)
156
+ usage = token_usage(response)
157
+ return nil unless usage
158
+
159
+ model ||= model_used(response)
160
+ return nil unless model
161
+
162
+ # Rough pricing estimates (as of 2025)
163
+ pricing = case model
164
+ when /gpt-4-turbo/
165
+ { prompt: 0.01 / 1000, completion: 0.03 / 1000 }
166
+ when /gpt-4/
167
+ { prompt: 0.03 / 1000, completion: 0.06 / 1000 }
168
+ when /gpt-3.5-turbo/
169
+ { prompt: 0.0015 / 1000, completion: 0.002 / 1000 }
170
+ when /claude-3-opus/
171
+ { prompt: 0.015 / 1000, completion: 0.075 / 1000 }
172
+ when /claude-3-sonnet/
173
+ { prompt: 0.003 / 1000, completion: 0.015 / 1000 }
174
+ else
175
+ return nil # Unknown model
176
+ end
177
+
178
+ (usage[:prompt_tokens] * pricing[:prompt]) +
179
+ (usage[:completion_tokens] * pricing[:completion])
180
+ end
181
+ end
182
+ end
183
+ end
184
+
185
+ # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
@@ -4,7 +4,7 @@
4
4
 
5
5
  module Durable
6
6
  module Llm
7
- VERSION = '0.1.5'
7
+ VERSION = '0.1.6'
8
8
  end
9
9
  end
10
10
 
data/lib/durable/llm.rb CHANGED
@@ -21,7 +21,7 @@
21
21
  #
22
22
  # # Create a client and make a request
23
23
  # client = Durable::Llm.new(:openai, model: 'gpt-4')
24
- # response = client.quick_complete('Hello, world!')
24
+ # response = client.complete('Hello, world!')
25
25
  # puts response # => "Hello! How can I help you today?"
26
26
  # ```
27
27
  #
@@ -91,7 +91,7 @@ module Durable
91
91
  # end
92
92
  #
93
93
  # client = Durable::Llm.new(:openai)
94
- # response = client.quick_complete('Hello!')
94
+ # response = client.complete('Hello!')
95
95
  #
96
96
  # @see Durable::Llm::Client
97
97
  # @see Durable::Llm::Configuration
@@ -155,6 +155,73 @@ module Durable
155
155
  self.configuration ||= Configuration.new
156
156
  yield(configuration)
157
157
  end
158
+
159
+ # Creates a quick completion with minimal setup.
160
+ #
161
+ # This is a convenience method for one-off completions that automatically
162
+ # creates a client, performs the completion, and returns the text result.
163
+ #
164
+ # @param text [String] The input text to complete
165
+ # @param provider [Symbol] The provider to use (default: :openai)
166
+ # @param model [String] The model to use (required)
167
+ # @param options [Hash] Additional options for the client
168
+ # @return [String] The completion text
169
+ # @raise [ArgumentError] If required parameters are missing
170
+ # @example Quick completion with OpenAI
171
+ # result = Durable::Llm.complete('What is Ruby?', model: 'gpt-4')
172
+ # puts result
173
+ # @example Quick completion with Anthropic
174
+ # result = Durable::Llm.complete('Explain AI', provider: :anthropic, model: 'claude-3-opus-20240229')
175
+ # puts result
176
+ def self.complete(text, provider: :openai, model: nil, **options)
177
+ raise ArgumentError, 'text is required' if text.nil? || text.to_s.strip.empty?
178
+ raise ArgumentError, 'model is required' if model.nil? || model.to_s.strip.empty?
179
+
180
+ client = new(provider, options.merge(model: model))
181
+ client.complete(text)
182
+ end
183
+
184
+ # Creates a chat completion with minimal setup.
185
+ #
186
+ # This is a convenience method for quick chat interactions that automatically
187
+ # creates a client and performs the chat completion.
188
+ #
189
+ # @param messages [Array<Hash>] Array of message hashes with :role and :content
190
+ # @param provider [Symbol] The provider to use (default: :openai)
191
+ # @param model [String] The model to use (required)
192
+ # @param options [Hash] Additional options for the client and request
193
+ # @return [Object] The chat response object
194
+ # @raise [ArgumentError] If required parameters are missing
195
+ # @example Simple chat
196
+ # response = Durable::Llm.chat(
197
+ # [{ role: 'user', content: 'Hello!' }],
198
+ # model: 'gpt-4'
199
+ # )
200
+ # puts response.choices.first.message.content
201
+ def self.chat(messages, provider: :openai, model: nil, **options)
202
+ raise ArgumentError, 'messages are required' if messages.nil? || messages.empty?
203
+ raise ArgumentError, 'model is required' if model.nil? || model.to_s.strip.empty?
204
+
205
+ request_keys = %i[temperature max_tokens top_p frequency_penalty presence_penalty]
206
+ request_params = options.select { |k, _| request_keys.include?(k) }
207
+ client_options = options.reject { |k, _| request_keys.include?(k) }
208
+
209
+ client = new(provider, client_options.merge(model: model))
210
+ client.chat(messages: messages, **request_params)
211
+ end
212
+
213
+ # Lists available models for a provider.
214
+ #
215
+ # @param provider [Symbol] The provider name (default: :openai)
216
+ # @param options [Hash] Provider options (e.g., api_key)
217
+ # @return [Array<String>] List of available model IDs
218
+ # @example List OpenAI models
219
+ # models = Durable::Llm.models(:openai)
220
+ # puts models.inspect
221
+ def self.models(provider = :openai, **options)
222
+ client = new(provider, options)
223
+ client.provider.models
224
+ end
158
225
  end
159
226
  end
160
227
 
@@ -163,5 +230,11 @@ end
163
230
 
164
231
  require 'durable/llm/providers'
165
232
  require 'durable/llm/client'
233
+ require 'durable/llm/response_helpers'
234
+ require 'durable/llm/provider_utilities'
235
+
236
+ # Load global convenience functions for easier access
237
+ # Users can skip this by requiring 'durable/llm/core' instead of 'durable/llm'
238
+ require 'durable/llm/convenience' unless ENV['DLLM_NO_CONVENIENCE']
166
239
 
167
240
  # Copyright (c) 2025 Durable Programming, LLC. All rights reserved.
data/lib/durable.rb CHANGED
@@ -16,7 +16,7 @@
16
16
  # end
17
17
  #
18
18
  # client = Durable::Llm.new(:openai)
19
- # response = client.quick_complete('Hello!')
19
+ # response = client.complete('Hello!')
20
20
  #
21
21
  # @see Durable::Llm
22
22
 
data/sig/durable/llm.rbs CHANGED
@@ -69,7 +69,8 @@ module Durable
69
69
 
70
70
  def initialize: (Symbol | String provider_name, ?Hash[Symbol | String, untyped] options) -> void
71
71
  def default_params: () -> Hash[Symbol, String?]
72
- def quick_complete: (String text, ?Hash[Symbol, untyped] _opts) -> String
72
+ def complete: (String text, ?Hash[Symbol, untyped] _opts) -> String
73
+ alias quick_complete complete
73
74
  def completion: (?Hash[Symbol, untyped] params) -> untyped
74
75
  def chat: (?Hash[Symbol, untyped] params) -> untyped
75
76
  def embed: (?Hash[Symbol, untyped] params) -> untyped
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: durable-llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Durable Programming Team
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: event_stream_parser
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '1.0'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,20 +26,29 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: '1.0'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.0.0
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: faraday
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
- - - ">"
36
+ - - ">="
31
37
  - !ruby/object:Gem::Version
32
38
  version: '1.0'
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '3.0'
33
42
  type: :runtime
34
43
  prerelease: false
35
44
  version_requirements: !ruby/object:Gem::Requirement
36
45
  requirements:
37
- - - ">"
46
+ - - ">="
38
47
  - !ruby/object:Gem::Version
39
48
  version: '1.0'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '3.0'
40
52
  - !ruby/object:Gem::Dependency
41
53
  name: highline
42
54
  requirement: !ruby/object:Gem::Requirement
@@ -44,6 +56,9 @@ dependencies:
44
56
  - - "~>"
45
57
  - !ruby/object:Gem::Version
46
58
  version: '3.1'
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.1.0
47
62
  type: :runtime
48
63
  prerelease: false
49
64
  version_requirements: !ruby/object:Gem::Requirement
@@ -51,6 +66,9 @@ dependencies:
51
66
  - - "~>"
52
67
  - !ruby/object:Gem::Version
53
68
  version: '3.1'
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.1.0
54
72
  - !ruby/object:Gem::Dependency
55
73
  name: json
56
74
  requirement: !ruby/object:Gem::Requirement
@@ -58,6 +76,9 @@ dependencies:
58
76
  - - "~>"
59
77
  - !ruby/object:Gem::Version
60
78
  version: '2.6'
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.6.0
61
82
  type: :runtime
62
83
  prerelease: false
63
84
  version_requirements: !ruby/object:Gem::Requirement
@@ -65,6 +86,23 @@ dependencies:
65
86
  - - "~>"
66
87
  - !ruby/object:Gem::Version
67
88
  version: '2.6'
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 2.6.0
92
+ - !ruby/object:Gem::Dependency
93
+ name: ostruct
94
+ requirement: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: 0.6.0
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 0.6.0
68
106
  - !ruby/object:Gem::Dependency
69
107
  name: thor
70
108
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +110,9 @@ dependencies:
72
110
  - - "~>"
73
111
  - !ruby/object:Gem::Version
74
112
  version: '1.3'
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 1.3.0
75
116
  type: :runtime
76
117
  prerelease: false
77
118
  version_requirements: !ruby/object:Gem::Requirement
@@ -79,6 +120,9 @@ dependencies:
79
120
  - - "~>"
80
121
  - !ruby/object:Gem::Version
81
122
  version: '1.3'
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 1.3.0
82
126
  - !ruby/object:Gem::Dependency
83
127
  name: zeitwerk
84
128
  requirement: !ruby/object:Gem::Requirement
@@ -86,6 +130,9 @@ dependencies:
86
130
  - - "~>"
87
131
  - !ruby/object:Gem::Version
88
132
  version: '2.6'
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 2.6.0
89
136
  type: :runtime
90
137
  prerelease: false
91
138
  version_requirements: !ruby/object:Gem::Requirement
@@ -93,6 +140,9 @@ dependencies:
93
140
  - - "~>"
94
141
  - !ruby/object:Gem::Version
95
142
  version: '2.6'
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: 2.6.0
96
146
  - !ruby/object:Gem::Dependency
97
147
  name: dotenv
98
148
  requirement: !ruby/object:Gem::Requirement
@@ -163,6 +213,20 @@ dependencies:
163
213
  - - "~>"
164
214
  - !ruby/object:Gem::Version
165
215
  version: '6.0'
216
+ - !ruby/object:Gem::Dependency
217
+ name: yard
218
+ requirement: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - "~>"
221
+ - !ruby/object:Gem::Version
222
+ version: '0.9'
223
+ type: :development
224
+ prerelease: false
225
+ version_requirements: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - "~>"
228
+ - !ruby/object:Gem::Version
229
+ version: '0.9'
166
230
  description: Durable-LLM is a unified interface for interacting with multiple Large
167
231
  Language Model APIs, simplifying integration of AI capabilities into Ruby applications.
168
232
  email:
@@ -176,7 +240,6 @@ files:
176
240
  - CLI.md
177
241
  - CONFIGURE.md
178
242
  - Gemfile
179
- - Gemfile.lock
180
243
  - LICENSE.txt
181
244
  - README.md
182
245
  - Rakefile
@@ -193,7 +256,9 @@ files:
193
256
  - lib/durable/llm/cli.rb
194
257
  - lib/durable/llm/client.rb
195
258
  - lib/durable/llm/configuration.rb
259
+ - lib/durable/llm/convenience.rb
196
260
  - lib/durable/llm/errors.rb
261
+ - lib/durable/llm/provider_utilities.rb
197
262
  - lib/durable/llm/providers.rb
198
263
  - lib/durable/llm/providers/anthropic.rb
199
264
  - lib/durable/llm/providers/azure_openai.rb
@@ -211,6 +276,7 @@ files:
211
276
  - lib/durable/llm/providers/perplexity.rb
212
277
  - lib/durable/llm/providers/together.rb
213
278
  - lib/durable/llm/providers/xai.rb
279
+ - lib/durable/llm/response_helpers.rb
214
280
  - lib/durable/llm/version.rb
215
281
  - sig/durable/llm.rbs
216
282
  homepage: https://github.com/durableprogramming/durable-llm
data/Gemfile.lock DELETED
@@ -1,103 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- durable-llm (0.1.5)
5
- event_stream_parser (~> 1.0)
6
- faraday (> 1.0)
7
- highline (~> 3.1)
8
- json (~> 2.6)
9
- thor (~> 1.3)
10
- zeitwerk (~> 2.6)
11
-
12
- GEM
13
- remote: https://rubygems.org/
14
- specs:
15
- addressable (2.8.7)
16
- public_suffix (>= 2.0.2, < 7.0)
17
- ast (2.4.2)
18
- base64 (0.2.0)
19
- bigdecimal (3.1.8)
20
- crack (1.0.0)
21
- bigdecimal
22
- rexml
23
- dotenv (2.8.1)
24
- event_stream_parser (1.0.0)
25
- faraday (2.12.0)
26
- faraday-net_http (>= 2.0, < 3.4)
27
- json
28
- logger
29
- faraday-multipart (1.0.4)
30
- multipart-post (~> 2)
31
- faraday-net_http (3.3.0)
32
- net-http
33
- hashdiff (1.1.1)
34
- highline (3.1.1)
35
- reline
36
- io-console (0.7.2)
37
- json (2.7.2)
38
- language_server-protocol (3.17.0.3)
39
- logger (1.6.1)
40
- minitest (5.25.1)
41
- mocha (2.4.5)
42
- ruby2_keywords (>= 0.0.5)
43
- multipart-post (2.4.1)
44
- net-http (0.4.1)
45
- uri
46
- parallel (1.26.3)
47
- parser (3.3.5.0)
48
- ast (~> 2.4.1)
49
- racc
50
- public_suffix (6.0.1)
51
- racc (1.8.1)
52
- rainbow (3.1.1)
53
- rake (13.2.1)
54
- regexp_parser (2.9.2)
55
- reline (0.5.10)
56
- io-console (~> 0.5)
57
- rexml (3.3.8)
58
- rubocop (1.66.1)
59
- json (~> 2.3)
60
- language_server-protocol (>= 3.17.0)
61
- parallel (~> 1.10)
62
- parser (>= 3.3.0.2)
63
- rainbow (>= 2.2.2, < 4.0)
64
- regexp_parser (>= 2.4, < 3.0)
65
- rubocop-ast (>= 1.32.2, < 2.0)
66
- ruby-progressbar (~> 1.7)
67
- unicode-display_width (>= 2.4.0, < 3.0)
68
- rubocop-ast (1.32.3)
69
- parser (>= 3.3.1.0)
70
- ruby-openai (7.1.0)
71
- event_stream_parser (>= 0.3.0, < 2.0.0)
72
- faraday (>= 1)
73
- faraday-multipart (>= 1)
74
- ruby-progressbar (1.13.0)
75
- ruby2_keywords (0.0.5)
76
- thor (1.3.2)
77
- unicode-display_width (2.6.0)
78
- uri (0.13.1)
79
- vcr (6.3.1)
80
- base64
81
- webmock (3.24.0)
82
- addressable (>= 2.8.0)
83
- crack (>= 0.3.2)
84
- hashdiff (>= 0.4.0, < 2.0.0)
85
- zeitwerk (2.6.18)
86
-
87
- PLATFORMS
88
- x86_64-linux
89
-
90
- DEPENDENCIES
91
- dotenv (~> 2.8)
92
- durable-llm!
93
- minitest (~> 5.0)
94
- mocha (~> 2.1)
95
- rake (~> 13.0)
96
- rubocop (~> 1.21, ~> 1.0)
97
- ruby-openai (~> 7.1)
98
- thor (~> 1.3)
99
- vcr (~> 6.0)
100
- webmock (~> 3.24)
101
-
102
- BUNDLED WITH
103
- 2.7.1