rubyrlm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +32 -0
  3. data/LICENSE +21 -0
  4. data/README.md +300 -0
  5. data/bin/rubyrlm +168 -0
  6. data/lib/rubyrlm/backends/base.rb +9 -0
  7. data/lib/rubyrlm/backends/gemini_rest.rb +317 -0
  8. data/lib/rubyrlm/client.rb +643 -0
  9. data/lib/rubyrlm/completion.rb +71 -0
  10. data/lib/rubyrlm/errors.rb +9 -0
  11. data/lib/rubyrlm/logger/jsonl_logger.rb +27 -0
  12. data/lib/rubyrlm/pricing.rb +88 -0
  13. data/lib/rubyrlm/prompts/system_prompt.rb +108 -0
  14. data/lib/rubyrlm/protocol/action_parser.rb +84 -0
  15. data/lib/rubyrlm/repl/code_validator.rb +113 -0
  16. data/lib/rubyrlm/repl/docker_repl/container_manager.rb +158 -0
  17. data/lib/rubyrlm/repl/docker_repl/host_rpc_server.rb +164 -0
  18. data/lib/rubyrlm/repl/docker_repl/protocol.rb +26 -0
  19. data/lib/rubyrlm/repl/docker_repl.rb +190 -0
  20. data/lib/rubyrlm/repl/execution_result.rb +41 -0
  21. data/lib/rubyrlm/repl/local_repl.rb +476 -0
  22. data/lib/rubyrlm/sub_call_cache.rb +47 -0
  23. data/lib/rubyrlm/version.rb +3 -0
  24. data/lib/rubyrlm/web/app.rb +41 -0
  25. data/lib/rubyrlm/web/public/css/components.css +649 -0
  26. data/lib/rubyrlm/web/public/css/design-system.css +1396 -0
  27. data/lib/rubyrlm/web/public/js/app.js +1016 -0
  28. data/lib/rubyrlm/web/public/js/components/charts.js +68 -0
  29. data/lib/rubyrlm/web/public/js/components/context-inspector.js +94 -0
  30. data/lib/rubyrlm/web/public/js/components/exec-chain.js +105 -0
  31. data/lib/rubyrlm/web/public/js/components/kpi-dashboard.js +187 -0
  32. data/lib/rubyrlm/web/public/js/components/query-panel.js +335 -0
  33. data/lib/rubyrlm/web/public/js/components/recursion-tree.js +83 -0
  34. data/lib/rubyrlm/web/public/js/components/session-list.js +160 -0
  35. data/lib/rubyrlm/web/public/js/components/step-navigator.js +129 -0
  36. data/lib/rubyrlm/web/public/js/components/timeline.js +281 -0
  37. data/lib/rubyrlm/web/public/js/lib/animation.js +46 -0
  38. data/lib/rubyrlm/web/public/js/lib/chart-renderer.js +116 -0
  39. data/lib/rubyrlm/web/public/js/lib/diagram-renderer.js +233 -0
  40. data/lib/rubyrlm/web/public/js/lib/sse-client.js +94 -0
  41. data/lib/rubyrlm/web/public/js/lib/theme-manager.js +39 -0
  42. data/lib/rubyrlm/web/public/js/utils.js +57 -0
  43. data/lib/rubyrlm/web/routes/api.rb +129 -0
  44. data/lib/rubyrlm/web/routes/pages.rb +365 -0
  45. data/lib/rubyrlm/web/routes/sse.rb +95 -0
  46. data/lib/rubyrlm/web/services/event_broadcaster.rb +36 -0
  47. data/lib/rubyrlm/web/services/export_service.rb +903 -0
  48. data/lib/rubyrlm/web/services/query_service.rb +221 -0
  49. data/lib/rubyrlm/web/services/session_loader.rb +356 -0
  50. data/lib/rubyrlm/web/services/streaming_logger.rb +22 -0
  51. data/lib/rubyrlm.rb +18 -0
  52. metadata +208 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: be38bd741250692279b5207395414b43c3d473dd00dd8835e208a8a5ffe3fb43
4
+ data.tar.gz: 8d8facc2f1c7b0773345bd6930f361501a0f75fb7017a5f45d905bd9614098fb
5
+ SHA512:
6
+ metadata.gz: d53be5b4873058dec0dbdb564806d66fdcc63555365613e3df19e8eca49e356ec78df3fd72eca503bba3d12411172b71d3f66061f7ac84c41f05411dc9cfebac
7
+ data.tar.gz: e33f04262ccd83d8f867e49d98d6b8b93a64f1eb5745c0a54a97c45f61e0a51c4e4abfd996c49dc77483c223624de59205150adeb08a51a02e1842c3916583e6
data/CHANGELOG.md ADDED
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-03-12
6
+
7
+ ### Added
8
+ - Core RLM client with Gemini backend, multi-turn conversation, and streaming support
9
+ - Local and Docker-isolated REPL runtimes with configurable execution timeout
10
+ - AST-based code validation (Ripper syntax checking + dangerous call detection)
11
+ - Sub-call caching with SHA256-keyed deduplication for `llm_query`
12
+ - Patch tracking with undo support (`undo_last_patch` / `undo_all_patches`)
13
+ - Per-model USD cost tracking with cache-aware billing
14
+ - Shared backend client for child RLMs to reduce per-subcall overhead
15
+ - Web UI with session management, streaming timeline, and Mermaid diagram rendering
16
+ - Theme-aware HTML and PNG exports with glassmorphism styling
17
+ - Session continuation and Controller view with inline prompt
18
+ - Time-scoped filtering and cache tracking in analytics dashboard
19
+ - Docker session reuse and keep-alive configuration options
20
+ - LocalRepl helper primitives for common agent workflows
21
+ - JSONL structured logging
22
+ - `rlm` CLI executable
23
+ - Custom Night Owl syntax highlighting theme
24
+
25
+ ### Fixed
26
+ - Docker container DNS resolution and `network_mode` wiring
27
+ - Docker agent symbol/string key mismatch for `allow_network`
28
+ - Session continuation logic and UI display
29
+ - Kramdown rendering with GFM parser dependency
30
+ - CSS specificity for headless Chrome export rendering
31
+ - UI pivot masking during live stream execution tracking
32
+ - Cache-busting for static assets
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Taylor Weibley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,300 @@
1
+ # RubyRLM
2
+
3
+ RubyRLM is an MVP Ruby implementation of Recursive Language Models (RLMs) that uses Gemini as the model backend and a Ruby REPL for iterative reasoning.
4
+
5
+ ## What This MVP Includes
6
+
7
+ - `RubyRLM::Client` API similar to `rlm.completion(...)`.
8
+ - Gemini backend via direct REST (`generateContent`).
9
+ - Local and Docker-isolated REPL backends with iterative `exec` actions and `final` answer action.
10
+ - Recursive sub-calls with `llm_query(...)` up to `max_depth`.
11
+ - **AST code validation** — Ripper-based syntax checking and dangerous call detection before eval.
12
+ - **Sub-call caching** — SHA256-keyed deduplication of `llm_query` calls within a session.
13
+ - **Patch tracking** — Audit trail for all `patch_file` operations with undo support.
14
+ - JSONL trajectory logging for iteration debugging.
15
+ - Web UI with session replay, continuation, and environment selection.
16
+ - RSpec test suite for parser, loop, REPL, recursion, and backend retries.
17
+
18
+ ## Safety Model
19
+
20
+ RubyRLM executes model-produced Ruby code. Choose execution environment based on your trust boundary.
21
+
22
+ - `environment: "local"` runs code directly on the host process (unsafe for untrusted prompts).
23
+ - `environment: "docker"` runs code in a Docker container with isolation defaults.
24
+ - Keep side effects disabled unless intentionally requested.
25
+
26
+ ### Code Validation
27
+
28
+ Before executing any LLM-generated code, RubyRLM validates it using Ruby's `Ripper` parser:
29
+
30
+ - **Syntax errors** are caught immediately without running the code, saving an iteration.
31
+ - **Dangerous calls** (`system`, `exec`, `fork`, `exit`, `File.delete`, `Kernel.exit`, etc.) are detected and surfaced as warnings in the `ExecutionResult`. These are non-blocking since the REPL intentionally provides safe wrappers like `sh()`, but they alert you when the model bypasses those wrappers.
32
+
33
+ Warnings appear in iteration metadata:
34
+
35
+ ```ruby
36
+ result.metadata[:iterations].each do |it|
37
+ puts it[:execution][:warnings] if it.dig(:execution, :warnings)&.any?
38
+ end
39
+ ```
40
+
41
+ ## Requirements
42
+
43
+ - Ruby `>= 3.1`
44
+ - `GEMINI_API_KEY` in your shell environment
45
+ - Docker (optional, required only for `environment: "docker"`)
46
+
47
+ ## Installation
48
+
49
+ Add to your Gemfile:
50
+
51
+ ```ruby
52
+ gem "rubyrlm"
53
+ ```
54
+
55
+ Or install directly:
56
+
57
+ ```bash
58
+ gem install rubyrlm
59
+ ```
60
+
61
+ ### Development Setup
62
+
63
+ ```bash
64
+ git clone https://github.com/tweibley/rubyrlm.git
65
+ cd rubyrlm
66
+ bundle install
67
+ bundle exec rspec
68
+ ```
69
+
70
+ If you plan to use Docker execution, build the REPL image once:
71
+
72
+ ```bash
73
+ docker build -t rubyrlm/repl:latest -f docker/Dockerfile.repl docker/
74
+ ```
75
+
76
+ ## Quickstart
77
+
78
+ ```ruby
79
+ require "rubyrlm"
80
+
81
+ client = RubyRLM::Client.new(
82
+ backend: "gemini",
83
+ model_name: "gemini-3.1-pro-preview",
84
+ api_key: ENV["GEMINI_API_KEY"],
85
+ max_depth: 1,
86
+ max_iterations: 20,
87
+ logger: RubyRLM::Logger::JsonlLogger.new(log_dir: "./logs"),
88
+ verbose: true
89
+ )
90
+
91
+ result = client.completion(prompt: "Calculate 2^(2^(2^2)) with Ruby and explain the result.")
92
+ puts result.response
93
+ puts result.usage_summary.to_h
94
+ ```
95
+
96
+ Run in Docker-isolated mode:
97
+
98
+ ```ruby
99
+ client = RubyRLM::Client.new(
100
+ backend: "gemini",
101
+ model_name: "gemini-3.1-pro-preview",
102
+ api_key: ENV["GEMINI_API_KEY"],
103
+ environment: "docker",
104
+ environment_options: {
105
+ memory_limit: "256m",
106
+ allow_network: true
107
+ }
108
+ )
109
+ ```
110
+
111
+ You can also run:
112
+
113
+ ```bash
114
+ ruby examples/quickstart.rb
115
+ ```
116
+
117
+ For an interactive session with a preloaded client:
118
+
119
+ ```bash
120
+ bundle exec bin/console
121
+ ```
122
+
123
+ Inside console:
124
+
125
+ ```ruby
126
+ ask(client, "What is the latency to google.com from this machine?")
127
+ ```
128
+
129
+ With `verbose: true`, you'll now see each iteration's actual `exec` Ruby code plus execution output/error summaries, not just the action names.
130
+
131
+ ## CLI
132
+
133
+ RubyRLM ships with a `rubyrlm` command:
134
+
135
+ ```bash
136
+ rubyrlm "Calculate 2^(2^(2^2)) with Ruby and explain the result."
137
+ ```
138
+
139
+ Options:
140
+
141
+ ```
142
+ -m, --model MODEL Model name (default: gemini-3.1-pro-preview)
143
+ -e, --env ENV Execution environment: local or docker (default: local)
144
+ --max-iterations NUM Maximum iterations (default: 30)
145
+ --max-depth NUM Maximum recursion depth (default: 1)
146
+ --timeout SECS Iteration execution timeout (default: 60)
147
+ --thinking LEVEL Thinking level: low|medium|high (default: medium)
148
+ --keep-alive Keep docker container alive after run
149
+ --reuse-container-id ID Reuse existing docker container
150
+ --allow-network Allow docker container to access host networking
151
+ -v, --verbose Enable verbose debug output
152
+ ```
153
+
154
+ Prompts can also be piped via stdin:
155
+
156
+ ```bash
157
+ echo "What is 1+1?" | rubyrlm
158
+ ```
159
+
160
+ ## How the Action Protocol Works
161
+
162
+ The model must return exactly one JSON object per turn:
163
+
164
+ - `{"action":"exec","code":"<ruby code>"}` to run code in REPL
165
+ - `{"action":"final","answer":"<final answer>"}` to finish
166
+
167
+ If model output is malformed, RubyRLM issues one repair re-prompt. If `max_iterations` is reached, RubyRLM forces a final response.
168
+
169
+ ## REPL Variables and Helpers
170
+
171
+ Within `exec` code:
172
+
173
+ - `context` is the original prompt/context
174
+ - `llm_query(sub_prompt, model_name: nil)` launches a recursive sub-call
175
+ - `fetch(url, headers: {})` performs HTTP GET with redirect following
176
+ - `sh(command, timeout: 5)` runs a shell command safely
177
+ - `patch_file(path, old_text, new_text)` replaces text exactly once (tracked for undo)
178
+ - `grep(pattern, path: ".")` searches with ripgrep
179
+ - `chunk_text(text, max_length: 2000)` splits long text semantically
180
+
181
+ RubyRLM sends a compact context summary to the model and keeps full data in REPL memory. This significantly reduces repeated prompt tokens for large datasets.
182
+
183
+ For state between iterations, prefer instance variables (for example `@memo`) or helper methods.
184
+
185
+ ## Sub-Call Caching
186
+
187
+ Identical `llm_query` calls within a session are automatically deduplicated. The cache keys on `SHA256(model_name + prompt)`, so the same question to the same model returns the cached result.
188
+
189
+ Cache stats are included in the completion result:
190
+
191
+ ```ruby
192
+ result = client.completion(prompt: data)
193
+ puts result.metadata[:sub_call_cache]
194
+ # => { hits: 3, misses: 2, size: 2 }
195
+ ```
196
+
197
+ ## Patch Tracking & Undo
198
+
199
+ Every `patch_file` call is recorded with old/new text and a timestamp. The modification log is surfaced in the completion result:
200
+
201
+ ```ruby
202
+ result = client.completion(prompt: "Fix the typo in config.yml")
203
+ puts result.metadata[:file_modifications]
204
+ # => [{ path: "config.yml", timestamp: "2026-02-28T12:34:56-05:00" }]
205
+ ```
206
+
207
+ Patches can be undone programmatically through the REPL:
208
+
209
+ ```ruby
210
+ # Inside exec code
211
+ undo_result = undo_last_patch # reverses the most recent patch_file
212
+ undo_all = undo_all_patches # reverses all patches in LIFO order
213
+ ```
214
+
215
+ ## Docker Environment Options
216
+
217
+ When `environment: "docker"` is selected, `environment_options` supports:
218
+
219
+ - `image` (default: `"rubyrlm/repl:latest"`)
220
+ - `memory_limit` (default: `"256m"`)
221
+ - `cpu_quota` (default: `50000`)
222
+ - `network_mode` (`"none"` by default, `"bridge"` to allow outbound)
223
+ - `allow_network` (boolean shorthand for bridge networking)
224
+ - `keep_alive` (optional boolean to bypass container teardown on completion)
225
+ - `reuse_container_id` (optional Docker container ID to eagerly attach to instead of spinning up a new instance)
226
+ - `connect_timeout` (default: `10` seconds)
227
+ - `gemini_api_key_secret` (default: `"gemini_api_key"`)
228
+ - `gemini_api_key_secret_path` (optional absolute path to a host secret file)
229
+
230
+ Notes:
231
+
232
+ - Docker mode is strict isolation by default (no project workspace mount).
233
+ - `llm_query`, `fetch`, `sh`, and `chunk_text` run inside the container.
234
+ - `patch_file` and `grep` are intentionally disabled in strict Docker mode.
235
+ - Gemini credentials are read in-container from `GEMINI_API_KEY_FILE` (mounted from your secret file).
236
+
237
+ Example secret-file setup for Docker mode:
238
+
239
+ ```bash
240
+ mkdir -p .secrets
241
+ printf '%s\n' "$GEMINI_API_KEY" > .secrets/gemini_api_key
242
+ chmod 600 .secrets/gemini_api_key
243
+ ```
244
+
245
+ ```ruby
246
+ client = RubyRLM::Client.new(
247
+ backend: "gemini",
248
+ model_name: "gemini-3.1-pro-preview",
249
+ environment: "docker",
250
+ environment_options: {
251
+ gemini_api_key_secret_path: File.expand_path(".secrets/gemini_api_key")
252
+ }
253
+ )
254
+ ```
255
+
256
+ ## Web UI
257
+
258
+ Start the web UI:
259
+
260
+ ```bash
261
+ ruby viewer.rb -p 8080
262
+ ```
263
+
264
+ or in dev mode:
265
+
266
+ ```bash
267
+ bin/dev -p 8080
268
+ ```
269
+
270
+ In the **Controller** sidebar you can select:
271
+
272
+ - **Execution Environment**: Local or Docker
273
+ - **Allow Docker Network Access**: enable outbound networking in Docker mode
274
+ - **Keep Container Alive**: prevents Docker from terminating and removing the instance when the run completes
275
+ - **Reuse Container Instance**: actively queries running isolate workers and allows you to submit queries directly into persistent host environments
276
+
277
+ ## Logging
278
+
279
+ Pass `RubyRLM::Logger::JsonlLogger.new(log_dir: "./logs")` to the client.
280
+
281
+ Events are written per-run as JSONL and include:
282
+
283
+ - run start/end
284
+ - per-iteration actions and execution results
285
+ - parent-child run relationship for recursive sub-calls
286
+
287
+ ## Examples
288
+
289
+ - `examples/quickstart.rb`: single prompt run with logger
290
+ - `examples/needle_in_haystack.rb`: synthetic long-context retrieval task
291
+
292
+ ## Testing
293
+
294
+ ```bash
295
+ bundle exec rspec
296
+ ```
297
+
298
+ ## Future Extensions
299
+
300
+ - More backend adapters
data/bin/rubyrlm ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+ require "rubyrlm"
6
+ require "optparse"
7
+
8
+ options = {
9
+ model_name: "gemini-3.1-pro-preview",
10
+ environment: "local",
11
+ max_iterations: 30,
12
+ max_depth: 1,
13
+ iteration_timeout: 60,
14
+ thinking_level: "medium",
15
+ environment_options: {},
16
+ verbose: false,
17
+ log_dir: File.expand_path("../logs", __dir__)
18
+ }
19
+
20
+ parser = OptionParser.new do |opts|
21
+ opts.banner = "Usage: rubyrlm [options] \"<prompt>\""
22
+
23
+ opts.on("-m", "--model MODEL", "Model name (default: gemini-3.1-pro-preview)") do |v|
24
+ options[:model_name] = v
25
+ end
26
+
27
+ opts.on("-e", "--env ENV", "Execution environment: local or docker (default: local)") do |v|
28
+ options[:environment] = v
29
+ end
30
+
31
+ opts.on("--max-iterations NUM", Integer, "Maximum iterations (default: 30)") do |v|
32
+ options[:max_iterations] = v
33
+ end
34
+
35
+ opts.on("--max-depth NUM", Integer, "Maximum recursion depth (default: 1)") do |v|
36
+ options[:max_depth] = v
37
+ end
38
+
39
+ opts.on("--timeout SECS", Integer, "Iteration execution timeout (default: 60)") do |v|
40
+ options[:iteration_timeout] = v
41
+ end
42
+
43
+ opts.on("--thinking LEVEL", "Thinking level: low|medium|high (default: medium)") do |v|
44
+ options[:thinking_level] = v
45
+ end
46
+
47
+ opts.on("--keep-alive", "Keep docker container alive after run") do
48
+ options[:environment_options][:keep_alive] = true
49
+ end
50
+
51
+ opts.on("--reuse-container-id ID", "Reuse existing docker container by its ID") do |v|
52
+ options[:environment_options][:reuse_container_id] = v
53
+ end
54
+
55
+ opts.on("--allow-network", "Allow docker container to access host networking") do
56
+ options[:environment_options][:allow_network] = true
57
+ end
58
+
59
+ opts.on("-v", "--verbose", "Enable verbose debug output") do
60
+ options[:verbose] = true
61
+ end
62
+
63
+ opts.on("-h", "--help", "Prints this help") do
64
+ puts opts
65
+ exit
66
+ end
67
+ end
68
+
69
+ parser.parse!
70
+
71
+ prompt = ARGV.join(" ").strip
72
+ if prompt.empty? && !STDIN.tty?
73
+ prompt = STDIN.read.strip
74
+ end
75
+
76
+ if prompt.empty?
77
+ puts parser.help
78
+ exit 1
79
+ end
80
+
81
+ api_key = ENV["GEMINI_API_KEY"]
82
+ if api_key.to_s.strip.empty?
83
+ warn "Error: GEMINI_API_KEY is missing in environment."
84
+ exit 1
85
+ end
86
+
87
+ class MultiLogger
88
+ def initialize(*loggers)
89
+ @loggers = loggers.compact
90
+ end
91
+
92
+ def log(payload)
93
+ @loggers.each { |l| l.log(payload) }
94
+ rescue => e
95
+ warn "Logger error: #{e.message}"
96
+ end
97
+ end
98
+
99
+ class CliStreamer
100
+ def initialize
101
+ @in_chunk = false
102
+ end
103
+
104
+ def log(payload)
105
+ case payload[:type]
106
+ when "chunk"
107
+ print payload[:chunk]
108
+ @in_chunk = true
109
+ when "iteration"
110
+ puts "" if @in_chunk
111
+ @in_chunk = false
112
+ data = payload[:data]
113
+ if data && data[:action] == "exec"
114
+ puts "\n\n[Executing Ruby Code] ->"
115
+ puts "```ruby\n#{data[:code]}\n```"
116
+ if data[:execution]
117
+ puts "[Result: #{data[:execution][:ok] ? 'Success' : 'Failed'}]"
118
+ puts data[:execution][:output]
119
+ puts data[:execution][:error] if data[:execution][:error]
120
+ end
121
+ puts "-" * 40
122
+ end
123
+ when "run_error"
124
+ puts "" if @in_chunk
125
+ @in_chunk = false
126
+ puts "\n[Error] #{payload[:error]}"
127
+ end
128
+ end
129
+ end
130
+
131
+ cli_streamer = CliStreamer.new
132
+ jsonl_logger = RubyRLM::Logger::JsonlLogger.new(log_dir: options[:log_dir])
133
+ multi_logger = MultiLogger.new(cli_streamer, jsonl_logger)
134
+
135
+ generation_config = {}
136
+ if %w[low medium high].include?(options[:thinking_level].to_s.downcase)
137
+ generation_config[:thinking_config] = { thinkingLevel: options[:thinking_level].to_s.downcase }
138
+ end
139
+
140
+ client_kwargs = {
141
+ backend: "gemini",
142
+ api_key: api_key,
143
+ model_name: options[:model_name],
144
+ environment: options[:environment],
145
+ max_iterations: options[:max_iterations],
146
+ max_depth: options[:max_depth],
147
+ iteration_timeout: options[:iteration_timeout],
148
+ generation_config: generation_config,
149
+ verbose: options[:verbose],
150
+ streaming: true,
151
+ logger: multi_logger
152
+ }
153
+ client_kwargs[:environment_options] = options[:environment_options] unless options[:environment_options].empty?
154
+
155
+ begin
156
+ client = RubyRLM::Client.new(**client_kwargs)
157
+ rescue => e
158
+ warn "Failed to initialize client: #{e.message}"
159
+ exit 1
160
+ end
161
+
162
+ puts "=> Starting query..."
163
+
164
+ result = client.completion(prompt: prompt)
165
+
166
+ puts "\n\n=== Final Result ==="
167
+ puts result.response
168
+ puts "\n[Usage Summary: #{result.usage_summary.total_tokens} tokens | Cached: #{result.usage_summary.cached_content_tokens} | Cost: $#{'%.4f' % result.usage_summary.compute_total_usd_cost}]"
@@ -0,0 +1,9 @@
1
+ module RubyRLM
2
+ module Backends
3
+ class Base
4
+ def complete(messages:, generation_config: {})
5
+ raise NotImplementedError, "#{self.class} must implement #complete"
6
+ end
7
+ end
8
+ end
9
+ end