Class: Kward::Agent
- Inherits:
-
Object
- Object
- Kward::Agent
- Defined in:
- lib/kward/agent.rb
Overview
Runs model turns, handles context compaction, dispatches tool calls, and streams high-level events back to CLI and RPC callers.
Instance Attribute Summary collapse
-
#conversation ⇒ Object
readonly
Returns the value of attribute conversation.
Instance Method Summary collapse
-
#ask(input, display_input: nil, on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil) {|event| ... } ⇒ String
Adds a user message, compacts context when needed, and runs the turn.
-
#initialize(client:, tool_registry: ToolRegistry.new, conversation: Conversation.new, telemetry_logger: TelemetryLogger.new) ⇒ Agent
constructor
A new instance of Agent.
-
#run_turn(on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil) {|event| ... } ⇒ String
Runs model calls until the assistant returns an answer without pending tool calls, including tool dispatch and one context-overflow retry.
Constructor Details
#initialize(client:, tool_registry: ToolRegistry.new, conversation: Conversation.new, telemetry_logger: TelemetryLogger.new) ⇒ Agent
Returns a new instance of Agent.
15 16 17 18 19 20 |
# File 'lib/kward/agent.rb', line 15 def initialize(client:, tool_registry: ToolRegistry.new, conversation: Conversation.new, telemetry_logger: TelemetryLogger.new) @client = client @tool_registry = tool_registry @conversation = conversation @telemetry_logger = telemetry_logger end |
Instance Attribute Details
#conversation ⇒ Object (readonly)
Returns the value of attribute conversation.
22 23 24 |
# File 'lib/kward/agent.rb', line 22 def conversation @conversation end |
Instance Method Details
#ask(input, display_input: nil, on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil) {|event| ... } ⇒ String
Adds a user message, compacts context when needed, and runs the turn.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/kward/agent.rb', line 30 def ask(input, display_input: nil, on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil, &block) started_at = @telemetry_logger.monotonic_now status = "completed" error = nil cancellation&.raise_if_cancelled! @conversation. @conversation.append_user(input, display_content: display_input) auto_compact_if_needed run_turn(on_reasoning_delta: on_reasoning_delta, on_retry: on_retry, cancellation: cancellation, steering: steering, &block) rescue StandardError => e status = "failed" error = e raise e ensure log_turn(duration_ms: @telemetry_logger.duration_ms(started_at), status: status, error: error) end |
#run_turn(on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil) {|event| ... } ⇒ String
Runs model calls until the assistant returns an answer without pending tool calls, including tool dispatch and one context-overflow retry.
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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/kward/agent.rb', line 52 def run_turn(on_reasoning_delta: nil, on_retry: nil, cancellation: nil, steering: nil) overflow_retried = false steering_state = build_steering_state(steering) do |event| yield event if block_given? end loop do cancellation&.raise_if_cancelled! begin = chat(on_reasoning_delta: on_reasoning_delta, on_retry: on_retry, cancellation: cancellation, steering: steering) do |event| yield event if block_given? end rescue StandardError => e raise if cancellation&.cancelled? raise unless !overflow_retried && ContextOverflow.error?(e) && compact_after_context_overflow(e) overflow_retried = true next end yield Events::AssistantMessage.new(message: ) if block_given? @conversation.append_assistant() = append_steering_events(steering_state) yield Events::SteeringApplied.new(count: ) if block_given? && .positive? tool_calls = ["tool_calls"] || [:tool_calls] || [] if tool_calls.empty? next if .positive? answer = safe_answer(.fetch("content", [:content] || "")) yield Events::Answer.new(content: answer) if block_given? return answer end tool_calls.each do |tool_call| cancellation&.raise_if_cancelled! yield Events::ToolCall.new(tool_call: tool_call) if block_given? tool_started_at = @telemetry_logger.monotonic_now content = nil status = "completed" error = nil begin content = @tool_registry.dispatch(tool_call, @conversation, cancellation: cancellation) rescue StandardError => e status = "failed" error = e raise e ensure log_tool(tool_call, content: content, duration_ms: @telemetry_logger.duration_ms(tool_started_at), status: status, error: error) end cancellation&.raise_if_cancelled! yield Events::ToolResult.new(tool_call: tool_call, content: content) if block_given? end steered_after_tools = append_steering_events(steering_state) yield Events::SteeringApplied.new(count: steered_after_tools) if block_given? && steered_after_tools.positive? end ensure steering_state&.fetch(:unsubscribe)&.call end |