activeagent 1.2.0 → 1.3.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.
@@ -26,6 +26,15 @@ module ActiveAgent
26
26
 
27
27
  protected
28
28
 
29
+ # Chat Completions reports a streamed request's token usage on a final
30
+ # chunk, and only when the request opts in.
31
+ #
32
+ # @return [Hash]
33
+ # @see Base#api_stream_usage_parameters
34
+ def api_stream_usage_parameters
35
+ { stream_options: { include_usage: true } }
36
+ end
37
+
29
38
  # @return [OpenAI::Client::Completions] the API client for chat completions
30
39
  # @see Base#api_prompt_executer
31
40
  def api_prompt_executer
@@ -100,7 +109,12 @@ module ActiveAgent
100
109
  # Called Multiple Times: [Chunk<T>, T]<Content, ToolsCall>
101
110
  case api_response_event.type
102
111
  when :chunk
112
+ # The usage chunk carries usage and an empty choices array, so it
113
+ # must be read before choices.first is dereferenced below.
114
+ record_stream_usage(api_response_event.chunk.try(:usage))
115
+
103
116
  api_message = api_response_event.chunk.choices.first
117
+ return if api_message.nil?
104
118
 
105
119
  # If we have a delta, we need to update a message in the stack
106
120
  message = find_or_create_message(api_message.index)
@@ -118,8 +132,13 @@ module ActiveAgent
118
132
  # Returns the full content when complete
119
133
  # => {type: :"content.done", content: "Hi there! How can I help you today?", parsed: nil}
120
134
 
121
- # Once we are finished, close out and run tooling callbacks (Recursive)
122
- process_prompt_finished
135
+ # Close out and run tooling callbacks (Recursive) — but not here.
136
+ # The usage chunk is emitted *after* content.done, and finishing
137
+ # at this point builds the response from a usage_stack that has
138
+ # not received it yet, so a streamed generation reports zero
139
+ # tokens. Deferred to stream_finished!, which the base provider
140
+ # calls once the stream has drained.
141
+ self.stream_completion_pending = true
123
142
  when :"tool_calls.function.arguments.delta"
124
143
  # => {type: :"tool_calls.function.arguments.delta", name: "get_current_weather", index: 0, arguments: "", parsed: nil, arguments_delta: ""}
125
144
  when :"tool_calls.function.arguments.done"
@@ -94,12 +94,24 @@ module ActiveAgent
94
94
  prompt_span.set_attribute("prompt.input.tools", JSON.generate(roster)) if roster.any?
95
95
  end
96
96
 
97
- if (outbound = prompt_options[:messages]).present?
97
+ # prompt_options[:messages] holds the turns a caller passed
98
+ # explicitly. An agent that renders its user turn from the
99
+ # action's template — the idiomatic form, `instructions:` plus
100
+ # `locals:` — has none at this point: the rendering happens
101
+ # later, in prepare_prompt_parameters. Falling back to it means
102
+ # the message the model actually received is on the trace either
103
+ # way, which is what an evaluation scores.
104
+ outbound = prompt_options[:messages]
105
+ outbound = rendered_prompt_messages if outbound.blank?
106
+
107
+ if outbound.present?
98
108
  serialized = Array(outbound).map { |message|
99
109
  if message.is_a?(Hash)
100
110
  role = message[:role] || message["role"] || "user"
101
111
  content = message[:content] || message["content"]
102
112
  { role: role.to_s, content: telemetry_truncate(content) }
113
+ elsif message.respond_to?(:content)
114
+ { role: (message.try(:role) || "user").to_s, content: telemetry_truncate(message.content) }
103
115
  else
104
116
  { role: "user", content: telemetry_truncate(message) }
105
117
  end
@@ -257,6 +269,27 @@ module ActiveAgent
257
269
  # tool loop) can't bloat the trace payload.
258
270
  TELEMETRY_ATTRIBUTE_MAX_CHARS = 4_000
259
271
 
272
+ # The turns this generation will actually send, for an agent that
273
+ # renders its user message from the action's template rather than
274
+ # passing `messages:`. prepare_prompt_parameters is a pure function of
275
+ # prompt_options — it deep_dups its input and mutates no instance
276
+ # state — so calling it here is a read, not a side effect. It does
277
+ # re-render the templates, which is why it is only reached when there
278
+ # are no explicit messages to record.
279
+ #
280
+ # Never raises: a provider that builds parameters differently, or an
281
+ # agent whose templates need context this call does not have, must
282
+ # cost the generation nothing more than an absent attribute.
283
+ def rendered_prompt_messages
284
+ return unless respond_to?(:prepare_prompt_parameters, true)
285
+
286
+ parameters = prepare_prompt_parameters
287
+ parameters[:messages] || parameters["messages"]
288
+ rescue StandardError => e
289
+ logger&.debug { "[ActiveAgent::Telemetry] could not read rendered messages: #{e.class}: #{e.message}" }
290
+ nil
291
+ end
292
+
260
293
  def telemetry_truncate(value)
261
294
  text = value.to_s
262
295
  return text if text.length <= TELEMETRY_ATTRIBUTE_MAX_CHARS
@@ -1,3 +1,3 @@
1
1
  module ActiveAgent
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.1"
3
3
  end
data/lib/active_agent.rb CHANGED
@@ -93,6 +93,7 @@ module ActiveAgent
93
93
  # These components are loaded on-demand when first referenced.
94
94
  autoload :Base
95
95
  autoload :Callbacks, "active_agent/concerns/callbacks"
96
+ autoload :Delegation, "active_agent/concerns/delegation"
96
97
  autoload :Streaming, "active_agent/concerns/streaming"
97
98
  autoload :InlinePreviewInterceptor
98
99
  autoload :Generation
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activeagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Bowen
@@ -410,6 +410,7 @@ files:
410
410
  - lib/active_agent/base.rb
411
411
  - lib/active_agent/collector.rb
412
412
  - lib/active_agent/concerns/callbacks.rb
413
+ - lib/active_agent/concerns/delegation.rb
413
414
  - lib/active_agent/concerns/observers.rb
414
415
  - lib/active_agent/concerns/parameterized.rb
415
416
  - lib/active_agent/concerns/preview.rb
@@ -420,6 +421,14 @@ files:
420
421
  - lib/active_agent/concerns/tooling.rb
421
422
  - lib/active_agent/concerns/view.rb
422
423
  - lib/active_agent/configuration.rb
424
+ - lib/active_agent/delegation/backend.rb
425
+ - lib/active_agent/delegation/budget.rb
426
+ - lib/active_agent/delegation/contract.rb
427
+ - lib/active_agent/delegation/definition.rb
428
+ - lib/active_agent/delegation/ledger.rb
429
+ - lib/active_agent/delegation/pricing.rb
430
+ - lib/active_agent/delegation/runner.rb
431
+ - lib/active_agent/delegation/schema.rb
423
432
  - lib/active_agent/deprecator.rb
424
433
  - lib/active_agent/generation.rb
425
434
  - lib/active_agent/generation_job.rb