Class: AgentRuntime::Planner

Inherits:
Object
  • Object
show all
Defined in:
lib/agent_runtime/planner.rb

Overview

LLM interface for planning and execution.

Provides methods for interacting with the LLM client:

  • #plan: Single-shot planning using /generate endpoint

  • #chat: Chat-based execution returning content

  • #chat_raw: Chat-based execution returning full response with tool calls

Examples:

Initialize with schema and prompt builder

schema = { type: "object", properties: { action: { type: "string" } } }
builder = ->(input:, state:) { "Plan: #{input}" }
planner = Planner.new(client: ollama_client, schema: schema, prompt_builder: builder)

Planning a decision

decision = planner.plan(input: "What should I do?", state: state.snapshot)
# => #<AgentRuntime::Decision action="search", params={...}>

Instance Method Summary collapse

Constructor Details

#initialize(client:, schema: nil, prompt_builder: nil) ⇒ Planner

Initialize a new Planner instance.

Parameters:

  • client (#generate, #chat, #chat_raw)

    The LLM client (e.g., OllamaClient)

  • schema (Hash, nil) (defaults to: nil)

    Optional JSON schema for structured generation (required for #plan)

  • prompt_builder (Proc, nil) (defaults to: nil)

    Optional proc to build prompts (required for #plan). Called as ‘prompt_builder.call(input: input, state: state)`



26
27
28
29
30
# File 'lib/agent_runtime/planner.rb', line 26

def initialize(client:, schema: nil, prompt_builder: nil)
  @client = client
  @schema = schema
  @prompt_builder = prompt_builder
end

Instance Method Details

#chat(messages:, tools: nil) ⇒ String, Hash

EXECUTE state: Chat-based execution using /chat.

Returns content by default (for simple responses without tool calls). Use this when you only need the text response from the LLM.

Additional keyword arguments are passed through to the client.

Examples:

messages = [{ role: "user", content: "Hello" }]
response = planner.chat(messages: messages)
# => "Hello! How can I help you?"

Parameters:

  • messages (Array<Hash>)

    Array of message hashes with :role and :content

  • tools (Array<Hash>, nil) (defaults to: nil)

    Optional array of tool definitions

Returns:

  • (String, Hash)

    The chat response content (format depends on client)



69
70
71
# File 'lib/agent_runtime/planner.rb', line 69

def chat(messages:, tools: nil, **)
  @client.chat(messages: messages, tools: tools, allow_chat: true, **)
end

#chat_raw(messages:, tools: nil) ⇒ Hash

EXECUTE state: Chat with full response (for tool calling).

Returns full response including tool_calls. Use this when you need to extract tool calls from the LLM’s response.

Additional keyword arguments are passed through to the client.

Examples:

messages = [{ role: "user", content: "Search for weather" }]
tools = [{ type: "function", function: { name: "search", ... } }]
response = planner.chat_raw(messages: messages, tools: tools)
# => { message: { content: "...", tool_calls: [...] } }

Parameters:

  • messages (Array<Hash>)

    Array of message hashes with :role and :content

  • tools (Array<Hash>, nil) (defaults to: nil)

    Optional array of tool definitions

Returns:

  • (Hash)

    Full response hash including message and tool_calls



89
90
91
# File 'lib/agent_runtime/planner.rb', line 89

def chat_raw(messages:, tools: nil, **)
  @client.chat_raw(messages: messages, tools: tools, allow_chat: true, **)
end

#plan(input:, state:) ⇒ Decision

PLAN state: Single-shot planning using /generate.

Returns a structured Decision object based on the LLM’s response. This method never loops and is used for one-shot planning decisions.

Examples:

decision = planner.plan(input: "What should I do next?", state: { step: 1 })
# => #<AgentRuntime::Decision action="search", params={query: "..."}, confidence=0.9>

Parameters:

  • input (String)

    The input prompt for planning

  • state (Hash)

    The current state snapshot

Returns:

  • (Decision)

    A structured decision with action, params, and optional confidence

Raises:



45
46
47
48
49
50
51
52
# File 'lib/agent_runtime/planner.rb', line 45

def plan(input:, state:)
  raise ExecutionError, "Planner requires schema and prompt_builder for plan" unless @schema && @prompt_builder

  prompt = @prompt_builder.call(input: input, state: state)
  raw = @client.generate(prompt: prompt, schema: @schema)

  Decision.new(**raw.transform_keys(&:to_sym))
end