ruby_llm-agents 0.3.1 → 0.3.3
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 +88 -0
- data/app/models/ruby_llm/agents/execution/scopes.rb +10 -0
- data/app/models/ruby_llm/agents/execution.rb +7 -0
- data/app/views/layouts/rubyllm/agents/application.html.erb +11 -2
- data/app/views/rubyllm/agents/agents/_agent.html.erb +87 -0
- data/app/views/rubyllm/agents/agents/index.html.erb +2 -71
- data/app/views/rubyllm/agents/agents/show.html.erb +20 -33
- data/app/views/rubyllm/agents/dashboard/_action_center.html.erb +7 -7
- data/app/views/rubyllm/agents/dashboard/_execution_item.html.erb +54 -39
- data/app/views/rubyllm/agents/dashboard/index.html.erb +4 -34
- data/app/views/rubyllm/agents/executions/show.html.erb +166 -52
- data/lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt +28 -0
- data/lib/generators/ruby_llm_agents/templates/migration.rb.tt +7 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +13 -0
- data/lib/ruby_llm/agents/base.rb +208 -18
- data/lib/ruby_llm/agents/instrumentation.rb +36 -3
- data/lib/ruby_llm/agents/result.rb +235 -0
- data/lib/ruby_llm/agents/version.rb +1 -1
- data/lib/ruby_llm/agents.rb +1 -0
- metadata +4 -1
|
@@ -385,6 +385,12 @@ module RubyLLM
|
|
|
385
385
|
update_data[:response] = redacted_response(@last_response)
|
|
386
386
|
end
|
|
387
387
|
|
|
388
|
+
# Add tool calls from accumulated_tool_calls (captured from all responses)
|
|
389
|
+
if respond_to?(:accumulated_tool_calls) && accumulated_tool_calls.present?
|
|
390
|
+
update_data[:tool_calls] = accumulated_tool_calls
|
|
391
|
+
update_data[:tool_calls_count] = accumulated_tool_calls.size
|
|
392
|
+
end
|
|
393
|
+
|
|
388
394
|
# Add error data if failed
|
|
389
395
|
if error
|
|
390
396
|
update_data.merge!(
|
|
@@ -566,7 +572,11 @@ module RubyLLM
|
|
|
566
572
|
# @param response [RubyLLM::Message, nil] The LLM response
|
|
567
573
|
# @return [Hash] Extracted response data (empty if response invalid)
|
|
568
574
|
def safe_extract_response_data(response)
|
|
569
|
-
return {} unless response.
|
|
575
|
+
return {} unless response.respond_to?(:input_tokens)
|
|
576
|
+
|
|
577
|
+
# Use accumulated_tool_calls which captures tool calls from ALL responses
|
|
578
|
+
# during multi-turn conversations (when tools are used)
|
|
579
|
+
tool_calls_data = respond_to?(:accumulated_tool_calls) ? accumulated_tool_calls : []
|
|
570
580
|
|
|
571
581
|
{
|
|
572
582
|
input_tokens: safe_response_value(response, :input_tokens),
|
|
@@ -575,7 +585,9 @@ module RubyLLM
|
|
|
575
585
|
cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0),
|
|
576
586
|
model_id: safe_response_value(response, :model_id),
|
|
577
587
|
finish_reason: safe_extract_finish_reason(response),
|
|
578
|
-
response: safe_serialize_response(response)
|
|
588
|
+
response: safe_serialize_response(response),
|
|
589
|
+
tool_calls: tool_calls_data || [],
|
|
590
|
+
tool_calls_count: tool_calls_data&.size || 0
|
|
579
591
|
}.compact
|
|
580
592
|
end
|
|
581
593
|
|
|
@@ -689,16 +701,37 @@ module RubyLLM
|
|
|
689
701
|
# @param response [RubyLLM::Message] The LLM response
|
|
690
702
|
# @return [Hash] Serialized response data
|
|
691
703
|
def safe_serialize_response(response)
|
|
704
|
+
# Use accumulated_tool_calls which captures tool calls from ALL responses
|
|
705
|
+
tool_calls_data = respond_to?(:accumulated_tool_calls) ? accumulated_tool_calls : nil
|
|
706
|
+
|
|
692
707
|
{
|
|
693
708
|
content: safe_response_value(response, :content),
|
|
694
709
|
model_id: safe_response_value(response, :model_id),
|
|
695
710
|
input_tokens: safe_response_value(response, :input_tokens),
|
|
696
711
|
output_tokens: safe_response_value(response, :output_tokens),
|
|
697
712
|
cached_tokens: safe_response_value(response, :cached_tokens, 0),
|
|
698
|
-
cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0)
|
|
713
|
+
cache_creation_tokens: safe_response_value(response, :cache_creation_tokens, 0),
|
|
714
|
+
tool_calls: tool_calls_data.presence
|
|
699
715
|
}.compact
|
|
700
716
|
end
|
|
701
717
|
|
|
718
|
+
# Serializes tool calls to an array of hashes for storage
|
|
719
|
+
#
|
|
720
|
+
# @param response [RubyLLM::Message] The LLM response
|
|
721
|
+
# @return [Array<Hash>, nil] Serialized tool calls or nil if none
|
|
722
|
+
def serialize_tool_calls(response)
|
|
723
|
+
tool_calls = safe_response_value(response, :tool_calls)
|
|
724
|
+
return nil if tool_calls.nil? || tool_calls.empty?
|
|
725
|
+
|
|
726
|
+
tool_calls.map do |id, tool_call|
|
|
727
|
+
if tool_call.respond_to?(:to_h)
|
|
728
|
+
tool_call.to_h
|
|
729
|
+
else
|
|
730
|
+
{ id: id, name: tool_call[:name], arguments: tool_call[:arguments] }
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
end
|
|
734
|
+
|
|
702
735
|
# Emergency fallback to mark execution as failed
|
|
703
736
|
#
|
|
704
737
|
# Uses update_all to bypass ActiveRecord callbacks and validations,
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Agents
|
|
5
|
+
# Wrapper for agent execution results with full metadata
|
|
6
|
+
#
|
|
7
|
+
# Provides access to the response content along with execution details
|
|
8
|
+
# like token usage, cost, timing, and model information.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# result = MyAgent.call(query: "test")
|
|
12
|
+
# result.content # => processed response
|
|
13
|
+
# result.input_tokens # => 150
|
|
14
|
+
# result.total_cost # => 0.00025
|
|
15
|
+
#
|
|
16
|
+
# @example Backward compatible hash access
|
|
17
|
+
# result[:key] # delegates to result.content[:key]
|
|
18
|
+
# result.dig(:nested, :key)
|
|
19
|
+
#
|
|
20
|
+
# @api public
|
|
21
|
+
class Result
|
|
22
|
+
extend ActiveSupport::Delegation
|
|
23
|
+
|
|
24
|
+
# @!attribute [r] content
|
|
25
|
+
# @return [Hash, String] The processed response content
|
|
26
|
+
attr_reader :content
|
|
27
|
+
|
|
28
|
+
# @!group Token Usage
|
|
29
|
+
# @!attribute [r] input_tokens
|
|
30
|
+
# @return [Integer, nil] Number of input tokens consumed
|
|
31
|
+
# @!attribute [r] output_tokens
|
|
32
|
+
# @return [Integer, nil] Number of output tokens generated
|
|
33
|
+
# @!attribute [r] cached_tokens
|
|
34
|
+
# @return [Integer] Number of tokens served from cache
|
|
35
|
+
# @!attribute [r] cache_creation_tokens
|
|
36
|
+
# @return [Integer] Number of tokens used to create cache
|
|
37
|
+
attr_reader :input_tokens, :output_tokens, :cached_tokens, :cache_creation_tokens
|
|
38
|
+
|
|
39
|
+
# @!group Cost
|
|
40
|
+
# @!attribute [r] input_cost
|
|
41
|
+
# @return [Float, nil] Cost of input tokens in USD
|
|
42
|
+
# @!attribute [r] output_cost
|
|
43
|
+
# @return [Float, nil] Cost of output tokens in USD
|
|
44
|
+
# @!attribute [r] total_cost
|
|
45
|
+
# @return [Float, nil] Total cost in USD
|
|
46
|
+
attr_reader :input_cost, :output_cost, :total_cost
|
|
47
|
+
|
|
48
|
+
# @!group Model Info
|
|
49
|
+
# @!attribute [r] model_id
|
|
50
|
+
# @return [String, nil] The model that was requested
|
|
51
|
+
# @!attribute [r] chosen_model_id
|
|
52
|
+
# @return [String, nil] The model that actually responded (may differ if fallback used)
|
|
53
|
+
# @!attribute [r] temperature
|
|
54
|
+
# @return [Float, nil] Temperature setting used
|
|
55
|
+
attr_reader :model_id, :chosen_model_id, :temperature
|
|
56
|
+
|
|
57
|
+
# @!group Timing
|
|
58
|
+
# @!attribute [r] started_at
|
|
59
|
+
# @return [Time, nil] When execution started
|
|
60
|
+
# @!attribute [r] completed_at
|
|
61
|
+
# @return [Time, nil] When execution completed
|
|
62
|
+
# @!attribute [r] duration_ms
|
|
63
|
+
# @return [Integer, nil] Execution duration in milliseconds
|
|
64
|
+
# @!attribute [r] time_to_first_token_ms
|
|
65
|
+
# @return [Integer, nil] Time to first token (streaming only)
|
|
66
|
+
attr_reader :started_at, :completed_at, :duration_ms, :time_to_first_token_ms
|
|
67
|
+
|
|
68
|
+
# @!group Status
|
|
69
|
+
# @!attribute [r] finish_reason
|
|
70
|
+
# @return [String, nil] Why generation stopped (stop, length, tool_calls, etc.)
|
|
71
|
+
# @!attribute [r] streaming
|
|
72
|
+
# @return [Boolean] Whether streaming was enabled
|
|
73
|
+
attr_reader :finish_reason, :streaming
|
|
74
|
+
|
|
75
|
+
# @!group Error Info
|
|
76
|
+
# @!attribute [r] error_class
|
|
77
|
+
# @return [String, nil] Exception class name if failed
|
|
78
|
+
# @!attribute [r] error_message
|
|
79
|
+
# @return [String, nil] Exception message if failed
|
|
80
|
+
attr_reader :error_class, :error_message
|
|
81
|
+
|
|
82
|
+
# @!group Reliability
|
|
83
|
+
# @!attribute [r] attempts
|
|
84
|
+
# @return [Array<Hash>] Details of each attempt (for retries/fallbacks)
|
|
85
|
+
# @!attribute [r] attempts_count
|
|
86
|
+
# @return [Integer] Number of attempts made
|
|
87
|
+
attr_reader :attempts, :attempts_count
|
|
88
|
+
|
|
89
|
+
# @!group Tool Calls
|
|
90
|
+
# @!attribute [r] tool_calls
|
|
91
|
+
# @return [Array<Hash>] Tool calls made during execution
|
|
92
|
+
# @!attribute [r] tool_calls_count
|
|
93
|
+
# @return [Integer] Number of tool calls made
|
|
94
|
+
attr_reader :tool_calls, :tool_calls_count
|
|
95
|
+
|
|
96
|
+
# Creates a new Result instance
|
|
97
|
+
#
|
|
98
|
+
# @param content [Hash, String] The processed response content
|
|
99
|
+
# @param options [Hash] Execution metadata
|
|
100
|
+
def initialize(content:, **options)
|
|
101
|
+
@content = content
|
|
102
|
+
|
|
103
|
+
# Token usage
|
|
104
|
+
@input_tokens = options[:input_tokens]
|
|
105
|
+
@output_tokens = options[:output_tokens]
|
|
106
|
+
@cached_tokens = options[:cached_tokens] || 0
|
|
107
|
+
@cache_creation_tokens = options[:cache_creation_tokens] || 0
|
|
108
|
+
|
|
109
|
+
# Cost
|
|
110
|
+
@input_cost = options[:input_cost]
|
|
111
|
+
@output_cost = options[:output_cost]
|
|
112
|
+
@total_cost = options[:total_cost]
|
|
113
|
+
|
|
114
|
+
# Model info
|
|
115
|
+
@model_id = options[:model_id]
|
|
116
|
+
@chosen_model_id = options[:chosen_model_id] || options[:model_id]
|
|
117
|
+
@temperature = options[:temperature]
|
|
118
|
+
|
|
119
|
+
# Timing
|
|
120
|
+
@started_at = options[:started_at]
|
|
121
|
+
@completed_at = options[:completed_at]
|
|
122
|
+
@duration_ms = options[:duration_ms]
|
|
123
|
+
@time_to_first_token_ms = options[:time_to_first_token_ms]
|
|
124
|
+
|
|
125
|
+
# Status
|
|
126
|
+
@finish_reason = options[:finish_reason]
|
|
127
|
+
@streaming = options[:streaming] || false
|
|
128
|
+
|
|
129
|
+
# Error
|
|
130
|
+
@error_class = options[:error_class]
|
|
131
|
+
@error_message = options[:error_message]
|
|
132
|
+
|
|
133
|
+
# Reliability
|
|
134
|
+
@attempts = options[:attempts] || []
|
|
135
|
+
@attempts_count = options[:attempts_count] || 1
|
|
136
|
+
|
|
137
|
+
# Tool calls
|
|
138
|
+
@tool_calls = options[:tool_calls] || []
|
|
139
|
+
@tool_calls_count = options[:tool_calls_count] || 0
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Returns total tokens (input + output)
|
|
143
|
+
#
|
|
144
|
+
# @return [Integer] Total token count
|
|
145
|
+
def total_tokens
|
|
146
|
+
(input_tokens || 0) + (output_tokens || 0)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Returns whether streaming was enabled
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean] true if streaming was used
|
|
152
|
+
def streaming?
|
|
153
|
+
streaming == true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Returns whether the execution succeeded
|
|
157
|
+
#
|
|
158
|
+
# @return [Boolean] true if no error occurred
|
|
159
|
+
def success?
|
|
160
|
+
error_class.nil?
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Returns whether the execution failed
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] true if an error occurred
|
|
166
|
+
def error?
|
|
167
|
+
!success?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Returns whether a fallback model was used
|
|
171
|
+
#
|
|
172
|
+
# @return [Boolean] true if chosen_model_id differs from model_id
|
|
173
|
+
def used_fallback?
|
|
174
|
+
chosen_model_id.present? && chosen_model_id != model_id
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Returns whether the response was truncated due to max tokens
|
|
178
|
+
#
|
|
179
|
+
# @return [Boolean] true if finish_reason is "length"
|
|
180
|
+
def truncated?
|
|
181
|
+
finish_reason == "length"
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Returns whether tool calls were made during execution
|
|
185
|
+
#
|
|
186
|
+
# @return [Boolean] true if tool_calls_count > 0
|
|
187
|
+
def has_tool_calls?
|
|
188
|
+
tool_calls_count.to_i > 0
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Converts the result to a hash
|
|
192
|
+
#
|
|
193
|
+
# @return [Hash] All result data as a hash
|
|
194
|
+
def to_h
|
|
195
|
+
{
|
|
196
|
+
content: content,
|
|
197
|
+
input_tokens: input_tokens,
|
|
198
|
+
output_tokens: output_tokens,
|
|
199
|
+
total_tokens: total_tokens,
|
|
200
|
+
cached_tokens: cached_tokens,
|
|
201
|
+
cache_creation_tokens: cache_creation_tokens,
|
|
202
|
+
input_cost: input_cost,
|
|
203
|
+
output_cost: output_cost,
|
|
204
|
+
total_cost: total_cost,
|
|
205
|
+
model_id: model_id,
|
|
206
|
+
chosen_model_id: chosen_model_id,
|
|
207
|
+
temperature: temperature,
|
|
208
|
+
started_at: started_at,
|
|
209
|
+
completed_at: completed_at,
|
|
210
|
+
duration_ms: duration_ms,
|
|
211
|
+
time_to_first_token_ms: time_to_first_token_ms,
|
|
212
|
+
finish_reason: finish_reason,
|
|
213
|
+
streaming: streaming,
|
|
214
|
+
error_class: error_class,
|
|
215
|
+
error_message: error_message,
|
|
216
|
+
attempts_count: attempts_count,
|
|
217
|
+
attempts: attempts,
|
|
218
|
+
tool_calls: tool_calls,
|
|
219
|
+
tool_calls_count: tool_calls_count
|
|
220
|
+
}
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Delegate hash methods to content for backward compatibility
|
|
224
|
+
delegate :[], :dig, :keys, :values, :each, :map, to: :content, allow_nil: true
|
|
225
|
+
|
|
226
|
+
# Custom to_json that returns content as JSON for backward compatibility
|
|
227
|
+
#
|
|
228
|
+
# @param args [Array] Arguments passed to to_json
|
|
229
|
+
# @return [String] JSON representation
|
|
230
|
+
def to_json(*args)
|
|
231
|
+
content.to_json(*args)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
data/lib/ruby_llm/agents.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "agents/circuit_breaker"
|
|
|
10
10
|
require_relative "agents/budget_tracker"
|
|
11
11
|
require_relative "agents/alert_manager"
|
|
12
12
|
require_relative "agents/attempt_tracker"
|
|
13
|
+
require_relative "agents/result"
|
|
13
14
|
require_relative "agents/inflections" if defined?(Rails)
|
|
14
15
|
require_relative "agents/engine" if defined?(Rails::Engine)
|
|
15
16
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby_llm-agents
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- adham90
|
|
@@ -121,6 +121,7 @@ files:
|
|
|
121
121
|
- app/models/ruby_llm/agents/execution/scopes.rb
|
|
122
122
|
- app/services/ruby_llm/agents/agent_registry.rb
|
|
123
123
|
- app/views/layouts/rubyllm/agents/application.html.erb
|
|
124
|
+
- app/views/rubyllm/agents/agents/_agent.html.erb
|
|
124
125
|
- app/views/rubyllm/agents/agents/_version_comparison.html.erb
|
|
125
126
|
- app/views/rubyllm/agents/agents/index.html.erb
|
|
126
127
|
- app/views/rubyllm/agents/agents/show.html.erb
|
|
@@ -155,6 +156,7 @@ files:
|
|
|
155
156
|
- lib/generators/ruby_llm_agents/templates/add_prompts_migration.rb.tt
|
|
156
157
|
- lib/generators/ruby_llm_agents/templates/add_routing_migration.rb.tt
|
|
157
158
|
- lib/generators/ruby_llm_agents/templates/add_streaming_migration.rb.tt
|
|
159
|
+
- lib/generators/ruby_llm_agents/templates/add_tool_calls_migration.rb.tt
|
|
158
160
|
- lib/generators/ruby_llm_agents/templates/add_tracing_migration.rb.tt
|
|
159
161
|
- lib/generators/ruby_llm_agents/templates/agent.rb.tt
|
|
160
162
|
- lib/generators/ruby_llm_agents/templates/application_agent.rb.tt
|
|
@@ -175,6 +177,7 @@ files:
|
|
|
175
177
|
- lib/ruby_llm/agents/instrumentation.rb
|
|
176
178
|
- lib/ruby_llm/agents/redactor.rb
|
|
177
179
|
- lib/ruby_llm/agents/reliability.rb
|
|
180
|
+
- lib/ruby_llm/agents/result.rb
|
|
178
181
|
- lib/ruby_llm/agents/version.rb
|
|
179
182
|
homepage: https://github.com/adham90/ruby_llm-agents
|
|
180
183
|
licenses:
|