Class: OxAiWorkers::Iterator

Inherits:
StateTools show all
Includes:
LoadI18n, ToolDefinition
Defined in:
lib/oxaiworkers/iterator.rb

Constant Summary collapse

ITERATOR_FUNCTIONS =
%i[inner_monologue outer_voice action_request summarize finish_it].freeze

Instance Attribute Summary collapse

Attributes included from LoadI18n

#locale

Attributes included from ToolDefinition

#white_list

Class Method Summary collapse

Instance Method Summary collapse

Methods included from LoadI18n

#store_locale, #with_locale

Methods included from ToolDefinition

#define_function, #full_function_name, #function_schemas, #init_white_list_with

Methods included from StateHelper

#log_me

Constructor Details

#initialize(worker:, role: nil, tools: [], on_inner_monologue: nil, on_outer_voice: nil, on_action_request: nil, on_summarize: nil, after_finish: nil, steps: nil, def_except: [], def_only: nil, locale: nil) ⇒ Iterator

Returns a new instance of Iterator.



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/oxaiworkers/iterator.rb', line 13

def initialize(worker:, role: nil, tools: [], on_inner_monologue: nil, on_outer_voice: nil, on_action_request: nil,
               on_summarize: nil, after_finish: nil, steps: nil, def_except: [], def_only: nil, locale: nil)

  @locale = locale || I18n.locale

  with_locale do
    define_function :inner_monologue, description: I18n.t('oxaiworkers.iterator.inner_monologue.description') do
      property :speach, type: 'string', description: I18n.t('oxaiworkers.iterator.inner_monologue.speach'),
                        required: true
    end

    define_function :outer_voice, description: I18n.t('oxaiworkers.iterator.outer_voice.description') do
      property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.outer_voice.text'), required: true
    end

    define_function :action_request, description: I18n.t('oxaiworkers.iterator.action_request.description') do
      property :action, type: 'string', description: I18n.t('oxaiworkers.iterator.action_request.action'),
                        required: true
    end

    define_function :summarize, description: I18n.t('oxaiworkers.iterator.summarize.description') do
      property :text, type: 'string', description: I18n.t('oxaiworkers.iterator.summarize.text'), required: true
    end

    define_function :finish_it, description: I18n.t('oxaiworkers.iterator.finish_it.description')

    @monologue = steps || I18n.t('oxaiworkers.iterator.monologue')
  end

  @worker = worker
  @tools = tools
  @role = role
  @context = []
  @def_only = def_only || ITERATOR_FUNCTIONS
  @def_except = def_except

  @on_inner_monologue = on_inner_monologue
  @on_outer_voice = on_outer_voice
  @on_action_request = on_action_request
  @on_summarize = on_summarize
  @after_finish = after_finish

  cleanup

  super()

  tick_or_wait if requested?
end

Instance Attribute Details

#contextObject

Returns the value of attribute context.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def context
  @context
end

#def_exceptObject

Returns the value of attribute def_except.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def def_except
  @def_except
end

#def_onlyObject

Returns the value of attribute def_only.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def def_only
  @def_only
end

#messagesObject

Returns the value of attribute messages.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def messages
  @messages
end

#milestonesObject

Returns the value of attribute milestones.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def milestones
  @milestones
end

#monologueObject

Returns the value of attribute monologue.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def monologue
  @monologue
end

#on_action_requestObject

Returns the value of attribute on_action_request.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def on_action_request
  @on_action_request
end

#on_inner_monologueObject

Returns the value of attribute on_inner_monologue.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def on_inner_monologue
  @on_inner_monologue
end

#on_outer_voiceObject

Returns the value of attribute on_outer_voice.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def on_outer_voice
  @on_outer_voice
end

#on_summarizeObject

Returns the value of attribute on_summarize.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def on_summarize
  @on_summarize
end

#queueObject

Returns the value of attribute queue.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def queue
  @queue
end

#resultObject

Returns the value of attribute result.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def result
  @result
end

#roleObject

Returns the value of attribute role.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def role
  @role
end

#tasksObject

Returns the value of attribute tasks.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def tasks
  @tasks
end

#toolsObject

Returns the value of attribute tools.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def tools
  @tools
end

#workerObject

Returns the value of attribute worker.



10
11
12
# File 'lib/oxaiworkers/iterator.rb', line 10

def worker
  @worker
end

Class Method Details

.full_function_name(fun) ⇒ Object



165
166
167
168
169
170
171
172
# File 'lib/oxaiworkers/iterator.rb', line 165

def self.full_function_name(fun)
  tool_name ||= name
                .gsub('::', '_')
                .gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
                .downcase

  "#{tool_name}__#{fun}"
end

Instance Method Details

#action_request(action:) ⇒ Object



96
97
98
99
100
101
102
103
# File 'lib/oxaiworkers/iterator.rb', line 96

def action_request(action:)
  @result = action
  # @queue.pop
  @messages << { role: :assistant, content: action.to_s }
  complete! if can_complete?
  @on_action_request&.call(text: action)
  nil
end

#add_context(text, role: :system) ⇒ Object



243
244
245
# File 'lib/oxaiworkers/iterator.rb', line 243

def add_context(text, role: :system)
  add_raw_context({ role:, content: text })
end

#add_raw_context(c) ⇒ Object



247
248
249
# File 'lib/oxaiworkers/iterator.rb', line 247

def add_raw_context(c)
  @context << c
end

#add_task(task) ⇒ Object



237
238
239
240
241
# File 'lib/oxaiworkers/iterator.rb', line 237

def add_task(task)
  @tasks << task
  @messages << { role: :user, content: task }
  execute if OxAiWorkers.configuration.auto_execute
end

#available_defsObject



149
150
151
# File 'lib/oxaiworkers/iterator.rb', line 149

def available_defs
  @def_only - @def_except
end

#cancelObject



255
256
257
# File 'lib/oxaiworkers/iterator.rb', line 255

def cancel
  @worker.cancel if @worker.respond_to?(:cancel)
end

#cleanupObject

Resets the state of the object by setting all instance variables to their initial values.

Returns nothing.



67
68
69
70
71
72
73
74
# File 'lib/oxaiworkers/iterator.rb', line 67

def cleanup
  @result = nil
  @queue = []
  @tasks = []
  @milestones = []
  @messages = []
  complete_iteration
end

#complete_iterationObject



232
233
234
235
# File 'lib/oxaiworkers/iterator.rb', line 232

def complete_iteration
  @queue = []
  @worker.finish
end

#executeObject



251
252
253
# File 'lib/oxaiworkers/iterator.rb', line 251

def execute
  prepare! if valid?
end

#external_requestObject



181
182
183
184
185
186
187
188
# File 'lib/oxaiworkers/iterator.rb', line 181

def external_request
  @worker.request!
  tick_or_wait
rescue Faraday::ServerError => e
  OxAiWorkers.logger.warn "Iterator::ServerError #{e.message}. Waiting 10 seconds..."
  sleep(10)
  external_request
end

#finish_itObject



105
106
107
108
109
# File 'lib/oxaiworkers/iterator.rb', line 105

def finish_it
  complete! if can_complete?
  @after_finish&.call
  nil
end

#initObject



124
125
126
127
# File 'lib/oxaiworkers/iterator.rb', line 124

def init
  rebuild_worker
  request!
end

#inner_monologue(speach:) ⇒ nil

Updates the internal state of the iterator by adding the given ‘speach` to the `@queue` and calling the `@on_inner_monologue` callback with the `speach` text.

Parameters:

  • speach (String)

    The text to be added to the ‘@queue` and passed to the `@on_inner_monologue` callback.

Returns:

  • (nil)

    This method does not return a value.



81
82
83
84
85
86
# File 'lib/oxaiworkers/iterator.rb', line 81

def inner_monologue(speach:)
  # @queue.pop
  @queue << { role: :assistant, content: speach.to_s }
  @on_inner_monologue&.call(text: speach)
  nil
end

#next_iterationObject



174
175
176
177
178
179
# File 'lib/oxaiworkers/iterator.rb', line 174

def next_iteration
  @worker.append(messages: @queue)
  @messages += @queue
  @queue = []
  request!
end

#outer_voice(text:) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/oxaiworkers/iterator.rb', line 88

def outer_voice(text:)
  # @queue.pop
  @queue << { role: :assistant, content: text.to_s }
  complete! unless available_defs.include?(:action_request)
  @on_outer_voice&.call(text:)
  nil
end

#process_result(_transition) ⇒ Object



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/oxaiworkers/iterator.rb', line 211

def process_result(_transition)
  @result = @worker.result || @worker.errors
  if @worker.tool_calls.present?
    @queue << { role: :assistant, content: @worker.tool_calls_raw.to_s }
    @worker.tool_calls.each do |external_call|
      tool = ([self] + @tools).select do |t|
        tool_name = t.respond_to?(:tool_name) ? t.tool_name : t.class.tool_name
        tool_name == external_call[:class] && t.respond_to?(external_call[:name])
      end.first
      unless tool.nil?
        out = tool.send(external_call[:name], **external_call[:args])
        @queue << { role: :system, content: out.to_s } if out.present?
      end
    end
    @worker.finish
    iterate! if can_iterate?
  elsif @worker.result.present?
    action_request action: @worker.result
  end
end

#rebuild_workerObject



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/oxaiworkers/iterator.rb', line 129

def rebuild_worker
  @worker.messages = []
  @worker.append(role: :system, content: @role) if @role.present?
  @worker.append(role: :system, content: valid_monologue.join("\n"))
  @worker.append(messages: @context) if @context.present?
  @tasks.each { |task| @worker.append(role: :user, content: task) }
  @milestones.each { |milestone| @worker.append(role: :assistant, content: milestone) }
  @worker.append(messages: @messages)
  @worker.tools = function_schemas.to_openai_format(only: available_defs)
  return unless @tools.present?

  @worker.tools += @tools.map do |tool|
    if tool.respond_to?(:function_schemas)
      tool.function_schemas.to_openai_format
    else
      tool.class.function_schemas.to_openai_format
    end
  end.flatten
end

#summarize(text:) ⇒ Object



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/oxaiworkers/iterator.rb', line 111

def summarize(text:)
  @milestones << text.to_s
  @messages = []
  with_locale do
    @queue << { role: :assistant, content: I18n.t('oxaiworkers.iterator.summarize.result') }
  end
  @worker.finish
  rebuild_worker
  complete! if can_complete?
  @on_summarize&.call(text:)
  nil
end

#tick_or_waitObject



190
191
192
193
194
195
196
# File 'lib/oxaiworkers/iterator.rb', line 190

def tick_or_wait
  if OxAiWorkers.configuration.wait_for_complete
    wait_for_complete
  else
    ticker
  end
end

#tickerObject



198
199
200
201
202
203
# File 'lib/oxaiworkers/iterator.rb', line 198

def ticker
  return false unless @worker.completed?

  analyze!
  true
end

#tool_nameObject



158
159
160
161
162
163
# File 'lib/oxaiworkers/iterator.rb', line 158

def tool_name
  @tool_name ||= (respond_to?(:name) ? name : self.class.name)
                 .gsub('::', '_')
                 .gsub(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
                 .downcase
end

#valid?Boolean

Returns:

  • (Boolean)


259
260
261
# File 'lib/oxaiworkers/iterator.rb', line 259

def valid?
  @messages.present? || @milestones.present?
end

#valid_monologueObject



153
154
155
156
# File 'lib/oxaiworkers/iterator.rb', line 153

def valid_monologue
  arr = @monologue.reject { |item| @def_except.any? { |fun| item.include?(full_function_name(fun)) } }
  arr.each_with_index.map { |item, index| format(item, index + 1) }
end

#wait_for_completeObject



205
206
207
208
209
# File 'lib/oxaiworkers/iterator.rb', line 205

def wait_for_complete
  return unless requested?

  sleep(60) unless ticker
end