Class: Rubino::LLM::FakeProvider
- Inherits:
-
Object
- Object
- Rubino::LLM::FakeProvider
- Defined in:
- lib/rubino/llm/fake_provider.rb
Overview
Dev-only LLM adapter that replays a pre-recorded YAML scenario instead of hitting a real provider. The public surface mirrors RubyLLMAdapter so Agent::Loop can swap it in without further plumbing changes.
Selection:
- model_id starting with "fake/" pins the scenario (suffix is the name).
- otherwise ScenarioSelector.resolve(last_user_message_content) chooses
one based on keyword routing, falling back to "happy-path".
Streaming:
- "content" → yield { type: :content, text: ... }
- "thinking" → yield { type: :thinking, text: ... } (gated by
display.show_reasoning, mirroring RubyLLMAdapter)
- "tool_call" → buffered onto the final AdapterResponse (NOT yielded
mid-stream; this matches RubyLLMAdapter and is what Loop
expects).
- "delay_seconds" → cancellable sleep between events.
- unknown → logged and skipped.
Cancellation is checked between each event so Esc / Ctrl+C lands within one tick instead of waiting for the full scenario to drain.
Constant Summary collapse
- DEFAULT_DELAY =
0.1
Instance Attribute Summary collapse
-
#model_id ⇒ Object
readonly
Returns the value of attribute model_id.
-
#provider ⇒ Object
readonly
Returns the value of attribute provider.
Instance Method Summary collapse
-
#call(request) ⇒ Object
LLM boundary entry: dispatch an LLM::Request to the streaming vs non-streaming transport.
-
#chat(messages:, tools: nil, response_format: nil, image_paths: nil) ⇒ Object
Non-streaming entry point.
- #context_window ⇒ Object
-
#initialize(model_id: nil, provider: nil, config: nil, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil) ⇒ FakeProvider
constructor
A new instance of FakeProvider.
- #model_info ⇒ Object
-
#resolve_scenario(messages) ⇒ Object
Convenience: returns the scenario name FakeProvider would pick for this set of messages.
-
#stream(messages:, tools: nil, response_format: nil, image_paths: nil, &block) ⇒ Object
Streaming entry point.
Constructor Details
#initialize(model_id: nil, provider: nil, config: nil, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil) ⇒ FakeProvider
Returns a new instance of FakeProvider.
35 36 37 38 39 40 41 42 43 44 |
# File 'lib/rubino/llm/fake_provider.rb', line 35 def initialize(model_id: nil, provider: nil, config: nil, ui: nil, event_bus: nil, tool_executor: nil, cancel_token: nil) @config = config || Rubino.configuration @model_id = model_id || @config.model_default || "fake/happy-path" @provider = provider || "fake" @ui = ui @event_bus = event_bus @tool_executor = tool_executor @cancel_token = cancel_token end |
Instance Attribute Details
#model_id ⇒ Object (readonly)
Returns the value of attribute model_id.
31 32 33 |
# File 'lib/rubino/llm/fake_provider.rb', line 31 def model_id @model_id end |
#provider ⇒ Object (readonly)
Returns the value of attribute provider.
31 32 33 |
# File 'lib/rubino/llm/fake_provider.rb', line 31 def provider @provider end |
Instance Method Details
#call(request) ⇒ Object
LLM boundary entry: dispatch an LLM::Request to the streaming vs non-streaming transport. Mirrors RubyLLMAdapter#call so Loop can drive the fake through the same seam.
49 50 51 52 53 54 55 56 57 |
# File 'lib/rubino/llm/fake_provider.rb', line 49 def call(request, &) if request.stream? stream(messages: request., tools: request.tools, image_paths: request.image_paths, &) else chat(messages: request., tools: request.tools, image_paths: request.image_paths) end end |
#chat(messages:, tools: nil, response_format: nil, image_paths: nil) ⇒ Object
Non-streaming entry point. Plays the scenario with a no-op block and returns the accumulated AdapterResponse.
61 62 63 64 |
# File 'lib/rubino/llm/fake_provider.rb', line 61 def chat(messages:, tools: nil, response_format: nil, image_paths: nil) stream(messages: , tools: tools, response_format: response_format, image_paths: image_paths) { |_chunk| } end |
#context_window ⇒ Object
125 126 127 |
# File 'lib/rubino/llm/fake_provider.rb', line 125 def context_window @config.model_context_length || 128_000 end |
#model_info ⇒ Object
121 122 123 |
# File 'lib/rubino/llm/fake_provider.rb', line 121 def model_info nil end |
#resolve_scenario(messages) ⇒ Object
Convenience: returns the scenario name FakeProvider would pick for this set of messages. Useful in specs and the doctor command.
131 132 133 |
# File 'lib/rubino/llm/fake_provider.rb', line 131 def resolve_scenario() pick_scenario() end |
#stream(messages:, tools: nil, response_format: nil, image_paths: nil, &block) ⇒ Object
Streaming entry point. Yields chunk hashes shaped exactly like RubyLLMAdapter:
{ type: :content, text: String }
{ type: :thinking, text: String }
Returns AdapterResponse with concatenated content, accumulated tool_calls, zero usage tokens, and the model id.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 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 |
# File 'lib/rubino/llm/fake_provider.rb', line 72 def stream(messages:, tools: nil, response_format: nil, image_paths: nil, &block) # image_paths is accepted for signature parity with RubyLLMAdapter # (Loop passes it on every call). FakeProvider plays back recorded # scenarios verbatim, so it has nothing to do with attachments. _ = image_paths # If the runner is calling us back after a tool result, replaying the # original scenario would re-emit the same tool_call indefinitely # (FakeProvider has no inter-turn state). Detect the post-tool turn # and emit a short closing message instead so the run terminates. events = if post_tool_turn?() closing_events else scenario_name = pick_scenario() ScenarioLoader.load(scenario_name, scenarios_dir: scenarios_dir_from_config) end # {{input}} is the only placeholder scenarios currently use. The reference # had a richer template system, but in practice every scenario only # interpolated the user input. Keep it simple until a scenario actually # needs more (e.g. {{session_id}}). @scenario_vars = { "input" => extract_last_user_text().to_s } buffered = +"" tool_calls = [] events.each do |event| @cancel_token&.check! dispatch_event(event, buffered: buffered, tool_calls: tool_calls, &block) end AdapterResponse.new( content: buffered, tool_calls: tool_calls, input_tokens: 0, output_tokens: 0, model_id: @model_id ) rescue Rubino::Interrupted # Mirror RubyLLMAdapter: surface whatever was buffered as a clean # AdapterResponse instead of swallowing the partial output. AdapterResponse.new( content: buffered || "", tool_calls: tool_calls || [], input_tokens: 0, output_tokens: 0, model_id: @model_id ) end |