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
|
@@ -332,6 +332,14 @@ module RubyLLM
|
|
|
332
332
|
# @param temperature [Float] Override the class-level temperature
|
|
333
333
|
# @param options [Hash] Agent parameters defined via the param DSL
|
|
334
334
|
def initialize(model: self.class.model, temperature: self.class.temperature, **options)
|
|
335
|
+
# Merge tracker defaults (shared options like tenant) — explicit opts win
|
|
336
|
+
tracker = Thread.current[:ruby_llm_agents_tracker]
|
|
337
|
+
if tracker
|
|
338
|
+
options = tracker.defaults.merge(options)
|
|
339
|
+
@_track_request_id = tracker.request_id
|
|
340
|
+
@_track_tags = tracker.tags
|
|
341
|
+
end
|
|
342
|
+
|
|
335
343
|
@ask_message = options.delete(:_ask_message)
|
|
336
344
|
@parent_execution_id = options.delete(:_parent_execution_id)
|
|
337
345
|
@root_execution_id = options.delete(:_root_execution_id)
|
|
@@ -506,6 +514,7 @@ module RubyLLM
|
|
|
506
514
|
stream_block: (block if streaming_enabled?),
|
|
507
515
|
parent_execution_id: @parent_execution_id,
|
|
508
516
|
root_execution_id: @root_execution_id,
|
|
517
|
+
debug: @options[:debug],
|
|
509
518
|
options: execution_options
|
|
510
519
|
)
|
|
511
520
|
end
|
|
@@ -721,6 +730,12 @@ module RubyLLM
|
|
|
721
730
|
capture_response(response, context)
|
|
722
731
|
result = build_result(process_response(response), response, context)
|
|
723
732
|
context.output = result
|
|
733
|
+
rescue RubyLLM::Agents::CancelledError
|
|
734
|
+
context.output = Result.new(content: nil, cancelled: true)
|
|
735
|
+
rescue RubyLLM::UnauthorizedError, RubyLLM::ForbiddenError => e
|
|
736
|
+
raise_with_setup_hint(e, context)
|
|
737
|
+
rescue RubyLLM::ModelNotFoundError => e
|
|
738
|
+
raise_with_model_hint(e, context)
|
|
724
739
|
ensure
|
|
725
740
|
Thread.current[:ruby_llm_agents_caller_context] = previous_context
|
|
726
741
|
end
|
|
@@ -733,12 +748,16 @@ module RubyLLM
|
|
|
733
748
|
effective_model = context&.model || model
|
|
734
749
|
chat_opts = {model: effective_model}
|
|
735
750
|
|
|
736
|
-
#
|
|
751
|
+
# Use scoped RubyLLM::Context for thread-safe per-tenant API keys.
|
|
752
|
+
# RubyLLM::Context#chat creates a Chat with the scoped config,
|
|
753
|
+
# so we call .chat on the context instead of RubyLLM.chat.
|
|
737
754
|
llm_ctx = context&.llm
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
.
|
|
755
|
+
client = if llm_ctx.is_a?(RubyLLM::Context)
|
|
756
|
+
llm_ctx.chat(**chat_opts)
|
|
757
|
+
else
|
|
758
|
+
RubyLLM.chat(**chat_opts)
|
|
759
|
+
end
|
|
760
|
+
client = client.with_temperature(temperature)
|
|
742
761
|
|
|
743
762
|
client = client.with_instructions(system_prompt) if system_prompt
|
|
744
763
|
client = client.with_schema(schema) if schema
|
|
@@ -889,8 +908,9 @@ module RubyLLM
|
|
|
889
908
|
# @param context [Pipeline::Context] The context
|
|
890
909
|
# @return [Result] The result object
|
|
891
910
|
def build_result(content, response, context)
|
|
892
|
-
|
|
911
|
+
result_opts = {
|
|
893
912
|
content: content,
|
|
913
|
+
agent_class_name: self.class.name,
|
|
894
914
|
input_tokens: context.input_tokens,
|
|
895
915
|
output_tokens: context.output_tokens,
|
|
896
916
|
input_cost: context.input_cost,
|
|
@@ -907,7 +927,12 @@ module RubyLLM
|
|
|
907
927
|
streaming: streaming_enabled?,
|
|
908
928
|
attempts_count: context.attempts_made || 1,
|
|
909
929
|
execution_id: context.execution_id
|
|
910
|
-
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
# Attach pipeline trace when debug mode is enabled
|
|
933
|
+
result_opts[:trace] = context.trace if context.trace_enabled? && context.trace.any?
|
|
934
|
+
|
|
935
|
+
Result.new(**result_opts)
|
|
911
936
|
end
|
|
912
937
|
|
|
913
938
|
# Extracts thinking data from a response for inclusion in Result
|
|
@@ -1077,6 +1102,44 @@ module RubyLLM
|
|
|
1077
1102
|
tool_call[key] || tool_call[key.to_s]
|
|
1078
1103
|
end
|
|
1079
1104
|
end
|
|
1105
|
+
|
|
1106
|
+
# Re-raises auth errors with actionable setup guidance
|
|
1107
|
+
def raise_with_setup_hint(error, context)
|
|
1108
|
+
effective_model = context&.model || model
|
|
1109
|
+
provider = detect_provider(effective_model)
|
|
1110
|
+
|
|
1111
|
+
hint = "#{self.class.name} failed: #{error.message}\n\n" \
|
|
1112
|
+
"The API key for #{provider || "your provider"} is missing or invalid.\n" \
|
|
1113
|
+
"Fix: Set the key in config/initializers/ruby_llm_agents.rb\n" \
|
|
1114
|
+
" or run: rails ruby_llm_agents:doctor"
|
|
1115
|
+
|
|
1116
|
+
raise RubyLLM::Agents::ConfigurationError, hint
|
|
1117
|
+
end
|
|
1118
|
+
|
|
1119
|
+
# Re-raises model errors with actionable guidance
|
|
1120
|
+
def raise_with_model_hint(error, context)
|
|
1121
|
+
effective_model = context&.model || model
|
|
1122
|
+
|
|
1123
|
+
hint = "#{self.class.name} failed: #{error.message}\n\n" \
|
|
1124
|
+
"Model '#{effective_model}' was not found.\n" \
|
|
1125
|
+
"Fix: Check the model name or set a default in your initializer:\n" \
|
|
1126
|
+
" config.default_model = \"gpt-4o\""
|
|
1127
|
+
|
|
1128
|
+
raise RubyLLM::Agents::ConfigurationError, hint
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
# Best-effort provider detection from model name
|
|
1132
|
+
def detect_provider(model_id)
|
|
1133
|
+
return nil unless model_id
|
|
1134
|
+
|
|
1135
|
+
case model_id.to_s
|
|
1136
|
+
when /gpt|o[1-9]|dall-e|whisper|tts/i then "OpenAI"
|
|
1137
|
+
when /claude/i then "Anthropic"
|
|
1138
|
+
when /gemini|gemma/i then "Google (Gemini)"
|
|
1139
|
+
when /deepseek/i then "DeepSeek"
|
|
1140
|
+
when /mistral|mixtral/i then "Mistral"
|
|
1141
|
+
end
|
|
1142
|
+
end
|
|
1080
1143
|
end
|
|
1081
1144
|
end
|
|
1082
1145
|
end
|
|
@@ -87,6 +87,10 @@ module RubyLLM
|
|
|
87
87
|
run_callbacks(:after, context, response)
|
|
88
88
|
|
|
89
89
|
context.output = build_result(processed_content, response, context)
|
|
90
|
+
rescue RubyLLM::UnauthorizedError, RubyLLM::ForbiddenError => e
|
|
91
|
+
raise_with_setup_hint(e, context)
|
|
92
|
+
rescue RubyLLM::ModelNotFoundError => e
|
|
93
|
+
raise_with_model_hint(e, context)
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
# Returns the resolved tenant ID for tracking
|
|
@@ -387,6 +387,7 @@ module RubyLLM
|
|
|
387
387
|
:default_total_timeout,
|
|
388
388
|
:default_streaming,
|
|
389
389
|
:default_tools,
|
|
390
|
+
:default_tool_timeout,
|
|
390
391
|
:default_thinking,
|
|
391
392
|
:on_alert,
|
|
392
393
|
:persist_prompts,
|
|
@@ -639,6 +640,7 @@ module RubyLLM
|
|
|
639
640
|
# Streaming, tools, and thinking defaults
|
|
640
641
|
@default_streaming = false
|
|
641
642
|
@default_tools = []
|
|
643
|
+
@default_tool_timeout = nil
|
|
642
644
|
@default_thinking = nil
|
|
643
645
|
|
|
644
646
|
# Governance defaults
|
|
@@ -819,6 +821,16 @@ module RubyLLM
|
|
|
819
821
|
tenant_resolver&.call
|
|
820
822
|
end
|
|
821
823
|
|
|
824
|
+
# Returns a concise string representation for debugging
|
|
825
|
+
#
|
|
826
|
+
# @return [String] Summary of key configuration values
|
|
827
|
+
def inspect
|
|
828
|
+
"#<#{self.class} model=#{default_model.inspect} temperature=#{default_temperature} " \
|
|
829
|
+
"timeout=#{default_timeout} streaming=#{default_streaming} " \
|
|
830
|
+
"multi_tenancy=#{multi_tenancy_enabled} async_logging=#{async_logging} " \
|
|
831
|
+
"track_executions=#{track_executions}>"
|
|
832
|
+
end
|
|
833
|
+
|
|
822
834
|
# Returns whether the async gem is available
|
|
823
835
|
#
|
|
824
836
|
# @return [Boolean] true if async gem is loaded
|
|
@@ -29,6 +29,9 @@ module RubyLLM
|
|
|
29
29
|
# Raised when an execution cannot be replayed
|
|
30
30
|
class ReplayError < Error; end
|
|
31
31
|
|
|
32
|
+
# Raised when an agent execution is cancelled via on_cancelled
|
|
33
|
+
class CancelledError < Error; end
|
|
34
|
+
|
|
32
35
|
# Raised when the TTS API returns an error response
|
|
33
36
|
class SpeechApiError < Error
|
|
34
37
|
attr_reader :status, :response_body
|
|
@@ -46,6 +46,9 @@ module RubyLLM
|
|
|
46
46
|
# Response metadata
|
|
47
47
|
attr_accessor :model_used, :finish_reason, :time_to_first_token_ms
|
|
48
48
|
|
|
49
|
+
# Debug trace (set when debug: true is passed)
|
|
50
|
+
attr_accessor :trace
|
|
51
|
+
|
|
49
52
|
# Streaming support
|
|
50
53
|
attr_accessor :stream_block, :skip_cache
|
|
51
54
|
|
|
@@ -85,6 +88,10 @@ module RubyLLM
|
|
|
85
88
|
@skip_cache = skip_cache
|
|
86
89
|
@stream_block = stream_block
|
|
87
90
|
|
|
91
|
+
# Debug trace
|
|
92
|
+
@trace = []
|
|
93
|
+
@trace_enabled = options[:debug] == true
|
|
94
|
+
|
|
88
95
|
# Initialize tracking fields
|
|
89
96
|
@attempt = 0
|
|
90
97
|
@attempts_made = 0
|
|
@@ -108,6 +115,23 @@ module RubyLLM
|
|
|
108
115
|
((@completed_at - @started_at) * 1000).to_i
|
|
109
116
|
end
|
|
110
117
|
|
|
118
|
+
# Is debug tracing enabled?
|
|
119
|
+
#
|
|
120
|
+
# @return [Boolean]
|
|
121
|
+
def trace_enabled?
|
|
122
|
+
@trace_enabled
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Adds a trace entry for a middleware execution
|
|
126
|
+
#
|
|
127
|
+
# @param middleware_name [String] Name of the middleware
|
|
128
|
+
# @param started_at [Time] When the middleware started
|
|
129
|
+
# @param duration_ms [Float] How long the middleware took in ms
|
|
130
|
+
# @param action [String, nil] Optional action description (e.g., "cache hit")
|
|
131
|
+
def add_trace(middleware_name, started_at:, duration_ms:, action: nil)
|
|
132
|
+
@trace << {middleware: middleware_name, started_at: started_at, duration_ms: duration_ms, action: action}.compact
|
|
133
|
+
end
|
|
134
|
+
|
|
111
135
|
# Was the result served from cache?
|
|
112
136
|
#
|
|
113
137
|
# @return [Boolean]
|
|
@@ -230,6 +254,8 @@ module RubyLLM
|
|
|
230
254
|
# Preserve execution hierarchy
|
|
231
255
|
new_ctx.parent_execution_id = @parent_execution_id
|
|
232
256
|
new_ctx.root_execution_id = @root_execution_id
|
|
257
|
+
# Preserve trace across retries
|
|
258
|
+
new_ctx.trace = @trace
|
|
233
259
|
new_ctx
|
|
234
260
|
end
|
|
235
261
|
|
|
@@ -41,6 +41,8 @@ module RubyLLM
|
|
|
41
41
|
# @abstract Subclass and implement {#call}
|
|
42
42
|
#
|
|
43
43
|
class Base
|
|
44
|
+
LOG_TAG = "[RubyLLM::Agents::Pipeline]"
|
|
45
|
+
|
|
44
46
|
# @param app [#call] The next handler in the chain
|
|
45
47
|
# @param agent_class [Class] The agent class (for reading DSL config)
|
|
46
48
|
def initialize(app, agent_class)
|
|
@@ -100,22 +102,74 @@ module RubyLLM
|
|
|
100
102
|
RubyLLM::Agents.configuration
|
|
101
103
|
end
|
|
102
104
|
|
|
105
|
+
# Builds a log prefix with context from the execution
|
|
106
|
+
#
|
|
107
|
+
# Includes agent type, execution ID, and tenant when available
|
|
108
|
+
# so log messages can be traced through the full pipeline.
|
|
109
|
+
#
|
|
110
|
+
# @param context [Context, nil] The execution context
|
|
111
|
+
# @return [String] Formatted log prefix
|
|
112
|
+
def log_prefix(context = nil)
|
|
113
|
+
return LOG_TAG unless context
|
|
114
|
+
|
|
115
|
+
parts = [LOG_TAG]
|
|
116
|
+
parts << context.agent_class.name if context.agent_class
|
|
117
|
+
parts << "exec=#{context.execution_id}" if context.execution_id
|
|
118
|
+
parts << "tenant=#{context.tenant_id}" if context.tenant_id
|
|
119
|
+
parts.join(" ")
|
|
120
|
+
end
|
|
121
|
+
|
|
103
122
|
# Log a debug message if Rails logger is available
|
|
104
123
|
#
|
|
105
124
|
# @param message [String] The message to log
|
|
106
|
-
|
|
125
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
126
|
+
def debug(message, context = nil)
|
|
107
127
|
return unless defined?(Rails) && Rails.logger
|
|
108
128
|
|
|
109
|
-
Rails.logger.debug("
|
|
129
|
+
Rails.logger.debug("#{log_prefix(context)} #{message}")
|
|
110
130
|
end
|
|
111
131
|
|
|
112
132
|
# Log an error message if Rails logger is available
|
|
113
133
|
#
|
|
114
134
|
# @param message [String] The message to log
|
|
115
|
-
|
|
135
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
136
|
+
def error(message, context = nil)
|
|
137
|
+
return unless defined?(Rails) && Rails.logger
|
|
138
|
+
|
|
139
|
+
Rails.logger.error("#{log_prefix(context)} #{message}")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Traces middleware execution when debug mode is enabled.
|
|
143
|
+
#
|
|
144
|
+
# Wraps a block with timing instrumentation. When tracing is not
|
|
145
|
+
# enabled, yields directly with zero overhead.
|
|
146
|
+
#
|
|
147
|
+
# @param context [Context] The execution context
|
|
148
|
+
# @param action [String, nil] Optional action description
|
|
149
|
+
# @yield The block to trace
|
|
150
|
+
# @return [Object] The block's return value
|
|
151
|
+
def trace(context, action: nil)
|
|
152
|
+
unless context.trace_enabled?
|
|
153
|
+
return yield
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
middleware_name = self.class.name&.split("::")&.last || self.class.to_s
|
|
157
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
158
|
+
result = yield
|
|
159
|
+
duration_ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at) * 1000).round(2)
|
|
160
|
+
context.add_trace(middleware_name, started_at: Time.current, duration_ms: duration_ms, action: action)
|
|
161
|
+
debug("#{middleware_name} completed in #{duration_ms}ms#{" (#{action})" if action}", context)
|
|
162
|
+
result
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Log a warning message if Rails logger is available
|
|
166
|
+
#
|
|
167
|
+
# @param message [String] The message to log
|
|
168
|
+
# @param context [Context, nil] Optional execution context for structured prefix
|
|
169
|
+
def warn(message, context = nil)
|
|
116
170
|
return unless defined?(Rails) && Rails.logger
|
|
117
171
|
|
|
118
|
-
Rails.logger.
|
|
172
|
+
Rails.logger.warn("#{log_prefix(context)} #{message}")
|
|
119
173
|
end
|
|
120
174
|
end
|
|
121
175
|
end
|
|
@@ -32,21 +32,23 @@ module RubyLLM
|
|
|
32
32
|
def call(context)
|
|
33
33
|
return @app.call(context) unless budgets_enabled?
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
trace(context) do
|
|
36
|
+
# Check budget before execution
|
|
37
|
+
check_budget!(context)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
# Execute the chain
|
|
40
|
+
@app.call(context)
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
# Record spend after successful execution (if not cached)
|
|
43
|
+
if context.success? && !context.cached?
|
|
44
|
+
record_spend!(context)
|
|
45
|
+
emit_budget_notification("ruby_llm_agents.budget.record", context,
|
|
46
|
+
total_cost: context.total_cost,
|
|
47
|
+
total_tokens: context.total_tokens)
|
|
48
|
+
end
|
|
48
49
|
|
|
49
|
-
|
|
50
|
+
context
|
|
51
|
+
end
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
private
|
|
@@ -65,7 +67,7 @@ module RubyLLM
|
|
|
65
67
|
}.merge(extras)
|
|
66
68
|
)
|
|
67
69
|
rescue => e
|
|
68
|
-
debug("Budget notification failed: #{e.message}")
|
|
70
|
+
debug("Budget notification failed: #{e.message}", context)
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
# Returns whether budgets are enabled globally
|
|
@@ -106,7 +108,7 @@ module RubyLLM
|
|
|
106
108
|
raise
|
|
107
109
|
rescue => e
|
|
108
110
|
# Log at error level so unexpected failures are visible in logs
|
|
109
|
-
error("Budget check failed: #{e.class}: #{e.message}")
|
|
111
|
+
error("Budget check failed: #{e.class}: #{e.message}", context)
|
|
110
112
|
end
|
|
111
113
|
|
|
112
114
|
# Records spend after execution
|
|
@@ -122,7 +124,7 @@ module RubyLLM
|
|
|
122
124
|
tenant.record_execution!(
|
|
123
125
|
cost: context.total_cost || 0,
|
|
124
126
|
tokens: context.total_tokens || 0,
|
|
125
|
-
error: context.
|
|
127
|
+
error: context.failed?
|
|
126
128
|
)
|
|
127
129
|
return
|
|
128
130
|
end
|
|
@@ -144,8 +146,6 @@ module RubyLLM
|
|
|
144
146
|
tenant_id: context.tenant_id
|
|
145
147
|
)
|
|
146
148
|
end
|
|
147
|
-
rescue => e
|
|
148
|
-
error("Failed to record spend: #{e.message}")
|
|
149
149
|
end
|
|
150
150
|
end
|
|
151
151
|
end
|
|
@@ -31,34 +31,46 @@ module RubyLLM
|
|
|
31
31
|
def call(context)
|
|
32
32
|
return @app.call(context) unless cache_enabled?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
cache_action = nil
|
|
35
|
+
result = trace(context, action: "cache") do
|
|
36
|
+
cache_key = generate_cache_key(context)
|
|
37
|
+
|
|
38
|
+
# Skip cache read if skip_cache is true
|
|
39
|
+
unless context.skip_cache
|
|
40
|
+
# Try to read from cache
|
|
41
|
+
if (cached = cache_read(cache_key))
|
|
42
|
+
context.output = cached
|
|
43
|
+
context.cached = true
|
|
44
|
+
context[:cache_key] = cache_key
|
|
45
|
+
cache_action = "hit"
|
|
46
|
+
debug("Cache hit for #{cache_key}", context)
|
|
47
|
+
emit_cache_notification("ruby_llm_agents.cache.hit", cache_key)
|
|
48
|
+
next context
|
|
49
|
+
end
|
|
46
50
|
end
|
|
47
|
-
end
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
cache_action = "miss"
|
|
53
|
+
emit_cache_notification("ruby_llm_agents.cache.miss", cache_key)
|
|
54
|
+
|
|
55
|
+
# Execute the chain
|
|
56
|
+
@app.call(context)
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
58
|
+
# Cache successful results
|
|
59
|
+
if context.success?
|
|
60
|
+
cache_write(cache_key, context.output)
|
|
61
|
+
debug("Cache write for #{cache_key}", context)
|
|
62
|
+
emit_cache_notification("ruby_llm_agents.cache.write", cache_key)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context
|
|
66
|
+
end
|
|
53
67
|
|
|
54
|
-
#
|
|
55
|
-
if context.
|
|
56
|
-
|
|
57
|
-
debug("Cache write for #{cache_key}")
|
|
58
|
-
emit_cache_notification("ruby_llm_agents.cache.write", cache_key)
|
|
68
|
+
# Update the last trace entry with the specific cache action
|
|
69
|
+
if context.trace_enabled? && cache_action && context.trace.last
|
|
70
|
+
context.trace.last[:action] = cache_action
|
|
59
71
|
end
|
|
60
72
|
|
|
61
|
-
|
|
73
|
+
result
|
|
62
74
|
end
|
|
63
75
|
|
|
64
76
|
private
|