Class: Ollama::Agent::Executor
- Inherits:
-
Object
- Object
- Ollama::Agent::Executor
- Defined in:
- lib/ollama/agent/executor.rb
Overview
Stateful executor-style agent using /api/chat + tool-calling loop.
The LLM never executes tools. It can only request tool calls; this class executes Ruby callables and feeds results back as role: "tool" messages.
Instance Attribute Summary collapse
-
#messages ⇒ Object
readonly
Returns the value of attribute messages.
Instance Method Summary collapse
-
#initialize(client, tools:, max_steps: 20, stream: nil) ⇒ Executor
constructor
A new instance of Executor.
-
#run(system:, user:) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity.
Constructor Details
#initialize(client, tools:, max_steps: 20, stream: nil) ⇒ Executor
Returns a new instance of Executor.
15 16 17 18 19 20 21 |
# File 'lib/ollama/agent/executor.rb', line 15 def initialize(client, tools:, max_steps: 20, stream: nil) @client = client @tools = tools || {} @max_steps = max_steps @stream = stream @messages = [] end |
Instance Attribute Details
#messages ⇒ Object (readonly)
Returns the value of attribute messages.
13 14 15 |
# File 'lib/ollama/agent/executor.rb', line 13 def @messages end |
Instance Method Details
#run(system:, user:) ⇒ Object
rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/ollama/agent/executor.rb', line 24 def run(system:, user:) @messages = [ Messages.system(system), Messages.user(user) ] last_assistant_content = nil @max_steps.times do @stream&.emit(:state, state: :assistant_streaming) response = request_chat = response. content = &.content tool_calls = &.tool_calls || [] # Preserve the assistant turn in history (including tool_calls if present). @messages << Messages.assistant(content.to_s, tool_calls: tool_calls.map(&:to_h)) if content || !tool_calls.empty? last_assistant_content = content if content && !content.empty? break if tool_calls.empty? tool_calls.each do |call| name = call.name raise Ollama::Error, "Tool call missing function name: #{call.inspect}" if name.nil? || name.empty? args_hash = call.arguments.is_a?(Hash) ? call.arguments : {} tool_entry = @tools[name] || @tools[name.to_sym] raise Ollama::Error, "Tool '#{name}' not found. Available: #{@tools.keys.sort.join(", ")}" unless tool_entry # Extract callable from tool entry callable = extract_callable(tool_entry) raise Ollama::Error, "Tool '#{name}' has no associated callable" unless callable @stream&.emit(:state, state: :tool_executing) result = invoke_tool(callable, args_hash) tool_content = encode_tool_result(result) tool_call_id = call.id @messages << Messages.tool(content: tool_content, name: name, tool_call_id: tool_call_id) @stream&.emit(:state, state: :tool_result_injected) end end if last_assistant_content.nil? raise Ollama::Error, "Executor exceeded max_steps=#{@max_steps} (possible infinite tool loop)" end @stream&.emit(:final, text: last_assistant_content.to_s) last_assistant_content end |