Class: ActiveHarness::Agent
- Inherits:
-
Object
- Object
- ActiveHarness::Agent
- Defined in:
- lib/active_harness/agent.rb,
lib/active_harness/agent/hooks.rb,
lib/active_harness/agent/models.rb,
lib/active_harness/agent/prompt.rb,
lib/active_harness/agent/providers.rb,
lib/active_harness/agent/output_parser.rb
Direct Known Subclasses
Constant Summary collapse
- VALID_HOOKS =
%i[ setup before_call after_call before_system_prompt after_system_prompt before_parse after_parse parse_error retry failure ].freeze
- RETRYABLE_ERRORS =
Errors that allow retrying the next model in the chain
[ Errors::TimeoutError, Errors::RateLimitError, Errors::ServerError, Errors::ProviderUnavailableError ].freeze
- STOP_ERRORS =
Errors that abort the entire chain immediately
[ Errors::InvalidRequestError, Errors::InvalidApiKeyError, Errors::SafetyBlockedError ].freeze
- PROVIDERS =
{ openai: -> { Providers::OpenAI.new }, openrouter: -> { Providers::OpenRouter.new }, groq: -> { Providers::Groq.new }, gemini: -> { Providers::Gemini.new }, anthropic: -> { Providers::Anthropic.new } }.freeze
Instance Attribute Summary collapse
-
#context ⇒ Object
readonly
Returns the value of attribute context.
-
#input ⇒ Object
————————————————————————- Instance API ————————————————————————-.
-
#result ⇒ Object
readonly
Returns the value of attribute result.
-
#system_prompt ⇒ Object
readonly
Returns the value of attribute system_prompt.
Class Method Summary collapse
- .after(event, &block) ⇒ Object
-
.agent_config ⇒ Object
Each subclass gets its own isolated config hash.
-
.before(event, &block) ⇒ Object
Rails-style aliases for
on:. -
.call(input: nil, context: {}, models: nil, memory: nil, stream: nil) ⇒ Object
Class-level entry point.
- .callback(event, &block) ⇒ Object
-
.format(type) ⇒ Object
Output format for this agent.
- .inherited(subclass) ⇒ Object
-
.model(&block) ⇒ Object
Block-based model DSL:.
-
.models(list) ⇒ Object
Array-style model list:.
-
.on(event, &block) ⇒ Object
Unified hook DSL.
-
.system_prompt(text_or_class) ⇒ Object
(also: prompt)
System prompt for this agent.
Instance Method Summary collapse
-
#call(input = nil, stream: nil) ⇒ Object
Attempts each model in order, returns the first successful Result.
-
#initialize(input: nil, context: {}, models: nil, memory: nil, stream: nil) ⇒ Agent
constructor
A new instance of Agent.
-
#models ⇒ Object
Public instance API — returns a ModelList proxy for this agent instance.
Constructor Details
#initialize(input: nil, context: {}, models: nil, memory: nil, stream: nil) ⇒ Agent
Returns a new instance of Agent.
34 35 36 37 38 39 40 41 42 43 |
# File 'lib/active_harness/agent.rb', line 34 def initialize(input: nil, context: {}, models: nil, memory: nil, stream: nil) @input = input @context = context @config = self.class.agent_config @models_override = Array(models) if models @stream = stream # memory: can be passed directly or via context[:memory] @memory = memory || @context[:memory] run_hook(:setup) end |
Instance Attribute Details
#context ⇒ Object (readonly)
Returns the value of attribute context.
32 33 34 |
# File 'lib/active_harness/agent.rb', line 32 def context @context end |
#input ⇒ Object
Instance API
31 32 33 |
# File 'lib/active_harness/agent.rb', line 31 def input @input end |
#result ⇒ Object (readonly)
Returns the value of attribute result.
32 33 34 |
# File 'lib/active_harness/agent.rb', line 32 def result @result end |
#system_prompt ⇒ Object (readonly)
Returns the value of attribute system_prompt.
21 22 23 |
# File 'lib/active_harness/agent/prompt.rb', line 21 def system_prompt @system_prompt end |
Class Method Details
.after(event, &block) ⇒ Object
53 54 55 |
# File 'lib/active_harness/agent/hooks.rb', line 53 def after(event, &block) on(:"after_#{event}", &block) end |
.agent_config ⇒ Object
Each subclass gets its own isolated config hash.
19 20 21 |
# File 'lib/active_harness/agent.rb', line 19 def agent_config @agent_config ||= {} end |
.before(event, &block) ⇒ Object
Rails-style aliases for on:
before :call do ... end # → on :before_call
before :system_prompt do ... end # → on :before_system_prompt
after :call do |r| end # → on :after_call
after :system_prompt do |p| end # → on :after_system_prompt
after :parse do |p| end # → on :after_parse
callback :retry do |e,err| end
callback :failure do |a| end
callback :setup do end
callback :parse_error do |r,e| end
49 50 51 |
# File 'lib/active_harness/agent/hooks.rb', line 49 def before(event, &block) on(:"before_#{event}", &block) end |
.call(input: nil, context: {}, models: nil, memory: nil, stream: nil) ⇒ Object
Class-level entry point.
SupportAgent.call(input: "Hi")
SupportAgent.call(input: "Hi", context: { user_id: 42 })
SupportAgent.call(input: "Hi", memory: memory)
14 15 16 |
# File 'lib/active_harness/agent.rb', line 14 def call(input: nil, context: {}, models: nil, memory: nil, stream: nil) new(input: input, context: context, models: models, memory: memory, stream: stream).call end |
.callback(event, &block) ⇒ Object
57 58 59 |
# File 'lib/active_harness/agent/hooks.rb', line 57 def callback(event, &block) on(event, &block) end |
.format(type) ⇒ Object
Output format for this agent.
format :text # default — output is returned as-is
format :json # output is parsed; result.parsed is a Ruby Hash/Array
18 19 20 21 22 23 24 |
# File 'lib/active_harness/agent/models.rb', line 18 def format(type) unless %i[text json].include?(type) raise ArgumentError, "Unknown format :#{type}. Valid values: :text, :json" end agent_config[:format] = type end |
.inherited(subclass) ⇒ Object
23 24 25 |
# File 'lib/active_harness/agent.rb', line 23 def inherited(subclass) subclass.instance_variable_set(:@agent_config, {}) end |
.model(&block) ⇒ Object
Block-based model DSL:
model do
use provider: :openrouter, model: "mistralai/mistral-nemo"
fallback provider: :openrouter, model: "meta-llama/llama-3.3-70b-instruct:free"
end
32 33 34 35 36 |
# File 'lib/active_harness/agent/models.rb', line 32 def model(&block) config = ModelConfig.new config.instance_eval(&block) agent_config[:model] = config.to_h end |
.models(list) ⇒ Object
Array-style model list:
models [
{ provider: :openai, model: "gpt-4.1-mini" },
{ provider: :openrouter, model: "mistralai/mistral-nemo" }
]
10 11 12 |
# File 'lib/active_harness/agent/models.rb', line 10 def models(list) agent_config[:models] = Array(list) end |
.on(event, &block) ⇒ Object
Unified hook DSL.
on :setup do ... end
on :before_call do ... end
on :after_call do |result| ... end
on :before_system_prompt do ... end
on :after_system_prompt do |prompt| ... end
on :before_parse do |raw| ... end
on :after_parse do |parsed| ... end
on :parse_error do |raw, error| ... end
on :retry do |entry, error| ... end
on :failure do |attempts| ... end
29 30 31 32 33 34 35 36 |
# File 'lib/active_harness/agent/hooks.rb', line 29 def on(event, &block) unless VALID_HOOKS.include?(event) raise ArgumentError, "Unknown hook :#{event}. Valid hooks: #{VALID_HOOKS.map { |h| ":#{h}" }.join(", ")}" end agent_config[:hooks] ||= {} agent_config[:hooks][event] = block end |
.system_prompt(text_or_class) ⇒ Object Also known as: prompt
System prompt for this agent. Accepts:
- a String → used as-is
- a Class → instantiated and resolved via #call or #text
- a Proc → called at request time (no arguments)
system_prompt "You are a helpful assistant."
system_prompt MyPromptClass
system_prompt -> { "Dynamic prompt built at #{Time.now}" }
13 14 15 |
# File 'lib/active_harness/agent/prompt.rb', line 13 def system_prompt(text_or_class) agent_config[:system_prompt] = text_or_class end |
Instance Method Details
#call(input = nil, stream: nil) ⇒ Object
Attempts each model in order, returns the first successful Result. Raises Errors::AllModelsFailed if every model in the chain fails.
Optionally accepts input and stream callback inline:
agent.call("What is the capital of Japan?")
agent.call("...", stream: ->(token) { print token })
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 78 79 80 81 82 |
# File 'lib/active_harness/agent.rb', line 51 def call(input = nil, stream: nil) @input = input if input @stream = stream if stream @memory&.load @system_prompt = resolve_system_prompt run_hook(:before_call) attempts = [] model_list.each do |entry| t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) response = attempt_model(entry, @system_prompt) elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3) result = build_result(response, entry, attempts, elapsed) save_to_memory(result) run_hook(:after_call, result) @result = result return result rescue *RETRYABLE_ERRORS => e elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3) attempts << attempt_entry(entry, e, elapsed) run_hook(:retry, entry, e) next rescue *STOP_ERRORS => e elapsed = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0).round(3) attempts << attempt_entry(entry, e, elapsed) run_hook(:retry, entry, e) raise end run_hook(:failure, attempts) raise Errors::AllModelsFailed, "All models failed. Attempts: #{attempts.inspect}" end |
#models ⇒ Object
Public instance API — returns a ModelList proxy for this agent instance.
Allows adding models at runtime before calling the agent:
agent.models.prepend([{ provider: :openrouter, model: "..." }])
agent.models.push([{ provider: :openrouter, model: "..." }])
Any prepended/pushed models are combined with the class-defined chain:
[prepended...] + [class chain / constructor override] + [pushed...]
48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/active_harness/agent/models.rb', line 48 def models @model_list_proxy ||= begin base = if @models_override&.any? @models_override elsif (m = @config[:model]) m[:models] || [] else @config[:models] || [] end ModelList.new(base) end end |