Class: Phronomy::Agent::Base
- Inherits:
-
Object
- Object
- Phronomy::Agent::Base
- Includes:
- AsyncEventApi, Concerns::BeforeCompletion, Concerns::ErrorTranslation, Concerns::Filterable, Runnable
- Defined in:
- lib/phronomy/agent/base.rb
Overview
Base class for all Phronomy agents.
Subclass this to create a conversational agent powered by an LLM. DSL class methods configure the model, instructions, tools, memory, and execution hooks. Instance methods handle invocation.
Direct Known Subclasses
Instance Attribute Summary
Attributes included from Concerns::BeforeCompletion
Class Method Summary collapse
-
.approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Object
Continues a suspended AgentInvocation.
-
.approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Phronomy::Task
Continues a suspended AgentInvocation without blocking the caller.
-
.cache_instructions(enabled = nil) ⇒ Object
When enabled, attaches Anthropic prompt-cache markers to the system message so that the fixed instructions are served from cache on subsequent turns, reducing input-token costs.
-
.context_overhead(val = nil) ⇒ Object
Tokens reserved for the system prompt + tool definitions overhead.
-
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations.
-
.instructions(text = nil) { ... } ⇒ String, ...
Sets or reads the system instructions for this agent.
-
.max_iterations(val = nil) ⇒ Integer
Sets or reads the maximum number of LLM call cycles for ReAct agents.
-
.max_output_tokens(val = nil) ⇒ Object
Tokens to reserve for the model's output.
-
.model(name = nil) ⇒ String?
Sets or reads the LLM model identifier for this agent.
-
.provider(name = nil) ⇒ Symbol?
Sets or reads the LLM provider for this agent.
-
.static_knowledge(*sources) ⇒ Object
Registers one or more static knowledge sources on the agent class.
-
.static_knowledge_chunks ⇒ Array<Hash>
Returns the fetched content from all static knowledge sources.
-
.static_knowledge_refresh! ⇒ nil
Clears the class-level knowledge cache so that the next
invokecall re-fetches content from all registered static knowledge sources. -
.static_knowledge_sources ⇒ Array<Phronomy::Agent::Context::Knowledge::Base>
Returns the registered static knowledge sources.
-
.temperature(val = nil) ⇒ Float?
Sets or reads the sampling temperature sent to the LLM.
-
.tool_aliases ⇒ Hash{Class => String}
Returns the alias map registered via the hash form of .tools.
-
.tools(*args) ⇒ Object
Registers tool classes for this agent.
Instance Method Summary collapse
-
#_add_handoff_tool(tool_class) ⇒ self
private
Registers an anonymous handoff tool class on this agent instance.
-
#_handoff_tools ⇒ Array<Class>
private
Returns handoff tool classes registered on this instance by Runner.
-
#approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Hash
Continues a suspended AgentInvocation.
-
#approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Phronomy::Task
Continues a suspended AgentInvocation without blocking the caller.
-
#context_version_cache ⇒ Object
deprecated
private
Deprecated.
The context version cache has been removed. Returns nil. Retained for backward compatibility with callers using safe navigation (+&.reset+).
-
#invoke(input, messages: [], thread_id: nil, config: {}, invocation_context: nil) ⇒ Hash
Invokes the agent with the given input and returns a result Hash.
-
#invoke_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil) ⇒ Phronomy::Task
Invokes this agent asynchronously and returns a Task.
-
#on_tool_approval_required(&block) ⇒ self
Registers a non-blocking Application notification listener.
-
#stream(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil) {|Phronomy::Agent::StreamEvent| ... } ⇒ Hash
Synchronous wrapper around #stream_async.
-
#stream_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil, &block) ⇒ Phronomy::Task
Invokes this agent asynchronously and delivers stream events from the Runtime-owned EventLoop thread.
-
#tool_approval_policy(&block) ⇒ self
Registers the final Agent/Application authorization policy.
Methods included from Concerns::BeforeCompletion
Methods included from Concerns::Filterable
#add_input_filter, #add_output_filter, #add_tool_result_filter, included
Methods included from Runnable
Class Method Details
.approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Object
Continues a suspended AgentInvocation.
323 324 325 326 327 328 329 330 |
# File 'lib/phronomy/agent/base.rb', line 323 def approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) new.approve( agent_invocation_id, approval_request_id: approval_request_id, approved: approved, config: config ) end |
.approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Phronomy::Task
Continues a suspended AgentInvocation without blocking the caller.
339 340 341 342 343 344 345 346 |
# File 'lib/phronomy/agent/base.rb', line 339 def approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) new.approve_async( agent_invocation_id, approval_request_id: approval_request_id, approved: approved, config: config ) end |
.cache_instructions(enabled = nil) ⇒ Object
When enabled, attaches Anthropic prompt-cache markers to the system message so that the fixed instructions are served from cache on subsequent turns, reducing input-token costs.
Only has an effect when the agent also declares provider :anthropic.
The cache_control field is provider-specific (the format differs
between Anthropic direct, Bedrock, etc.), so the agent must explicitly
declare its provider via the DSL rather than having it inferred from
the model name.
259 260 261 262 263 264 265 |
# File 'lib/phronomy/agent/base.rb', line 259 def cache_instructions(enabled = nil) if enabled.nil? @cache_instructions else @cache_instructions = enabled end end |
.context_overhead(val = nil) ⇒ Object
Tokens reserved for the system prompt + tool definitions overhead. Subtract this from the context window before computing the memory budget.
309 310 311 312 313 314 315 |
# File 'lib/phronomy/agent/base.rb', line 309 def context_overhead(val = nil) if val.nil? @context_overhead || 0 else @context_overhead = val.to_i end end |
.context_window(val = nil) ⇒ Object
Overrides the context window size used for token budget calculations. When set, this value takes precedence over the RubyLLM model registry, which is useful for locally-hosted models (e.g. LM Studio) where the actually-loaded context length may differ from the catalogue value.
293 294 295 296 297 298 299 |
# File 'lib/phronomy/agent/base.rb', line 293 def context_window(val = nil) if val.nil? @context_window else @context_window = val.to_i end end |
.instructions(text = nil) { ... } ⇒ String, ...
Sets or reads the system instructions for this agent. Accepts a String, a Context::Instruction::PromptTemplate, or a block (Proc). When used as a reader (no argument, no block), returns the stored value.
76 77 78 79 80 81 82 83 |
# File 'lib/phronomy/agent/base.rb', line 76 def instructions(text = nil, &block) if text || block_given? @instructions = text || block else return @instructions if instance_variable_defined?(:@instructions) superclass.respond_to?(:instructions) ? superclass.instructions : nil end end |
.max_iterations(val = nil) ⇒ Integer
Sets or reads the maximum number of LLM call cycles for ReAct agents. Each tool call and follow-up counts as one iteration. Defaults to 10.
183 184 185 186 187 188 189 |
# File 'lib/phronomy/agent/base.rb', line 183 def max_iterations(val = nil) if val @max_iterations = val else @max_iterations || 10 end end |
.max_output_tokens(val = nil) ⇒ Object
Tokens to reserve for the model's output. When nil, the model's max_output_tokens from the registry is used.
275 276 277 278 279 280 281 |
# File 'lib/phronomy/agent/base.rb', line 275 def max_output_tokens(val = nil) if val.nil? @max_output_tokens else @max_output_tokens = val.to_i end end |
.model(name = nil) ⇒ String?
Sets or reads the LLM model identifier for this agent. When called without an argument, returns the stored model or the global default from Phronomy.configuration.
52 53 54 55 56 57 58 |
# File 'lib/phronomy/agent/base.rb', line 52 def model(name = nil) if name @model = name else @model || Phronomy.configuration.default_model end end |
.provider(name = nil) ⇒ Symbol?
Sets or reads the LLM provider for this agent. Required when using a model not registered in RubyLLM's model registry (e.g. locally-hosted models via LM Studio or Ollama).
146 147 148 149 150 151 152 153 |
# File 'lib/phronomy/agent/base.rb', line 146 def provider(name = nil) if name @provider = name else return @provider if instance_variable_defined?(:@provider) superclass.respond_to?(:provider) ? superclass.provider : nil end end |
.static_knowledge(*sources) ⇒ Object
Registers one or more static knowledge sources on the agent class.
Static source content is fetched and memoized at the class level
the first time invoke is called. The cache persists for the lifetime
of the process; call static_knowledge_refresh! to force a reload.
202 203 204 205 206 207 |
# File 'lib/phronomy/agent/base.rb', line 202 def static_knowledge(*sources) @static_knowledge_sources = sources.flatten # Invalidate the cached chunks so the new sources are fetched on # the next call to static_knowledge_chunks. @static_knowledge_chunks = nil end |
.static_knowledge_chunks ⇒ Array<Hash>
Returns the fetched content from all static knowledge sources. Results are cached at the class level so that each source is fetched only once regardless of how many times the agent is invoked.
221 222 223 224 225 |
# File 'lib/phronomy/agent/base.rb', line 221 def static_knowledge_chunks @static_knowledge_chunks ||= static_knowledge_sources.flat_map { |ks| ks.fetch(query: nil) } end |
.static_knowledge_refresh! ⇒ nil
Clears the class-level knowledge cache so that the next invoke call
re-fetches content from all registered static knowledge sources.
Call this method when the underlying knowledge source has been updated at runtime (e.g. a file was rewritten, a DB record changed) and you want the agent to pick up the new content without restarting the process.
239 240 241 |
# File 'lib/phronomy/agent/base.rb', line 239 def static_knowledge_refresh! @static_knowledge_chunks = nil end |
.static_knowledge_sources ⇒ Array<Phronomy::Agent::Context::Knowledge::Base>
Returns the registered static knowledge sources.
212 213 214 |
# File 'lib/phronomy/agent/base.rb', line 212 def static_knowledge_sources @static_knowledge_sources || [] end |
.temperature(val = nil) ⇒ Float?
Sets or reads the sampling temperature sent to the LLM. When nil, the provider's default is used.
165 166 167 168 169 170 171 |
# File 'lib/phronomy/agent/base.rb', line 165 def temperature(val = nil) if val @temperature = val else @temperature end end |
.tool_aliases ⇒ Hash{Class => String}
Returns the alias map registered via the hash form of .tools. Merges parent class aliases so subclasses inherit their parent's mappings. Subclass-specific aliases take precedence over parent aliases.
125 126 127 128 129 130 131 132 |
# File 'lib/phronomy/agent/base.rb', line 125 def tool_aliases own = @tool_aliases || {} if superclass.respond_to?(:tool_aliases) superclass.tool_aliases.merge(own) else own end end |
.tools(*args) ⇒ Object
Registers tool classes for this agent.
Accepts either a splat of classes (backward-compatible) or a Hash mapping each class to an explicit alias name (String) or nil (use tool's own name). The alias form is useful when two tools share the same auto-generated name (e.g. two SearchTool classes from different modules).
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/phronomy/agent/base.rb', line 102 def tools(*args) if args.empty? if instance_variable_defined?(:@tools) return @tools end return superclass.respond_to?(:tools) ? superclass.tools : [] end if args.length == 1 && args.first.is_a?(Hash) hash = args.first @tools = hash.keys @tool_aliases = hash.transform_values { |v| v&.to_s }.reject { |_, v| v.nil? } else @tools = args @tool_aliases = {} end end |
Instance Method Details
#_add_handoff_tool(tool_class) ⇒ self
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Registers an anonymous handoff tool class on this agent instance. Called by Runner during construction when routes are configured.
354 355 356 357 358 |
# File 'lib/phronomy/agent/base.rb', line 354 def _add_handoff_tool(tool_class) @_handoff_tools ||= [] @_handoff_tools << tool_class self end |
#_handoff_tools ⇒ Array<Class>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns handoff tool classes registered on this instance by Runner.
363 364 365 |
# File 'lib/phronomy/agent/base.rb', line 363 def _handoff_tools @_handoff_tools || [] end |
#approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Hash
Continues a suspended AgentInvocation. The parent session is registered asynchronously; this method is only the synchronous wrapper.
983 984 985 986 987 988 989 990 991 |
# File 'lib/phronomy/agent/base.rb', line 983 def approve(agent_invocation_id, approval_request_id:, approved: true, config: {}) _check_scheduler_reentrancy(:approve, :approve_async) approve_async( agent_invocation_id, approval_request_id: approval_request_id, approved: approved, config: config ).wait_result end |
#approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) ⇒ Phronomy::Task
Continues a suspended AgentInvocation without blocking the caller.
This method is safe to call from an EventLoop stream callback. The returned Task completes when the resumed AgentInvocation finishes, suspends again, or fails.
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 |
# File 'lib/phronomy/agent/base.rb', line 1001 def approve_async(agent_invocation_id, approval_request_id:, approved: true, config: {}) result_task = Phronomy::Task.deferred( name: "agent-approval-resume:#{agent_invocation_id}" ) begin entry = Agent::AgentInvocationRegistry.consume_approval( agent_invocation_id, approval_request_id ) unless entry raise ArgumentError, "No pending approval found for AgentInvocation #{agent_invocation_id}" end _start_approval_resume( result_task, entry.invocation, approved: approved, config: config ) rescue => e _fail_result_task(result_task, e) end result_task end |
#context_version_cache ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The context version cache has been removed. Returns nil. Retained for backward compatibility with callers using safe navigation (+&.reset+).
552 553 554 |
# File 'lib/phronomy/agent/base.rb', line 552 def context_version_cache nil end |
#invoke(input, messages: [], thread_id: nil, config: {}, invocation_context: nil) ⇒ Hash
Invokes the agent with the given input and returns a result Hash. Provider errors are translated after the configured LLM adapter returns its final result. Phronomy does not replay the Agent invocation.
433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 |
# File 'lib/phronomy/agent/base.rb', line 433 def invoke(input, messages: [], thread_id: nil, config: {}, invocation_context: nil) if invocation_context thread_id, config = _apply_invocation_context(thread_id, config, invocation_context) end _check_scheduler_reentrancy(:invoke, :invoke_async) trace("agent.invoke", input: input, **(config)) do |_span| result = invoke_async( input, messages: , thread_id: thread_id, config: config ).wait_result [result, result[:usage]] end end |
#invoke_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil) ⇒ Phronomy::Task
Invokes this agent asynchronously and returns a Task.
This is the primary async entry point. #invoke is a synchronous wrapper
that calls this method and blocks the caller until the task completes.
Calling #invoke from inside an active scheduler task raises
SchedulerReentrancyError; use invoke_async directly in that
context.
The task is registered with the Runtime task registry so Runtime#shutdown drains in-flight invocations before process exit.
472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'lib/phronomy/agent/base.rb', line 472 def invoke_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil) if invocation_context thread_id, config = _apply_invocation_context(thread_id, config, invocation_context) end result_task = Phronomy::Task.deferred(name: "agent-#{(self.class.name || "anonymous").downcase}-async") approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required) _start_invocation( result_task, input, messages: , thread_id: thread_id, config: config, approval_snapshot: approval_snapshot ) result_task end |
#on_tool_approval_required(&block) ⇒ self
Registers a non-blocking Application notification listener.
382 383 384 385 386 387 |
# File 'lib/phronomy/agent/base.rb', line 382 def on_tool_approval_required(&block) raise ArgumentError, "on_tool_approval_required requires a block" unless block _approval_configuration_mutex.synchronize { @tool_approval_listener = block } self end |
#stream(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil) {|Phronomy::Agent::StreamEvent| ... } ⇒ Hash
Synchronous wrapper around #stream_async.
Stream callbacks execute on the EventLoop thread, not on the thread that calls this method. This method only blocks while waiting for the final Task.
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 |
# File 'lib/phronomy/agent/base.rb', line 527 def stream(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil, &block) raise ArgumentError, "stream requires a block" unless block if invocation_context thread_id, config = _apply_invocation_context(thread_id, config, invocation_context) end _check_scheduler_reentrancy(:stream, :stream_async) trace("agent.stream", input: input, **(config)) do |_span| result = stream_async( input, messages: , thread_id: thread_id, config: config, on_tool_approval_required: on_tool_approval_required, &block ).wait_result [result, result[:usage]] end end |
#stream_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil, &block) ⇒ Phronomy::Task
Invokes this agent asynchronously and delivers stream events from the Runtime-owned EventLoop thread.
The callback must return quickly. Blocking I/O, synchronous Agent calls, sleep, and heavy CPU work must be delegated by the Application.
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 |
# File 'lib/phronomy/agent/base.rb', line 495 def stream_async(input, messages: [], thread_id: nil, config: {}, invocation_context: nil, on_tool_approval_required: nil, &block) raise ArgumentError, "stream_async requires a block" unless block if invocation_context thread_id, config = _apply_invocation_context(thread_id, config, invocation_context) end result_task = Phronomy::Task.deferred( name: "agent-#{(self.class.name || "anonymous").downcase}-stream-async" ) approval_snapshot = _approval_configuration_snapshot(on_tool_approval_required) _start_invocation( result_task, input, messages: , thread_id: thread_id, config: config, approval_snapshot: approval_snapshot, mode: :stream, on_event: block ) result_task end |
#tool_approval_policy(&block) ⇒ self
Registers the final Agent/Application authorization policy. The block runs on the Runtime authorization pool and must return :allow, :require_approval, or :reject.
372 373 374 375 376 377 |
# File 'lib/phronomy/agent/base.rb', line 372 def tool_approval_policy(&block) raise ArgumentError, "tool_approval_policy requires a block" unless block _approval_configuration_mutex.synchronize { @tool_approval_policy = block } self end |