active_harness 0.2.26 → 0.2.28

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e251d772ac23a015473080cd54ec79dcd1d707b3ca9fa1c8de6132f8152fa873
4
- data.tar.gz: 68674b1744f7696cb26cc02a505e60e8c6ae117c7f20a118a2e8102c8f0821f5
3
+ metadata.gz: ce3118f24bd7e48d024a4b5c14d0e01531e453be2f56479918759dc425bc932d
4
+ data.tar.gz: 561d4d3f46b98a224d53ed964c319fbb1a73bffc5c6a3dd189d01e24d1af2427
5
5
  SHA512:
6
- metadata.gz: 99ef61764b9c6b7564f1064c191cb89780670c7e24966556f3f52e3fa15b631e492c9c29a3ac7f601249db9515afed291fe2e6d6e333f9fb6c2043dbad22dac1
7
- data.tar.gz: 8c8609086bc572183398cf20ff0ea2957e8acd8337db9dadfcb8045f1bbdbb7a708b3f3b27273809a717e91196abe4bf272778d521bfa0926338f0df03486e84
6
+ metadata.gz: 29f2a581e10290eb176a80e142a120ba5e9c9f0bc9e5decfacb7237fd11f35621bd35a64ebca2999399edd5f5865aec343edb8a11c8040e539329857b51c6d23
7
+ data.tar.gz: 86ef4129befa8b92af78f5666a4f6346f9d9eba46f39e880cf9968d03b7339f6de9620a0b558d67571bd4650425da0ea3455b8251c356e366717c87233dc3516
@@ -49,10 +49,15 @@ module ActiveHarness
49
49
  prompt
50
50
  end
51
51
 
52
+ # Injects agent state into a prompt class instance before #call.
53
+ # Available in prompt classes: @input, @context, @params, @memory, @context_window, @config
52
54
  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)
55
+ obj.instance_variable_set(:@input, @input)
56
+ obj.instance_variable_set(:@context, @context)
57
+ obj.instance_variable_set(:@params, @params)
58
+ obj.instance_variable_set(:@config, @config)
59
+ obj.instance_variable_set(:@memory, @memory)
60
+ obj.instance_variable_set(:@context_window, @context_window)
56
61
  end
57
62
  end
58
63
  end
@@ -14,6 +14,7 @@ module ActiveHarness
14
14
  input: nil,
15
15
  context: {},
16
16
  params: {},
17
+ memory: nil,
17
18
  models: nil,
18
19
  streams: {}
19
20
  )
@@ -21,6 +22,7 @@ module ActiveHarness
21
22
  input: input,
22
23
  context: context,
23
24
  params: params,
25
+ memory: memory,
24
26
  models: models,
25
27
  streams: streams
26
28
  ).call
@@ -49,8 +51,10 @@ module ActiveHarness
49
51
  # -------------------------------------------------------------------------
50
52
  attr_accessor :input,
51
53
  :context,
52
- :params
54
+ :params,
55
+ :memory
53
56
  attr_reader :result,
57
+ :context_window,
54
58
  :token_stream,
55
59
  :event_stream
56
60
 
@@ -63,6 +67,7 @@ module ActiveHarness
63
67
  input: nil,
64
68
  context: {},
65
69
  params: {},
70
+ memory: nil,
66
71
  models: nil,
67
72
  streams: {}
68
73
  )
@@ -71,9 +76,11 @@ module ActiveHarness
71
76
  normalize_input!
72
77
  @context = context
73
78
  @params = params
74
- @models_override = Array(models) if models
75
- @token_stream = streams[:token]
76
- @event_stream = streams[:agent]
79
+ @memory = memory
80
+ @models_override = Array(models) if models
81
+ @context_window = lookup_context_window(self.models.to_a.first)
82
+ @token_stream = streams[:token]
83
+ @event_stream = streams[:agent]
77
84
  fire(:setup)
78
85
  end
79
86
 
@@ -139,9 +146,10 @@ module ActiveHarness
139
146
  end
140
147
 
141
148
  def build_result(response, entry, attempts, elapsed)
142
- raw = response[:content]
149
+ raw = response[:content]
143
150
  processed = parse_output(raw)
144
- usage = response[:usage]
151
+ usage = response[:usage]
152
+ cw = lookup_context_window(entry)
145
153
 
146
154
  Result.new(
147
155
  input: @input,
@@ -155,10 +163,18 @@ module ActiveHarness
155
163
  attempts: attempts,
156
164
  execution_time: elapsed,
157
165
  usage: usage,
158
- cost: calculate_cost(entry[:model], usage)
166
+ cost: calculate_cost(entry[:model], usage),
167
+ context_window: cw
159
168
  )
160
169
  end
161
170
 
171
+ def lookup_context_window(entry)
172
+ return nil unless entry
173
+ Costs.find(entry[:model])&.context_window
174
+ rescue StandardError
175
+ nil
176
+ end
177
+
162
178
  def normalize_input!
163
179
  return if @config.fetch(:normalize_input, true) == false
164
180
  @input = @input&.strip&.gsub(/\s+/, " ")
@@ -62,12 +62,15 @@ module ActiveHarness
62
62
  :output_per_million,
63
63
  :cache_read_input_per_million,
64
64
  :cache_write_input_per_million,
65
+ :context_window,
66
+ :max_output_tokens,
65
67
  keyword_init: true
66
68
  ) do
67
69
  def inspect
68
70
  parts = ["id=#{id.inspect}", "provider=#{provider.inspect}"]
69
71
  parts << "input=$#{input_per_million}/M" if input_per_million
70
72
  parts << "output=$#{output_per_million}/M" if output_per_million
73
+ parts << "ctx=#{context_window}" if context_window
71
74
  "#<ModelCost #{parts.join(' ')}>"
72
75
  end
73
76
  end
@@ -230,10 +233,12 @@ module ActiveHarness
230
233
  }.compact
231
234
 
232
235
  {
233
- id: m[:id],
234
- name: m[:name] || m[:id],
235
- provider: ah_provider,
236
- pricing: standard.any? ? { text_tokens: { standard: standard } } : {}
236
+ id: m[:id],
237
+ name: m[:name] || m[:id],
238
+ provider: ah_provider,
239
+ context_window: m[:context_window] || m.dig(:limit, :context),
240
+ max_output_tokens: m[:max_output_tokens] || m.dig(:limit, :output),
241
+ pricing: standard.any? ? { text_tokens: { standard: standard } } : {}
237
242
  }
238
243
  end
239
244
  end
@@ -248,7 +253,9 @@ module ActiveHarness
248
253
  input_per_million: standard[:input_per_million],
249
254
  output_per_million: standard[:output_per_million],
250
255
  cache_read_input_per_million: standard[:cache_read_input_per_million],
251
- cache_write_input_per_million: standard[:cache_write_input_per_million]
256
+ cache_write_input_per_million: standard[:cache_write_input_per_million],
257
+ context_window: raw[:context_window],
258
+ max_output_tokens: raw[:max_output_tokens]
252
259
  )
253
260
  end
254
261
 
@@ -138,13 +138,15 @@ module ActiveHarness
138
138
 
139
139
  # Returns messages array for LLM consumption, respecting depth.
140
140
  # Optional filters:
141
- # filter: ->(turn) { turn[:agent] == "SupportAgent" }
142
- # since: Time.now - 3600
143
- def to_messages(filter: nil, since: nil)
141
+ # filter: ->(turn) { turn[:agent] == "SupportAgent" }
142
+ # since: Time.now - 3600
143
+ # token_budget: 4000 # rough limit (chars / 4 estimate); trims oldest turns first
144
+ def to_messages(filter: nil, since: nil, token_budget: nil)
144
145
  turns = @turns.dup
145
146
  turns.select! { |t| filter.call(t) } if filter
146
147
  turns.select! { |t| after?(t, since) } if since
147
148
  turns = turns.last(@depth) if @depth
149
+ turns = trim_to_token_budget(turns, token_budget) if token_budget
148
150
 
149
151
  turns.flat_map do |t|
150
152
  [
@@ -210,5 +212,16 @@ module ActiveHarness
210
212
  turn_time = Time.parse(turn[:at].to_s) rescue nil
211
213
  turn_time ? turn_time >= time : true
212
214
  end
215
+
216
+ def trim_to_token_budget(turns, budget)
217
+ return turns if turns.empty?
218
+
219
+ total = 0
220
+ turns.reverse.take_while do |t|
221
+ tokens = ((t[:request].to_s.length + t[:response].to_s.length) / 4.0).ceil
222
+ total += tokens
223
+ total <= budget
224
+ end.reverse
225
+ end
213
226
  end
214
227
  end
@@ -25,7 +25,14 @@ module ActiveHarness
25
25
  "anthropic-version" => ANTHROPIC_VERSION
26
26
  }
27
27
 
28
- return call_streaming(url: config.anthropic_api_url, headers: headers, body: body, stream: stream, provider: :anthropic, model: model) if stream
28
+ return call_streaming(
29
+ url: config.anthropic_api_url,
30
+ headers: headers,
31
+ body: body,
32
+ stream: stream,
33
+ provider: :anthropic,
34
+ model: model
35
+ ) if stream
29
36
 
30
37
  raw = post_json(URI(config.anthropic_api_url), headers: headers, body: body)
31
38
  data = parse!(raw)
@@ -11,7 +11,7 @@ module ActiveHarness
11
11
  :input,
12
12
  :output,
13
13
  :processed,
14
- :system_prompt,
14
+ :system_prompt,
15
15
  :provider, :model,
16
16
  :temperature,
17
17
  :model_list,
@@ -19,5 +19,6 @@ module ActiveHarness
19
19
  :execution_time,
20
20
  :usage,
21
21
  :cost,
22
+ :context_window,
22
23
  keyword_init: true)
23
24
  end
@@ -48,7 +48,8 @@ module ActiveHarness
48
48
  # -------------------------------------------------------------------------
49
49
  attr_accessor :input,
50
50
  :context,
51
- :params
51
+ :params,
52
+ :memory
52
53
  attr_reader :results,
53
54
  :errors,
54
55
  :verdict,
@@ -62,6 +63,7 @@ module ActiveHarness
62
63
  input: nil,
63
64
  context: {},
64
65
  params: {},
66
+ memory: nil,
65
67
  agents: nil,
66
68
  timeout: 7,
67
69
  streams: {},
@@ -72,6 +74,7 @@ module ActiveHarness
72
74
  @input = input
73
75
  @context = context
74
76
  @params = params
77
+ @memory = memory
75
78
  @agents = agents || config[:agents]
76
79
  @timeout = timeout
77
80
  @process_block = config[:process]
@@ -30,7 +30,7 @@ require_relative "active_harness/pipeline"
30
30
  require_relative "active_harness/railtie" if defined?(Rails::Railtie)
31
31
 
32
32
  module ActiveHarness
33
- VERSION = "0.2.26"
33
+ VERSION = "0.2.28"
34
34
 
35
35
  class << self
36
36
  # Configure ActiveHarness.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_harness
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.26
4
+ version: 0.2.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - the-teacher