robot_lab-to 0.2.8 → 0.3.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.
- checksums.yaml +4 -4
- data/.envrc +1 -1
- data/README.md +9 -7
- data/docs/concepts/stop-conditions.md +3 -3
- data/docs/configuration/cli.md +4 -5
- data/docs/configuration/index.md +2 -3
- data/docs/configuration/settings.md +7 -7
- data/docs/getting-started/installation.md +5 -5
- data/docs/index.md +3 -3
- data/docs/local-models/index.md +14 -17
- data/docs/local-models/lm-studio.md +92 -0
- data/docs/reference/architecture.md +4 -4
- data/examples/01_basic_usage/basic_usage.rb +1 -1
- data/examples/02_advanced_usage/advanced_usage.rb +3 -3
- data/examples/03_scored/scored_run.rb +1 -1
- data/examples/04_prose/prose_run.rb +1 -1
- data/examples/common.rb +9 -12
- data/lib/robot_lab/to/orchestrator.rb +6 -2
- data/lib/robot_lab/to/tools/bash.rb +2 -2
- data/lib/robot_lab/to/tools/edit.rb +4 -4
- data/lib/robot_lab/to/tools/read.rb +3 -3
- data/lib/robot_lab/to/tools/request_decision.rb +10 -10
- data/lib/robot_lab/to/tools/submit_result.rb +11 -11
- data/lib/robot_lab/to/tools/write.rb +2 -2
- data/lib/robot_lab/to/version.rb +1 -1
- data/lib/robot_lab/to.rb +22 -0
- data/mkdocs.yml +1 -1
- metadata +7 -7
- data/docs/local-models/ollama.md +0 -122
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3dad27a958d30948be8ed4f93401f169e799c24d3a4d77fc4d205bf00c80849c
|
|
4
|
+
data.tar.gz: bdd675595bb524d3e4f2f1e24dce9df7b7ef2070195b5b60e3317780c4e51163
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92a7c42b8b8586085a9894c15d2443e809b65147db939707b1d1c1ec2b83eeaec09752a165fa2193fb38d9f7fb10211fe33b8c0cce8a86b4a40a4987affb4be8
|
|
7
|
+
data.tar.gz: 06bd6bf4074ea7a35e969736de4ba5946db22a2313ee7551528d93630950fddfc2572acee1e3116bf923fe5b9aeffcbc198234106a7f32e9ebbd67d758887b57
|
data/.envrc
CHANGED
data/README.md
CHANGED
|
@@ -244,16 +244,18 @@ RobotLab::To.run(
|
|
|
244
244
|
## Local models
|
|
245
245
|
|
|
246
246
|
`robot_lab-to` can drive a local model running offline against
|
|
247
|
-
[
|
|
247
|
+
[LM Studio](https://lmstudio.ai) via the
|
|
248
|
+
[ruby_llm-providers-lms](https://github.com/madbomber/ruby_llm-providers-lms)
|
|
249
|
+
gem — no API keys, no per-token cost.
|
|
248
250
|
|
|
249
251
|
```bash
|
|
250
|
-
|
|
252
|
+
lms server start
|
|
253
|
+
lms get qwen/qwen3.8-27b
|
|
251
254
|
|
|
252
255
|
robot-to "Add a greet(name) method in greeter.rb" \
|
|
253
|
-
--provider
|
|
254
|
-
--model
|
|
256
|
+
--provider lms \
|
|
257
|
+
--model qwen/qwen3.8-27b \
|
|
255
258
|
--local-guards \
|
|
256
|
-
--no-stream \
|
|
257
259
|
--max-iterations 5
|
|
258
260
|
```
|
|
259
261
|
|
|
@@ -320,8 +322,8 @@ export ROBOT_LAB_TO_STREAM=false
|
|
|
320
322
|
|
|
321
323
|
API keys are read by the underlying provider (via RobotLab / RubyLLM), **not** by
|
|
322
324
|
`robot_lab-to` itself — e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`. Local
|
|
323
|
-
[
|
|
324
|
-
[Local Models guide](https://madbomber.github.io/robot_lab-to/local-models/
|
|
325
|
+
[LM Studio](https://lmstudio.ai) models need no key; see the
|
|
326
|
+
[Local Models guide](https://madbomber.github.io/robot_lab-to/local-models/lm-studio/).
|
|
325
327
|
|
|
326
328
|
---
|
|
327
329
|
|
|
@@ -37,9 +37,9 @@ limit.
|
|
|
37
37
|
|
|
38
38
|
- **Streaming runs** (the default) account tokens per chunk and can interrupt an
|
|
39
39
|
in-flight iteration the moment the budget is exhausted.
|
|
40
|
-
- **Non-streaming runs** (`--no-stream
|
|
41
|
-
|
|
42
|
-
[Local Models](../local-models/
|
|
40
|
+
- **Non-streaming runs** (`--no-stream`) account tokens from each iteration's
|
|
41
|
+
result and stop at the next iteration boundary. See
|
|
42
|
+
[Local Models](../local-models/index.md).
|
|
43
43
|
|
|
44
44
|
### Consecutive failures
|
|
45
45
|
|
data/docs/configuration/cli.md
CHANGED
|
@@ -43,7 +43,7 @@ echo "Add request logging middleware and tests" | robot-to
|
|
|
43
43
|
| `--commit-format` | `default`\|`conventional` | `default` | Commit message format. |
|
|
44
44
|
| `--run-dir` | `PATH` | `.robot_lab_to` | Directory for run state. |
|
|
45
45
|
| `--local-guards` | — | off | Add built-in file tools + small-model guardrails. |
|
|
46
|
-
| `--no-stream` | — | streaming on | Disable streaming (
|
|
46
|
+
| `--no-stream` | — | streaming on | Disable streaming (tokens are then accounted per iteration instead of per chunk). |
|
|
47
47
|
| `--debug` | — | off | Keep verbose provider logging enabled. |
|
|
48
48
|
| `--version` | — | — | Print version and exit. |
|
|
49
49
|
| `-h`, `--help` | — | — | Show help and exit. |
|
|
@@ -92,14 +92,13 @@ robot-to "Write an opinionated guide to the Viable Systems Model" \
|
|
|
92
92
|
--stop-on-plateau 3
|
|
93
93
|
```
|
|
94
94
|
|
|
95
|
-
A fully local run on
|
|
95
|
+
A fully local run on LM Studio (see [Local Models](../local-models/index.md)):
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
robot-to "Add a greet(name) method in greeter.rb" \
|
|
99
|
-
--provider
|
|
100
|
-
--model
|
|
99
|
+
--provider lms \
|
|
100
|
+
--model qwen/qwen3.8-27b \
|
|
101
101
|
--local-guards \
|
|
102
|
-
--no-stream \
|
|
103
102
|
--max-iterations 5
|
|
104
103
|
```
|
|
105
104
|
|
data/docs/configuration/index.md
CHANGED
|
@@ -43,10 +43,9 @@ Create `~/.config/robot_lab/to.yml` to set your own defaults — for example, to
|
|
|
43
43
|
always use a local model:
|
|
44
44
|
|
|
45
45
|
```yaml
|
|
46
|
-
provider:
|
|
47
|
-
model:
|
|
46
|
+
provider: lms # LM Studio via the ruby_llm-providers-lms gem
|
|
47
|
+
model: qwen/qwen3.8-27b
|
|
48
48
|
local_guards: true
|
|
49
|
-
stream: false
|
|
50
49
|
max_consecutive_failures: 4
|
|
51
50
|
```
|
|
52
51
|
|
|
@@ -13,8 +13,9 @@ CLI flag, or a `RobotLab::To.run` keyword argument (see
|
|
|
13
13
|
- **CLI:** `--provider NAME`
|
|
14
14
|
|
|
15
15
|
The LLM provider, passed through to RobotLab / RubyLLM. Use `anthropic`,
|
|
16
|
-
`openai`, etc. For **local
|
|
17
|
-
|
|
16
|
+
`openai`, etc. For **local LM Studio models**, require the
|
|
17
|
+
ruby_llm-providers-lms gem and set this to `lms` — see
|
|
18
|
+
[LM Studio Setup](../local-models/lm-studio.md).
|
|
18
19
|
|
|
19
20
|
### `model`
|
|
20
21
|
|
|
@@ -22,7 +23,7 @@ RubyLLM at Ollama's OpenAI-compatible endpoint — see [Ollama Setup](../local-m
|
|
|
22
23
|
- **CLI:** `--model MODEL`
|
|
23
24
|
|
|
24
25
|
The model identifier for the chosen provider (e.g. `claude-sonnet-4-6`,
|
|
25
|
-
`gpt-5.5`, `
|
|
26
|
+
`gpt-5.5`, `qwen/qwen3.8-27b`).
|
|
26
27
|
|
|
27
28
|
### `stream`
|
|
28
29
|
|
|
@@ -30,10 +31,9 @@ The model identifier for the chosen provider (e.g. `claude-sonnet-4-6`,
|
|
|
30
31
|
- **CLI:** `--no-stream` to disable
|
|
31
32
|
|
|
32
33
|
Whether to stream the model response. Streaming enables per-chunk token
|
|
33
|
-
accounting and mid-iteration token-budget enforcement.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
[Streaming and tool calls](../local-models/ollama.md#streaming-and-tool-calls).
|
|
34
|
+
accounting and mid-iteration token-budget enforcement. With streaming off,
|
|
35
|
+
tokens are accounted from each iteration's result instead. See
|
|
36
|
+
[LM Studio Setup](../local-models/lm-studio.md).
|
|
37
37
|
|
|
38
38
|
## Loop control
|
|
39
39
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
| Ruby **>= 3.2** | Set in the gemspec `required_ruby_version`. |
|
|
8
8
|
| Git | A repository with at least one commit. The loop creates a branch and commits there. |
|
|
9
9
|
| `robot_lab` | The core framework `robot_lab-to` builds on. |
|
|
10
|
-
| An LLM provider | A cloud API key, **or** a local [
|
|
10
|
+
| An LLM provider | A cloud API key, **or** a local [LM Studio](https://lmstudio.ai) server (see [Local Models](../local-models/index.md)). |
|
|
11
11
|
|
|
12
12
|
## Install the gem
|
|
13
13
|
|
|
@@ -46,11 +46,11 @@ RobotLab / RubyLLM. Provide credentials the usual way for your provider:
|
|
|
46
46
|
robot-to "..." --provider openai --model gpt-5.5
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
=== "Local (
|
|
49
|
+
=== "Local (LM Studio)"
|
|
50
50
|
|
|
51
|
-
No API key required. See [
|
|
52
|
-
full configuration —
|
|
53
|
-
|
|
51
|
+
No API key required. See [LM Studio Setup](../local-models/lm-studio.md)
|
|
52
|
+
for the full configuration — require the ruby_llm-providers-lms gem, then
|
|
53
|
+
run with `--provider lms` and `--local-guards`.
|
|
54
54
|
|
|
55
55
|
The default provider/model is `openai` / `gpt-5.5` (see
|
|
56
56
|
[Settings Reference](../configuration/settings.md)).
|
data/docs/index.md
CHANGED
|
@@ -68,7 +68,7 @@ See [The Iteration Loop](concepts/iteration-loop.md) for the full lifecycle.
|
|
|
68
68
|
`robot-to --resume <run_id>` continues a stopped run and an external scheduler
|
|
69
69
|
can drive it one commit per tick.
|
|
70
70
|
- **Runs on local models.** With `--local-guards` it ships built-in file tools and
|
|
71
|
-
small-model guardrails, so it can drive a local
|
|
71
|
+
small-model guardrails, so it can drive a local LM Studio model offline. See
|
|
72
72
|
[Local Models](local-models/index.md).
|
|
73
73
|
|
|
74
74
|
---
|
|
@@ -80,7 +80,7 @@ See [The Iteration Loop](concepts/iteration-loop.md) for the full lifecycle.
|
|
|
80
80
|
- :material-rocket-launch: **[Installation](getting-started/installation.md)** — install the gem and the `robot-to` CLI.
|
|
81
81
|
- :material-play: **[Quick Start](getting-started/quick-start.md)** — your first overnight run in five minutes.
|
|
82
82
|
- :material-cog: **[Configuration](configuration/index.md)** — every setting, the config cascade, and the CLI.
|
|
83
|
-
- :material-laptop: **[Local Models](local-models/index.md)** — drive a local
|
|
83
|
+
- :material-laptop: **[Local Models](local-models/index.md)** — drive a local LM Studio model with guardrails.
|
|
84
84
|
- :material-sitemap: **[Architecture](reference/architecture.md)** — how the pieces fit together.
|
|
85
85
|
|
|
86
86
|
</div>
|
|
@@ -93,4 +93,4 @@ See [The Iteration Loop](concepts/iteration-loop.md) for the full lifecycle.
|
|
|
93
93
|
- A git repository with at least one commit (the loop branches and commits there)
|
|
94
94
|
- The [`robot_lab`](https://github.com/MadBomber/robot_lab) gem and an LLM provider
|
|
95
95
|
(a cloud key such as `ANTHROPIC_API_KEY` / `OPENAI_API_KEY`, **or** a local
|
|
96
|
-
[
|
|
96
|
+
[LM Studio](https://lmstudio.ai) server via ruby_llm-providers-lms)
|
data/docs/local-models/index.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Local Models
|
|
2
2
|
|
|
3
3
|
`robot_lab-to` can drive a **local** model — running entirely offline against an
|
|
4
|
-
[
|
|
4
|
+
[LM Studio](https://lmstudio.ai) server via the `:lms` provider — instead of a cloud API. This is the
|
|
5
5
|
"local assistant" mode: no API keys, no per-token cost, no data leaving your
|
|
6
6
|
machine.
|
|
7
7
|
|
|
@@ -19,33 +19,31 @@ Both are enabled together with `--local-guards`.
|
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
21
|
# 1. Serve a tool-capable model
|
|
22
|
-
|
|
22
|
+
lms server start
|
|
23
|
+
lms get qwen/qwen3.8-27b
|
|
23
24
|
|
|
24
25
|
# 2. Run robot-to against it
|
|
25
26
|
robot-to "Add a greet(name) method in greeter.rb" \
|
|
26
|
-
--provider
|
|
27
|
-
--model
|
|
27
|
+
--provider lms \
|
|
28
|
+
--model qwen/qwen3.8-27b \
|
|
28
29
|
--local-guards \
|
|
29
|
-
--no-stream \
|
|
30
30
|
--max-iterations 5
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
The full setup — including the
|
|
34
|
-
|
|
33
|
+
The full setup — including requiring the ruby_llm-providers-lms gem — is on
|
|
34
|
+
the [LM Studio Setup](lm-studio.md) page.
|
|
35
35
|
|
|
36
36
|
## Why these flags
|
|
37
37
|
|
|
38
|
-
Driving a local model end-to-end
|
|
39
|
-
|
|
38
|
+
Driving a local model end-to-end takes two settings. Each exists because of a
|
|
39
|
+
concrete limitation discovered in testing:
|
|
40
40
|
|
|
41
41
|
| Flag / setting | Why |
|
|
42
42
|
|----------------|-----|
|
|
43
|
-
| `--provider
|
|
44
|
-
| `--no-stream` | Ollama suppresses tool calls when the response is streamed. With streaming off, tool calls come through. |
|
|
43
|
+
| `--provider lms` | The ruby_llm-providers-lms gem's LM Studio provider — no API key, and local model ids are assumed to exist. Its default `:chat_completions` protocol supports client tools, structured output, and streaming. |
|
|
45
44
|
| `--local-guards` | Attaches the file tools the model needs to do work, plus guardrails that make those tools safe for a small model. |
|
|
46
45
|
|
|
47
|
-
See [
|
|
48
|
-
for the details.
|
|
46
|
+
See [LM Studio Setup](lm-studio.md) for the details.
|
|
49
47
|
|
|
50
48
|
## The design philosophy
|
|
51
49
|
|
|
@@ -74,15 +72,14 @@ The model **must support tool calling**. In testing on an M2 Max:
|
|
|
74
72
|
|
|
75
73
|
| Model | Size | Tool calls? | Notes |
|
|
76
74
|
|-------|------|-------------|-------|
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `phi4-mini` | 3.8B | ❌ | Explains instead of calling tools. |
|
|
75
|
+
| `qwen/qwen3.8-27b` | 27B | ✅ reliable | Honors `tool_choice` and structured output; the right pick for the autonomous loop. **Recommended.** |
|
|
76
|
+
| `openai/gpt-oss-20b` | 20B | ⚠️ partial | Accepts tool definitions but ignores `tool_choice: required`, and its structured output parses without meaning anything. Fine for simple chat, not for the loop. |
|
|
80
77
|
|
|
81
78
|
Prefer a larger, instruction-following model for the autonomous loop — it has to
|
|
82
79
|
both use tools *and* remember to submit its result every iteration.
|
|
83
80
|
|
|
84
81
|
---
|
|
85
82
|
|
|
86
|
-
- [
|
|
83
|
+
- [LM Studio Setup](lm-studio.md) — install, serve, and configure.
|
|
87
84
|
- [Built-in Tools](tools.md) — what `read`/`write`/`edit`/`bash` do.
|
|
88
85
|
- [Guardrails](guardrails.md) — the small-model safety policies.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# LM Studio Setup
|
|
2
|
+
|
|
3
|
+
This page covers running `robot_lab-to` against a local
|
|
4
|
+
[LM Studio](https://lmstudio.ai) server end-to-end, through the
|
|
5
|
+
[ruby_llm-providers-lms](https://github.com/madbomber/ruby_llm-providers-lms)
|
|
6
|
+
gem, which registers the `:lms` provider with RubyLLM.
|
|
7
|
+
|
|
8
|
+
## 1. Install and start LM Studio
|
|
9
|
+
|
|
10
|
+
Install LM Studio, then bootstrap its CLI and start the server:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
lms bootstrap # puts the `lms` CLI on your PATH
|
|
14
|
+
lms server start # serves on http://localhost:1234
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## 2. Download a tool-capable model
|
|
18
|
+
|
|
19
|
+
The model **must** support tool calling *and honor it*. `qwen/qwen3.8-27b` is
|
|
20
|
+
the recommended choice — it obeys `tool_choice` and returns meaningful
|
|
21
|
+
structured output, where `gpt-oss` models accept the request shape but ignore
|
|
22
|
+
it (see [model selection](index.md#choosing-a-model)):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
lms get qwen/qwen3.8-27b
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 3. Point RubyLLM at LM Studio
|
|
29
|
+
|
|
30
|
+
`robot_lab-to` reaches the model through RobotLab / RubyLLM. Require the
|
|
31
|
+
provider gem and, only if your server is not on the default endpoint,
|
|
32
|
+
configure the base URL:
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
require "ruby_llm"
|
|
36
|
+
require "ruby_llm/providers/lms"
|
|
37
|
+
require "robot_lab"
|
|
38
|
+
require "robot_lab/to"
|
|
39
|
+
|
|
40
|
+
RubyLLM.configure do |c|
|
|
41
|
+
c.lms_api_base = "http://localhost:1234/v1" # the default; override if needed
|
|
42
|
+
c.request_timeout = 600
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
RobotLab::To.run(
|
|
46
|
+
"Add a greet(name) method in greeter.rb",
|
|
47
|
+
provider: :lms,
|
|
48
|
+
model: "qwen/qwen3.8-27b",
|
|
49
|
+
local_guards: true,
|
|
50
|
+
max_iterations: 5
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
No API key is needed — LM Studio's local server does not require one.
|
|
55
|
+
|
|
56
|
+
## 4. Run
|
|
57
|
+
|
|
58
|
+
From the launcher above, or from the CLI:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
robot-to "Add a greet(name) method in greeter.rb" \
|
|
62
|
+
--provider lms \
|
|
63
|
+
--model qwen/qwen3.8-27b \
|
|
64
|
+
--local-guards \
|
|
65
|
+
--max-iterations 5
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Because LM Studio is a local provider, RubyLLM assumes any model id you pass
|
|
69
|
+
exists — LM Studio just-in-time loads the model if it isn't loaded yet. No
|
|
70
|
+
registry refresh is needed.
|
|
71
|
+
|
|
72
|
+
## Protocols
|
|
73
|
+
|
|
74
|
+
LM Studio serves several API protocols on one port; the `:lms` provider
|
|
75
|
+
defaults to `:chat_completions`, which is the most capable one here — client
|
|
76
|
+
tools, structured output, streaming, and reasoning control all work. Stay on
|
|
77
|
+
the default for `robot_lab-to`. See the
|
|
78
|
+
[ruby_llm-providers-lms README](https://github.com/madbomber/ruby_llm-providers-lms)
|
|
79
|
+
for the full protocol matrix.
|
|
80
|
+
|
|
81
|
+
## Troubleshooting
|
|
82
|
+
|
|
83
|
+
| Symptom | Cause | Fix |
|
|
84
|
+
|---------|-------|-----|
|
|
85
|
+
| Connection error naming `lms server start` | Server not running | `lms server start`. |
|
|
86
|
+
| Tool calls accepted but ignored, or `{"name":"analysis","age":0}`-style junk from schemas | Model doesn't honor `tool_choice` / grammar output (e.g. `gpt-oss`) | Use `qwen/qwen3.8-27b`. |
|
|
87
|
+
| Every iteration "did not submit" | Model too small to follow the final-report step | Use a larger model (e.g. `qwen/qwen3.8-27b`). |
|
|
88
|
+
| No tool calls at all, model only explains | Model lacks tool support | Pick a tool-capable model. |
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
Next: [Built-in Tools](tools.md).
|
|
@@ -103,10 +103,10 @@ robot actually exposes the submit tool, guarding against that class of mistake.
|
|
|
103
103
|
|
|
104
104
|
### Streaming is optional
|
|
105
105
|
|
|
106
|
-
By default the robot streams, enabling per-chunk token accounting and
|
|
107
|
-
budget enforcement.
|
|
108
|
-
tokens
|
|
109
|
-
|
|
106
|
+
By default the robot streams, enabling per-chunk token accounting and
|
|
107
|
+
mid-stream budget enforcement. Non-streaming runs (`stream: false`) account
|
|
108
|
+
tokens from each iteration's result and enforce the budget at iteration
|
|
109
|
+
boundaries. See [LM Studio Setup](../local-models/lm-studio.md).
|
|
110
110
|
|
|
111
111
|
### Interruptible by design
|
|
112
112
|
|
|
@@ -70,7 +70,7 @@ PROVIDER = ENV.fetch("RLTO_PROVIDER", LOCAL ? "lms" : "anthropic").to_sym
|
|
|
70
70
|
MODEL = ENV.fetch("RLTO_MODEL", LOCAL ? "qwen/qwen3.8-27b" : "claude-sonnet-4-6")
|
|
71
71
|
|
|
72
72
|
# ruby_llm has no native "lms" adapter. "lms" is this example's friendly label for
|
|
73
|
-
# "a local LM Studio model"; setup (common.rb) resolves it to
|
|
73
|
+
# "a local LM Studio model"; setup (common.rb) resolves it to the :lms provider
|
|
74
74
|
# adapter pointed at LM Studio, starting the server and loading MODEL as needed.
|
|
75
75
|
# Everything passed to RobotLab uses the resolved provider; PROVIDER itself is
|
|
76
76
|
# kept only for display.
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
# the robot must earn each commit on correctness AND quality.
|
|
26
26
|
#
|
|
27
27
|
# Models (per your request): reasoning on OpenAI gpt-5.5, building on local
|
|
28
|
-
# LM Studio qwen/qwen3.8-27b. Because
|
|
28
|
+
# LM Studio qwen/qwen3.8-27b. Because the phases use different providers and different
|
|
29
29
|
# endpoints (api.openai.com vs LM Studio's /v1), and openai_api_base is global,
|
|
30
30
|
# we toggle it between the (sequential) phases.
|
|
31
31
|
#
|
|
@@ -98,9 +98,9 @@ def use_real_openai!
|
|
|
98
98
|
RubyLLM.logger.level = Logger::ERROR # keep raw API traffic out of the feed
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
# Implementation phase
|
|
101
|
+
# Implementation phase uses the :lms provider against the local LM Studio endpoint
|
|
102
102
|
# (or whatever BUILD_PROVIDER resolves to). common.rb's setup starts the LM Studio
|
|
103
|
-
# server and loads BUILD_MODEL as needed; the "lms" label resolves to :
|
|
103
|
+
# server and loads BUILD_MODEL as needed; the "lms" label resolves to the :lms provider.
|
|
104
104
|
def use_build_provider!
|
|
105
105
|
setup(provider: BUILD_PROVIDER, model: BUILD_MODEL)
|
|
106
106
|
end
|
|
@@ -64,7 +64,7 @@ PROVIDER = ENV.fetch("RLTO_PROVIDER", LOCAL ? "lms" : "anthropic").to_sym
|
|
|
64
64
|
MODEL = ENV.fetch("RLTO_MODEL", LOCAL ? "qwen/qwen3.8-27b" : "claude-sonnet-4-6")
|
|
65
65
|
|
|
66
66
|
# ruby_llm has no native "lms" adapter. "lms" is this example's friendly label for
|
|
67
|
-
# "a local LM Studio model"; setup (common.rb) resolves it to
|
|
67
|
+
# "a local LM Studio model"; setup (common.rb) resolves it to the :lms provider
|
|
68
68
|
# adapter pointed at LM Studio, starting the server and loading MODEL as needed.
|
|
69
69
|
# Everything passed to RobotLab uses the resolved provider; PROVIDER itself is
|
|
70
70
|
# kept only for display.
|
|
@@ -65,7 +65,7 @@ JUDGE_MODEL = ENV.fetch("RLTO_JUDGE_MODEL", LOCAL ? "qwen/qwen3.8-27b" : "claude
|
|
|
65
65
|
TOPIC = ENV.fetch("RLTO_TOPIC", "writing good Git commit messages")
|
|
66
66
|
|
|
67
67
|
# ruby_llm has no native "lms" adapter. "lms" is this example's friendly label for
|
|
68
|
-
# "a local LM Studio model"; setup (common.rb) resolves it to
|
|
68
|
+
# "a local LM Studio model"; setup (common.rb) resolves it to the :lms provider
|
|
69
69
|
# adapter pointed at LM Studio, starting the server and loading each model as
|
|
70
70
|
# needed -- once for the doer, again for the judge (a no-op if they're the same
|
|
71
71
|
# model, or if it's already loaded). Everything passed to RobotLab uses the
|
data/examples/common.rb
CHANGED
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
# actual_provider = setup(provider: PROVIDER, model: MODEL)
|
|
13
13
|
# RobotLab::To.run(objective, provider: actual_provider, model: MODEL, ...)
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# 1. points
|
|
15
|
+
# "lms" is the ruby_llm-providers-lms gem's native LM Studio provider.
|
|
16
|
+
# When `provider` is "lms", setup:
|
|
17
|
+
# 1. points the :lms provider at LMS_BASE_URL
|
|
18
18
|
# 2. starts the LM Studio server if it isn't already running
|
|
19
19
|
# 3. loads `model` into LM Studio if it isn't already loaded
|
|
20
|
-
# and returns :
|
|
20
|
+
# and returns :lms -- the RubyLLM-recognized provider to pass to RobotLab.
|
|
21
21
|
# Any other provider (a cloud one) passes straight through untouched; setup
|
|
22
22
|
# does nothing else for it (the example configures its own API key as before).
|
|
23
23
|
#
|
|
@@ -32,6 +32,7 @@ require "open3"
|
|
|
32
32
|
require "uri"
|
|
33
33
|
|
|
34
34
|
require "ruby_llm"
|
|
35
|
+
require "ruby_llm/providers/lms"
|
|
35
36
|
|
|
36
37
|
LMS_BASE_URL = ENV.fetch("LMS_BASE_URL", "http://localhost:1234/v1")
|
|
37
38
|
|
|
@@ -44,7 +45,7 @@ def setup(provider: ENV.fetch("RLTO_PROVIDER", "lms"), model: ENV.fetch("RLTO_MO
|
|
|
44
45
|
configure_lms!
|
|
45
46
|
ensure_lms_server_running!
|
|
46
47
|
ensure_lms_model_loaded!(model)
|
|
47
|
-
:
|
|
48
|
+
:lms
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
def teardown
|
|
@@ -58,18 +59,14 @@ at_exit { teardown }
|
|
|
58
59
|
|
|
59
60
|
# --- internals ---------------------------------------------------------------
|
|
60
61
|
|
|
61
|
-
#
|
|
62
|
-
#
|
|
62
|
+
# Point the :lms provider at the LM Studio endpoint. Local models are assumed
|
|
63
|
+
# to exist, so no registry refresh is needed.
|
|
63
64
|
def configure_lms!
|
|
64
65
|
RubyLLM.configure do |c|
|
|
65
|
-
c.
|
|
66
|
-
c.openai_api_key = "lm-studio" # ignored by LM Studio, but RubyLLM wants a value
|
|
66
|
+
c.lms_api_base = LMS_BASE_URL
|
|
67
67
|
c.request_timeout = 600
|
|
68
68
|
end
|
|
69
69
|
RubyLLM.logger.level = Logger::ERROR
|
|
70
|
-
RubyLLM.models.refresh!
|
|
71
|
-
rescue StandardError => e
|
|
72
|
-
warn "warning: could not refresh LM Studio models (#{e.class}: #{e.message})"
|
|
73
70
|
end
|
|
74
71
|
|
|
75
72
|
def lms_server_running?
|
|
@@ -597,10 +597,14 @@ module RobotLab
|
|
|
597
597
|
tools + [Tools::Read.new, Tools::Write.new, Tools::Edit.new, Tools::Bash.new]
|
|
598
598
|
end
|
|
599
599
|
|
|
600
|
+
# :reek:TooManyStatements -- the streaming callback accumulates usage and enforces the stop in one place.
|
|
600
601
|
def token_tracker
|
|
601
602
|
lambda do |chunk|
|
|
602
|
-
|
|
603
|
-
|
|
603
|
+
# ruby_llm 2.0 nests counts under chunk.tokens (usually only on the
|
|
604
|
+
# final chunk of a stream).
|
|
605
|
+
tokens = chunk.respond_to?(:tokens) ? chunk.tokens : nil
|
|
606
|
+
@run.input_tokens += tokens&.input.to_i
|
|
607
|
+
@run.output_tokens += tokens&.output.to_i
|
|
604
608
|
|
|
605
609
|
return unless @stop_conditions.token_limit_exceeded?
|
|
606
610
|
|
|
@@ -17,8 +17,8 @@ module RobotLab
|
|
|
17
17
|
output and exit status. Use for building, testing, listing, and git.
|
|
18
18
|
DESC
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
parameter :command, type: "string", description: "The shell command to run"
|
|
21
|
+
parameter :timeout, type: "integer", description: "Seconds before the command is killed", required: false
|
|
22
22
|
|
|
23
23
|
def initialize(robot: nil, timeout: DEFAULT_TIMEOUT)
|
|
24
24
|
super(robot: robot)
|
|
@@ -14,10 +14,10 @@ module RobotLab
|
|
|
14
14
|
replace_all is true. Read the file first to get the exact text.
|
|
15
15
|
DESC
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
parameter :path, type: "string", description: "Path of the file to edit"
|
|
18
|
+
parameter :old_text, type: "string", description: "Exact text to replace"
|
|
19
|
+
parameter :new_text, type: "string", description: "Replacement text"
|
|
20
|
+
parameter :replace_all, type: "boolean", description: "Replace every occurrence", required: false
|
|
21
21
|
|
|
22
22
|
# :reek:BooleanParameter -- replace_all is a tool param the LLM sets;
|
|
23
23
|
# the `param` declarations above fix this signature.
|
|
@@ -14,9 +14,9 @@ module RobotLab
|
|
|
14
14
|
start line) and limit (max lines). Always read a file before editing it.
|
|
15
15
|
DESC
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
parameter :path, type: "string", description: "Path to the file to read"
|
|
18
|
+
parameter :offset, type: "integer", description: "1-based first line to return", required: false
|
|
19
|
+
parameter :limit, type: "integer", description: "Maximum number of lines to return", required: false
|
|
20
20
|
|
|
21
21
|
def execute(path:, offset: nil, limit: nil, **)
|
|
22
22
|
resolved = File.expand_path(path, Dir.pwd)
|
|
@@ -25,20 +25,20 @@ module RobotLab
|
|
|
25
25
|
for routine engineering choices you are equipped to make yourself.
|
|
26
26
|
DESC
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
parameter :question, type: "string",
|
|
29
|
+
description: "The decision that needs a human answer, phrased as a question"
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
parameter :situation, type: "string",
|
|
32
|
+
description: "Why this needs a human and what is at stake", required: false
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
parameter :options, type: "array",
|
|
35
|
+
description: "The distinct options you see (strings)", required: false
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
parameter :recommendation, type: "string",
|
|
38
|
+
description: "Your recommended option and the reasoning behind it", required: false
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
parameter :blocking, type: "boolean",
|
|
41
|
+
description: "true if work cannot correctly proceed until this is answered", required: false
|
|
42
42
|
|
|
43
43
|
def captured_requests
|
|
44
44
|
@captured_requests ||= []
|
|
@@ -13,23 +13,23 @@ module RobotLab
|
|
|
13
13
|
FINAL action before finishing. Do not call it until your work is complete.
|
|
14
14
|
DESC
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
parameter :success, type: "boolean",
|
|
17
|
+
description: "true if you made meaningful progress toward the objective; " \
|
|
18
|
+
"false if you made no meaningful changes AND have no new learnings"
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
parameter :summary, type: "string",
|
|
21
|
+
description: "Brief one-sentence description of what you accomplished or why you stopped"
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
parameter :key_changes, type: "array",
|
|
24
|
+
description: "List of files or changes made this iteration (empty if none)",
|
|
25
25
|
required: false
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
parameter :key_learnings, type: "array",
|
|
28
|
+
description: "Insights worth remembering for future iterations (empty if none)",
|
|
29
29
|
required: false
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
parameter :should_fully_stop, type: "boolean",
|
|
32
|
+
description: "Set to true only when instructed by a stop condition",
|
|
33
33
|
required: false
|
|
34
34
|
|
|
35
35
|
attr_reader :captured_result
|
|
@@ -15,8 +15,8 @@ module RobotLab
|
|
|
15
15
|
do not exist yet; to change an existing file, use Edit.
|
|
16
16
|
DESC
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
parameter :path, type: "string", description: "Path of the file to create"
|
|
19
|
+
parameter :content, type: "string", description: "Full text content to write"
|
|
20
20
|
|
|
21
21
|
def execute(path:, content:, **)
|
|
22
22
|
resolved = File.expand_path(path, Dir.pwd)
|
data/lib/robot_lab/to/version.rb
CHANGED
data/lib/robot_lab/to.rb
CHANGED
|
@@ -44,6 +44,7 @@ module RobotLab
|
|
|
44
44
|
# @return [void]
|
|
45
45
|
def run(objective, **)
|
|
46
46
|
config = Config.new(**)
|
|
47
|
+
ensure_provider_loaded(config.provider)
|
|
47
48
|
suppress_llm_logging unless config.debug?
|
|
48
49
|
Orchestrator.new(objective, config).run
|
|
49
50
|
end
|
|
@@ -52,6 +53,7 @@ module RobotLab
|
|
|
52
53
|
# objective and prior state are loaded from the run's run.json.
|
|
53
54
|
def resume(run_id, **)
|
|
54
55
|
config = Config.new(**)
|
|
56
|
+
ensure_provider_loaded(config.provider)
|
|
55
57
|
suppress_llm_logging unless config.debug?
|
|
56
58
|
Orchestrator.new(nil, config, resume_run_id: run_id).run
|
|
57
59
|
end
|
|
@@ -66,6 +68,26 @@ module RobotLab
|
|
|
66
68
|
|
|
67
69
|
private
|
|
68
70
|
|
|
71
|
+
# Provider gems register themselves on require by convention
|
|
72
|
+
# (ruby_llm-providers-<name> defines ruby_llm/providers/<name>). When the
|
|
73
|
+
# configured provider isn't registered yet — e.g. `robot-to --provider
|
|
74
|
+
# lms` from the CLI — try that conventional require so the user doesn't
|
|
75
|
+
# need a wrapper script. A miss falls through to RubyLLM's own "Unknown
|
|
76
|
+
# provider" error at request time.
|
|
77
|
+
def ensure_provider_loaded(provider)
|
|
78
|
+
return unless provider
|
|
79
|
+
return unless defined?(RubyLLM::Provider)
|
|
80
|
+
|
|
81
|
+
name = provider.to_s
|
|
82
|
+
return if RubyLLM::Provider.resolve(name)
|
|
83
|
+
|
|
84
|
+
begin
|
|
85
|
+
require "ruby_llm/providers/#{name}"
|
|
86
|
+
rescue LoadError
|
|
87
|
+
# Not a provider-gem provider; RubyLLM will report it if truly unknown.
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
69
91
|
def suppress_llm_logging
|
|
70
92
|
require "logger"
|
|
71
93
|
null = Logger.new(File::NULL)
|
data/mkdocs.yml
CHANGED
|
@@ -136,7 +136,7 @@ nav:
|
|
|
136
136
|
- CLI Reference: configuration/cli.md
|
|
137
137
|
- Local Models:
|
|
138
138
|
- Overview: local-models/index.md
|
|
139
|
-
-
|
|
139
|
+
- LM Studio Setup: local-models/lm-studio.md
|
|
140
140
|
- Built-in Tools: local-models/tools.md
|
|
141
141
|
- Guardrails: local-models/guardrails.md
|
|
142
142
|
- Reference:
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: robot_lab-to
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -13,16 +13,16 @@ dependencies:
|
|
|
13
13
|
name: robot_lab
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
|
-
- - "
|
|
16
|
+
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version:
|
|
18
|
+
version: 0.3.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
|
-
- - "
|
|
23
|
+
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version:
|
|
25
|
+
version: 0.3.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: myway_config
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -83,7 +83,7 @@ files:
|
|
|
83
83
|
- docs/index.md
|
|
84
84
|
- docs/local-models/guardrails.md
|
|
85
85
|
- docs/local-models/index.md
|
|
86
|
-
- docs/local-models/
|
|
86
|
+
- docs/local-models/lm-studio.md
|
|
87
87
|
- docs/local-models/tools.md
|
|
88
88
|
- docs/reference/architecture.md
|
|
89
89
|
- examples/.envrc
|
|
@@ -172,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
172
172
|
- !ruby/object:Gem::Version
|
|
173
173
|
version: '0'
|
|
174
174
|
requirements: []
|
|
175
|
-
rubygems_version: 4.0.
|
|
175
|
+
rubygems_version: 4.0.21
|
|
176
176
|
specification_version: 4
|
|
177
177
|
summary: Autonomous overnight agent loop for RobotLab — run robots while you sleep.
|
|
178
178
|
test_files: []
|
data/docs/local-models/ollama.md
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
# Ollama Setup
|
|
2
|
-
|
|
3
|
-
This page covers running `robot_lab-to` against a local
|
|
4
|
-
[Ollama](https://ollama.com) server end-to-end.
|
|
5
|
-
|
|
6
|
-
## 1. Install and start Ollama
|
|
7
|
-
|
|
8
|
-
```bash
|
|
9
|
-
# macOS / Linux
|
|
10
|
-
curl -fsSL https://ollama.com/install.sh | sh
|
|
11
|
-
|
|
12
|
-
# Ollama runs a server on http://localhost:11434
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
## 2. Pull a tool-capable model
|
|
16
|
-
|
|
17
|
-
The model **must** support tool calling. `gpt-oss:20b` is the recommended choice
|
|
18
|
-
(see [model selection](index.md#choosing-a-model)):
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
ollama pull gpt-oss:20b
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
You can confirm a model advertises tool support:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
curl -s http://localhost:11434/api/tags | jq '.models[] | {name, caps: .capabilities}'
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## 3. Point RubyLLM at Ollama
|
|
31
|
-
|
|
32
|
-
`robot_lab-to` reaches the model through RobotLab / RubyLLM. The robust path is
|
|
33
|
-
the **`:openai` provider** aimed at Ollama's OpenAI-compatible `/v1` endpoint.
|
|
34
|
-
Configure RubyLLM once, before launching the run:
|
|
35
|
-
|
|
36
|
-
```ruby
|
|
37
|
-
require "ruby_llm"
|
|
38
|
-
require "robot_lab"
|
|
39
|
-
require "robot_lab/to"
|
|
40
|
-
|
|
41
|
-
RubyLLM.configure do |c|
|
|
42
|
-
c.openai_api_base = "http://localhost:11434/v1"
|
|
43
|
-
c.openai_api_key = "ollama" # ignored by Ollama, but RubyLLM requires a value
|
|
44
|
-
c.request_timeout = 600
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
RobotLab::To.run(
|
|
48
|
-
"Add a greet(name) method in greeter.rb",
|
|
49
|
-
provider: :openai,
|
|
50
|
-
model: "gpt-oss:20b",
|
|
51
|
-
local_guards: true,
|
|
52
|
-
stream: false,
|
|
53
|
-
max_iterations: 5
|
|
54
|
-
)
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
!!! note "Why a wrapper script"
|
|
58
|
-
The Ollama base-URL configuration lives in RubyLLM's global config, so it's
|
|
59
|
-
set in a small Ruby launcher (as above) rather than via a `robot-to` CLI flag.
|
|
60
|
-
The CLI flags `--provider openai --model gpt-oss:20b --local-guards --no-stream`
|
|
61
|
-
cover the rest; only the base URL needs the wrapper. You can also set it in
|
|
62
|
-
your application's RubyLLM initializer.
|
|
63
|
-
|
|
64
|
-
## 4. Run
|
|
65
|
-
|
|
66
|
-
From the launcher above, or — once the base URL is configured in your environment
|
|
67
|
-
— from the CLI:
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
robot-to "Add a greet(name) method in greeter.rb" \
|
|
71
|
-
--provider openai \
|
|
72
|
-
--model gpt-oss:20b \
|
|
73
|
-
--local-guards \
|
|
74
|
-
--no-stream \
|
|
75
|
-
--max-iterations 5
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Streaming and tool calls
|
|
79
|
-
|
|
80
|
-
**Local Ollama models must run non-streaming** (`--no-stream` / `stream: false`).
|
|
81
|
-
|
|
82
|
-
Ollama's OpenAI-compatible endpoint suppresses tool calls when the response is
|
|
83
|
-
streamed — the model "thinks" but never emits a tool call, so the robot can't do
|
|
84
|
-
any work. With streaming disabled, tool calls come through normally.
|
|
85
|
-
|
|
86
|
-
The trade-off: streaming is what enables per-chunk token accounting and
|
|
87
|
-
mid-iteration token-budget enforcement. With `--no-stream`, tokens are accounted
|
|
88
|
-
from each iteration's *result* instead, and `--max-tokens` is enforced at
|
|
89
|
-
iteration boundaries rather than mid-stream. For overnight local runs this is
|
|
90
|
-
almost always fine.
|
|
91
|
-
|
|
92
|
-
## Why not the native `:ollama` provider?
|
|
93
|
-
|
|
94
|
-
RubyLLM ships a native `:ollama` provider, but in testing it did **not** reliably
|
|
95
|
-
get tool-capable models (e.g. qwen3) to emit tool calls — the same model tool-calls
|
|
96
|
-
correctly through the `:openai` provider against Ollama's `/v1` endpoint. Until
|
|
97
|
-
that changes, prefer `provider: :openai` + `openai_api_base` for `robot_lab-to`.
|
|
98
|
-
|
|
99
|
-
## Registering models (if needed)
|
|
100
|
-
|
|
101
|
-
RubyLLM validates models against its registry. `robot_lab-to` sets
|
|
102
|
-
`assume_model_exists` when a provider is given, which is enough for a bare chat —
|
|
103
|
-
but attaching tools triggers a capability lookup that can raise
|
|
104
|
-
`ModelNotFoundError` for an unregistered model. If you hit that, refresh the
|
|
105
|
-
registry so your local models are known:
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
RubyLLM.models.refresh! # discovers locally-served Ollama models
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
## Troubleshooting
|
|
112
|
-
|
|
113
|
-
| Symptom | Cause | Fix |
|
|
114
|
-
|---------|-------|-----|
|
|
115
|
-
| Model thinks but never calls a tool | Streaming is on | Add `--no-stream`. |
|
|
116
|
-
| `RobotLab::ModelNotFoundError` when tools attach | Model not in RubyLLM registry | `RubyLLM.models.refresh!`. |
|
|
117
|
-
| Every iteration "did not submit" | Model too small to follow the final-report step | Use a larger model (e.g. `gpt-oss:20b`). |
|
|
118
|
-
| No tool calls at all, model only explains | Model lacks tool support | Pick a model whose `capabilities` include `tools`. |
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
Next: [Built-in Tools](tools.md).
|