roseflow 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.standard.yml +3 -1
  3. data/CHANGELOG.md +2 -2
  4. data/Gemfile +2 -0
  5. data/examples/github-repo-chat/lib/actions/clone_and_load_repository.rb +52 -0
  6. data/examples/github-repo-chat/lib/actions/create_prompt.rb +15 -0
  7. data/examples/github-repo-chat/lib/actions/embed_repository.rb +35 -0
  8. data/examples/github-repo-chat/lib/actions/initialize_vector_store.rb +40 -0
  9. data/examples/github-repo-chat/lib/actions/interact_with_model.rb +29 -0
  10. data/examples/github-repo-chat/lib/actions/load_documents_to_database.rb +0 -0
  11. data/examples/github-repo-chat/lib/actions/split_files_to_documents.rb +55 -0
  12. data/examples/github-repo-chat/lib/document_database.rb +0 -0
  13. data/examples/github-repo-chat/lib/github_chat_prompt.rb +24 -0
  14. data/examples/github-repo-chat/lib/github_repository_chat.rb +12 -0
  15. data/examples/github-repo-chat/lib/interactions/ask_llm.rb +31 -0
  16. data/examples/github-repo-chat/lib/interactions/github_repository_chat.rb +36 -0
  17. data/examples/github-repo-chat/lib/interactions/load_files_to_document_database.rb +18 -0
  18. data/examples/github-repo-chat/lib/interactions/load_repository.rb +20 -0
  19. data/examples/github-repo-chat/lib/interactions/prepare_vector_store.rb +21 -0
  20. data/examples/github-repo-chat/lib/repository.rb +9 -0
  21. data/examples/github-repo-chat/lib/repository_file.rb +31 -0
  22. data/examples/github-repo-chat/spec/actions/clone_and_load_repository_spec.rb +28 -0
  23. data/examples/github-repo-chat/spec/actions/embed_repository_spec.rb +24 -0
  24. data/examples/github-repo-chat/spec/actions/initialize_vector_store_spec.rb +20 -0
  25. data/examples/github-repo-chat/spec/actions/load_files_to_document_database_spec.rb +23 -0
  26. data/examples/github-repo-chat/spec/fixtures/ulid-ruby.zip +0 -0
  27. data/examples/github-repo-chat/spec/github_repository_chat_spec.rb +16 -0
  28. data/examples/github-repo-chat/spec/interactions/prepare_vector_store_spec.rb +4 -0
  29. data/examples/github-repo-chat/spec/spec_helper.rb +12 -0
  30. data/lib/roseflow/action.rb +13 -0
  31. data/lib/roseflow/actions/ai/resolve_model.rb +27 -0
  32. data/lib/roseflow/actions/ai/resolve_provider.rb +31 -0
  33. data/lib/roseflow/ai/model.rb +19 -0
  34. data/lib/roseflow/ai/provider.rb +30 -0
  35. data/lib/roseflow/chat/dialogue.rb +80 -0
  36. data/lib/roseflow/chat/exchange.rb +12 -0
  37. data/lib/roseflow/chat/message.rb +39 -0
  38. data/lib/roseflow/chat/personality.rb +10 -0
  39. data/lib/roseflow/embeddings/embedding.rb +26 -0
  40. data/lib/roseflow/finite_machine.rb +298 -0
  41. data/lib/roseflow/interaction/with_http_api.rb +10 -0
  42. data/lib/roseflow/interaction.rb +14 -0
  43. data/lib/roseflow/interaction_context.rb +10 -0
  44. data/lib/roseflow/interactions/ai/initialize_llm.rb +26 -0
  45. data/lib/roseflow/primitives/vector.rb +19 -0
  46. data/lib/roseflow/prompt.rb +17 -0
  47. data/lib/roseflow/text/completion.rb +16 -0
  48. data/lib/roseflow/text/recursive_character_splitter.rb +43 -0
  49. data/lib/roseflow/text/sentence_splitter.rb +42 -0
  50. data/lib/roseflow/text/splitter.rb +18 -0
  51. data/lib/roseflow/text/tokenized_text.rb +20 -0
  52. data/lib/roseflow/text/word_splitter.rb +14 -0
  53. data/lib/roseflow/tokenizer.rb +13 -0
  54. data/lib/roseflow/types.rb +9 -0
  55. data/lib/roseflow/vector_stores/base.rb +39 -0
  56. data/lib/roseflow/vector_stores/hnsw.proto +18 -0
  57. data/lib/roseflow/vector_stores/hnsw_memory_store.rb +442 -0
  58. data/lib/roseflow/vector_stores/hnsw_pb.rb +27 -0
  59. data/lib/roseflow/vector_stores/type/vector.rb +38 -0
  60. data/lib/roseflow/vector_stores/vector.rb +19 -0
  61. data/lib/roseflow/version.rb +12 -1
  62. data/lib/roseflow.rb +10 -1
  63. data/roseflow.gemspec +53 -0
  64. metadata +274 -7
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/action"
4
+ require "roseflow/ai/provider"
5
+ require "roseflow/openai/config"
6
+
7
+ module Roseflow
8
+ module Actions
9
+ module AI
10
+ class ResolveProvider
11
+ extend Roseflow::Action
12
+
13
+ expects :provider
14
+ promises :provider
15
+
16
+ executed do |context|
17
+ context[:provider] = resolve_provider(context[:provider])
18
+ end
19
+
20
+ private_class_method
21
+
22
+ def self.resolve_provider(provider)
23
+ case provider
24
+ when :openai
25
+ Roseflow::AI::Provider.new(name: :openai, credentials: Roseflow::OpenAI::Config.new)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module AI
5
+ class Model
6
+ attr_reader :name, :provider
7
+
8
+ def initialize(name:, provider:)
9
+ @name = name
10
+ @provider = provider
11
+ @model_ = provider.models.find(name)
12
+ end
13
+
14
+ def call(operation, input)
15
+ @model_.call(operation, input)
16
+ end
17
+ end # Model
18
+ end # AI
19
+ end # Roseflow
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/openai/provider"
4
+
5
+ module Roseflow
6
+ module AI
7
+ class Provider
8
+ def initialize(name:, credentials:)
9
+ @name = name
10
+ @credentials = credentials
11
+ initialize_provider
12
+ end
13
+
14
+ def models
15
+ @models ||= provider.models
16
+ end
17
+
18
+ private
19
+
20
+ attr_reader :name, :credentials, :provider
21
+
22
+ def initialize_provider
23
+ case name
24
+ when :openai
25
+ @provider = Roseflow::OpenAI::Provider.new(credentials: credentials)
26
+ end
27
+ end
28
+ end # Provider
29
+ end # AI
30
+ end # Roseflow
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/chat/exchange"
4
+ require "roseflow/chat/message"
5
+
6
+ module Roseflow
7
+ module Chat
8
+ class Dialogue
9
+ attr_reader :messages
10
+ attr_reader :exchanges
11
+ attr_reader :personality
12
+
13
+ def initialize(personality: nil)
14
+ @messages = []
15
+ @exchanges = []
16
+ initialize_with_personality(personality) if personality
17
+ end
18
+
19
+ def model
20
+ @model ||= provider.models.chattable.first
21
+ end
22
+
23
+ def provider
24
+ @provider ||= OpenAI::Provider.new(Roseflow::OpenAI::Config.new)
25
+ end
26
+
27
+ def recall(messages)
28
+ @messages = messages
29
+ parse_exchanges
30
+ end
31
+
32
+ # This method takes a string input as input, which is a message to send to the LLM model.
33
+ # If the model is chattable, it sends the messages to the model and returns a response.
34
+ def say(input, **options)
35
+ if model.chattable?
36
+ message = create_user_message(input)
37
+ @messages.push(message)
38
+
39
+ model_response = provider.chat(model: model, messages: @messages, **options)
40
+
41
+ if model_response && model_response.success?
42
+ exchange = create_exchange(message, model_response.choices.first.message)
43
+ @exchanges.push(exchange)
44
+ @messages.push(exchange.response)
45
+ exchange.response
46
+ else
47
+ raise "Did not receive a response from the model"
48
+ end
49
+ else
50
+ raise "Model is not chattable"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def initialize_with_personality(personality)
57
+ @personality = personality
58
+ @messages << SystemMessage.new(role: "system", content: personality.description)
59
+ end
60
+
61
+ def create_user_message(input)
62
+ UserMessage.new(role: "user", content: input)
63
+ end
64
+
65
+ def create_exchange(prompt, response)
66
+ Exchange.new(prompt: prompt, response: response)
67
+ end
68
+
69
+ def parse_exchanges
70
+ user_assistant_messages.each_cons(2) do |prompt, response|
71
+ @exchanges << Exchange.new(prompt: prompt, response: response)
72
+ end
73
+ end
74
+
75
+ def user_assistant_messages
76
+ @messages.reject { |message| message.role == "system" }
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/chat/message"
4
+
5
+ module Roseflow
6
+ module Chat
7
+ class Exchange < Dry::Struct
8
+ attribute :prompt, UserMessage
9
+ attribute :response, ModelMessage
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-struct"
4
+ require "roseflow/types"
5
+
6
+ module Roseflow
7
+ module Chat
8
+ class Message < Dry::Struct
9
+ attribute :role, Types::String
10
+ attribute :content, Types::String
11
+ attribute? :name, Types::String
12
+ attribute? :token_count, Types::Integer
13
+
14
+ def user?
15
+ role == "user"
16
+ end
17
+
18
+ def system?
19
+ role == "system"
20
+ end
21
+
22
+ def model?
23
+ %w(system assistant).include?(role)
24
+ end
25
+ end
26
+
27
+ class ModelMessage < Message
28
+ attribute :role, Types::String.constrained(included_in: %w(system assistant))
29
+ end
30
+
31
+ class UserMessage < Message
32
+ attribute :role, Types::String.constrained(included_in: %w(user))
33
+ end
34
+
35
+ class SystemMessage < Message
36
+ attribute :role, Types::String.constrained(included_in: %w(system))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module Chat
5
+ class Personality < Dry::Struct
6
+ attribute :name, Types::Strict::String
7
+ attribute :description, Types::Strict::String
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module Embeddings
5
+ EmbeddingModelNotSpecifiedError = Class.new(StandardError)
6
+
7
+ class Embedding
8
+ attr_reader :length, :vector
9
+ attr_writer :input
10
+
11
+ def initialize(**kwargs)
12
+ @length = kwargs.fetch(:length, 1024)
13
+ @input = kwargs.fetch(:input, nil)
14
+ @model = kwargs.fetch(:model, nil)
15
+ @vector = kwargs.fetch(:vector, nil)
16
+ end
17
+
18
+ def call
19
+ raise ArgumentError, "An input must be provided" unless @input
20
+ raise EmbeddingModelNotSpecifiedError, "An embedding model must be specified" unless @model
21
+ response = @model.provider.create_embedding(model: @model, input: @input)
22
+ response.embedding
23
+ end
24
+ end # Embedding
25
+ end # Embeddings
26
+ end # Roseflow
@@ -0,0 +1,298 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ulid"
4
+ require "finite_machine"
5
+
6
+ ## FIXME:
7
+ ## Monkey patching FiniteMachine::GenericDSL for Ruby 3.2 support.
8
+ ## See: https://github.com/piotrmurach/finite_machine/pull/74
9
+
10
+ module FiniteMachine
11
+ # A class responsible for observing state changes
12
+ class Observer < GenericDSL
13
+ include Safety
14
+
15
+ # Clean up callback queue
16
+ #
17
+ # @api private
18
+ def cleanup_callback_queue(_id)
19
+ if callback_queue.alive?
20
+ callback_queue.shutdown
21
+ end
22
+ rescue MessageQueueDeadError
23
+ end
24
+
25
+ # The current state machine
26
+ attr_reader :machine
27
+
28
+ # The hooks to trigger around the transition lifecycle.
29
+ attr_reader :hooks
30
+
31
+ # Initialize an Observer
32
+ #
33
+ # @param [StateMachine] machine
34
+ # reference to the current machine
35
+ #
36
+ # @api public
37
+ def initialize(machine)
38
+ @id = ULID.generate
39
+ @machine = machine
40
+ @hooks = Hooks.new
41
+
42
+ @machine.subscribe(self)
43
+ ObjectSpace.define_finalizer(@id, method(:cleanup_callback_queue))
44
+ end
45
+
46
+ def callback_queue
47
+ @callback_queue ||= MessageQueue.new
48
+ end
49
+
50
+ # Evaluate in current context
51
+ #
52
+ # @api private
53
+ def call(&block)
54
+ instance_eval(&block)
55
+ end
56
+
57
+ # Register callback for a given hook type
58
+ #
59
+ # @param [HookEvent] hook_type
60
+ # @param [Symbol] state_or_event_name
61
+ # @param [Proc] callback
62
+ #
63
+ # @example
64
+ # observer.on HookEvent::Enter, :green
65
+ #
66
+ # @api public
67
+ def on(hook_type, state_or_event_name = nil, async = nil, &callback)
68
+ sync_exclusive do
69
+ if state_or_event_name.nil?
70
+ state_or_event_name = HookEvent.any_state_or_event(hook_type)
71
+ end
72
+ async = false if async.nil?
73
+ ensure_valid_callback_name!(hook_type, state_or_event_name)
74
+ callback.extend(Async) if async == :async
75
+ hooks.register(hook_type, state_or_event_name, callback)
76
+ end
77
+ end
78
+
79
+ # Unregister callback for a given event
80
+ #
81
+ # @api public
82
+ def off(hook_type, name = ANY_STATE, &callback)
83
+ sync_exclusive do
84
+ hooks.unregister hook_type, name, callback
85
+ end
86
+ end
87
+
88
+ module Once; end
89
+
90
+ module Async; end
91
+
92
+ def on_enter(*args, &callback)
93
+ on HookEvent::Enter, *args, &callback
94
+ end
95
+
96
+ def on_transition(*args, &callback)
97
+ on HookEvent::Transition, *args, &callback
98
+ end
99
+
100
+ def on_exit(*args, &callback)
101
+ on HookEvent::Exit, *args, &callback
102
+ end
103
+
104
+ def once_on_enter(*args, &callback)
105
+ on HookEvent::Enter, *args, &callback.extend(Once)
106
+ end
107
+
108
+ def once_on_transition(*args, &callback)
109
+ on HookEvent::Transition, *args, &callback.extend(Once)
110
+ end
111
+
112
+ def once_on_exit(*args, &callback)
113
+ on HookEvent::Exit, *args, &callback.extend(Once)
114
+ end
115
+
116
+ def on_before(*args, &callback)
117
+ on HookEvent::Before, *args, &callback
118
+ end
119
+
120
+ def on_after(*args, &callback)
121
+ on HookEvent::After, *args, &callback
122
+ end
123
+
124
+ def once_on_before(*args, &callback)
125
+ on HookEvent::Before, *args, &callback.extend(Once)
126
+ end
127
+
128
+ def once_on_after(*args, &callback)
129
+ on HookEvent::After, *args, &callback.extend(Once)
130
+ end
131
+
132
+ # Execute each of the hooks in order with supplied data
133
+ #
134
+ # @param [HookEvent] event
135
+ # the hook event
136
+ #
137
+ # @param [Array[Object]] data
138
+ #
139
+ # @return [nil]
140
+ #
141
+ # @api public
142
+ def emit(event, *data)
143
+ sync_exclusive do
144
+ [event.type].each do |hook_type|
145
+ any_state_or_event = HookEvent.any_state_or_event(hook_type)
146
+ [any_state_or_event, event.name].each do |event_name|
147
+ hooks[hook_type][event_name].each do |hook|
148
+ handle_callback(hook, event, *data)
149
+ off(hook_type, event_name, &hook) if hook.is_a?(Once)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Cancel the current event
157
+ #
158
+ # This should be called inside a on_before or on_exit callbacks
159
+ # to prevent event transition.
160
+ #
161
+ # @param [String] msg
162
+ # the message used for failure
163
+ #
164
+ # @api public
165
+ def cancel_event(msg = nil)
166
+ raise CallbackError.new(msg)
167
+ end
168
+
169
+ private
170
+
171
+ # Handle callback and decide if run synchronously or asynchronously
172
+ #
173
+ # @param [Proc] :hook
174
+ # The hook to evaluate
175
+ #
176
+ # @param [HookEvent] :event
177
+ # The event for which the hook is called
178
+ #
179
+ # @param [Array[Object]] :data
180
+ #
181
+ # @api private
182
+ def handle_callback(hook, event, *data)
183
+ to = machine.events_map.move_to(event.event_name, event.from, *data)
184
+ trans_event = TransitionEvent.new(event.event_name, event.from, to)
185
+ callable = create_callable(hook)
186
+
187
+ if hook.is_a?(Async)
188
+ defer(callable, trans_event, *data)
189
+ else
190
+ callable.call(trans_event, *data)
191
+ end
192
+ end
193
+
194
+ # Defer callback execution
195
+ #
196
+ # @api private
197
+ def defer(callable, trans_event, *data)
198
+ async_call = AsyncCall.new(machine, callable, trans_event, *data)
199
+ callback_queue.start unless callback_queue.running?
200
+ callback_queue << async_call
201
+ end
202
+
203
+ # Create callable instance
204
+ #
205
+ # @api private
206
+ def create_callable(hook)
207
+ callback = proc do |trans_event, *data|
208
+ machine.instance_exec(trans_event, *data, &hook)
209
+ end
210
+ Callable.new(callback)
211
+ end
212
+
213
+ # Callback names including all states and events
214
+ #
215
+ # @return [Array[Symbol]]
216
+ # valid callback names
217
+ #
218
+ # @api private
219
+ def callback_names
220
+ machine.states + machine.events + [ANY_EVENT, ANY_STATE]
221
+ end
222
+
223
+ # Forward the message to observer
224
+ #
225
+ # @param [String] method_name
226
+ #
227
+ # @param [Array] args
228
+ #
229
+ # @return [self]
230
+ #
231
+ # @api private
232
+ def method_missing(method_name, *args, &block)
233
+ _, event_name, callback_name = *method_name.to_s.match(/^(\w*?on_\w+?)_(\w+)$/)
234
+ if callback_name && callback_names.include?(callback_name.to_sym)
235
+ public_send(event_name, :"#{callback_name}", *args, &block)
236
+ else
237
+ super
238
+ end
239
+ end
240
+
241
+ # Test if a message can be handled by observer
242
+ #
243
+ # @param [String] method_name
244
+ #
245
+ # @param [Boolean] include_private
246
+ #
247
+ # @return [Boolean]
248
+ #
249
+ # @api private
250
+ def respond_to_missing?(method_name, include_private = false)
251
+ *_, callback_name = *method_name.to_s.match(/^(\w*?on_\w+?)_(\w+)$/)
252
+ callback_name && callback_names.include?(:"#{callback_name}")
253
+ end
254
+ end # Observer
255
+
256
+ class StateMachine
257
+ def initialize(*args, &block)
258
+ options = args.last.is_a?(::Hash) ? args.pop : {}
259
+ @initial_state = DEFAULT_STATE
260
+ @auto_methods = options.fetch(:auto_methods, true)
261
+ @subscribers = Subscribers.new
262
+ @observer = Observer.new(self)
263
+ @events_map = EventsMap.new
264
+ @env = Env.new(self, [])
265
+ @dsl = DSL.new(self, options)
266
+ @name = options.fetch(:name) { ULID.generate }
267
+
268
+ env.target = args.pop unless args.empty?
269
+ env.aliases << options[:alias_target] if options[:alias_target]
270
+ dsl.call(&block) if block
271
+ trigger_init
272
+ end
273
+
274
+ def transition!(event_name, *data, &block)
275
+ from_state = current
276
+ to_state = events_map.move_to(event_name, from_state, *data)
277
+
278
+ block.call(from_state, to_state) if block
279
+
280
+ if log_transitions
281
+ Logger.report_transition(@name, event_name, from_state, to_state, *data)
282
+ end
283
+
284
+ try_trigger(event_name) { transition_to!(to_state) }
285
+ end
286
+ end # StateMachine
287
+
288
+ module Logger
289
+ def report_transition(machine_name, event_name, from, to, *args)
290
+ message = ["Transition: @machine=#{machine_name} @event=#{event_name} "]
291
+ unless args.empty?
292
+ message << "@with=[#{args.join(",")}] "
293
+ end
294
+ message << "#{from} -> #{to}"
295
+ info(message.join)
296
+ end
297
+ end # Logger
298
+ end # FiniteMachine
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/interaction"
4
+
5
+ module Roseflow
6
+ module Interaction
7
+ module WithHttpApi
8
+ end # WithHttpApi
9
+ end # Interaction
10
+ end # Roseflow
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "light-service"
4
+
5
+ module Roseflow
6
+ module Interaction
7
+ extend LightService::Organizer
8
+
9
+ def self.extended(base_class)
10
+ base_class.extend LightService::Organizer::ClassMethods
11
+ base_class.extend LightService::Organizer::Macros
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "light-service/context"
4
+ require "hashie/extensions/method_access"
5
+
6
+ module Roseflow
7
+ class InteractionContext < LightService::Context
8
+ include Hashie::Extensions::MethodAccess
9
+ end
10
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/interaction"
4
+ require "roseflow/actions/ai/resolve_model"
5
+ require "roseflow/actions/ai/resolve_provider"
6
+
7
+ module Roseflow
8
+ module Interactions
9
+ module AI
10
+ class InitializeLlm
11
+ extend Roseflow::Interaction
12
+
13
+ def self.call(context)
14
+ with(context).reduce(actions)
15
+ end
16
+
17
+ def self.actions
18
+ [
19
+ Actions::AI::ResolveProvider,
20
+ Actions::AI::ResolveModel
21
+ ]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-types"
4
+
5
+ module Types
6
+ include Dry.Types()
7
+ Number = Types::Float | Types::Integer
8
+ end
9
+
10
+ module Roseflow
11
+ module Primitives
12
+ class Vector < Dry::Struct
13
+ transform_keys(&:to_sym)
14
+
15
+ attribute :values, Types::Array.of(Types::Number)
16
+ attribute :dimensions, Types::Integer
17
+ end # Vector
18
+ end # Primitives
19
+ end # Roseflow
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Roseflow
6
+ class Prompt < Phlex::SGML
7
+ private
8
+
9
+ def plain_raw(content)
10
+ unsafe_raw(content)
11
+ end
12
+
13
+ def condensed(string)
14
+ string.gsub(/\s+/, " ").strip
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module Text
5
+ class Completion
6
+ def initialize(input)
7
+ @input = input
8
+ end
9
+
10
+ # Creates a new completion for the given input.
11
+ def call(model:, prompt:, **options)
12
+ provider.completions(model: model, prompt: @input, **options).choices
13
+ end
14
+ end
15
+ end
16
+ end