ruby_llm-agents 3.8.0 → 3.10.0
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/README.md +30 -10
- data/app/controllers/ruby_llm/agents/requests_controller.rb +117 -0
- data/app/models/ruby_llm/agents/execution.rb +4 -0
- data/app/models/ruby_llm/agents/tool_execution.rb +25 -0
- data/app/views/layouts/ruby_llm/agents/application.html.erb +4 -2
- data/app/views/ruby_llm/agents/requests/index.html.erb +153 -0
- data/app/views/ruby_llm/agents/requests/show.html.erb +136 -0
- data/config/routes.rb +2 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +2 -2
- data/lib/generators/ruby_llm_agents/demo_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/doctor_generator.rb +196 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +7 -19
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +27 -80
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +18 -51
- data/lib/generators/ruby_llm_agents/templates/initializer.rb.tt +19 -17
- data/lib/ruby_llm/agents/base_agent.rb +70 -7
- data/lib/ruby_llm/agents/core/base.rb +4 -0
- data/lib/ruby_llm/agents/core/configuration.rb +12 -0
- data/lib/ruby_llm/agents/core/errors.rb +3 -0
- data/lib/ruby_llm/agents/core/version.rb +1 -1
- data/lib/ruby_llm/agents/pipeline/context.rb +26 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +58 -4
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +17 -17
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +34 -22
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +105 -50
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +7 -5
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +6 -4
- data/lib/ruby_llm/agents/rails/engine.rb +11 -0
- data/lib/ruby_llm/agents/results/background_removal_result.rb +7 -1
- data/lib/ruby_llm/agents/results/base.rb +39 -2
- data/lib/ruby_llm/agents/results/embedding_result.rb +4 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_edit_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_generation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_transform_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +7 -1
- data/lib/ruby_llm/agents/results/image_variation_result.rb +7 -1
- data/lib/ruby_llm/agents/results/speech_result.rb +6 -0
- data/lib/ruby_llm/agents/results/trackable.rb +25 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +6 -0
- data/lib/ruby_llm/agents/text/embedder.rb +7 -4
- data/lib/ruby_llm/agents/tool.rb +169 -0
- data/lib/ruby_llm/agents/tool_context.rb +71 -0
- data/lib/ruby_llm/agents/track_report.rb +127 -0
- data/lib/ruby_llm/agents/tracker.rb +32 -0
- data/lib/ruby_llm/agents.rb +212 -0
- data/lib/tasks/ruby_llm_agents.rake +6 -0
- metadata +13 -2
|
@@ -23,6 +23,8 @@ module RubyLLM
|
|
|
23
23
|
#
|
|
24
24
|
# @api public
|
|
25
25
|
class SpeechResult
|
|
26
|
+
include Trackable
|
|
27
|
+
|
|
26
28
|
# @!group Audio Content
|
|
27
29
|
|
|
28
30
|
# @!attribute [r] audio
|
|
@@ -229,6 +231,10 @@ module RubyLLM
|
|
|
229
231
|
|
|
230
232
|
# Execution record
|
|
231
233
|
@execution_id = attributes[:execution_id]
|
|
234
|
+
|
|
235
|
+
# Tracking
|
|
236
|
+
@agent_class_name = attributes[:agent_class_name]
|
|
237
|
+
register_with_tracker
|
|
232
238
|
end
|
|
233
239
|
|
|
234
240
|
# Loads the associated Execution record from the database
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Mixin that registers a result object with the active Tracker.
|
|
6
|
+
#
|
|
7
|
+
# Included in every result class so that RubyLLM::Agents.track
|
|
8
|
+
# can collect results automatically.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
module Trackable
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.attr_reader :agent_class_name unless base.method_defined?(:agent_class_name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Call from the end of initialize to register with the active tracker
|
|
19
|
+
def register_with_tracker
|
|
20
|
+
tracker = Thread.current[:ruby_llm_agents_tracker]
|
|
21
|
+
tracker << self if tracker
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -26,6 +26,8 @@ module RubyLLM
|
|
|
26
26
|
#
|
|
27
27
|
# @api public
|
|
28
28
|
class TranscriptionResult
|
|
29
|
+
include Trackable
|
|
30
|
+
|
|
29
31
|
# @!group Content
|
|
30
32
|
|
|
31
33
|
# @!attribute [r] text
|
|
@@ -250,6 +252,10 @@ module RubyLLM
|
|
|
250
252
|
|
|
251
253
|
# Execution record
|
|
252
254
|
@execution_id = attributes[:execution_id]
|
|
255
|
+
|
|
256
|
+
# Tracking
|
|
257
|
+
@agent_class_name = attributes[:agent_class_name]
|
|
258
|
+
register_with_tracker
|
|
253
259
|
end
|
|
254
260
|
|
|
255
261
|
# Loads the associated Execution record from the database
|
|
@@ -337,11 +337,14 @@ module RubyLLM
|
|
|
337
337
|
embed_options = {model: context&.model || resolved_model}
|
|
338
338
|
embed_options[:dimensions] = resolved_dimensions if resolved_dimensions
|
|
339
339
|
|
|
340
|
-
#
|
|
340
|
+
# Use scoped RubyLLM::Context for thread-safe per-tenant API keys.
|
|
341
|
+
# RubyLLM::Context#embed creates an Embedding with the scoped config.
|
|
341
342
|
llm_ctx = context&.llm
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
343
|
+
response = if llm_ctx.is_a?(RubyLLM::Context)
|
|
344
|
+
llm_ctx.embed(preprocessed, **embed_options)
|
|
345
|
+
else
|
|
346
|
+
RubyLLM.embed(preprocessed, **embed_options)
|
|
347
|
+
end
|
|
345
348
|
|
|
346
349
|
# ruby_llm returns vectors as an array (even for single text)
|
|
347
350
|
vectors = response.vectors
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
# Base class for tools that need access to the agent's execution context.
|
|
8
|
+
#
|
|
9
|
+
# Inherits from RubyLLM::Tool and adds:
|
|
10
|
+
# - `context` accessor: read agent params, tenant, execution ID
|
|
11
|
+
# - `timeout` DSL: per-tool timeout in seconds
|
|
12
|
+
# - Error handling: exceptions become error strings for the LLM
|
|
13
|
+
# - Tool execution tracking: records each tool call in the database
|
|
14
|
+
#
|
|
15
|
+
# Users implement `execute()` — the standard RubyLLM convention.
|
|
16
|
+
# This class overrides `call()` to wrap execution with its features.
|
|
17
|
+
#
|
|
18
|
+
# @example Defining a tool
|
|
19
|
+
# class BashTool < RubyLLM::Agents::Tool
|
|
20
|
+
# description "Run a shell command"
|
|
21
|
+
# timeout 30
|
|
22
|
+
#
|
|
23
|
+
# param :command, desc: "The command to run", required: true
|
|
24
|
+
#
|
|
25
|
+
# def execute(command:)
|
|
26
|
+
# context.container_id # reads agent param
|
|
27
|
+
# # ... run command ...
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Using with an agent
|
|
32
|
+
# class CodingAgent < ApplicationAgent
|
|
33
|
+
# param :container_id, required: true
|
|
34
|
+
# tools [BashTool]
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# CodingAgent.call(query: "list files", container_id: "abc123")
|
|
38
|
+
#
|
|
39
|
+
class Tool < RubyLLM::Tool
|
|
40
|
+
# The execution context, set before each call.
|
|
41
|
+
# Provides access to agent params, tenant, execution ID.
|
|
42
|
+
#
|
|
43
|
+
# @return [ToolContext, nil]
|
|
44
|
+
attr_reader :context
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
# Sets or gets the per-tool timeout in seconds.
|
|
48
|
+
#
|
|
49
|
+
# @param value [Integer, nil] Timeout in seconds (setter)
|
|
50
|
+
# @return [Integer, nil] The configured timeout (getter)
|
|
51
|
+
def timeout(value = nil)
|
|
52
|
+
if value
|
|
53
|
+
@timeout = value
|
|
54
|
+
else
|
|
55
|
+
@timeout
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Wraps RubyLLM's call() with context, timeout, tracking, and error handling.
|
|
61
|
+
#
|
|
62
|
+
# RubyLLM's Chat calls tool.call(args) during the tool loop.
|
|
63
|
+
# We set up context, create a tracking record, apply timeout,
|
|
64
|
+
# then delegate to super (which validates args and calls execute).
|
|
65
|
+
#
|
|
66
|
+
# @param args [Hash] Tool arguments from the LLM
|
|
67
|
+
# @return [String, Tool::Halt] The tool result or a Halt signal
|
|
68
|
+
def call(args)
|
|
69
|
+
pipeline_context = Thread.current[:ruby_llm_agents_caller_context]
|
|
70
|
+
@context = pipeline_context ? ToolContext.new(pipeline_context) : nil
|
|
71
|
+
|
|
72
|
+
record = start_tool_tracking(pipeline_context, args)
|
|
73
|
+
|
|
74
|
+
check_cancelled!(pipeline_context)
|
|
75
|
+
|
|
76
|
+
timeout_seconds = self.class.timeout
|
|
77
|
+
timeout_seconds ||= RubyLLM::Agents.configuration.default_tool_timeout
|
|
78
|
+
|
|
79
|
+
result = if timeout_seconds
|
|
80
|
+
Timeout.timeout(timeout_seconds) { super }
|
|
81
|
+
else
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
complete_tool_tracking(record, result, status: "success")
|
|
86
|
+
result
|
|
87
|
+
rescue Timeout::Error
|
|
88
|
+
complete_tool_tracking(record, nil, status: "timed_out", error: "Timed out after #{timeout_seconds}s")
|
|
89
|
+
"TIMEOUT: Tool did not complete within #{timeout_seconds}s."
|
|
90
|
+
rescue RubyLLM::Agents::CancelledError
|
|
91
|
+
complete_tool_tracking(record, nil, status: "cancelled")
|
|
92
|
+
raise # Let cancellation propagate to BaseAgent
|
|
93
|
+
rescue => e
|
|
94
|
+
complete_tool_tracking(record, nil, status: "error", error: e.message)
|
|
95
|
+
"ERROR (#{e.class}): #{e.message}"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Creates a "running" ToolExecution record before the tool runs.
|
|
101
|
+
# Silently skips if no execution_id or ToolExecution is not available.
|
|
102
|
+
#
|
|
103
|
+
# @param pipeline_context [Pipeline::Context, nil]
|
|
104
|
+
# @param args [Hash] The tool arguments
|
|
105
|
+
# @return [ToolExecution, nil]
|
|
106
|
+
def start_tool_tracking(pipeline_context, args)
|
|
107
|
+
return nil unless pipeline_context&.execution_id
|
|
108
|
+
return nil unless defined?(ToolExecution)
|
|
109
|
+
|
|
110
|
+
@tool_iteration = (@tool_iteration || 0) + 1
|
|
111
|
+
|
|
112
|
+
ToolExecution.create!(
|
|
113
|
+
execution_id: pipeline_context.execution_id,
|
|
114
|
+
tool_name: name,
|
|
115
|
+
iteration: @tool_iteration,
|
|
116
|
+
status: "running",
|
|
117
|
+
input: normalize_input(args),
|
|
118
|
+
started_at: Time.current
|
|
119
|
+
)
|
|
120
|
+
rescue => e
|
|
121
|
+
# Don't let tracking failures break tool execution
|
|
122
|
+
Rails.logger.debug("[RubyLLM::Agents::Tool] Tracking failed: #{e.message}") if defined?(Rails) && Rails.logger
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Updates the ToolExecution record after the tool completes.
|
|
127
|
+
#
|
|
128
|
+
# @param record [ToolExecution, nil]
|
|
129
|
+
# @param result [Object, nil] The tool result
|
|
130
|
+
# @param status [String] Final status
|
|
131
|
+
# @param error [String, nil] Error message
|
|
132
|
+
def complete_tool_tracking(record, result, status:, error: nil)
|
|
133
|
+
return unless record
|
|
134
|
+
|
|
135
|
+
completed_at = Time.current
|
|
136
|
+
duration_ms = record.started_at ? ((completed_at - record.started_at) * 1000).to_i : nil
|
|
137
|
+
output_str = result.is_a?(RubyLLM::Tool::Halt) ? result.content.to_s : result.to_s
|
|
138
|
+
|
|
139
|
+
record.update!(
|
|
140
|
+
status: status,
|
|
141
|
+
output: truncate_output(output_str),
|
|
142
|
+
output_bytes: output_str.bytesize,
|
|
143
|
+
error_message: error,
|
|
144
|
+
completed_at: completed_at,
|
|
145
|
+
duration_ms: duration_ms
|
|
146
|
+
)
|
|
147
|
+
rescue => e
|
|
148
|
+
Rails.logger.debug("[RubyLLM::Agents::Tool] Tracking update failed: #{e.message}") if defined?(Rails) && Rails.logger
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def check_cancelled!(pipeline_context)
|
|
152
|
+
return unless pipeline_context
|
|
153
|
+
on_cancelled = pipeline_context[:on_cancelled]
|
|
154
|
+
return unless on_cancelled.respond_to?(:call)
|
|
155
|
+
raise CancelledError, "Execution cancelled" if on_cancelled.call
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def normalize_input(args)
|
|
159
|
+
return {} if args.nil?
|
|
160
|
+
args.respond_to?(:to_h) ? args.to_h : {}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def truncate_output(str)
|
|
164
|
+
max = RubyLLM::Agents.configuration.try(:tool_result_max_length) || 10_000
|
|
165
|
+
(str.length > max) ? str[0, max] + "... (truncated)" : str
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Read-only wrapper around Pipeline::Context for tool authors.
|
|
6
|
+
#
|
|
7
|
+
# Exposes agent params and execution metadata to tools without
|
|
8
|
+
# leaking pipeline internals. Supports both method-style and
|
|
9
|
+
# hash-style access to agent params.
|
|
10
|
+
#
|
|
11
|
+
# @example Method-style access
|
|
12
|
+
# context.container_id # reads agent param
|
|
13
|
+
# context.tenant_id # fixed attribute
|
|
14
|
+
#
|
|
15
|
+
# @example Hash-style access
|
|
16
|
+
# context[:container_id]
|
|
17
|
+
#
|
|
18
|
+
class ToolContext
|
|
19
|
+
# Execution record ID — links tool calls to the agent execution
|
|
20
|
+
#
|
|
21
|
+
# @return [Integer, nil]
|
|
22
|
+
def id
|
|
23
|
+
@ctx.execution_id
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Tenant ID from the pipeline context
|
|
27
|
+
#
|
|
28
|
+
# @return [String, nil]
|
|
29
|
+
def tenant_id
|
|
30
|
+
@ctx.tenant_id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Agent class name
|
|
34
|
+
#
|
|
35
|
+
# @return [String, nil]
|
|
36
|
+
def agent_type
|
|
37
|
+
@ctx.agent_class&.name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Hash-style access to agent params
|
|
41
|
+
#
|
|
42
|
+
# @param key [Symbol, String] The param key
|
|
43
|
+
# @return [Object, nil]
|
|
44
|
+
def [](key)
|
|
45
|
+
@agent_options[key.to_sym] || @agent_options[key.to_s]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def initialize(pipeline_context)
|
|
49
|
+
@ctx = pipeline_context
|
|
50
|
+
@agent_options = @ctx.agent_instance&.send(:options) || {}
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
# Method-style access to agent params
|
|
56
|
+
def method_missing(method_name, *args)
|
|
57
|
+
key = method_name.to_sym
|
|
58
|
+
if @agent_options.key?(key) || @agent_options.key?(key.to_s)
|
|
59
|
+
self[key]
|
|
60
|
+
else
|
|
61
|
+
super
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
66
|
+
key = method_name.to_sym
|
|
67
|
+
@agent_options.key?(key) || @agent_options.key?(key.to_s) || super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Aggregated read-only report returned by RubyLLM::Agents.track.
|
|
6
|
+
#
|
|
7
|
+
# Provides totals and breakdowns across all agent calls made
|
|
8
|
+
# inside the tracked block.
|
|
9
|
+
#
|
|
10
|
+
# @example
|
|
11
|
+
# report = RubyLLM::Agents.track do
|
|
12
|
+
# TranscribeAgent.call(with: audio_path)
|
|
13
|
+
# ChatAgent.call(message: "hello")
|
|
14
|
+
# end
|
|
15
|
+
# report.total_cost # => 0.0078
|
|
16
|
+
# report.call_count # => 2
|
|
17
|
+
#
|
|
18
|
+
# @api public
|
|
19
|
+
class TrackReport
|
|
20
|
+
attr_reader :value, :error, :results, :request_id
|
|
21
|
+
attr_reader :started_at, :completed_at
|
|
22
|
+
|
|
23
|
+
def initialize(value:, error:, results:, request_id:, started_at:, completed_at:)
|
|
24
|
+
@value = value
|
|
25
|
+
@error = error
|
|
26
|
+
@results = results.freeze
|
|
27
|
+
@request_id = request_id
|
|
28
|
+
@started_at = started_at
|
|
29
|
+
@completed_at = completed_at
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def successful?
|
|
33
|
+
@error.nil?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def failed?
|
|
37
|
+
!successful?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def call_count
|
|
41
|
+
@results.size
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def total_cost
|
|
45
|
+
@results.sum { |r| r.total_cost || 0 }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def input_cost
|
|
49
|
+
@results.sum { |r| r.input_cost || 0 }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def output_cost
|
|
53
|
+
@results.sum { |r| r.output_cost || 0 }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def total_tokens
|
|
57
|
+
@results.sum { |r| r.total_tokens }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def input_tokens
|
|
61
|
+
@results.sum { |r| r.input_tokens || 0 }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def output_tokens
|
|
65
|
+
@results.sum { |r| r.output_tokens || 0 }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def duration_ms
|
|
69
|
+
return nil unless @started_at && @completed_at
|
|
70
|
+
((@completed_at - @started_at) * 1000).to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def all_successful?
|
|
74
|
+
@results.all?(&:success?)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def any_errors?
|
|
78
|
+
@results.any?(&:error?)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def errors
|
|
82
|
+
@results.select(&:error?)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def successful
|
|
86
|
+
@results.select(&:success?)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def models_used
|
|
90
|
+
@results.filter_map(&:chosen_model_id).uniq
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def cost_breakdown
|
|
94
|
+
@results.map do |r|
|
|
95
|
+
{
|
|
96
|
+
agent: r.respond_to?(:agent_class_name) ? r.agent_class_name : nil,
|
|
97
|
+
model: r.chosen_model_id,
|
|
98
|
+
cost: r.total_cost || 0,
|
|
99
|
+
tokens: r.total_tokens,
|
|
100
|
+
duration_ms: r.duration_ms
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def to_h
|
|
106
|
+
{
|
|
107
|
+
successful: successful?,
|
|
108
|
+
value: value,
|
|
109
|
+
error: error&.message,
|
|
110
|
+
request_id: request_id,
|
|
111
|
+
call_count: call_count,
|
|
112
|
+
total_cost: total_cost,
|
|
113
|
+
input_cost: input_cost,
|
|
114
|
+
output_cost: output_cost,
|
|
115
|
+
total_tokens: total_tokens,
|
|
116
|
+
input_tokens: input_tokens,
|
|
117
|
+
output_tokens: output_tokens,
|
|
118
|
+
duration_ms: duration_ms,
|
|
119
|
+
started_at: started_at,
|
|
120
|
+
completed_at: completed_at,
|
|
121
|
+
models_used: models_used,
|
|
122
|
+
cost_breakdown: cost_breakdown
|
|
123
|
+
}
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Internal collector used by RubyLLM::Agents.track to accumulate
|
|
6
|
+
# Result objects produced during a tracked block.
|
|
7
|
+
#
|
|
8
|
+
# Not part of the public API — users interact with TrackReport instead.
|
|
9
|
+
#
|
|
10
|
+
# @api private
|
|
11
|
+
class Tracker
|
|
12
|
+
attr_reader :results, :defaults, :request_id, :tags
|
|
13
|
+
|
|
14
|
+
def initialize(defaults: {}, request_id: nil, tags: {})
|
|
15
|
+
@results = []
|
|
16
|
+
@defaults = defaults
|
|
17
|
+
@request_id = request_id || generate_request_id
|
|
18
|
+
@tags = tags
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def <<(result)
|
|
22
|
+
@results << result
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def generate_request_id
|
|
28
|
+
"track_#{SecureRandom.hex(8)}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|