active_harness 0.2.27 → 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: 5b406bc5feeebdb4cc26e215a65d25dd931605684c6f05ee7cdd71de5910a1d6
4
- data.tar.gz: a34d2e22840ae906d47b0e27fe271d97b42d97a0fce64a19a58e15c296f00bf7
3
+ metadata.gz: ce3118f24bd7e48d024a4b5c14d0e01531e453be2f56479918759dc425bc932d
4
+ data.tar.gz: 561d4d3f46b98a224d53ed964c319fbb1a73bffc5c6a3dd189d01e24d1af2427
5
5
  SHA512:
6
- metadata.gz: f53ee25213ac7404840349cee732aa59b1dc25168b4e362028e7542fd51a0c51e9f2a2af4b8cbad9a552e74a9c4769d147b7d642e3b051df9c293af58bc87f34
7
- data.tar.gz: d56efdd52b38afd9153903611fd860e316a59eb8f5d5b5c3ed0510b22647b8008a3b22f4e0c8430322548bb82a50c69cc4d6c086e8d965653c22d795fd43cae7
6
+ metadata.gz: 29f2a581e10290eb176a80e142a120ba5e9c9f0bc9e5decfacb7237fd11f35621bd35a64ebca2999399edd5f5865aec343edb8a11c8040e539329857b51c6d23
7
+ data.tar.gz: 86ef4129befa8b92af78f5666a4f6346f9d9eba46f39e880cf9968d03b7339f6de9620a0b558d67571bd4650425da0ea3455b8251c356e366717c87233dc3516
@@ -49,11 +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)
56
- obj.instance_variable_set(:@memory, @memory)
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)
57
61
  end
58
62
  end
59
63
  end
@@ -54,6 +54,7 @@ module ActiveHarness
54
54
  :params,
55
55
  :memory
56
56
  attr_reader :result,
57
+ :context_window,
57
58
  :token_stream,
58
59
  :event_stream
59
60
 
@@ -76,9 +77,10 @@ module ActiveHarness
76
77
  @context = context
77
78
  @params = params
78
79
  @memory = memory
79
- @models_override = Array(models) if models
80
- @token_stream = streams[:token]
81
- @event_stream = streams[:agent]
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]
82
84
  fire(:setup)
83
85
  end
84
86
 
@@ -144,9 +146,10 @@ module ActiveHarness
144
146
  end
145
147
 
146
148
  def build_result(response, entry, attempts, elapsed)
147
- raw = response[:content]
149
+ raw = response[:content]
148
150
  processed = parse_output(raw)
149
- usage = response[:usage]
151
+ usage = response[:usage]
152
+ cw = lookup_context_window(entry)
150
153
 
151
154
  Result.new(
152
155
  input: @input,
@@ -160,10 +163,18 @@ module ActiveHarness
160
163
  attempts: attempts,
161
164
  execution_time: elapsed,
162
165
  usage: usage,
163
- cost: calculate_cost(entry[:model], usage)
166
+ cost: calculate_cost(entry[:model], usage),
167
+ context_window: cw
164
168
  )
165
169
  end
166
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
+
167
178
  def normalize_input!
168
179
  return if @config.fetch(:normalize_input, true) == false
169
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
@@ -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
@@ -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.27"
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.27
4
+ version: 0.2.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - the-teacher