robot_lab 0.2.8 → 0.3.1
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/CHANGELOG.md +65 -0
- data/README.md +16 -18
- data/lib/robot_lab/ask_user.rb +3 -3
- data/lib/robot_lab/config/defaults.yml +5 -5
- data/lib/robot_lab/config.rb +2 -2
- data/lib/robot_lab/mcp/transports/streamable_http.rb +2 -4
- data/lib/robot_lab/memory.rb +16 -7
- data/lib/robot_lab/robot/hooking.rb +26 -0
- data/lib/robot_lab/robot/result_building.rb +119 -0
- data/lib/robot_lab/robot.rb +37 -120
- data/lib/robot_lab/run_config.rb +52 -20
- data/lib/robot_lab/tool.rb +7 -12
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +3 -3
- metadata +15 -274
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -139
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -200
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -1
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -215
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -13
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -13
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -18
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -13
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -533
- data/examples/common.rb +0 -148
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ae306b218ea8c7c5d68b9de5c0bce088e40a9f1c482278fd54557624bc0ad72
|
|
4
|
+
data.tar.gz: 92206741b254631a7e09da7247db35702eb12a70409622aa3634c594e83207a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 36c52283dfd0edc88cfc71e358a82e463ddfad501a69097ef2aaa28b37f7c5f5d08938d39b1c6312c2b1a3aaf19494895d3bcfd6f74870b4b770942f3ae60d3e
|
|
7
|
+
data.tar.gz: 1477bf01cb700d41bab40950cd650960676f1589a5f2f4dc8747b1f940e9f21a5b3ff53a15ca4aa2ef01c493b38c1f957406ddc1b365f1592a21b1e6bda1d992
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,71 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **Minimum Ruby raised to 4.0.9.** `required_ruby_version` is now `>= 4.0.9` (was `>= 3.2.0`), and a `.ruby-version` file pins development to 4.0.9.
|
|
14
|
+
- **Slimmer gem.** The gemspec now whitelists what ships in the `.gem`: `lib/`, `sig/`, `exe/`, README, CHANGELOG, and LICENSE. Previous releases packaged everything tracked by git — the docs site, examples with generated output, tooling config, and local design notes — which made 0.3.0 a 1.1 MB gem of 300+ files. 0.3.1 is ~130 KB and 65 files; the docs and examples remain on GitHub.
|
|
15
|
+
- Verified against ruby_llm 2.1.1 (the newest release the `~> 2.0` constraint admits) in addition to 2.0.0; the full suite passes on both.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `MCP::Transports::StreamableHTTP#connect` no longer raises inside its Async task when the initialize handshake returns no result; the session id is simply left unset.
|
|
20
|
+
|
|
21
|
+
## [0.3.0] - 2026-09-18
|
|
22
|
+
|
|
23
|
+
The ruby_llm 2.0 release. Core moves from ruby_llm 1.x to the official 2.0.0
|
|
24
|
+
release; every `robot_lab-*` extension gem releases in lockstep at 0.3.0 and
|
|
25
|
+
now requires `robot_lab ~> 0.3.0`.
|
|
26
|
+
|
|
27
|
+
### Changed (BREAKING)
|
|
28
|
+
- **ruby_llm 1.x → 2.0.** The gemspec now requires `ruby_llm "~> 2.0"`
|
|
29
|
+
(official release). Robot tools written with the 1.x DSL must migrate:
|
|
30
|
+
`param` → `parameter`, `desc:` → `description:`; `halt` is gone (the caller
|
|
31
|
+
controls the loop). Token counts moved to `response.tokens.input/.output`,
|
|
32
|
+
and `finish_reason` (Symbol) replaces `stop_reason`. The `claude-sonnet-4`
|
|
33
|
+
registry alias no longer resolves; the default models are now
|
|
34
|
+
`claude-sonnet-4-6` (production) and `claude-haiku-4-5` (test).
|
|
35
|
+
- **Dropped hard dependencies** on `ruby_llm-mcp`, `ruby_llm-schema`, and
|
|
36
|
+
`ruby_llm-semantic_cache`. Semantic caching is now optional: `Memory#cache`
|
|
37
|
+
returns `nil` when `ruby_llm-semantic_cache` is not installed and Memory
|
|
38
|
+
runs with caching disabled. (Its released versions still pin ruby_llm 1.x,
|
|
39
|
+
so it cannot co-resolve with 2.0 yet.)
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- `ruby_llm-providers-apfel` and `ruby_llm-providers-lms` (`~> 0.2.1`) as
|
|
43
|
+
runtime dependencies, consumed as released gems — the local `path:`
|
|
44
|
+
overrides for sibling checkouts are gone from the Gemfiles.
|
|
45
|
+
- `Robot::ResultBuilding` — extracted module that adapts a ruby_llm response
|
|
46
|
+
into a `RobotResult` (token accounting, stop-reason normalization, message
|
|
47
|
+
coercion).
|
|
48
|
+
- `examples/run_all.rb` — runs every executable `NN_*.rb` demo serially with
|
|
49
|
+
a banner between each; works from any cwd, and Ctrl-C terminates only the
|
|
50
|
+
demo currently running (the runner notes it and moves on).
|
|
51
|
+
- READMEs for the subdirectory demos: `15_memory_network_and_bus`,
|
|
52
|
+
`26_document_store` (corpus), and `27_incident_response`.
|
|
53
|
+
- Fasterer quality gate now actually runs: `fasterer` added to the
|
|
54
|
+
development group, with a `.fasterer.yml` documenting the disabled
|
|
55
|
+
speed-over-readability checks.
|
|
56
|
+
|
|
57
|
+
### Changed
|
|
58
|
+
- Robot, Tool, RunConfig, and the hook system adapted to the ruby_llm 2.0
|
|
59
|
+
API; docs and examples synced to match.
|
|
60
|
+
- Examples run against a local LM Studio server through the `:lms` provider;
|
|
61
|
+
`examples/common.rb` anchors `BUNDLE_GEMFILE` to the gem root so demos run
|
|
62
|
+
from any directory, and `examples/README.md` notes that demo speed and
|
|
63
|
+
answer quality depend on the provider/model in use.
|
|
64
|
+
- `.envrc` no longer exports `BUNDLE_GEMFILE`; it is inherited from the
|
|
65
|
+
project root (`asgard dev` / `asgard prod`).
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
- amazing_print 3.0 crash at demo startup (`undefined method 'try' for
|
|
69
|
+
module ActiveSupport`): robot_lab's dependencies load parts of
|
|
70
|
+
ActiveSupport, which tricked amazing_print into loading its Rails
|
|
71
|
+
extension; `examples/common.rb` now preloads
|
|
72
|
+
`active_support/core_ext/object/try` and `active_support/log_subscriber`.
|
|
73
|
+
|
|
9
74
|
## [0.2.8] - 2026-09-09
|
|
10
75
|
|
|
11
76
|
### Added
|
data/README.md
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
- <strong>Human-in-the-Loop</strong> - AskUser tool for interactive prompting<br>
|
|
22
22
|
- <strong>Content Streaming</strong> - Stored callbacks, per-call blocks, or both<br>
|
|
23
23
|
- <strong>MCP Integration</strong> - Connect to external tool servers with timeouts and retry<br>
|
|
24
|
-
- <strong>Local LLM Providers</strong> - Ollama and GPUStack via provider passthrough<br>
|
|
24
|
+
- <strong>Local LLM Providers</strong> - LM Studio (via ruby_llm-providers-lms), Ollama, and GPUStack via provider passthrough<br>
|
|
25
25
|
- <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
|
|
26
26
|
- <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
|
|
27
27
|
- <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
|
|
@@ -55,7 +55,7 @@ gem install robot_lab
|
|
|
55
55
|
|
|
56
56
|
### Requirements
|
|
57
57
|
|
|
58
|
-
- Ruby >=
|
|
58
|
+
- Ruby >= 4.0.9
|
|
59
59
|
- [One or more API Keys for LLM providers supported by RubyLLM](https://rubyllm.com/configuration/#provider-configuration)
|
|
60
60
|
|
|
61
61
|
For comprehensive guides and API documentation, visit **[https://madbomber.github.io/robot_lab](https://madbomber.github.io/robot_lab)**
|
|
@@ -82,17 +82,15 @@ puts result.last_text_content
|
|
|
82
82
|
|
|
83
83
|
### Local LLM Providers
|
|
84
84
|
|
|
85
|
-
For local LLM providers, use the `provider:` parameter. RubyLLM's provider list is the authority on valid values — `:ollama` and `:gpustack` are
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
export ROBOT_LAB_RUBY_LLM__OLLAMA_API_BASE=http://localhost:11434/v1
|
|
89
|
-
```
|
|
85
|
+
For local LLM providers, use the `provider:` parameter. RubyLLM's provider list is the authority on valid values — `:ollama` and `:gpustack` are built in, and provider gems add more (alongside `:anthropic`, `:azure`, `:bedrock`, `:deepseek`, `:gemini`, `:mistral`, `:openai`, `:openrouter`, `:perplexity`, `:vertexai`, and `:xai`). The examples in this repo use LM Studio through the [ruby_llm-providers-lms](https://github.com/madbomber/ruby_llm-providers-lms) gem, which registers the `:lms` provider (defaults to `http://localhost:1234/v1`, no API key needed):
|
|
90
86
|
|
|
91
87
|
```ruby
|
|
88
|
+
require "ruby_llm/providers/lms"
|
|
89
|
+
|
|
92
90
|
robot = RobotLab.build(
|
|
93
91
|
name: "local_bot",
|
|
94
|
-
model: "
|
|
95
|
-
provider: :
|
|
92
|
+
model: "qwen/qwen3.8-27b", # complex work; use "openai/gpt-oss-20b" for simple chat
|
|
93
|
+
provider: :lms,
|
|
96
94
|
system_prompt: "You are a helpful assistant."
|
|
97
95
|
)
|
|
98
96
|
```
|
|
@@ -113,12 +111,12 @@ RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layer
|
|
|
113
111
|
# Set API keys via environment variables (double underscore for nesting)
|
|
114
112
|
export ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
115
113
|
export ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
|
|
116
|
-
export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
114
|
+
export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4-6
|
|
117
115
|
```
|
|
118
116
|
|
|
119
117
|
```ruby
|
|
120
118
|
# Access configuration values
|
|
121
|
-
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
119
|
+
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4-6"
|
|
122
120
|
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
123
121
|
```
|
|
124
122
|
|
|
@@ -126,7 +124,7 @@ Or create a project config file at `./config/robot_lab.yml`:
|
|
|
126
124
|
|
|
127
125
|
```yaml
|
|
128
126
|
ruby_llm:
|
|
129
|
-
model: claude-sonnet-4
|
|
127
|
+
model: claude-sonnet-4-6
|
|
130
128
|
anthropic_api_key: sk-ant-...
|
|
131
129
|
request_timeout: 180
|
|
132
130
|
```
|
|
@@ -202,7 +200,7 @@ mcp:
|
|
|
202
200
|
type: stdio
|
|
203
201
|
command: npx
|
|
204
202
|
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
205
|
-
model: claude-sonnet-4
|
|
203
|
+
model: claude-sonnet-4-6
|
|
206
204
|
---
|
|
207
205
|
You are a GitHub assistant. Use available tools to help with repository tasks.
|
|
208
206
|
```
|
|
@@ -265,7 +263,7 @@ robot = RobotLab.build(
|
|
|
265
263
|
```ruby
|
|
266
264
|
# Create a shared config
|
|
267
265
|
shared = RobotLab::RunConfig.new(
|
|
268
|
-
model: "claude-sonnet-4",
|
|
266
|
+
model: "claude-sonnet-4-6",
|
|
269
267
|
temperature: 0.7,
|
|
270
268
|
max_tokens: 2000
|
|
271
269
|
)
|
|
@@ -302,16 +300,16 @@ RunConfig supports keyword construction, block DSL, and merge semantics:
|
|
|
302
300
|
```ruby
|
|
303
301
|
# Block DSL
|
|
304
302
|
config = RobotLab::RunConfig.new do |c|
|
|
305
|
-
c.model "claude-sonnet-4"
|
|
303
|
+
c.model "claude-sonnet-4-6"
|
|
306
304
|
c.temperature 0.7
|
|
307
305
|
end
|
|
308
306
|
|
|
309
307
|
# Merge (more-specific wins)
|
|
310
|
-
network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
|
|
308
|
+
network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4-6", temperature: 0.5)
|
|
311
309
|
robot_config = RobotLab::RunConfig.new(temperature: 0.9)
|
|
312
310
|
effective = network_config.merge(robot_config)
|
|
313
311
|
effective.temperature #=> 0.9
|
|
314
|
-
effective.model #=> "claude-sonnet-4"
|
|
312
|
+
effective.model #=> "claude-sonnet-4-6"
|
|
315
313
|
```
|
|
316
314
|
|
|
317
315
|
### Chaining Configuration
|
|
@@ -323,7 +321,7 @@ robot = RobotLab.build(name: "writer", system_prompt: "You are a creative writer
|
|
|
323
321
|
|
|
324
322
|
result = robot
|
|
325
323
|
.with_temperature(0.9)
|
|
326
|
-
.with_model("claude-sonnet-4")
|
|
324
|
+
.with_model("claude-sonnet-4-6")
|
|
327
325
|
.run("Write a haiku about Ruby programming")
|
|
328
326
|
```
|
|
329
327
|
|
data/lib/robot_lab/ask_user.rb
CHANGED
|
@@ -33,9 +33,9 @@ module RobotLab
|
|
|
33
33
|
#
|
|
34
34
|
class AskUser < Tool
|
|
35
35
|
description "Ask the user a question and wait for their typed response"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
parameter :question, type: "string", description: "The question to ask the user"
|
|
37
|
+
parameter :choices, type: "array", description: "Optional list of choices to present", required: false
|
|
38
|
+
parameter :default, type: "string", description: "Default value if user presses Enter", required: false
|
|
39
39
|
|
|
40
40
|
# :reek:FeatureEnvy -- rendering and resolving the caller-supplied choices list is this tool's whole job.
|
|
41
41
|
# :reek:TooManyStatements -- linear prompt/read/resolve terminal interaction.
|
|
@@ -41,13 +41,13 @@ defaults:
|
|
|
41
41
|
with_model:
|
|
42
42
|
provider: null # chat-specific provider
|
|
43
43
|
model: null # chat-specific model
|
|
44
|
-
|
|
44
|
+
assume_model_exists: null # assume the model exists primarily for local providers
|
|
45
45
|
with_temperature: 0.7 # Controls randomness (0.0-2.0, null = model default)
|
|
46
|
+
with_max_output_tokens: null # Maximum tokens in response
|
|
46
47
|
with_tools: null # ?? not sure about this one.
|
|
47
|
-
|
|
48
|
+
with_provider_options: # merged into the request payload (ruby_llm 2.0)
|
|
48
49
|
top_p: null # Nucleus sampling threshold (0.0-1.0)
|
|
49
50
|
top_k: null # Top-k sampling (integer, provider-specific)
|
|
50
|
-
max_tokens: null # Maximum tokens in response
|
|
51
51
|
presence_penalty: null # Penalize new tokens based on presence (-2.0 to 2.0)
|
|
52
52
|
frequency_penalty: null # Penalize new tokens based on frequency (-2.0 to 2.0)
|
|
53
53
|
stop: null # Stop sequences (string or array of strings)
|
|
@@ -55,7 +55,7 @@ defaults:
|
|
|
55
55
|
# RubyLLM Configuration Section
|
|
56
56
|
ruby_llm:
|
|
57
57
|
provider: :anthropic
|
|
58
|
-
model: claude-sonnet-4
|
|
58
|
+
model: claude-sonnet-4-6
|
|
59
59
|
assume_model_exists: false # (dep of assume_exists in chat section) set true for Ollama and other local LLM providers
|
|
60
60
|
# Provider API Keys (null = use env vars directly)
|
|
61
61
|
anthropic_api_key: null
|
|
@@ -117,7 +117,7 @@ test:
|
|
|
117
117
|
max_iterations: 3
|
|
118
118
|
streaming_enabled: false
|
|
119
119
|
ruby_llm:
|
|
120
|
-
model: claude-
|
|
120
|
+
model: claude-haiku-4-5
|
|
121
121
|
request_timeout: 30
|
|
122
122
|
max_retries: 1
|
|
123
123
|
log_level: :warn
|
data/lib/robot_lab/config.rb
CHANGED
|
@@ -14,7 +14,7 @@ module RobotLab
|
|
|
14
14
|
# - Automatic RubyLLM configuration application
|
|
15
15
|
#
|
|
16
16
|
# @example Access configuration values
|
|
17
|
-
# RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
17
|
+
# RobotLab.config.ruby_llm.model #=> "claude-sonnet-4-6"
|
|
18
18
|
# RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
19
19
|
# RobotLab.config.development? #=> true
|
|
20
20
|
#
|
|
@@ -28,7 +28,7 @@ module RobotLab
|
|
|
28
28
|
# # defaults.yml. This file is NOT run through ERB, so keep secrets in
|
|
29
29
|
# # environment variables or in ./config/robot_lab.yml (which is).
|
|
30
30
|
# ruby_llm:
|
|
31
|
-
# model: claude-sonnet-4
|
|
31
|
+
# model: claude-sonnet-4-6
|
|
32
32
|
# request_timeout: 120
|
|
33
33
|
#
|
|
34
34
|
class Config < MywayConfig::Base
|
|
@@ -50,7 +50,7 @@ module RobotLab
|
|
|
50
50
|
|
|
51
51
|
# Initialize MCP protocol
|
|
52
52
|
result = send_initialize
|
|
53
|
-
@session_id ||= result
|
|
53
|
+
@session_id ||= result&.dig(:serverInfo, :sessionId)
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
self
|
|
@@ -116,9 +116,7 @@ module RobotLab
|
|
|
116
116
|
# Returns the session identifier.
|
|
117
117
|
#
|
|
118
118
|
# @return [String, nil] the session ID
|
|
119
|
-
|
|
120
|
-
@session_id
|
|
121
|
-
end
|
|
119
|
+
attr_reader :session_id
|
|
122
120
|
|
|
123
121
|
private
|
|
124
122
|
|
data/lib/robot_lab/memory.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Optional: ruby_llm-semantic_cache has not shipped a ruby_llm 2.0-compatible
|
|
4
|
+
# release. When the gem is absent, Memory#cache returns nil and semantic
|
|
5
|
+
# caching is silently disabled.
|
|
6
|
+
begin
|
|
7
|
+
require "ruby_llm/semantic_cache"
|
|
8
|
+
rescue LoadError
|
|
9
|
+
# Semantic caching unavailable; Memory runs without it.
|
|
10
|
+
end
|
|
4
11
|
|
|
5
12
|
module RobotLab
|
|
6
13
|
# Raised when a blocking get times out
|
|
@@ -106,7 +113,7 @@ module RobotLab
|
|
|
106
113
|
set_internal(:results, Array(results))
|
|
107
114
|
set_internal(:messages, Array(messages).map { |m| normalize_message(m) })
|
|
108
115
|
set_internal(:session_id, session_id)
|
|
109
|
-
set_internal(:cache, @enable_cache ?
|
|
116
|
+
set_internal(:cache, @enable_cache ? create_semantic_cache : nil)
|
|
110
117
|
|
|
111
118
|
# Data proxy for method-style access
|
|
112
119
|
@data = nil
|
|
@@ -215,9 +222,11 @@ module RobotLab
|
|
|
215
222
|
|
|
216
223
|
# Get the semantic cache module
|
|
217
224
|
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# responses
|
|
225
|
+
# When the optional ruby_llm-semantic_cache gem is installed (and
|
|
226
|
+
# enable_cache is true), provides semantic similarity matching for LLM
|
|
227
|
+
# responses, reducing costs and latency by returning cached responses
|
|
228
|
+
# for semantically equivalent queries. Returns nil when the gem is
|
|
229
|
+
# absent or caching is disabled.
|
|
221
230
|
#
|
|
222
231
|
# @example Using the cache with fetch
|
|
223
232
|
# response = memory.cache.fetch("What is Ruby?") do
|
|
@@ -228,7 +237,7 @@ module RobotLab
|
|
|
228
237
|
# chat = memory.cache.wrap(RubyLLM.chat(model: "gpt-4"))
|
|
229
238
|
# chat.ask("What is Ruby?") # Cached on semantic similarity
|
|
230
239
|
#
|
|
231
|
-
# @return [RubyLLM::SemanticCache] the semantic cache module
|
|
240
|
+
# @return [RubyLLM::SemanticCache, nil] the semantic cache module, or nil when unavailable
|
|
232
241
|
#
|
|
233
242
|
def cache
|
|
234
243
|
get_internal(:cache)
|
|
@@ -702,7 +711,7 @@ module RobotLab
|
|
|
702
711
|
end
|
|
703
712
|
|
|
704
713
|
def create_semantic_cache
|
|
705
|
-
RubyLLM::SemanticCache
|
|
714
|
+
defined?(RubyLLM::SemanticCache) ? RubyLLM::SemanticCache : nil
|
|
706
715
|
end
|
|
707
716
|
|
|
708
717
|
# :reek:ControlParameter -- factory method; the preference symbol is exactly what selects the backend.
|
|
@@ -54,6 +54,32 @@ module RobotLab
|
|
|
54
54
|
def hook_registries(network = nil)
|
|
55
55
|
[RobotLab.hooks, network&.hooks, @hooks]
|
|
56
56
|
end
|
|
57
|
+
|
|
58
|
+
# Arm the per-run circuit breaker checked by the chat's before_tool_call
|
|
59
|
+
# dispatcher (see Robot#register_chat_callbacks). Raises ToolLoopError
|
|
60
|
+
# once tool calls exceed @config.max_tool_rounds. ruby_llm 2.0 callbacks
|
|
61
|
+
# are additive and cannot be removed, so the breaker toggles a flag the
|
|
62
|
+
# permanent dispatcher consults instead of swapping callbacks per run.
|
|
63
|
+
def install_circuit_breaker
|
|
64
|
+
@circuit_breaker_call_count = 0
|
|
65
|
+
@circuit_breaker_armed = true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Disarm the circuit breaker after a run.
|
|
69
|
+
def restore_tool_call_callback
|
|
70
|
+
@circuit_breaker_armed = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Count a tool call against max_tool_rounds and raise once exceeded.
|
|
74
|
+
def enforce_circuit_breaker!
|
|
75
|
+
max = @config.max_tool_rounds
|
|
76
|
+
@circuit_breaker_call_count += 1
|
|
77
|
+
return if @circuit_breaker_call_count <= max
|
|
78
|
+
|
|
79
|
+
raise ToolLoopError,
|
|
80
|
+
"Circuit breaker triggered: #{@circuit_breaker_call_count} tool calls exceeded " \
|
|
81
|
+
"max_tool_rounds (#{max})"
|
|
82
|
+
end
|
|
57
83
|
end
|
|
58
84
|
end
|
|
59
85
|
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
class Robot < RubyLLM::Agent
|
|
5
|
+
# Adapts a ruby_llm response into a RobotResult, including token
|
|
6
|
+
# accounting, stop-reason normalization, and message coercion helpers.
|
|
7
|
+
#
|
|
8
|
+
# Owns: nothing (pure adapters over the response and @chat)
|
|
9
|
+
# Reads: @chat, @name; Writes: @total_input_tokens, @total_output_tokens
|
|
10
|
+
module ResultBuilding
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# :reek:TooManyStatements :reek:FeatureEnvy -- adapting a provider response's many optional fields into
|
|
14
|
+
# a RobotResult is inherently response-centric.
|
|
15
|
+
def build_result(response, _memory)
|
|
16
|
+
text = result_text(response)
|
|
17
|
+
output = text ? [TextMessage.new(role: 'assistant', content: text)] : []
|
|
18
|
+
|
|
19
|
+
tool_calls = response.respond_to?(:tool_calls) ? (response.tool_calls || []) : []
|
|
20
|
+
|
|
21
|
+
input_toks, output_toks = extract_token_counts(response)
|
|
22
|
+
@total_input_tokens += input_toks
|
|
23
|
+
@total_output_tokens += output_toks
|
|
24
|
+
|
|
25
|
+
RobotResult.new(
|
|
26
|
+
robot_name: @name,
|
|
27
|
+
output: output,
|
|
28
|
+
tool_calls: normalize_tool_calls(tool_calls),
|
|
29
|
+
stop_reason: extract_stop_reason(response),
|
|
30
|
+
raw: response,
|
|
31
|
+
input_tokens: input_toks,
|
|
32
|
+
output_tokens: output_toks
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Token usage from the response. ruby_llm 2.0 nests counts under
|
|
37
|
+
# response.tokens; duck-typed responses may still answer input_tokens.
|
|
38
|
+
# :reek:FeatureEnvy -- reading the response's optional token fields is the extraction itself.
|
|
39
|
+
def extract_token_counts(response)
|
|
40
|
+
if response.respond_to?(:tokens) && (tokens = response.tokens)
|
|
41
|
+
[tokens.input.to_i, tokens.output.to_i]
|
|
42
|
+
elsif response.respond_to?(:input_tokens)
|
|
43
|
+
[response.input_tokens.to_i,
|
|
44
|
+
response.respond_to?(:output_tokens) ? response.output_tokens.to_i : 0]
|
|
45
|
+
else
|
|
46
|
+
[0, 0]
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# ruby_llm 2.0's add_message accepts a Message, an attribute Hash, or a
|
|
51
|
+
# record responding to to_llm. Compression summaries (and tests) hand us
|
|
52
|
+
# plain role/content value objects; convert those to attribute hashes so
|
|
53
|
+
# the chat can coerce them.
|
|
54
|
+
def coerce_replacement_message(message)
|
|
55
|
+
if message.is_a?(RubyLLM::Message) || message.is_a?(Hash) || message.respond_to?(:to_llm)
|
|
56
|
+
message
|
|
57
|
+
else
|
|
58
|
+
{ role: message.role, content: message.content }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The response's normalized stop reason. ruby_llm 2.0 exposes it as
|
|
63
|
+
# finish_reason (a Symbol such as :stop or :tool_calls); older or
|
|
64
|
+
# duck-typed responses may still answer stop_reason.
|
|
65
|
+
def extract_stop_reason(response)
|
|
66
|
+
return response.finish_reason if response.respond_to?(:finish_reason)
|
|
67
|
+
|
|
68
|
+
response.respond_to?(:stop_reason) ? response.stop_reason : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Text for the result's output. Prefers the final response's content, then
|
|
72
|
+
# falls back in order to: (1) thinking text for models that route all output
|
|
73
|
+
# through reasoning_content (e.g. qwen3 on Ollama), (2) the most recent
|
|
74
|
+
# assistant text within the current turn for models that end on a tool call
|
|
75
|
+
# with no trailing text.
|
|
76
|
+
#
|
|
77
|
+
# The chat-history fallback is scoped to messages AFTER the last user message
|
|
78
|
+
# (the current turn) to prevent a previous turn's response from being returned
|
|
79
|
+
# when a thinking-mode model emits nothing in response.content.
|
|
80
|
+
# :reek:TooManyStatements :reek:FeatureEnvy -- documented fallback chain over the response's optional content/thinking/history fields.
|
|
81
|
+
def result_text(response)
|
|
82
|
+
content = response.content if response.respond_to?(:content)
|
|
83
|
+
return content if content && !content.to_s.empty?
|
|
84
|
+
|
|
85
|
+
# Ollama routes qwen3's reasoning to reasoning_content, which ruby_llm
|
|
86
|
+
# surfaces as response.thinking (a RubyLLM::Thinking object). When content
|
|
87
|
+
# is nil and thinking is present, the thinking IS the response for that turn.
|
|
88
|
+
if response.respond_to?(:thinking) && (thinking = response.thinking)
|
|
89
|
+
thinking_text = thinking.respond_to?(:text) ? thinking.text.to_s : thinking.to_s
|
|
90
|
+
return thinking_text unless thinking_text.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return nil unless @chat.respond_to?(:messages)
|
|
94
|
+
|
|
95
|
+
messages = @chat.messages
|
|
96
|
+
last_user_idx = messages.rindex { |m| m.role == :user } || -1
|
|
97
|
+
current_turn = messages[(last_user_idx + 1)..]
|
|
98
|
+
|
|
99
|
+
last = current_turn.rfind { |m| m.role == :assistant && m.content && !m.content.to_s.empty? }
|
|
100
|
+
last&.content
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def normalize_tool_calls(tool_calls)
|
|
104
|
+
return [] unless tool_calls
|
|
105
|
+
|
|
106
|
+
tool_calls.map do |tc|
|
|
107
|
+
if tc.is_a?(Hash)
|
|
108
|
+
ToolResultMessage.new(
|
|
109
|
+
tool: tc,
|
|
110
|
+
content: tc[:result] || tc['result']
|
|
111
|
+
)
|
|
112
|
+
else
|
|
113
|
+
tc
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|