Class: DSPy::ChainOfThought

Inherits:
Predict show all
Extended by:
T::Sig
Includes:
Mixins::StructBuilder
Defined in:
lib/dspy/chain_of_thought.rb

Overview

Enhances prediction by encouraging step-by-step reasoning before providing a final answer using Sorbet signatures.

Constant Summary collapse

FieldDescriptor =
DSPy::Signature::FieldDescriptor

Instance Attribute Summary collapse

Attributes inherited from Predict

#prompt, #signature_class

Instance Method Summary collapse

Methods inherited from Predict

#add_examples, #forward, from_h, #system_signature, #user_signature

Methods inherited from Module

#call, #call_untyped, #forward, #lm

Constructor Details

#initialize(signature_class) ⇒ ChainOfThought

Returns a new instance of ChainOfThought.



19
20
21
22
23
24
25
26
# File 'lib/dspy/chain_of_thought.rb', line 19

def initialize(signature_class)
  @original_signature = signature_class
  enhanced_signature = build_enhanced_signature(signature_class)
  
  # Call parent constructor with enhanced signature
  super(enhanced_signature)
  @signature_class = enhanced_signature
end

Instance Attribute Details

#original_signatureObject (readonly)

Returns the value of attribute original_signature.



83
84
85
# File 'lib/dspy/chain_of_thought.rb', line 83

def original_signature
  @original_signature
end

Instance Method Details

#forward_untyped(**input_values) ⇒ Object



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/dspy/chain_of_thought.rb', line 87

def forward_untyped(**input_values)
  # Wrap in chain-specific span tracking (overrides parent's span attributes)
  DSPy::Context.with_span(
    operation: "#{self.class.name}.forward",
    'langfuse.observation.type' => 'chain',
    'langfuse.observation.input' => input_values.to_json,
    'dspy.module' => 'ChainOfThought',
    'dspy.signature' => @original_signature.name
  ) do |span|
    # Call parent prediction logic (which will create its own nested span)
    prediction_result = super(**input_values)
    
    # Enhance span with reasoning data
    if span && prediction_result
      # Include reasoning in output for chain observation
      output_with_reasoning = if prediction_result.respond_to?(:reasoning) && prediction_result.reasoning
        output_hash = prediction_result.respond_to?(:to_h) ? prediction_result.to_h : {}
        output_hash.merge(reasoning: prediction_result.reasoning)
      else
        prediction_result.respond_to?(:to_h) ? prediction_result.to_h : prediction_result.to_s
      end
      
      span.set_attribute('langfuse.observation.output', output_with_reasoning.to_json)
      
      # Add reasoning metrics
      if prediction_result.respond_to?(:reasoning) && prediction_result.reasoning
        span.set_attribute('cot.reasoning_length', prediction_result.reasoning.length)
        span.set_attribute('cot.has_reasoning', true)
        span.set_attribute('cot.reasoning_steps', count_reasoning_steps(prediction_result.reasoning))
      end
    end
    
    # Analyze reasoning (emits events for backwards compatibility)
    analyze_reasoning(prediction_result)
    
    prediction_result
  end
end

#with_examples(examples) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/dspy/chain_of_thought.rb', line 62

def with_examples(examples)
  # Convert examples to include reasoning if they don't have it
  enhanced_examples = examples.map do |example|
    if example.reasoning.nil? || example.reasoning.empty?
      # Try to extract reasoning from the output if it contains a reasoning field
      reasoning = example.output[:reasoning] || "Step by step reasoning for this example."
      DSPy::FewShotExample.new(
        input: example.input,
        output: example.output,
        reasoning: reasoning
      )
    else
      example
    end
  end
  
  super(enhanced_examples)
end

#with_instruction(instruction) ⇒ Object



56
57
58
59
# File 'lib/dspy/chain_of_thought.rb', line 56

def with_instruction(instruction)
  enhanced_instruction = ensure_chain_of_thought_instruction(instruction)
  super(enhanced_instruction)
end

#with_prompt(new_prompt) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/dspy/chain_of_thought.rb', line 31

def with_prompt(new_prompt)
  # Create a new ChainOfThought with the same original signature
  instance = self.class.new(@original_signature)
  
  # Ensure the instruction includes "Think step by step" if not already present
  enhanced_instruction = if new_prompt.instruction.include?("Think step by step")
                           new_prompt.instruction
                         else
                           "#{new_prompt.instruction} Think step by step."
                         end
  
  # Create enhanced prompt with ChainOfThought-specific schemas
  enhanced_prompt = Prompt.new(
    instruction: enhanced_instruction,
    input_schema: @signature_class.input_json_schema,
    output_schema: @signature_class.output_json_schema,
    few_shot_examples: new_prompt.few_shot_examples,
    signature_class_name: @signature_class.name
  )
  
  instance.instance_variable_set(:@prompt, enhanced_prompt)
  instance
end