dspy 0.17.0 → 0.18.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.
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sorbet-runtime'
4
- require_relative '../instrumentation'
5
4
  require_relative '../evaluate'
6
5
  require_relative '../example'
7
6
 
@@ -313,22 +312,22 @@ module DSPy
313
312
  # Instrument optimization steps
314
313
  sig { params(step_name: String, payload: T::Hash[Symbol, T.untyped], block: T.proc.returns(T.untyped)).returns(T.untyped) }
315
314
  def instrument_step(step_name, payload = {}, &block)
316
- event_name = "dspy.optimization.#{step_name}"
317
-
318
- Instrumentation.instrument(event_name, payload.merge({
319
- teleprompter_class: self.class.name,
320
- config: @config.to_h
321
- }), &block)
315
+ DSPy::Context.with_span(
316
+ operation: "optimization.#{step_name}",
317
+ 'dspy.module' => 'Teleprompter',
318
+ 'teleprompter.class' => self.class.name,
319
+ 'teleprompter.config' => @config.to_h,
320
+ **payload
321
+ ) do
322
+ yield
323
+ end
322
324
  end
323
325
 
324
326
  # Emit optimization events
325
327
  sig { params(event_name: String, payload: T::Hash[Symbol, T.untyped]).void }
326
328
  def emit_event(event_name, payload = {})
327
- full_event_name = "dspy.optimization.#{event_name}"
328
-
329
- Instrumentation.emit(full_event_name, payload.merge({
330
- teleprompter_class: self.class.name,
331
- timestamp: Time.now.iso8601
329
+ DSPy.log("optimization.#{event_name}", **payload.merge({
330
+ 'teleprompter.class': self.class.name
332
331
  }))
333
332
  end
334
333
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'sorbet-runtime'
4
- require_relative '../instrumentation'
5
4
  require_relative '../evaluate'
6
5
  require_relative '../example'
7
6
  require_relative 'data_handler'
@@ -104,11 +103,13 @@ module DSPy
104
103
  ).returns(BootstrapResult)
105
104
  end
106
105
  def self.create_n_fewshot_demo_sets(program, trainset, config: BootstrapConfig.new, metric: nil)
107
- Instrumentation.instrument('dspy.optimization.bootstrap_start', {
108
- trainset_size: trainset.size,
109
- max_bootstrapped_examples: config.max_bootstrapped_examples,
110
- num_candidate_sets: config.num_candidate_sets
111
- }) do
106
+ DSPy::Context.with_span(
107
+ operation: 'optimization.bootstrap_start',
108
+ 'dspy.module' => 'Bootstrap',
109
+ 'bootstrap.trainset_size' => trainset.size,
110
+ 'bootstrap.max_examples' => config.max_bootstrapped_examples,
111
+ 'bootstrap.num_candidate_sets' => config.num_candidate_sets
112
+ ) do
112
113
  # Convert to typed examples if needed
113
114
  typed_examples = ensure_typed_examples(trainset)
114
115
 
@@ -172,11 +173,13 @@ module DSPy
172
173
  ).returns(DSPy::Evaluate::BatchEvaluationResult)
173
174
  end
174
175
  def self.eval_candidate_program_minibatch(program, examples, config, metric)
175
- Instrumentation.instrument('dspy.optimization.minibatch_evaluation', {
176
- total_examples: examples.size,
177
- minibatch_size: config.minibatch_size,
178
- num_batches: (examples.size.to_f / config.minibatch_size).ceil
179
- }) do
176
+ DSPy::Context.with_span(
177
+ operation: 'optimization.minibatch_evaluation',
178
+ 'dspy.module' => 'Bootstrap',
179
+ 'minibatch.total_examples' => examples.size,
180
+ 'minibatch.size' => config.minibatch_size,
181
+ 'minibatch.num_batches' => (examples.size.to_f / config.minibatch_size).ceil
182
+ ) do
180
183
  # Randomly sample a minibatch for evaluation
181
184
  sample_size = [config.minibatch_size, examples.size].min
182
185
  sampled_examples = examples.sample(sample_size)
@@ -340,23 +343,22 @@ module DSPy
340
343
  # Emit bootstrap completion event
341
344
  sig { params(statistics: T::Hash[Symbol, T.untyped]).void }
342
345
  def self.emit_bootstrap_complete_event(statistics)
343
- Instrumentation.emit('dspy.optimization.bootstrap_complete', {
344
- successful_count: statistics[:successful_count],
345
- failed_count: statistics[:failed_count],
346
- success_rate: statistics[:success_rate],
347
- candidate_sets_created: statistics[:candidate_sets_created],
348
- average_set_size: statistics[:average_set_size]
346
+ DSPy.log('optimization.bootstrap_complete', **{
347
+ 'bootstrap.successful_count' => statistics[:successful_count],
348
+ 'bootstrap.failed_count' => statistics[:failed_count],
349
+ 'bootstrap.success_rate' => statistics[:success_rate],
350
+ 'bootstrap.candidate_sets_created' => statistics[:candidate_sets_created],
351
+ 'bootstrap.average_set_size' => statistics[:average_set_size]
349
352
  })
350
353
  end
351
354
 
352
355
  # Emit individual bootstrap example event
353
356
  sig { params(index: Integer, success: T::Boolean, error: T.nilable(String)).void }
354
357
  def self.emit_bootstrap_example_event(index, success, error)
355
- Instrumentation.emit('dspy.optimization.bootstrap_example', {
356
- example_index: index,
357
- success: success,
358
- error: error,
359
- timestamp: Time.now.iso8601
358
+ DSPy.log('optimization.bootstrap_example', **{
359
+ 'bootstrap.example_index' => index,
360
+ 'bootstrap.success' => success,
361
+ 'bootstrap.error' => error
360
362
  })
361
363
  end
362
364
 
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.17.0"
4
+ VERSION = "0.18.0"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -7,18 +7,11 @@ require 'securerandom'
7
7
  require_relative 'dspy/version'
8
8
  require_relative 'dspy/errors'
9
9
  require_relative 'dspy/type_serializer'
10
+ require_relative 'dspy/observability'
11
+ require_relative 'dspy/context'
10
12
 
11
13
  module DSPy
12
14
  extend Dry::Configurable
13
-
14
- # Timestamp format options for instrumentation events
15
- class TimestampFormat < T::Enum
16
- enums do
17
- ISO8601 = new('iso8601')
18
- RFC3339_NANO = new('rfc3339_nano')
19
- UNIX_NANO = new('unix_nano')
20
- end
21
- end
22
15
 
23
16
  setting :lm
24
17
  setting :logger, default: Dry.Logger(:dspy, formatter: :string)
@@ -35,86 +28,52 @@ module DSPy
35
28
 
36
29
  # Test mode disables sleeps in retry logic
37
30
  setting :test_mode, default: false
38
-
39
- # Nested instrumentation configuration using proper dry-configurable syntax
40
- setting :instrumentation do
41
- # Core settings
42
- setting :enabled, default: false
43
- setting :subscribers, default: []
44
- setting :sampling_rate, default: 1.0
45
- setting :async_processing, default: false
46
- setting :buffer_size, default: 1000
47
- setting :flush_interval, default: 30
48
- setting :error_reporting, default: false
49
- setting :error_service, default: nil
50
- setting :sampling_rules, default: {}
51
- setting :timestamp_format, default: TimestampFormat::ISO8601
52
-
53
- # Nested correlation ID configuration
54
- setting :correlation_id do
55
- setting :enabled, default: false
56
- setting :header, default: 'X-Correlation-ID'
57
- setting :generator, default: -> { SecureRandom.uuid }
58
- end
59
-
60
- # Nested logger configuration
61
- setting :logger do
62
- setting :level, default: :info
63
- setting :include_payloads, default: true
64
- setting :correlation_id, default: true
65
- setting :sampling, default: {}
66
- setting :sampling_conditions, default: {}
67
- end
68
-
69
- # Nested OpenTelemetry configuration
70
- setting :otel do
71
- setting :tracer_name, default: 'dspy-ruby'
72
- setting :service_name, default: 'dspy-application'
73
- setting :service_version, default: DSPy::VERSION
74
- setting :endpoint, default: -> { ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] }
75
- end
76
-
77
- # Nested New Relic configuration
78
- setting :newrelic do
79
- setting :app_name, default: 'DSPy Application'
80
- setting :license_key, default: -> { ENV['NEW_RELIC_LICENSE_KEY'] }
81
- setting :custom_attributes, default: {}
82
- end
83
-
84
- # Nested Langfuse configuration
85
- setting :langfuse do
86
- setting :public_key, default: -> { ENV['LANGFUSE_PUBLIC_KEY'] }
87
- setting :secret_key, default: -> { ENV['LANGFUSE_SECRET_KEY'] }
88
- setting :host, default: -> { ENV['LANGFUSE_HOST'] }
89
- setting :track_tokens, default: true
90
- setting :track_costs, default: true
91
- setting :track_prompts, default: true
92
- end
93
- end
94
31
 
95
32
  def self.logger
96
- config.logger
33
+ @logger ||= create_logger
97
34
  end
98
35
 
99
- # Validation methods for instrumentation configuration
100
- def self.validate_instrumentation!
101
- config = self.config.instrumentation
36
+ def self.log(event, **attributes)
37
+ return unless logger
102
38
 
103
- raise ArgumentError, "Sampling rate must be between 0.0 and 1.0" unless config.sampling_rate.between?(0.0, 1.0)
104
- raise ArgumentError, "Buffer size must be positive" unless config.buffer_size > 0
105
- raise ArgumentError, "Flush interval must be positive" unless config.flush_interval > 0
106
- raise ArgumentError, "Invalid timestamp format" unless config.timestamp_format.is_a?(TimestampFormat)
39
+ # Merge context automatically (but don't include span_stack)
40
+ context = Context.current.dup
41
+ context.delete(:span_stack)
42
+ attributes = context.merge(attributes)
43
+ attributes[:event] = event
107
44
 
108
- if config.enabled && config.subscribers.empty?
109
- raise ArgumentError, "Must specify at least one subscriber when instrumentation is enabled"
110
- end
45
+ # Use Dry::Logger's structured logging
46
+ logger.info(attributes)
47
+ end
48
+
49
+ def self.create_logger
50
+ env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
51
+ log_output = ENV['DSPY_LOG'] # Allow override
111
52
 
112
- # Validate subscribers are valid symbols
113
- invalid_subscribers = config.subscribers - [:logger, :otel, :newrelic, :langfuse]
114
- unless invalid_subscribers.empty?
115
- raise ArgumentError, "Invalid subscribers: #{invalid_subscribers.join(', ')}"
53
+ case env
54
+ when 'test'
55
+ # Test: key=value format to log/test.log (or override)
56
+ Dry.Logger(:dspy, formatter: :string) do |config|
57
+ config.add_backend(stream: log_output || "log/test.log")
58
+ end
59
+ when 'development'
60
+ # Development: key=value format to log/development.log (or override)
61
+ Dry.Logger(:dspy, formatter: :string) do |config|
62
+ config.add_backend(stream: log_output || "log/development.log")
63
+ end
64
+ when 'production', 'staging'
65
+ # Production: JSON to STDOUT (or override)
66
+ Dry.Logger(:dspy, formatter: :json) do |config|
67
+ config.add_backend(stream: log_output || $stdout)
68
+ end
69
+ else
70
+ # Fallback: key=value to STDOUT
71
+ Dry.Logger(:dspy, formatter: :string) do |config|
72
+ config.add_backend(stream: log_output || $stdout)
73
+ end
116
74
  end
117
75
  end
76
+
118
77
  end
119
78
 
120
79
  require_relative 'dspy/module'
@@ -138,15 +97,16 @@ require_relative 'dspy/teleprompt/data_handler'
138
97
  require_relative 'dspy/propose/grounded_proposer'
139
98
  require_relative 'dspy/teleprompt/simple_optimizer'
140
99
  require_relative 'dspy/teleprompt/mipro_v2'
141
- require_relative 'dspy/subscribers/logger_subscriber'
142
100
  require_relative 'dspy/tools'
143
101
  require_relative 'dspy/memory'
144
- require_relative 'dspy/instrumentation'
145
102
  require_relative 'dspy/storage/program_storage'
146
103
  require_relative 'dspy/storage/storage_manager'
147
104
  require_relative 'dspy/registry/signature_registry'
148
105
  require_relative 'dspy/registry/registry_manager'
149
106
 
107
+ # Auto-configure observability if Langfuse env vars are present
108
+ DSPy::Observability.configure!
109
+
150
110
  # LoggerSubscriber will be lazy-initialized when first accessed
151
111
 
152
112
  # Detect potential gem conflicts and warn users
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
@@ -37,20 +37,6 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.0'
40
- - !ruby/object:Gem::Dependency
41
- name: dry-monitor
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: '1.0'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.0'
54
40
  - !ruby/object:Gem::Dependency
55
41
  name: async
56
42
  requirement: !ruby/object:Gem::Requirement
@@ -149,6 +135,34 @@ dependencies:
149
135
  - - "~>"
150
136
  - !ruby/object:Gem::Version
151
137
  version: '1.2'
138
+ - !ruby/object:Gem::Dependency
139
+ name: opentelemetry-sdk
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.8'
145
+ type: :runtime
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.8'
152
+ - !ruby/object:Gem::Dependency
153
+ name: opentelemetry-exporter-otlp
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '0.30'
159
+ type: :runtime
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '0.30'
152
166
  description: The Ruby framework for programming with large language models.
153
167
  email:
154
168
  - hey@vicente.services
@@ -160,16 +174,13 @@ files:
160
174
  - lib/dspy.rb
161
175
  - lib/dspy/chain_of_thought.rb
162
176
  - lib/dspy/code_act.rb
177
+ - lib/dspy/context.rb
163
178
  - lib/dspy/errors.rb
164
179
  - lib/dspy/evaluate.rb
165
180
  - lib/dspy/example.rb
166
181
  - lib/dspy/few_shot_example.rb
167
182
  - lib/dspy/field.rb
168
183
  - lib/dspy/image.rb
169
- - lib/dspy/instrumentation.rb
170
- - lib/dspy/instrumentation/event_payload_factory.rb
171
- - lib/dspy/instrumentation/event_payloads.rb
172
- - lib/dspy/instrumentation/token_tracker.rb
173
184
  - lib/dspy/lm.rb
174
185
  - lib/dspy/lm/adapter.rb
175
186
  - lib/dspy/lm/adapter_factory.rb
@@ -200,10 +211,10 @@ files:
200
211
  - lib/dspy/memory/memory_manager.rb
201
212
  - lib/dspy/memory/memory_record.rb
202
213
  - lib/dspy/memory/memory_store.rb
203
- - lib/dspy/mixins/instrumentation_helpers.rb
204
214
  - lib/dspy/mixins/struct_builder.rb
205
215
  - lib/dspy/mixins/type_coercion.rb
206
216
  - lib/dspy/module.rb
217
+ - lib/dspy/observability.rb
207
218
  - lib/dspy/predict.rb
208
219
  - lib/dspy/prediction.rb
209
220
  - lib/dspy/prompt.rb
@@ -216,10 +227,6 @@ files:
216
227
  - lib/dspy/storage/program_storage.rb
217
228
  - lib/dspy/storage/storage_manager.rb
218
229
  - lib/dspy/strategy.rb
219
- - lib/dspy/subscribers/langfuse_subscriber.rb
220
- - lib/dspy/subscribers/logger_subscriber.rb
221
- - lib/dspy/subscribers/newrelic_subscriber.rb
222
- - lib/dspy/subscribers/otel_subscriber.rb
223
230
  - lib/dspy/teleprompt/data_handler.rb
224
231
  - lib/dspy/teleprompt/mipro_v2.rb
225
232
  - lib/dspy/teleprompt/simple_optimizer.rb
@@ -1,282 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sorbet-runtime'
4
- require_relative 'event_payloads'
5
-
6
- module DSPy
7
- module Instrumentation
8
- # Factory for creating typed event payloads from hash data
9
- module EventPayloadFactory
10
- extend T::Sig
11
- extend self
12
-
13
- # Create appropriate event struct based on event name
14
- sig { params(event_name: String, payload: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
15
- def create_event(event_name, payload)
16
- case event_name
17
- when 'dspy.lm.request'
18
- create_lm_request_event(payload)
19
- when 'dspy.lm.tokens'
20
- create_lm_tokens_event(payload)
21
- when 'dspy.lm.response.parsed'
22
- create_lm_response_parsed_event(payload)
23
- when 'dspy.predict'
24
- create_predict_event(payload)
25
- when 'dspy.predict.validation_error'
26
- create_predict_validation_error_event(payload)
27
- when 'dspy.chain_of_thought'
28
- create_chain_of_thought_event(payload)
29
- when 'dspy.chain_of_thought.reasoning_complete'
30
- create_chain_of_thought_reasoning_complete_event(payload)
31
- when 'dspy.react.iteration'
32
- create_react_iteration_event(payload)
33
- when 'dspy.react.tool_call'
34
- create_react_tool_call_event(payload)
35
- when 'dspy.react.iteration_complete'
36
- create_react_iteration_complete_event(payload)
37
- when 'dspy.react.max_iterations'
38
- create_react_max_iterations_event(payload)
39
- when 'dspy.codeact.iteration'
40
- create_codeact_iteration_event(payload)
41
- when 'dspy.codeact.code_execution'
42
- create_codeact_code_execution_event(payload)
43
- else
44
- # Return original payload for unhandled events
45
- payload
46
- end
47
- end
48
-
49
- private
50
-
51
- # LM Request Event
52
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMRequestEvent) }
53
- def create_lm_request_event(payload)
54
- # Extract timestamp, handling both timestamp and timestamp_ns keys
55
- timestamp = extract_timestamp(payload)
56
-
57
- LMRequestEvent.new(
58
- timestamp: timestamp,
59
- duration_ms: payload[:duration_ms] || 0.0,
60
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
61
- status: payload[:status] || 'success',
62
- gen_ai_operation_name: payload[:gen_ai_operation_name] || 'unknown',
63
- gen_ai_system: payload[:gen_ai_system] || 'unknown',
64
- gen_ai_request_model: payload[:gen_ai_request_model] || 'unknown',
65
- signature_class: payload[:signature_class],
66
- provider: payload[:provider] || 'unknown',
67
- adapter_class: payload[:adapter_class] || 'unknown',
68
- input_size: payload[:input_size] || 0,
69
- error_type: payload[:error_type],
70
- error_message: payload[:error_message]
71
- )
72
- end
73
-
74
- # Helper to extract timestamp from various formats
75
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(String) }
76
- def extract_timestamp(payload)
77
- if payload[:timestamp]
78
- payload[:timestamp]
79
- elsif payload[:timestamp_ns]
80
- # Convert nanoseconds to ISO8601 for storage in struct
81
- Time.at(payload[:timestamp_ns] / 1_000_000_000.0).iso8601
82
- else
83
- Time.now.iso8601
84
- end
85
- end
86
-
87
- # LM Tokens Event
88
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMTokensEvent) }
89
- def create_lm_tokens_event(payload)
90
- LMTokensEvent.new(
91
- timestamp: extract_timestamp(payload),
92
- status: payload[:status] || 'success',
93
- input_tokens: payload[:input_tokens] || 0,
94
- output_tokens: payload[:output_tokens] || 0,
95
- total_tokens: payload[:total_tokens] || 0,
96
- gen_ai_system: payload[:gen_ai_system] || 'unknown',
97
- gen_ai_request_model: payload[:gen_ai_request_model] || 'unknown',
98
- signature_class: payload[:signature_class]
99
- )
100
- end
101
-
102
- # LM Response Parsed Event
103
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMResponseParsedEvent) }
104
- def create_lm_response_parsed_event(payload)
105
- LMResponseParsedEvent.new(
106
- timestamp: extract_timestamp(payload),
107
- duration_ms: payload[:duration_ms] || 0.0,
108
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
109
- status: payload[:status] || 'success',
110
- signature_class: payload[:signature_class] || 'unknown',
111
- provider: payload[:provider] || 'unknown',
112
- success: payload[:success] || false,
113
- response_length: payload[:response_length] || 0,
114
- parse_type: payload[:parse_type],
115
- error_type: payload[:error_type],
116
- error_message: payload[:error_message]
117
- )
118
- end
119
-
120
- # Predict Event
121
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(PredictEvent) }
122
- def create_predict_event(payload)
123
- PredictEvent.new(
124
- timestamp: extract_timestamp(payload),
125
- duration_ms: payload[:duration_ms] || 0.0,
126
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
127
- status: payload[:status] || 'success',
128
- signature_class: payload[:signature_class] || 'unknown',
129
- module_name: payload[:module_name] || 'unknown',
130
- model: payload[:model] || 'unknown',
131
- provider: payload[:provider] || 'unknown',
132
- input_fields: payload[:input_fields] || [],
133
- input_size: payload[:input_size],
134
- output_size: payload[:output_size],
135
- error_type: payload[:error_type],
136
- error_message: payload[:error_message]
137
- )
138
- end
139
-
140
- # Predict Validation Error Event
141
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(PredictValidationErrorEvent) }
142
- def create_predict_validation_error_event(payload)
143
- PredictValidationErrorEvent.new(
144
- timestamp: payload[:timestamp] || Time.now.iso8601,
145
- status: payload[:status] || 'error',
146
- signature_class: payload[:signature_class] || 'unknown',
147
- module_name: payload[:module_name] || 'unknown',
148
- field_name: payload[:field_name] || 'unknown',
149
- error_message: payload[:error_message] || 'unknown error',
150
- retry_count: payload[:retry_count] || 0
151
- )
152
- end
153
-
154
- # Chain of Thought Event
155
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ChainOfThoughtEvent) }
156
- def create_chain_of_thought_event(payload)
157
- ChainOfThoughtEvent.new(
158
- timestamp: extract_timestamp(payload),
159
- duration_ms: payload[:duration_ms] || 0.0,
160
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
161
- status: payload[:status] || 'success',
162
- signature_class: payload[:signature_class] || 'unknown',
163
- module_name: payload[:module_name] || 'unknown',
164
- model: payload[:model] || 'unknown',
165
- provider: payload[:provider] || 'unknown',
166
- reasoning_length: payload[:reasoning_length],
167
- answer_length: payload[:answer_length],
168
- error_type: payload[:error_type],
169
- error_message: payload[:error_message]
170
- )
171
- end
172
-
173
- # Chain of Thought Reasoning Complete Event
174
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ChainOfThoughtReasoningCompleteEvent) }
175
- def create_chain_of_thought_reasoning_complete_event(payload)
176
- ChainOfThoughtReasoningCompleteEvent.new(
177
- timestamp: payload[:timestamp] || Time.now.iso8601,
178
- status: payload[:status] || 'success',
179
- signature_class: payload[:signature_class] || 'unknown',
180
- module_name: payload[:module_name] || 'unknown',
181
- reasoning_length: payload[:reasoning_length] || 0,
182
- answer_present: payload[:answer_present] || false
183
- )
184
- end
185
-
186
- # ReAct Iteration Event
187
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactIterationEvent) }
188
- def create_react_iteration_event(payload)
189
- ReactIterationEvent.new(
190
- timestamp: payload[:timestamp] || Time.now.iso8601,
191
- duration_ms: payload[:duration_ms] || 0.0,
192
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
193
- status: payload[:status] || 'success',
194
- iteration: payload[:iteration] || 0,
195
- max_iterations: payload[:max_iterations] || 5,
196
- history_length: payload[:history_length] || 0,
197
- tools_used_so_far: payload[:tools_used_so_far] || [],
198
- error_type: payload[:error_type],
199
- error_message: payload[:error_message]
200
- )
201
- end
202
-
203
- # ReAct Tool Call Event
204
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactToolCallEvent) }
205
- def create_react_tool_call_event(payload)
206
- ReactToolCallEvent.new(
207
- timestamp: payload[:timestamp] || Time.now.iso8601,
208
- duration_ms: payload[:duration_ms] || 0.0,
209
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
210
- status: payload[:status] || 'success',
211
- iteration: payload[:iteration] || 0,
212
- tool_name: payload[:tool_name] || 'unknown',
213
- tool_input: payload[:tool_input],
214
- error_type: payload[:error_type],
215
- error_message: payload[:error_message]
216
- )
217
- end
218
-
219
- # ReAct Iteration Complete Event
220
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactIterationCompleteEvent) }
221
- def create_react_iteration_complete_event(payload)
222
- ReactIterationCompleteEvent.new(
223
- timestamp: payload[:timestamp] || Time.now.iso8601,
224
- status: payload[:status] || 'success',
225
- iteration: payload[:iteration] || 0,
226
- thought: payload[:thought] || '',
227
- action: payload[:action] || '',
228
- action_input: payload[:action_input],
229
- observation: payload[:observation] || '',
230
- tools_used: payload[:tools_used] || []
231
- )
232
- end
233
-
234
- # ReAct Max Iterations Event
235
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactMaxIterationsEvent) }
236
- def create_react_max_iterations_event(payload)
237
- ReactMaxIterationsEvent.new(
238
- timestamp: payload[:timestamp] || Time.now.iso8601,
239
- status: payload[:status] || 'warning',
240
- iteration_count: payload[:iteration_count] || 0,
241
- max_iterations: payload[:max_iterations] || 5,
242
- tools_used: payload[:tools_used] || [],
243
- final_history_length: payload[:final_history_length] || 0
244
- )
245
- end
246
-
247
- # CodeAct Iteration Event
248
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(CodeActIterationEvent) }
249
- def create_codeact_iteration_event(payload)
250
- CodeActIterationEvent.new(
251
- timestamp: payload[:timestamp] || Time.now.iso8601,
252
- duration_ms: payload[:duration_ms] || 0.0,
253
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
254
- status: payload[:status] || 'success',
255
- iteration: payload[:iteration] || 0,
256
- max_iterations: payload[:max_iterations] || 5,
257
- history_length: payload[:history_length] || 0,
258
- code_blocks_executed: payload[:code_blocks_executed] || 0,
259
- error_type: payload[:error_type],
260
- error_message: payload[:error_message]
261
- )
262
- end
263
-
264
- # CodeAct Code Execution Event
265
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(CodeActCodeExecutionEvent) }
266
- def create_codeact_code_execution_event(payload)
267
- CodeActCodeExecutionEvent.new(
268
- timestamp: payload[:timestamp] || Time.now.iso8601,
269
- duration_ms: payload[:duration_ms] || 0.0,
270
- cpu_time_ms: payload[:cpu_time_ms] || 0.0,
271
- status: payload[:status] || 'success',
272
- iteration: payload[:iteration] || 0,
273
- code_type: payload[:code_type] || 'unknown',
274
- code_length: payload[:code_length] || 0,
275
- execution_success: payload[:execution_success] || false,
276
- error_type: payload[:error_type],
277
- error_message: payload[:error_message]
278
- )
279
- end
280
- end
281
- end
282
- end