roseflow 0.0.1 → 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.
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