Class: Phronomy::Agent::Base
- Inherits:
-
Object
- Object
- Phronomy::Agent::Base
- Includes:
- Concerns::BeforeCompletion, Concerns::Guardrailable, Concerns::Retryable, Concerns::Suspendable, 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 retry behaviour. Instance methods handle invocation.
Direct Known Subclasses
Instance Attribute Summary
Attributes included from Concerns::BeforeCompletion
Class Method Summary collapse
- ._on_compact_callback ⇒ Proc?
- ._on_compaction_trigger_callback ⇒ Proc?
- ._on_trim_callback ⇒ Proc?
-
.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.
-
.on_compact {|ctx| ... } ⇒ Object
Registers a callback that performs the actual compaction when the +on_compaction_trigger+ callback fires.
-
.on_compaction_trigger {|ctx| ... } ⇒ Boolean
Registers a callback that decides whether compaction should run.
-
.on_trim {|ctx| ... } ⇒ Object
Registers a callback that is invoked before every LLM call so the application can remove stale or irrelevant messages from the conversation history.
-
.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_sources ⇒ Array<Phronomy::KnowledgeSource::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
Registers an anonymous handoff tool class on this agent instance.
-
#_handoff_tools ⇒ Array<Class>
Returns handoff tool classes registered on this instance by Runner.
-
#context_version_cache ⇒ Object
private
Returns the Context::ContextVersionCache for the current thread.
-
#invoke(input, messages: [], thread_id: nil, config: {}) ⇒ Hash
Invokes the agent with the given input and returns a result Hash.
-
#run_as_child(input, ctx:, messages: [], config: {}) {|Hash| ... } ⇒ nil
Registers this agent as a child AgentFSM inside the given Workflow context.
-
#stream(input, messages: [], thread_id: nil, config: {}) {|Phronomy::Agent::StreamEvent| ... } ⇒ Hash
Streaming version of #invoke.
Methods included from Concerns::Suspendable
#on_approval_required, #resume
Methods included from Concerns::BeforeCompletion
Methods included from Concerns::Guardrailable
#add_input_guardrail, #add_output_guardrail
Methods included from Concerns::Retryable
Methods included from Runnable
Class Method Details
._on_compact_callback ⇒ Proc?
262 263 264 |
# File 'lib/phronomy/agent/base.rb', line 262 def _on_compact_callback @on_compact_callback end |
._on_compaction_trigger_callback ⇒ Proc?
240 241 242 |
# File 'lib/phronomy/agent/base.rb', line 240 def _on_compaction_trigger_callback @on_compaction_trigger_callback end |
._on_trim_callback ⇒ Proc?
217 218 219 |
# File 'lib/phronomy/agent/base.rb', line 217 def _on_trim_callback @on_trim_callback 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.
281 282 283 284 285 286 287 |
# File 'lib/phronomy/agent/base.rb', line 281 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.
328 329 330 331 332 333 334 |
# File 'lib/phronomy/agent/base.rb', line 328 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.
313 314 315 316 317 318 319 |
# File 'lib/phronomy/agent/base.rb', line 313 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 PromptTemplate, or a block (Proc). When used as a reader (no argument, no block), returns the stored value.
74 75 76 77 78 79 80 81 |
# File 'lib/phronomy/agent/base.rb', line 74 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.
169 170 171 172 173 174 175 |
# File 'lib/phronomy/agent/base.rb', line 169 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.
296 297 298 299 300 301 302 |
# File 'lib/phronomy/agent/base.rb', line 296 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.
51 52 53 54 55 56 57 |
# File 'lib/phronomy/agent/base.rb', line 51 def model(name = nil) if name @model = name else @model || Phronomy.configuration.default_model end end |
.on_compact {|ctx| ... } ⇒ Object
Registers a callback that performs the actual compaction when the +on_compaction_trigger+ callback fires. The block receives a Context::CompactionContext and should call +ctx.compact+ to specify which messages to summarise.
257 258 259 |
# File 'lib/phronomy/agent/base.rb', line 257 def on_compact(&block) @on_compact_callback = block end |
.on_compaction_trigger {|ctx| ... } ⇒ Boolean
Registers a callback that decides whether compaction should run. Evaluated before every LLM call (after on_trim). If the block returns truthy AND an +on_compact+ callback is also registered, the compact pipeline is executed.
The block receives a read-only Context::TriggerContext.
235 236 237 |
# File 'lib/phronomy/agent/base.rb', line 235 def on_compaction_trigger(&block) @on_compaction_trigger_callback = block end |
.on_trim {|ctx| ... } ⇒ Object
Registers a callback that is invoked before every LLM call so the application can remove stale or irrelevant messages from the conversation history.
The block receives a Context::TrimContext and may call +ctx.remove(seqs)+ to drop messages by seq number. Changes affect only the current invocation; the underlying memory store is unchanged.
212 213 214 |
# File 'lib/phronomy/agent/base.rb', line 212 def on_trim(&block) @on_trim_callback = block 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).
134 135 136 137 138 139 140 141 |
# File 'lib/phronomy/agent/base.rb', line 134 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 sources are fetched once per agent instance and their content is cached in ContextVersionCache keyed by a fingerprint of the instruction text + source content. The cache is invalidated automatically when the fingerprint changes (e.g. because a source was updated).
188 189 190 |
# File 'lib/phronomy/agent/base.rb', line 188 def static_knowledge(*sources) @static_knowledge_sources = sources.flatten end |
.static_knowledge_sources ⇒ Array<Phronomy::KnowledgeSource::Base>
Returns the registered static knowledge sources.
194 195 196 |
# File 'lib/phronomy/agent/base.rb', line 194 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.
152 153 154 155 156 157 158 |
# File 'lib/phronomy/agent/base.rb', line 152 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.
119 120 121 |
# File 'lib/phronomy/agent/base.rb', line 119 def tool_aliases @tool_aliases ||= {} 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).
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/phronomy/agent/base.rb', line 99 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
Registers an anonymous handoff tool class on this agent instance. Called by Runner during construction when routes are configured.
341 342 343 344 345 |
# File 'lib/phronomy/agent/base.rb', line 341 def _add_handoff_tool(tool_class) @_handoff_tools ||= [] @_handoff_tools << tool_class self end |
#_handoff_tools ⇒ Array<Class>
Returns handoff tool classes registered on this instance by Runner.
349 350 351 |
# File 'lib/phronomy/agent/base.rb', line 349 def _handoff_tools @_handoff_tools || [] 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.
Returns the Context::ContextVersionCache for the current thread.
491 492 493 |
# File 'lib/phronomy/agent/base.rb', line 491 def context_version_cache (Thread.current[:phronomy_context_version_caches] ||= {})[object_id] end |
#invoke(input, messages: [], thread_id: nil, config: {}) ⇒ Hash
Invokes the agent with the given input and returns a result Hash. Applies the retry policy configured via retry_policy when transient errors occur. GuardrailError is never retried.
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
# File 'lib/phronomy/agent/base.rb', line 385 def invoke(input, messages: [], thread_id: nil, config: {}) if Phronomy.configuration.event_loop # Protect against blocking the EventLoop thread itself. if Thread.current[:phronomy_event_loop_thread] raise Phronomy::Error, "Cannot call Agent#invoke (EventLoop mode) from within an EventLoop " \ "entry action. Use agent.run_as_child(input, ctx: ctx) instead." end fsm = Agent::FSM.new( agent: self, input: input, messages: , thread_id: thread_id || SecureRandom.uuid, config: config ) completion_queue = Phronomy::EventLoop.instance.register(fsm) result = completion_queue.pop raise result if result.is_a?(Exception) result else _invoke_impl(input, messages: , thread_id: thread_id, config: config) end end |
#run_as_child(input, ctx:, messages: [], config: {}) {|Hash| ... } ⇒ nil
Registers this agent as a child AgentFSM inside the given Workflow context.
Use this method from a Workflow entry action (running on the EventLoop thread) instead of #invoke, which would raise a deadlock error because +invoke+ blocks on a +Thread::Queue+ when EventLoop mode is active.
The agent runs asynchronously in a background IO thread. When it finishes, the parent FSMSession receives a +:child_completed+ event whose payload is the result hash +{ output:, messages:, usage: }+. Declare an +on: :child_completed+ transition in your Workflow to advance to the next state.
An optional block may be provided to write the result back into the parent WorkflowContext before the +:child_completed+ event is dispatched. +Thread::Queue+ provides the happens-before guarantee \u2014 no Mutex is needed.
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 |
# File 'lib/phronomy/agent/base.rb', line 444 def run_as_child(input, ctx:, messages: [], config: {}, &result_writer) unless Phronomy.configuration.event_loop raise Phronomy::Error, "run_as_child requires EventLoop mode. " \ "Enable with: Phronomy.configure { |c| c.event_loop = true }" end fsm = Agent::FSM.new( agent: self, input: input, messages: , thread_id: "#{ctx.thread_id}_agent_#{SecureRandom.uuid}", config: config, parent_id: ctx.thread_id, result_writer: result_writer ) Phronomy::EventLoop.instance.enqueue_child(fsm) nil end |
#stream(input, messages: [], thread_id: nil, config: {}) {|Phronomy::Agent::StreamEvent| ... } ⇒ Hash
Streaming version of #invoke. Yields StreamEvent objects as they are produced by the underlying LLM.
Events emitted (in order): :token — each content delta from the LLM :tool_call — when the LLM requests a tool (ReactAgent subclasses only) :tool_result — after a tool completes (ReactAgent subclasses only) :done — final event carrying output, messages, and usage :error — if an unrecoverable error occurs
480 481 482 483 484 485 486 487 |
# File 'lib/phronomy/agent/base.rb', line 480 def stream(input, messages: [], thread_id: nil, config: {}, &block) return invoke(input, messages: , thread_id: thread_id, config: config) unless block _stream_impl(input, messages: , thread_id: thread_id, config: config, &block) rescue => e block&.call(StreamEvent.new(type: :error, payload: {error: e})) raise end |