Class: Riffer::Providers::Mock

Inherits:
Base
  • Object
show all
Defined in:
lib/riffer/providers/mock.rb,
sig/generated/riffer/providers/mock.rbs

Overview

Mock provider for mocking LLM responses in tests; no external gems required.

Constant Summary

Constants inherited from Base

Base::REQUEST_PARAM_ATTRIBUTES, Base::WIRE_SEPARATOR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#apply_pricing, #capture_input, #capture_messages?, #capture_output, #chat_span_attributes, #decode_tool_name, #depends_on, #encode_tool_name, #generate_text, #in_chat_span, #merge_consecutive_messages, #normalize_messages, #parse_structured_output, #parse_tool_arguments, #pricing_rates, #record_finish_reason, #record_stream_outcome, #stream_text, #tag_attributes, #validate_input!, #validate_normalized_messages!, #yield_finish_reason

Constructor Details

#initialize(**options) ⇒ Mock

responses: pre-configures canned responses (same shape as #stub_response), typically set via provider_options responses: [...].

Riffer::Providers::Mock.new(responses: [
{content: "", tool_calls: [{name: "tool_a", arguments: "{}"}]},
{content: "Final answer"}
])

-- : (**untyped) -> void

Parameters:

  • (Object)


40
41
42
43
44
45
46
# File 'lib/riffer/providers/mock.rb', line 40

def initialize(**options)
  super()
  @responses = (options[:responses] || []).map { |r| normalize_response(r) }
  @current_index = 0
  @calls = []
  @stubbed_responses = []
end

Instance Attribute Details

#callsArray[Hash[Symbol, untyped]] (readonly)

Array of recorded method calls for assertions.

Returns:

  • (Array[Hash[Symbol, untyped]])


28
29
30
# File 'lib/riffer/providers/mock.rb', line 28

def calls
  @calls
end

Class Method Details

.semconv_provider_nameString

The GenAI semconv well-known provider name.

: () -> String

Returns:

  • (String)


23
24
25
# File 'lib/riffer/providers/mock.rb', line 23

def self.semconv_provider_name
  "mock"
end

.skills_adapter(model = nil) ⇒ singleton(Riffer::Skills::Adapter)

Returns the skill adapter for the mock model — XML when the model name contains claude (mirroring a real Claude provider), else Markdown.

: (?String?) -> singleton(Riffer::Skills::Adapter)

Parameters:

  • (String, nil)

Returns:



14
15
16
17
18
# File 'lib/riffer/providers/mock.rb', line 14

def self.skills_adapter(model = nil)
  return Riffer::Skills::XmlAdapter if model&.include?("claude")

  Riffer::Skills::MarkdownAdapter
end

Instance Method Details

#build_request_params(messages, model, options) ⇒ Hash[Symbol, untyped]

-- : (Array, String?, Hash[Symbol, untyped]) -> Hash[Symbol, untyped]

Parameters:

Returns:

  • (Hash[Symbol, untyped])


101
102
103
104
105
106
107
# File 'lib/riffer/providers/mock.rb', line 101

def build_request_params(messages, model, options)
  web_search = options[:web_search]
  @calls << { messages: messages.map(&:to_h), model: model, **options.except(:web_search) }
  response = next_response
  response[:web_search] = web_search if web_search
  { response: response }
end

#clear_stubsvoid

This method returns an undefined value.

Clears all stubbed responses.

-- : () -> void



72
73
74
# File 'lib/riffer/providers/mock.rb', line 72

def clear_stubs
  @stubbed_responses = []
end

#execute_generate(params) ⇒ Hash[Symbol, untyped]

-- : (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]

Parameters:

  • (Hash[Symbol, untyped])

Returns:

  • (Hash[Symbol, untyped])


111
112
113
# File 'lib/riffer/providers/mock.rb', line 111

def execute_generate(params)
  params[:response]
end

#execute_stream(params, yielder) ⇒ void

This method returns an undefined value.

-- : (Hash[Symbol, untyped], Riffer::Providers::_EventSink) -> void

Parameters:



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/riffer/providers/mock.rb', line 145

def execute_stream(params, yielder)
  response = params[:response]
  full_content = response[:content] || ""
  tool_calls = response[:tool_calls] || []
  token_usage = response[:token_usage]
  web_search = response[:web_search]

  if web_search
    yielder << Riffer::StreamEvents::WebSearchStatus.new("in_progress")
    yielder << Riffer::StreamEvents::WebSearchStatus.new("searching", query: "mock search query")
    yielder << Riffer::StreamEvents::WebSearchStatus.new("open_page", url: "https://example.com")
    yielder << Riffer::StreamEvents::WebSearchStatus.new("completed")
    yielder << Riffer::StreamEvents::WebSearchDone.new(
      "mock search query",
      sources: [{ title: "Example", url: "https://example.com" }],
    )
  end

  unless full_content.empty?
    content_parts = full_content.split(". ").map { |part| part + (part.end_with?(".") ? "" : ".") }
    content_parts.each do |part|
      yielder << Riffer::StreamEvents::TextDelta.new("#{part} ")
    end
  end

  tool_calls.each do |tc|
    yielder << Riffer::StreamEvents::ToolCallDelta.new(
      item_id: tc.call_id,
      name: tc.name,
      arguments_delta: tc.arguments,
    )
    yielder << Riffer::StreamEvents::ToolCallDone.new(
      item_id: tc.call_id,
      call_id: tc.call_id,
      name: tc.name,
      arguments: tc.arguments,
    )
  end

  yielder << Riffer::StreamEvents::TextDone.new(full_content)
  yield_finish_reason(yielder, extract_finish_reason(response))
  yielder << Riffer::StreamEvents::TokenUsageDone.new(token_usage: apply_pricing(token_usage)) if token_usage
end

#extract_content(response) ⇒ String

-- : (untyped) -> String

Parameters:

  • (Object)

Returns:

  • (String)


133
134
135
# File 'lib/riffer/providers/mock.rb', line 133

def extract_content(response)
  response.is_a?(Hash) ? (response[:content] || "") : response.content
end

#extract_finish_reason(response) ⇒ Riffer::Providers::FinishReason?

-- : (untyped) -> Riffer::Providers::FinishReason?

Parameters:

  • (Object)

Returns:



124
125
126
127
128
129
# File 'lib/riffer/providers/mock.rb', line 124

def extract_finish_reason(response)
  return nil unless response.is_a?(Hash)

  reason = response[:finish_reason]
  reason ? Riffer::Providers::FinishReason.new(reason: reason) : nil
end

#extract_token_usage(response) ⇒ Riffer::Providers::TokenUsage?

-- : (untyped) -> Riffer::Providers::TokenUsage?

Parameters:

  • (Object)

Returns:



117
118
119
120
# File 'lib/riffer/providers/mock.rb', line 117

def extract_token_usage(response)
  usage = response[:token_usage]
  usage && apply_pricing(usage)
end

#extract_tool_calls(response) ⇒ Array[Riffer::Messages::Assistant::ToolCall]

-- : (untyped) -> Array

Parameters:

  • (Object)

Returns:



139
140
141
# File 'lib/riffer/providers/mock.rb', line 139

def extract_tool_calls(response)
  response.is_a?(Hash) ? (response[:tool_calls] || []) : response.tool_calls
end

#next_responseHash[Symbol, untyped]

-- : () -> Hash[Symbol, untyped]

Returns:

  • (Hash[Symbol, untyped])


191
192
193
194
195
196
197
198
199
200
201
# File 'lib/riffer/providers/mock.rb', line 191

def next_response
  if @stubbed_responses.any?
    @stubbed_responses.shift
  elsif @current_index < @responses.size
    response = @responses[@current_index]
    @current_index += 1
    response
  else
    normalize_response(content: "Mock response")
  end
end

#normalize_response(response) ⇒ Hash[Symbol, untyped]

-- : (Hash[Symbol, untyped]) -> Hash[Symbol, untyped]

Parameters:

  • (Hash[Symbol, untyped])

Returns:

  • (Hash[Symbol, untyped])


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/riffer/providers/mock.rb', line 80

def normalize_response(response)
  formatted_tool_calls = (response[:tool_calls] || []).map.with_index do |tc, idx|
    next tc if tc.is_a?(Riffer::Messages::Assistant::ToolCall)

    Riffer::Messages::Assistant::ToolCall.new(
      call_id: tc[:call_id] || tc[:id] || "mock_call_#{idx}",
      name: tc[:name],
      arguments: tc[:arguments].is_a?(String) ? tc[:arguments] : tc[:arguments].to_json,
    )
  end
  {
    role: response[:role] || "assistant",
    content: response[:content] || "",
    tool_calls: formatted_tool_calls,
    token_usage: response[:token_usage],
    finish_reason: response[:finish_reason] || (formatted_tool_calls.empty? ? :stop : :tool_calls),
  }
end

#stub_response(content, tool_calls: [], token_usage: nil, finish_reason: nil) ⇒ void

This method returns an undefined value.

Stubs the next response; call repeatedly to queue several. finish_reason defaults to :tool_calls when tool calls are present, else :stop.

provider.stub_response("Hello")
provider.stub_response("", tool_calls: [{name: "my_tool", arguments: '{"key":"value"}'}])
provider.stub_response("Final response",
                     token_usage: Riffer::Providers::TokenUsage.new(input_tokens: 10, output_tokens: 5))
provider.stub_response("Truncated...", finish_reason: :length)

-- : (String, ?tool_calls: Array[Hash[Symbol, untyped]], ?token_usage: Riffer::Providers::TokenUsage?, ?finish_reason: Symbol?) -> void

Parameters:

  • (String)
  • tool_calls: (Array[Hash[Symbol, untyped]]) (defaults to: [])
  • token_usage: (Riffer::Providers::TokenUsage, nil) (defaults to: nil)
  • finish_reason: (Symbol, nil) (defaults to: nil)


59
60
61
62
63
64
65
66
# File 'lib/riffer/providers/mock.rb', line 59

def stub_response(content, tool_calls: [], token_usage: nil, finish_reason: nil)
  @stubbed_responses << normalize_response(
    content: content,
    tool_calls: tool_calls,
    token_usage: token_usage,
    finish_reason: finish_reason,
  )
end