Module: SolidAgent::HasReasons

Extended by:
ActiveSupport::Concern
Defined in:
lib/solid_agent/has_reasons.rb

Overview

HasReasons provides reasoning/thinking trace collection for agents.

This concern enables agents to capture and track extended thinking from LLMs that support it (Claude's extended thinking, OpenAI o1, etc.).

Reasoning traces are captured separately from the main response content, allowing for:

  • Transparent AI decision-making
  • Debugging and analysis of AI behavior
  • Audit trails for compliance
  • Cost tracking (reasoning tokens)

Examples:

Basic usage

class ResearchAgent < ApplicationAgent
  include SolidAgent::HasReasons

  def analyze
    result = prompt(
      messages: research_messages,
      extended_thinking: true  # Enable extended thinking
    )

    # Reasoning is automatically captured
    last_reasoning.content  #=> "Let me analyze this systematically..."
    total_reasoning_tokens  #=> 450
  end
end

Configuring reasoning capture

class AnalysisAgent < ApplicationAgent
  include SolidAgent::HasReasons

  has_reasons(
    auto_capture: true,        # Auto-capture from all generations
    persist: true,             # Persist to database
    budget_tokens: 10000       # Default reasoning token budget
  )
end

Accessing reasoning history

agent.reasons                  # All captured reasons
agent.reasoning_chain          # Formatted reasoning chain
agent.total_reasoning_tokens   # Sum of all reasoning tokens

Instance Method Summary collapse

Instance Method Details

#add_reason(content:, tokens: 0, **metadata) ⇒ Reasonable::Reason

Manually add a reason

Parameters:

  • content (String)

    Reasoning content

  • tokens (Integer) (defaults to: 0)

    Token count

  • metadata (Hash)

    Additional metadata

Returns:



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/solid_agent/has_reasons.rb', line 156

def add_reason(content:, tokens: 0, **)
  reason = Reasonable::Reason.new(
    content: content,
    tokens: tokens,
    model: current_model,
    **
  )

  @_captured_reasons ||= []
  @_captured_reasons << reason

  persist_reasoning(reason) if _reasons_config[:persist]

  reason
end

#capture_reasoning(response) ⇒ Reasonable::Reason?

Capture reasoning from an LLM response

Parameters:

  • response (Object)

    LLM response object

Returns:



137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/solid_agent/has_reasons.rb', line 137

def capture_reasoning(response)
  reason = Reasonable::Reason.from_response(response)
  return nil unless reason&.extended_thinking?

  @_captured_reasons ||= []
  @_captured_reasons << reason

  # Persist if configured and HasContext is available
  persist_reasoning(reason) if _reasons_config[:persist]

  reason
end

#clear_reasons!Object

Clear all captured reasons



173
174
175
# File 'lib/solid_agent/has_reasons.rb', line 173

def clear_reasons!
  @_captured_reasons = []
end

#has_reasoning?Boolean

Check if any reasoning has been captured

Returns:

  • (Boolean)


104
105
106
# File 'lib/solid_agent/has_reasons.rb', line 104

def has_reasoning?
  reasons.any?(&:extended_thinking?)
end

#last_reasoningReasonable::Reason?

Get the last captured reason

Returns:



90
91
92
# File 'lib/solid_agent/has_reasons.rb', line 90

def last_reasoning
  reasons.last
end

#reasoning_chain(separator: "\n\n---\n\n") ⇒ String

Get a formatted reasoning chain (all reasoning in sequence)

Parameters:

  • separator (String) (defaults to: "\n\n---\n\n")

    Separator between reasons

Returns:

  • (String)


112
113
114
115
116
117
118
# File 'lib/solid_agent/has_reasons.rb', line 112

def reasoning_chain(separator: "\n\n---\n\n")
  reasons
    .select(&:extended_thinking?)
    .reject(&:redacted?)
    .map(&:content)
    .join(separator)
end

#reasoning_prompt_optionsHash

Get default prompt options with reasoning configuration

Returns:

  • (Hash)


180
181
182
183
184
185
186
187
188
189
190
# File 'lib/solid_agent/has_reasons.rb', line 180

def reasoning_prompt_options
  options = {}

  if _reasons_config[:budget_tokens]
    options[:reasoning_budget_tokens] = _reasons_config[:budget_tokens]
  end

  options[:extended_thinking] = true if _reasons_config[:auto_capture]

  options
end

#reasoning_statsHash

Get reasoning statistics

Returns:

  • (Hash)


123
124
125
126
127
128
129
130
131
# File 'lib/solid_agent/has_reasons.rb', line 123

def reasoning_stats
  {
    count: reasons.count,
    total_tokens: total_reasoning_tokens,
    total_thinking_time_ms: reasons.sum { |r| r.thinking_time_ms || 0 },
    redacted_count: reasons.count(&:redacted?),
    models: reasons.map(&:model).compact.uniq
  }
end

#reasonsArray<Reasonable::Reason>

Get all captured reasons for this agent instance

Returns:



83
84
85
# File 'lib/solid_agent/has_reasons.rb', line 83

def reasons
  @_captured_reasons ||= []
end

#total_reasoning_tokensInteger

Get the total reasoning tokens used

Returns:

  • (Integer)


97
98
99
# File 'lib/solid_agent/has_reasons.rb', line 97

def total_reasoning_tokens
  reasons.sum(&:tokens)
end