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, #build_client, #capture_input, #capture_messages?, #capture_output, #chat_span_attributes, #client, #decode_tool_name, #depends_on, #encode_tool_name, #generate_text, #global_client, #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(responses: []) ⇒ Mock

responses: pre-configures canned responses (same shape as #stub_response) for standalone use; agent tests queue responses on agent.provider via #stub_response instead.

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

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

Parameters:

  • responses: (Array[Hash[Symbol, untyped]]) (defaults to: [])


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

def initialize(responses: [])
  super()
  @responses = 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])


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

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



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

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])


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

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:



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
188
# File 'lib/riffer/providers/mock.rb', line 146

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)


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

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:



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

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:



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

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:



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

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])


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

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])


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

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)


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

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