Commit 7dc4357
lib/elelem/agent.rb
@@ -6,7 +6,7 @@ module Elelem
def initialize(configuration)
@configuration = configuration
- transition_to(Idle.new(configuration))
+ transition_to(Idle.new)
end
def transition_to(next_state)
@@ -21,6 +21,10 @@ module Elelem
configuration.tui.say(message, colour: colour, newline: newline)
end
+ def execute(tool_call)
+ configuration.tools.execute(tool_call)
+ end
+
def quit
exit
end
lib/elelem/api.rb
@@ -8,14 +8,14 @@ module Elelem
@configuration = configuration
end
- def chat(messages, tools)
+ def chat(messages)
body = {
messages: messages,
model: configuration.model,
stream: true,
keep_alive: "5m",
options: { temperature: 0.1 },
- tools: tools.to_h
+ tools: configuration.tools.to_h
}
json_body = body.to_json
lib/elelem/state.rb
@@ -2,57 +2,111 @@
module Elelem
class Idle
- attr_reader :configuration
-
- def initialize(configuration)
- @configuration = configuration
- end
-
def run(agent)
input = agent.prompt("\n> ")
agent.quit if input.nil? || input.empty? || input == "exit"
- configuration.conversation.add(role: "user", content: input)
- agent.transition_to(ProcessingInput.new(configuration))
+ agent.configuration.conversation.add(role: "user", content: input)
+ agent.transition_to(ProcessingInput.new)
end
end
class ProcessingInput
- attr_reader :configuration, :conversation, :tools
+ class Waiting
+ attr_reader :agent
+
+ def initialize(agent)
+ @agent = agent
+ end
- def initialize(configuration)
- @configuration = configuration
- @conversation = configuration.conversation
- @tools = configuration.tools
+ def process(message)
+ state = self
+
+ if message["thinking"]
+ state = Thinking.new(agent)
+ elsif message["tool_calls"]&.any?
+ state = Executing.new(agent)
+ elsif message["content"].to_s.strip
+ state = Talking.new(agent)
+ elsif message["done"]
+ state = nil
+ else
+ raise message.inspect
+ end
+
+ state&.process(message)
+ end
+ end
+
+ class Thinking
+ attr_reader :agent
+
+ def initialize(agent)
+ @agent = agent
+ end
+
+ def process(message)
+ if message["thinking"]
+ agent.say(message["thinking"], colour: :gray, newline: false)
+ self
+ else
+ agent.say("", newline: true)
+ Waiting.new(agent).process(message)
+ end
+ end
+ end
+
+ class Executing
+ attr_reader :agent
+
+ def initialize(agent)
+ @agent = agent
+ end
+
+ def process(message)
+ if message["tool_calls"]&.any?
+ message["tool_calls"].each do |tool_call|
+ agent.configuration.conversation.add(role: "tool", content: agent.execute(tool_call))
+ end
+ end
+
+ Waiting.new(agent)
+ end
+ end
+
+ class Talking
+ attr_reader :agent
+
+ def initialize(agent)
+ @agent = agent
+ end
+
+ def process(message)
+ if message["content"]
+ agent.say(message["content"], colour: :default, newline: false)
+ self
+ else
+ agent.say("", newline: true)
+ Waiting.new(agent).process(message)
+ end
+ end
end
def run(agent)
- done = false
+ state = Waiting.new(agent)
loop do
- configuration.api.chat(conversation.history, tools) do |chunk|
+ agent.configuration.api.chat(agent.configuration.conversation.history) do |chunk|
response = JSON.parse(chunk)
- done = response["done"]
message = response["message"] || {}
- if message["thinking"]
- agent.say(message["thinking"], colour: :gray, newline: false)
- elsif message["tool_calls"]&.any?
- message["tool_calls"].each do |t|
- conversation.add(role: "tool", content: tools.execute(t))
- end
- done = false
- elsif message["content"].to_s.strip
- agent.say(message["content"], colour: :default, newline: false)
- else
- raise chunk.inspect
- end
+ state = state.process(message)
end
- break if done
+ break if state.nil?
end
- agent.transition_to(Idle.new(configuration))
+ agent.transition_to(Idle.new)
end
end
end