Class: DecisionAgent::Simulation::ShadowTestEngine
- Inherits:
-
Object
- Object
- DecisionAgent::Simulation::ShadowTestEngine
- Defined in:
- lib/decision_agent/simulation/shadow_test_engine.rb
Overview
Engine for shadow testing - comparing new rules against production without affecting outcomes
Defined Under Namespace
Classes: ProgressTracker
Instance Attribute Summary collapse
-
#production_agent ⇒ Object
readonly
Returns the value of attribute production_agent.
-
#version_manager ⇒ Object
readonly
Returns the value of attribute version_manager.
Instance Method Summary collapse
- #add_differences(result, production_decision, shadow_decision) ⇒ Object
-
#batch_test(contexts:, shadow_version:, options: {}) ⇒ Hash
Batch shadow test multiple contexts.
- #build_comparison_result(context, production_decision, shadow_decision) ⇒ Object
- #execute_contexts_with_progress(contexts, shadow_version, shadow_agent, options) ⇒ Object
- #execute_production_decision(context) ⇒ Object
- #execute_sequential_contexts(contexts, shadow_version, options, results, progress_tracker) ⇒ Object
- #execute_shadow_decision(context, shadow_version) ⇒ Object
-
#initialize(production_agent:, version_manager: nil) ⇒ ShadowTestEngine
constructor
A new instance of ShadowTestEngine.
- #normalize_context(context) ⇒ Object
-
#test(context:, shadow_version:, options: {}) ⇒ Hash
Execute shadow test - compare shadow version against production.
Constructor Details
#initialize(production_agent:, version_manager: nil) ⇒ ShadowTestEngine
Returns a new instance of ShadowTestEngine.
11 12 13 14 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 11 def initialize(production_agent:, version_manager: nil) @production_agent = production_agent @version_manager = version_manager || Versioning::VersionManager.new end |
Instance Attribute Details
#production_agent ⇒ Object (readonly)
Returns the value of attribute production_agent.
9 10 11 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 9 def production_agent @production_agent end |
#version_manager ⇒ Object (readonly)
Returns the value of attribute version_manager.
9 10 11 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 9 def version_manager @version_manager end |
Instance Method Details
#add_differences(result, production_decision, shadow_decision) ⇒ Object
70 71 72 73 74 75 76 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 70 def add_differences(result, production_decision, shadow_decision) result[:differences] = { decision_mismatch: true, production_explanations: production_decision&.explanations || [], shadow_explanations: shadow_decision&.explanations || [] } end |
#batch_test(contexts:, shadow_version:, options: {}) ⇒ Hash
Batch shadow test multiple contexts
86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 86 def batch_test(contexts:, shadow_version:, options: {}) = { parallel: true, thread_count: 4, progress_callback: nil, track_differences: true, record_results: false }.merge() shadow_agent = build_shadow_agent(shadow_version) results = execute_contexts_with_progress(contexts, shadow_version, shadow_agent, ) build_batch_report(results) end |
#build_comparison_result(context, production_decision, shadow_decision) ⇒ Object
57 58 59 60 61 62 63 64 65 66 67 68 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 57 def build_comparison_result(context, production_decision, shadow_decision) { context: context.to_h, production_decision: production_decision&.decision, production_confidence: production_decision&.confidence || 0.0, shadow_decision: shadow_decision&.decision, shadow_confidence: shadow_decision&.confidence || 0.0, matches: production_decision&.decision == shadow_decision&.decision, confidence_delta: (shadow_decision&.confidence || 0.0) - (production_decision&.confidence || 0.0), timestamp: Time.now.utc.iso8601 } end |
#execute_contexts_with_progress(contexts, shadow_version, shadow_agent, options) ⇒ Object
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 101 def execute_contexts_with_progress(contexts, shadow_version, shadow_agent, ) results = [] mutex = Mutex.new progress_tracker = ProgressTracker.new(contexts.size, [:progress_callback]) if [:parallel] && contexts.size > 1 execute_parallel(contexts, shadow_agent, , mutex) do |result| mutex.synchronize do results << result progress_tracker.increment end end else execute_sequential_contexts(contexts, shadow_version, , results, progress_tracker) end results end |
#execute_production_decision(context) ⇒ Object
44 45 46 47 48 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 44 def execute_production_decision(context) @production_agent.decide(context: context) rescue NoEvaluationsError nil end |
#execute_sequential_contexts(contexts, shadow_version, options, results, progress_tracker) ⇒ Object
120 121 122 123 124 125 126 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 120 def execute_sequential_contexts(contexts, shadow_version, , results, progress_tracker) contexts.each_with_index do |context, _index| result = test(context: context, shadow_version: shadow_version, options: ) results << result progress_tracker.increment end end |
#execute_shadow_decision(context, shadow_version) ⇒ Object
50 51 52 53 54 55 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 50 def execute_shadow_decision(context, shadow_version) shadow_agent = build_shadow_agent(shadow_version) shadow_agent.decide(context: context) rescue NoEvaluationsError nil end |
#normalize_context(context) ⇒ Object
40 41 42 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 40 def normalize_context(context) context.is_a?(Context) ? context : Context.new(context) end |
#test(context:, shadow_version:, options: {}) ⇒ Hash
Execute shadow test - compare shadow version against production
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/decision_agent/simulation/shadow_test_engine.rb', line 23 def test(context:, shadow_version:, options: {}) = { track_differences: true, record_results: false }.merge() ctx = normalize_context(context) production_decision = execute_production_decision(ctx) shadow_decision = execute_shadow_decision(ctx, shadow_version) result = build_comparison_result(ctx, production_decision, shadow_decision) add_differences(result, production_decision, shadow_decision) if [:track_differences] && !result[:matches] record_result(result, shadow_version) if [:record_results] result end |