active_harness 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active_harness/agent/hooks.rb +75 -0
- data/lib/active_harness/agent/models.rb +147 -0
- data/lib/active_harness/agent/output_parser.rb +57 -0
- data/lib/active_harness/agent/prompt.rb +58 -0
- data/lib/active_harness/agent/providers.rb +54 -0
- data/lib/active_harness/agent.rb +107 -228
- data/lib/active_harness/core/errors.rb +22 -28
- data/lib/active_harness/http/client.rb +8 -19
- data/lib/active_harness/http/streaming_client.rb +60 -0
- data/lib/active_harness/memory/adapter/base.rb +36 -0
- data/lib/active_harness/memory/adapter/file.rb +141 -0
- data/lib/active_harness/memory.rb +212 -0
- data/lib/active_harness/pipeline/step.rb +36 -0
- data/lib/active_harness/pipeline.rb +207 -0
- data/lib/active_harness/providers/PROVIDER_CONTRACT.md +54 -0
- data/lib/active_harness/providers/anthropic.rb +76 -4
- data/lib/active_harness/providers/base.rb +41 -13
- data/lib/active_harness/providers/gemini.rb +61 -0
- data/lib/active_harness/providers/groq.rb +64 -0
- data/lib/active_harness/providers/openai.rb +39 -47
- data/lib/active_harness/providers/openrouter.rb +40 -54
- data/lib/active_harness/railtie.rb +12 -0
- data/lib/active_harness/result.rb +10 -0
- data/lib/active_harness/tribunal.rb +216 -0
- data/lib/active_harness.rb +17 -46
- data/lib/generators/active_harness/agent/agent_generator.rb +16 -0
- data/lib/generators/active_harness/agent/templates/agent.rb.tt +8 -0
- data/lib/generators/active_harness/install/install_generator.rb +54 -0
- data/lib/generators/active_harness/install/templates/agents/test_support_agent.rb +10 -0
- data/lib/generators/active_harness/install/templates/agents/test_support_guard_agent.rb +11 -0
- data/lib/generators/active_harness/install/templates/controllers/ai_controller.rb +105 -0
- data/lib/generators/active_harness/install/templates/memory/test_support_memory.rb +16 -0
- data/lib/generators/active_harness/install/templates/pipelines/test_support_pipeline.rb +31 -0
- data/lib/generators/active_harness/install/templates/prompts/test_support_guard_prompt.rb +9 -0
- data/lib/generators/active_harness/install/templates/prompts/test_support_prompt.rb +5 -0
- data/lib/generators/active_harness/install/templates/tribunals/test_support_guard_tribunal.rb +11 -0
- data/lib/generators/active_harness/memory/memory_generator.rb +16 -0
- data/lib/generators/active_harness/memory/templates/memory.rb.tt +12 -0
- data/lib/generators/active_harness/pipeline/pipeline_generator.rb +16 -0
- data/lib/generators/active_harness/pipeline/templates/pipeline.rb.tt +19 -0
- data/lib/generators/active_harness/prompt/prompt_generator.rb +16 -0
- data/lib/generators/active_harness/prompt/templates/prompt.rb.tt +5 -0
- data/lib/generators/active_harness/tribunal/templates/tribunal.rb.tt +7 -0
- data/lib/generators/active_harness/tribunal/tribunal_generator.rb +16 -0
- metadata +42 -72
- data/LICENSE +0 -21
- data/README.md +0 -113
- data/lib/active_harness/core/configuration.rb +0 -55
- data/lib/active_harness/core/version.rb +0 -3
- data/lib/active_harness/http/retry_policy.rb +0 -47
- data/lib/active_harness/models/model_request.rb +0 -14
- data/lib/active_harness/models/model_response.rb +0 -13
- data/lib/active_harness/payload.rb +0 -47
- data/lib/active_harness/pipeline/engine.rb +0 -251
- data/lib/active_harness/pipeline/fallback_runner.rb +0 -76
- data/lib/active_harness/pipeline/guard_runner.rb +0 -125
- data/lib/active_harness/pipeline/output_parser.rb +0 -43
- data/lib/active_harness/pipeline/prompt_builder.rb +0 -46
- data/lib/active_harness/pipeline/provider_registry.rb +0 -16
- data/lib/active_harness/prompts/guard_system_prompt.rb +0 -33
- data/lib/active_harness/providers/google.rb +0 -11
- data/lib/active_harness/rate_limit/request_limiter.rb +0 -50
- data/lib/active_harness/rate_limit/risk_holdback.rb +0 -69
- data/lib/active_harness/results/debug_result.rb +0 -19
- data/lib/active_harness/results/input_result.rb +0 -27
- data/lib/active_harness/results/result.rb +0 -55
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e13cc7f12afbddbea9116cae2b3f5c571386ff21a5d7246107a62ceadc3253be
|
|
4
|
+
data.tar.gz: 8f4cdcf1333b9f10acdd664e36568e741bf09652f4b8898d028dc3bf601024d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf13a6f23ac3053316b31890505d39369bb2e932def11dcc7ecdcb2e948aed9466f91d9b1fe1b356dba35112698f7d2e901f1971e93356f32a3b34743d731e7c
|
|
7
|
+
data.tar.gz: 43e7202af753ffd6c6761f21ba2543498c6d04c70e52a57fb9c1aa2cb5063922f536036e28e99bc7710f7e079c50a19839ad510b97ba33841a6d6ff7b15d3220
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module ActiveHarness
|
|
2
|
+
class Agent
|
|
3
|
+
VALID_HOOKS = %i[
|
|
4
|
+
setup
|
|
5
|
+
before_call
|
|
6
|
+
after_call
|
|
7
|
+
before_system_prompt
|
|
8
|
+
after_system_prompt
|
|
9
|
+
before_parse
|
|
10
|
+
after_parse
|
|
11
|
+
parse_error
|
|
12
|
+
retry
|
|
13
|
+
failure
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Unified hook DSL.
|
|
18
|
+
#
|
|
19
|
+
# on :setup do ... end
|
|
20
|
+
# on :before_call do ... end
|
|
21
|
+
# on :after_call do |result| ... end
|
|
22
|
+
# on :before_system_prompt do ... end
|
|
23
|
+
# on :after_system_prompt do |prompt| ... end
|
|
24
|
+
# on :before_parse do |raw| ... end
|
|
25
|
+
# on :after_parse do |parsed| ... end
|
|
26
|
+
# on :parse_error do |raw, error| ... end
|
|
27
|
+
# on :retry do |entry, error| ... end
|
|
28
|
+
# on :failure do |attempts| ... end
|
|
29
|
+
def on(event, &block)
|
|
30
|
+
unless VALID_HOOKS.include?(event)
|
|
31
|
+
raise ArgumentError, "Unknown hook :#{event}. Valid hooks: #{VALID_HOOKS.map { |h| ":#{h}" }.join(", ")}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
agent_config[:hooks] ||= {}
|
|
35
|
+
agent_config[:hooks][event] = block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Rails-style aliases for +on+:
|
|
39
|
+
#
|
|
40
|
+
# before :call do ... end # → on :before_call
|
|
41
|
+
# before :system_prompt do ... end # → on :before_system_prompt
|
|
42
|
+
# after :call do |r| end # → on :after_call
|
|
43
|
+
# after :system_prompt do |p| end # → on :after_system_prompt
|
|
44
|
+
# after :parse do |p| end # → on :after_parse
|
|
45
|
+
# callback :retry do |e,err| end
|
|
46
|
+
# callback :failure do |a| end
|
|
47
|
+
# callback :setup do end
|
|
48
|
+
# callback :parse_error do |r,e| end
|
|
49
|
+
def before(event, &block)
|
|
50
|
+
on(:"before_#{event}", &block)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def after(event, &block)
|
|
54
|
+
on(:"after_#{event}", &block)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def callback(event, &block)
|
|
58
|
+
on(event, &block)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def run_hook(event, *args)
|
|
65
|
+
hooks = @config[:hooks] || {}
|
|
66
|
+
return unless hooks[event]
|
|
67
|
+
|
|
68
|
+
if args.any?
|
|
69
|
+
instance_exec(*args, &hooks[event])
|
|
70
|
+
else
|
|
71
|
+
instance_eval(&hooks[event])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module ActiveHarness
|
|
2
|
+
class Agent
|
|
3
|
+
class << self
|
|
4
|
+
# Array-style model list:
|
|
5
|
+
#
|
|
6
|
+
# models [
|
|
7
|
+
# { provider: :openai, model: "gpt-4.1-mini" },
|
|
8
|
+
# { provider: :openrouter, model: "mistralai/mistral-nemo" }
|
|
9
|
+
# ]
|
|
10
|
+
def models(list)
|
|
11
|
+
agent_config[:models] = Array(list)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Output format for this agent.
|
|
15
|
+
#
|
|
16
|
+
# format :text # default — output is returned as-is
|
|
17
|
+
# format :json # output is parsed; result.parsed is a Ruby Hash/Array
|
|
18
|
+
def format(type)
|
|
19
|
+
unless %i[text json].include?(type)
|
|
20
|
+
raise ArgumentError, "Unknown format :#{type}. Valid values: :text, :json"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
agent_config[:format] = type
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Block-based model DSL:
|
|
27
|
+
#
|
|
28
|
+
# model do
|
|
29
|
+
# use provider: :openrouter, model: "mistralai/mistral-nemo"
|
|
30
|
+
# fallback provider: :openrouter, model: "meta-llama/llama-3.3-70b-instruct:free"
|
|
31
|
+
# end
|
|
32
|
+
def model(&block)
|
|
33
|
+
config = ModelConfig.new
|
|
34
|
+
config.instance_eval(&block)
|
|
35
|
+
agent_config[:model] = config.to_h
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Public instance API — returns a ModelList proxy for this agent instance.
|
|
40
|
+
#
|
|
41
|
+
# Allows adding models at runtime before calling the agent:
|
|
42
|
+
#
|
|
43
|
+
# agent.models.prepend([{ provider: :openrouter, model: "..." }])
|
|
44
|
+
# agent.models.push([{ provider: :openrouter, model: "..." }])
|
|
45
|
+
#
|
|
46
|
+
# Any prepended/pushed models are combined with the class-defined chain:
|
|
47
|
+
# [prepended...] + [class chain / constructor override] + [pushed...]
|
|
48
|
+
def models
|
|
49
|
+
@model_list_proxy ||= begin
|
|
50
|
+
base = if @models_override&.any?
|
|
51
|
+
@models_override
|
|
52
|
+
elsif (m = @config[:model])
|
|
53
|
+
m[:models] || []
|
|
54
|
+
else
|
|
55
|
+
@config[:models] || []
|
|
56
|
+
end
|
|
57
|
+
ModelList.new(base)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def model_list
|
|
64
|
+
list = models.to_a
|
|
65
|
+
raise ArgumentError, "#{self.class.name}: no models configured. Use `model do...end` or `models [...]`." if list.empty?
|
|
66
|
+
|
|
67
|
+
list
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
# ModelList — mutable proxy returned by agent.models
|
|
73
|
+
# ---------------------------------------------------------------------------
|
|
74
|
+
class ModelList
|
|
75
|
+
def initialize(base_models)
|
|
76
|
+
@base = Array(base_models)
|
|
77
|
+
@prepended = []
|
|
78
|
+
@appended = []
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Add models BEFORE the existing chain. Calling multiple times keeps order
|
|
82
|
+
# (last prepend ends up first).
|
|
83
|
+
def prepend(models)
|
|
84
|
+
@prepended = Array(models) + @prepended
|
|
85
|
+
self
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Add models AFTER the existing chain.
|
|
89
|
+
def push(models)
|
|
90
|
+
@appended += Array(models)
|
|
91
|
+
self
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Replace the entire chain, discarding prepended/appended too.
|
|
95
|
+
#
|
|
96
|
+
# agent.models.replace([{ provider: :openrouter, model: "..." }])
|
|
97
|
+
def replace(models)
|
|
98
|
+
@base = Array(models)
|
|
99
|
+
@prepended = []
|
|
100
|
+
@appended = []
|
|
101
|
+
self
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Insert a single model entry at an arbitrary position in the final chain.
|
|
105
|
+
# Position 0 is the same as prepend; -1 appends after the last element.
|
|
106
|
+
#
|
|
107
|
+
# agent.models.insert(0, { provider: :openrouter, model: "fast-model" })
|
|
108
|
+
# agent.models.insert(2, { provider: :openrouter, model: "mid-model" })
|
|
109
|
+
# agent.models.insert(-1, { provider: :openrouter, model: "last-resort" })
|
|
110
|
+
def insert(position, model)
|
|
111
|
+
to_a.tap { |list| list.insert(position, model) }.then do |new_list|
|
|
112
|
+
@prepended = []
|
|
113
|
+
@base = new_list
|
|
114
|
+
@appended = []
|
|
115
|
+
end
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Return the fully assembled list: prepended + base + appended.
|
|
120
|
+
def to_a
|
|
121
|
+
@prepended + @base + @appended
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def inspect
|
|
125
|
+
"#<ModelList prepended=#{@prepended.size} base=#{@base.size} appended=#{@appended.size}>"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# DSL builder — used by Agent.model { ... }
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
class ModelConfig
|
|
133
|
+
def initialize
|
|
134
|
+
@models = []
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def use(provider:, model:, temperature: nil)
|
|
138
|
+
@models << { provider: provider, model: model, temperature: temperature }.compact
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
alias fallback use
|
|
142
|
+
|
|
143
|
+
def to_h
|
|
144
|
+
{ models: @models }
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module ActiveHarness
|
|
2
|
+
class Agent
|
|
3
|
+
private
|
|
4
|
+
|
|
5
|
+
# Strips markdown code fences that some models add despite being told not to:
|
|
6
|
+
# ```json\n{...}\n``` → {...}
|
|
7
|
+
def strip_code_fences(raw)
|
|
8
|
+
raw.to_s.strip
|
|
9
|
+
.gsub(/\A```(?:json)?\s*/i, "")
|
|
10
|
+
.gsub(/\s*```\z/, "")
|
|
11
|
+
.strip
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Like run_hook but uses the block's return value to replace the current value.
|
|
15
|
+
# If no hook is defined, returns the original value unchanged.
|
|
16
|
+
#
|
|
17
|
+
# on :before_parse do |raw|
|
|
18
|
+
# raw.gsub("'", '"') # fixed-up string is used for parsing
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# on :after_parse do |parsed|
|
|
22
|
+
# parsed.transform_keys(&:to_sym) # symbolized hash is stored in result
|
|
23
|
+
# end
|
|
24
|
+
def transform_hook(event, value)
|
|
25
|
+
hooks = @config[:hooks] || {}
|
|
26
|
+
return value unless hooks[event]
|
|
27
|
+
|
|
28
|
+
instance_exec(value, &hooks[event])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_output(raw)
|
|
32
|
+
return raw unless @config[:format] == :json
|
|
33
|
+
|
|
34
|
+
# :before_parse — return value replaces raw before stripping/parsing
|
|
35
|
+
raw = transform_hook(:before_parse, raw)
|
|
36
|
+
|
|
37
|
+
clean = strip_code_fences(raw)
|
|
38
|
+
|
|
39
|
+
begin
|
|
40
|
+
parsed = JSON.parse(clean)
|
|
41
|
+
|
|
42
|
+
# :after_parse — return value replaces parsed result stored in Result
|
|
43
|
+
transform_hook(:after_parse, parsed)
|
|
44
|
+
rescue JSON::ParserError => e
|
|
45
|
+
# :parse_error — if hook returns non-nil, it is used as fallback value
|
|
46
|
+
# and the exception is suppressed. Return nil (or omit return) to re-raise.
|
|
47
|
+
#
|
|
48
|
+
# on :parse_error do |raw, error|
|
|
49
|
+
# puts "Parse failed: #{error.message}"
|
|
50
|
+
# { "result" => nil, "reason" => "parse error" } # fallback
|
|
51
|
+
# end
|
|
52
|
+
fallback = run_hook(:parse_error, raw, e)
|
|
53
|
+
fallback.nil? ? raise : fallback
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module ActiveHarness
|
|
2
|
+
class Agent
|
|
3
|
+
class << self
|
|
4
|
+
# System prompt for this agent.
|
|
5
|
+
# Accepts:
|
|
6
|
+
# - a String → used as-is
|
|
7
|
+
# - a Class → instantiated and resolved via #call or #text
|
|
8
|
+
# - a Proc → called at request time (no arguments)
|
|
9
|
+
#
|
|
10
|
+
# system_prompt "You are a helpful assistant."
|
|
11
|
+
# system_prompt MyPromptClass
|
|
12
|
+
# system_prompt -> { "Dynamic prompt built at #{Time.now}" }
|
|
13
|
+
def system_prompt(text_or_class)
|
|
14
|
+
agent_config[:system_prompt] = text_or_class
|
|
15
|
+
end
|
|
16
|
+
alias prompt system_prompt
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_reader :system_prompt
|
|
22
|
+
public :system_prompt
|
|
23
|
+
|
|
24
|
+
def resolve_system_prompt
|
|
25
|
+
run_hook(:before_system_prompt)
|
|
26
|
+
|
|
27
|
+
sp = @config[:system_prompt]
|
|
28
|
+
|
|
29
|
+
prompt = case sp
|
|
30
|
+
when String then sp
|
|
31
|
+
when NilClass then nil
|
|
32
|
+
when Proc then instance_exec(&sp)
|
|
33
|
+
when Class
|
|
34
|
+
instance = sp.new
|
|
35
|
+
inject_agent_state(instance)
|
|
36
|
+
if instance.respond_to?(:call) then instance.call
|
|
37
|
+
elsif instance.respond_to?(:text) then instance.text
|
|
38
|
+
else instance.to_s
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
inject_agent_state(sp)
|
|
42
|
+
if sp.respond_to?(:call) then sp.call
|
|
43
|
+
elsif sp.respond_to?(:text) then sp.text
|
|
44
|
+
else sp.to_s
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
run_hook(:after_system_prompt, prompt)
|
|
49
|
+
prompt
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def inject_agent_state(obj)
|
|
53
|
+
obj.instance_variable_set(:@input, @input)
|
|
54
|
+
obj.instance_variable_set(:@context, @context)
|
|
55
|
+
obj.instance_variable_set(:@config, @config)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module ActiveHarness
|
|
2
|
+
class Agent
|
|
3
|
+
# Errors that allow retrying the next model in the chain
|
|
4
|
+
RETRYABLE_ERRORS = [
|
|
5
|
+
Errors::TimeoutError,
|
|
6
|
+
Errors::RateLimitError,
|
|
7
|
+
Errors::ServerError,
|
|
8
|
+
Errors::ProviderUnavailableError
|
|
9
|
+
].freeze
|
|
10
|
+
|
|
11
|
+
# Errors that abort the entire chain immediately
|
|
12
|
+
STOP_ERRORS = [
|
|
13
|
+
Errors::InvalidRequestError,
|
|
14
|
+
Errors::InvalidApiKeyError,
|
|
15
|
+
Errors::SafetyBlockedError
|
|
16
|
+
].freeze
|
|
17
|
+
|
|
18
|
+
PROVIDERS = {
|
|
19
|
+
openai: -> { Providers::OpenAI.new },
|
|
20
|
+
openrouter: -> { Providers::OpenRouter.new },
|
|
21
|
+
groq: -> { Providers::Groq.new },
|
|
22
|
+
gemini: -> { Providers::Gemini.new },
|
|
23
|
+
anthropic: -> { Providers::Anthropic.new }
|
|
24
|
+
}.freeze
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def attempt_model(entry, system_prompt)
|
|
29
|
+
provider = resolve_provider(entry[:provider])
|
|
30
|
+
messages = build_messages(system_prompt, @input)
|
|
31
|
+
opts = { model: entry[:model], messages: messages }
|
|
32
|
+
opts[:temperature] = entry[:temperature] if entry[:temperature]
|
|
33
|
+
opts[:stream] = @stream if @stream
|
|
34
|
+
provider.call(**opts)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def resolve_provider(name)
|
|
38
|
+
factory = PROVIDERS[name.to_sym]
|
|
39
|
+
raise ArgumentError, "Unknown provider: #{name.inspect}. Supported: #{PROVIDERS.keys.join(', ')}" unless factory
|
|
40
|
+
|
|
41
|
+
factory.call
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def build_messages(system_prompt, input)
|
|
45
|
+
# Memory is NOT auto-injected here.
|
|
46
|
+
# To use history in LLM context, inject it manually via a hook or prompt class.
|
|
47
|
+
# See ActiveHarness::Memory for examples.
|
|
48
|
+
messages = []
|
|
49
|
+
messages << { role: "system", content: system_prompt } if system_prompt
|
|
50
|
+
messages << { role: "user", content: input }
|
|
51
|
+
messages
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|