Commit e808660
Changed files (4)
lib
lib/elelem/agent.rb
@@ -7,30 +7,11 @@ module Elelem
def initialize(configuration)
@configuration = configuration
- @logger = configuration.logger
- @http = configuration.http
@conversation = configuration.conversation
@tools = configuration.tools
end
- def protocol(host)
- host.match?(/\A(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?\z/) ? 'http' : 'https'
- end
-
- def system_message
- <<~SYS
- You are ChatGPT, a helpful assistant with reasoning capabilities.
- Current date: #{Time.now.strftime('%Y-%m-%d')}.
- System info: `uname -a` output:
- #{`uname -a`.strip}
- Reasoning: high
- SYS
- end
-
def repl
- puts "Ollama Agent (#{configuration.model})"
- puts "Tools:\n #{@tools.banner}"
-
loop do
print "\n> "
user = STDIN.gets&.chomp
@@ -45,9 +26,9 @@ module Elelem
def process_input(text)
@conversation.add(role: 'user', content: text)
- # ::TODO state machine
done = false
loop do
+ debug_print("Calling API...")
call_api(@conversation.history) do |chunk|
debug_print(chunk)
@@ -57,10 +38,7 @@ module Elelem
print("\u001b[90m#{message['thinking']}\u001b[0m")
elsif message['tool_calls']&.any?
message['tool_calls'].each do |t|
- @conversation.add(
- role: 'tool',
- content: execute_tool(t.dig('function', 'name'), t.dig('function', 'arguments'))
- )
+ @conversation.add(role: 'tool', content: @tools.execute(t))
end
elsif message['content'].to_s.strip
print message['content'].to_s.strip
@@ -82,7 +60,7 @@ module Elelem
stream: true,
keep_alive: '5m',
options: { temperature: 0.1 },
- tools: tools
+ tools: tools.to_h
}
json_body = body.to_json
debug_print(json_body)
@@ -90,9 +68,12 @@ module Elelem
req = Net::HTTP::Post.new(configuration.uri)
req['Content-Type'] = 'application/json'
req.body = json_body
- req['Authorization'] = "Bearer #{@token}" if @token
+ req['Authorization'] = "Bearer #{configuration.token}" if configuration.token
+
+ configuration.http.request(req) do |response|
+ debug_print(response.inspect)
+ raise response.inspect unless response.code == "200"
- http.request(req) do |response|
response.read_body do |chunk|
block_given? ? yield(chunk) : debug_print(chunk)
$stdout.flush
@@ -100,26 +81,8 @@ module Elelem
end
end
- def execute_tool(name, args)
- case name
- when 'execute_command'
- result = run_cmd(args['command'])
- debug_print(result) unless result[:ok]
- result[:output]
- when 'ask_user'
- puts("\u001b[35m#{args['question']}\u001b[0m")
- print "> "
- "User: #{STDIN.gets&.chomp}"
- end
- end
-
- def run_cmd(command)
- stdout, stderr, status = Open3.capture3('/bin/sh', '-c', command)
- { output: stdout + stderr, ok: status.success? }
- end
-
def debug_print(body = nil)
- @logger.debug(body) if @debug && body
+ configuration.logger.debug(body) if configuration.debug && body
end
end
end
lib/elelem/application.rb
@@ -7,17 +7,21 @@ module Elelem
method_option :host, aliases: '--host', type: :string, desc: 'Ollama host', default: ENV.fetch('OLLAMA_HOST', 'localhost:11434')
method_option :model, aliases: '--model', type: :string, desc: 'Ollama model', default: ENV.fetch('OLLAMA_MODEL', 'gpt-oss')
method_option :token, aliases: '--token', type: :string, desc: 'Ollama token', default: ENV.fetch('OLLAMA_API_KEY', nil)
- method_option :debug, aliases: '--debug', type: :boolean, desc: 'Debug mode', default: ENV.fetch('DEBUG', '0') == '1'
+ method_option :debug, aliases: '--debug', type: :boolean, desc: 'Debug mode', default: false
def chat(*)
if options[:help]
invoke :help, ['chat']
else
- agent = Agent.new(Configuration.new(
+ configuration = Configuration.new(
host: options[:host],
model: options[:model],
token: options[:token],
debug: options[:debug],
- ))
+ )
+ say "Ollama Agent (#{configuration.model})", :green
+ say "Tools:\n #{configuration.tools.banner}", :green
+
+ agent = Agent.new(configuration)
agent.repl
end
end
lib/elelem/configuration.rb
@@ -12,7 +12,7 @@ module Elelem
end
def http
- Net::HTTP.new(uri.host, uri.port).tap do |h|
+ @http ||= Net::HTTP.new(uri.host, uri.port).tap do |h|
h.read_timeout = 3_600
h.open_timeout = 10
end
lib/elelem/tools.rb
@@ -13,6 +13,10 @@ module Elelem
properties: { command: { type: 'string' } },
required: ['command']
}
+ },
+ handler: -> (args) {
+ stdout, stderr, _status = Open3.capture3('/bin/sh', '-c', args['command'])
+ stdout + stderr
}
},
{
@@ -25,6 +29,11 @@ module Elelem
properties: { question: { type: 'string' } },
required: ['question']
}
+ },
+ handler: ->(args) {
+ puts("\u001b[35m#{args['question']}\u001b[0m")
+ print "> "
+ STDIN.gets&.chomp
}
}
]
@@ -41,5 +50,28 @@ module Elelem
].join(": ")
end.sort.join("\n ")
end
+
+ def execute(tool_call)
+ name = tool_call.dig('function', 'name')
+ args = tool_call.dig('function', 'arguments')
+
+ tool = @tools.find do |tool|
+ tool.dig(:function, :name) == name
+ end
+ tool.fetch(:handler).call(args)
+ end
+
+ def to_h
+ @tools.map do |tool|
+ {
+ type: tool[:type],
+ function: {
+ name: tool.dig(:function, :name),
+ description: tool.dig(:function, :description),
+ parameters: tool.dig(:function, :parameters)
+ }
+ }
+ end
+ end
end
end