robot_lab 0.3.0 → 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 +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- 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 -141
- 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 -197
- 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 -3
- 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 -217
- 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/README.md +0 -66
- 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 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- 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 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- 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/README.md +0 -65
- 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 -550
- data/examples/common.rb +0 -180
- 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/run_all.rb +0 -60
- 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
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class RobotLabResult < ApplicationRecord
|
|
4
|
-
belongs_to :thread,
|
|
5
|
-
class_name: "RobotLabThread",
|
|
6
|
-
foreign_key: :session_id,
|
|
7
|
-
primary_key: :session_id
|
|
8
|
-
|
|
9
|
-
validates :session_id, presence: true
|
|
10
|
-
validates :robot_name, presence: true
|
|
11
|
-
validates :sequence_number, presence: true,
|
|
12
|
-
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
|
|
13
|
-
|
|
14
|
-
default_scope { order(sequence_number: :asc) }
|
|
15
|
-
|
|
16
|
-
def output_messages
|
|
17
|
-
(output_data || []).map do |data|
|
|
18
|
-
RobotLab::Message.from_hash(data.symbolize_keys)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def tool_call_messages
|
|
23
|
-
(tool_calls_data || []).map do |data|
|
|
24
|
-
RobotLab::Message.from_hash(data.symbolize_keys)
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def to_robot_result
|
|
29
|
-
RobotLab::RobotResult.new(
|
|
30
|
-
robot_name: robot_name,
|
|
31
|
-
output: output_messages,
|
|
32
|
-
tool_calls: tool_call_messages,
|
|
33
|
-
stop_reason: stop_reason
|
|
34
|
-
)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class RobotLabThread < ApplicationRecord
|
|
4
|
-
has_many :results,
|
|
5
|
-
class_name: "RobotLabResult",
|
|
6
|
-
foreign_key: :session_id,
|
|
7
|
-
primary_key: :session_id,
|
|
8
|
-
dependent: :destroy
|
|
9
|
-
|
|
10
|
-
validates :session_id, presence: true, uniqueness: true
|
|
11
|
-
|
|
12
|
-
def self.find_or_create_by_session_id(id)
|
|
13
|
-
find_or_create_by(session_id: id)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def last_result
|
|
17
|
-
results.order(sequence_number: :desc).first
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def message_count
|
|
21
|
-
results.count
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Factory for the chat robot. RobotLab::RailsIntegration::Job calls
|
|
4
|
-
# ChatRobot.build(on_content:, on_tool_call:) to wire Turbo streaming.
|
|
5
|
-
#
|
|
6
|
-
# The model is served locally by Ollama. Ollama models are absent from
|
|
7
|
-
# RubyLLM's registry, so provider: must accompany model: — that is what makes
|
|
8
|
-
# RubyLLM skip the registry lookup.
|
|
9
|
-
class ChatRobot
|
|
10
|
-
SYSTEM_PROMPT = "You are a friendly assistant. Be concise."
|
|
11
|
-
|
|
12
|
-
PROVIDER = ENV.fetch("ROBOT_LAB_PROVIDER", "ollama")
|
|
13
|
-
MODEL = ENV.fetch("ROBOT_LAB_MODEL", "qwen3.6:latest")
|
|
14
|
-
|
|
15
|
-
def self.build(**options)
|
|
16
|
-
RobotLab.build(
|
|
17
|
-
name: "chat",
|
|
18
|
-
provider: PROVIDER,
|
|
19
|
-
model: MODEL,
|
|
20
|
-
system_prompt: SYSTEM_PROMPT,
|
|
21
|
-
local_tools: [TimeTool],
|
|
22
|
-
**options
|
|
23
|
-
)
|
|
24
|
-
end
|
|
25
|
-
end
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<div class="message user"><%= message %></div>
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<h1>RobotLab Chat</h1>
|
|
2
|
-
|
|
3
|
-
<%= turbo_stream_from "robot_lab_thread_#{@thread_id}" %>
|
|
4
|
-
|
|
5
|
-
<%= form_with url: chat_path, method: :post, class: "chat-form" do |f| %>
|
|
6
|
-
<%= f.text_field :message, placeholder: "Type a message...", autofocus: true, autocomplete: "off" %>
|
|
7
|
-
<%= f.submit "Send" %>
|
|
8
|
-
<% end %>
|
|
9
|
-
|
|
10
|
-
<div id="chat-scroll">
|
|
11
|
-
<div id="messages">
|
|
12
|
-
<% @results.each do |result| %>
|
|
13
|
-
<% if result.robot_name == "user" %>
|
|
14
|
-
<% result.output_messages.each do |msg| %>
|
|
15
|
-
<div class="message user"><%= msg.content %></div>
|
|
16
|
-
<% end %>
|
|
17
|
-
<% else %>
|
|
18
|
-
<% result.output_messages.each do |msg| %>
|
|
19
|
-
<div class="message assistant"><%= msg.content %></div>
|
|
20
|
-
<% end %>
|
|
21
|
-
<% unless result.tool_call_messages.empty? %>
|
|
22
|
-
<div>
|
|
23
|
-
<% result.tool_call_messages.each do |tc| %>
|
|
24
|
-
<span class="tool-badge">Used: <%= tc.content %></span>
|
|
25
|
-
<% end %>
|
|
26
|
-
</div>
|
|
27
|
-
<% end %>
|
|
28
|
-
<% end %>
|
|
29
|
-
<% end %>
|
|
30
|
-
</div>
|
|
31
|
-
|
|
32
|
-
<div id="robot_tools"></div>
|
|
33
|
-
<div id="robot_response" class="message assistant" style="display:none;"></div>
|
|
34
|
-
<div id="robot_errors"></div>
|
|
35
|
-
<div id="robot_status"></div>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<script>
|
|
39
|
-
const scrollContainer = document.getElementById('chat-scroll');
|
|
40
|
-
const responseEl = document.getElementById('robot_response');
|
|
41
|
-
|
|
42
|
-
function scrollToBottom() {
|
|
43
|
-
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Auto-scroll on any mutation inside the scroll container
|
|
47
|
-
const observer = new MutationObserver(function() {
|
|
48
|
-
if (responseEl.textContent.trim().length > 0) {
|
|
49
|
-
responseEl.style.display = 'block';
|
|
50
|
-
}
|
|
51
|
-
scrollToBottom();
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
observer.observe(scrollContainer, { childList: true, characterData: true, subtree: true });
|
|
55
|
-
|
|
56
|
-
// Clear the input field after form submission
|
|
57
|
-
document.addEventListener('turbo:submit-end', function(event) {
|
|
58
|
-
const input = event.target.querySelector('input[name="message"]');
|
|
59
|
-
if (input) {
|
|
60
|
-
input.value = '';
|
|
61
|
-
input.focus();
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// Scroll to bottom on initial load if there's history
|
|
66
|
-
scrollToBottom();
|
|
67
|
-
</script>
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>RobotLab Rails Demo</title>
|
|
5
|
-
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
-
<%= action_cable_meta_tag %>
|
|
7
|
-
<script type="importmap">
|
|
8
|
-
{
|
|
9
|
-
"imports": {
|
|
10
|
-
"@hotwired/turbo": "https://cdn.jsdelivr.net/npm/@hotwired/turbo@8.0.12/+esm",
|
|
11
|
-
"@hotwired/turbo-rails": "https://cdn.jsdelivr.net/npm/@hotwired/turbo-rails@8.0.12/+esm",
|
|
12
|
-
"@rails/actioncable": "https://cdn.jsdelivr.net/npm/@rails/actioncable@8.0.200/+esm"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
</script>
|
|
16
|
-
<script type="module">
|
|
17
|
-
import "@hotwired/turbo-rails"
|
|
18
|
-
</script>
|
|
19
|
-
<style>
|
|
20
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
21
|
-
body { font-family: system-ui, -apple-system, sans-serif; background: #1a1a2e; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; align-items: center; overflow: hidden; }
|
|
22
|
-
.container { max-width: 720px; width: 100%; padding: 2rem 1rem; display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
|
23
|
-
#chat-scroll { flex: 1; overflow-y: auto; scroll-behavior: smooth; padding-right: 0.25rem; }
|
|
24
|
-
#chat-scroll::-webkit-scrollbar { width: 6px; }
|
|
25
|
-
#chat-scroll::-webkit-scrollbar-track { background: transparent; }
|
|
26
|
-
#chat-scroll::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; }
|
|
27
|
-
h1 { color: #7c83ff; margin-bottom: 1.5rem; font-size: 1.5rem; }
|
|
28
|
-
.chat-form { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
29
|
-
.chat-form input[type="text"] { flex: 1; padding: 0.75rem; border-radius: 8px; border: 1px solid #333; background: #16213e; color: #e0e0e0; font-size: 1rem; }
|
|
30
|
-
.chat-form input[type="text"]:focus { outline: none; border-color: #7c83ff; }
|
|
31
|
-
.chat-form button { padding: 0.75rem 1.5rem; border-radius: 8px; border: none; background: #7c83ff; color: #fff; font-size: 1rem; cursor: pointer; }
|
|
32
|
-
.chat-form button:hover { background: #6a70e0; }
|
|
33
|
-
.message { padding: 0.75rem 1rem; margin-bottom: 0.5rem; border-radius: 8px; line-height: 1.5; white-space: pre-wrap; }
|
|
34
|
-
.message.user { background: #1f4068; margin-left: 4rem; }
|
|
35
|
-
.message.assistant { background: #16213e; margin-right: 4rem; border: 1px solid #2a2a4a; }
|
|
36
|
-
.tool-badge { display: inline-block; background: #2d1b69; color: #b8b0ff; padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.8rem; margin: 0.25rem 0.25rem 0.25rem 0; }
|
|
37
|
-
.thinking { color: #7c83ff; font-style: italic; }
|
|
38
|
-
.complete { color: #4ade80; font-size: 0.85rem; }
|
|
39
|
-
.error { color: #f87171; background: #2d1320; padding: 0.5rem 0.75rem; border-radius: 6px; margin-bottom: 0.5rem; }
|
|
40
|
-
#robot_response { white-space: pre-wrap; }
|
|
41
|
-
.history-label { color: #666; font-size: 0.75rem; text-transform: uppercase; margin-bottom: 0.75rem; letter-spacing: 0.05em; }
|
|
42
|
-
</style>
|
|
43
|
-
</head>
|
|
44
|
-
<body>
|
|
45
|
-
<div class="container">
|
|
46
|
-
<%= yield %>
|
|
47
|
-
</div>
|
|
48
|
-
</body>
|
|
49
|
-
</html>
|
data/examples/18_rails/bin/dev
DELETED
data/examples/18_rails/bin/rails
DELETED
data/examples/18_rails/bin/setup
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
cd "$(dirname "$0")/.."
|
|
5
|
-
|
|
6
|
-
echo "==> Installing gems..."
|
|
7
|
-
bundle install
|
|
8
|
-
|
|
9
|
-
echo "==> Creating database..."
|
|
10
|
-
bundle exec rails db:create
|
|
11
|
-
|
|
12
|
-
echo "==> Running migrations..."
|
|
13
|
-
bundle exec rails db:migrate
|
|
14
|
-
|
|
15
|
-
echo "==> Done! Run bin/dev to start the server."
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/setup"
|
|
4
|
-
|
|
5
|
-
require "rails"
|
|
6
|
-
require "active_record/railtie"
|
|
7
|
-
require "active_job/railtie"
|
|
8
|
-
require "action_controller/railtie"
|
|
9
|
-
require "action_view/railtie"
|
|
10
|
-
require "action_cable/engine"
|
|
11
|
-
|
|
12
|
-
Bundler.require(*Rails.groups)
|
|
13
|
-
|
|
14
|
-
module RobotLabDemo
|
|
15
|
-
class Application < Rails::Application
|
|
16
|
-
config.load_defaults 8.0
|
|
17
|
-
|
|
18
|
-
config.eager_load = false
|
|
19
|
-
config.consider_all_requests_local = true
|
|
20
|
-
config.secret_key_base = "demo-secret-key-for-development-only"
|
|
21
|
-
|
|
22
|
-
# Use async adapter for jobs — no external process needed
|
|
23
|
-
config.active_job.queue_adapter = :async
|
|
24
|
-
|
|
25
|
-
# Autoload app/robots and app/tools
|
|
26
|
-
config.autoload_paths << root.join("app", "robots")
|
|
27
|
-
config.autoload_paths << root.join("app", "tools")
|
|
28
|
-
|
|
29
|
-
# Disable unused middleware
|
|
30
|
-
config.api_only = false
|
|
31
|
-
config.hosts.clear
|
|
32
|
-
end
|
|
33
|
-
end
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RobotLab.config.logger = Rails.logger
|
|
4
|
-
|
|
5
|
-
# Runs against a local Ollama server — no API keys required.
|
|
6
|
-
# ollama serve
|
|
7
|
-
# ollama pull qwen3.6
|
|
8
|
-
RubyLLM.configure do |c|
|
|
9
|
-
c.logger = Rails.logger
|
|
10
|
-
c.ollama_api_base = ENV.fetch("OLLAMA_API_BASE", "http://localhost:11434/v1")
|
|
11
|
-
end
|
data/examples/18_rails/config.ru
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class CreateRobotLabTables < ActiveRecord::Migration[7.0]
|
|
4
|
-
def change
|
|
5
|
-
create_table :robot_lab_threads do |t|
|
|
6
|
-
t.string :session_id, null: false, index: { unique: true }
|
|
7
|
-
t.text :initial_input
|
|
8
|
-
t.json :input_metadata, default: {}
|
|
9
|
-
t.json :state_data, default: {}
|
|
10
|
-
t.text :last_user_message
|
|
11
|
-
t.datetime :last_user_message_at
|
|
12
|
-
|
|
13
|
-
t.timestamps
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
create_table :robot_lab_results do |t|
|
|
17
|
-
t.string :session_id, null: false, index: true
|
|
18
|
-
t.string :robot_name, null: false
|
|
19
|
-
t.integer :sequence_number, null: false, default: 0
|
|
20
|
-
t.json :output_data, default: []
|
|
21
|
-
t.json :tool_calls_data, default: []
|
|
22
|
-
t.string :stop_reason
|
|
23
|
-
t.string :checksum
|
|
24
|
-
|
|
25
|
-
t.timestamps
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
add_index :robot_lab_results, [:session_id, :sequence_number]
|
|
29
|
-
add_foreign_key :robot_lab_results, :robot_lab_threads,
|
|
30
|
-
column: :session_id, primary_key: :session_id
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Example 19: Per-Robot Token / Cost Tracking
|
|
5
|
-
#
|
|
6
|
-
# Demonstrates per-run and cumulative token tracking:
|
|
7
|
-
# - result.input_tokens / result.output_tokens — tokens used in that run
|
|
8
|
-
# - robot.total_input_tokens / total_output_tokens — running totals on the robot
|
|
9
|
-
# - robot.reset_token_totals — reset the accounting counter (not the chat)
|
|
10
|
-
#
|
|
11
|
-
# Key distinction:
|
|
12
|
-
# reset_token_totals resets the robot's *accounting counter* only.
|
|
13
|
-
# The underlying chat history keeps growing, so input_tokens per run
|
|
14
|
-
# naturally increase as conversation context accumulates.
|
|
15
|
-
# Use a fresh robot.build when you need a genuinely fresh context.
|
|
16
|
-
#
|
|
17
|
-
# LM Studio reports usage counts like the hosted providers do, so the tracking
|
|
18
|
-
# API behaves identically — only the price per token differs. Token counts are
|
|
19
|
-
# zero for providers that don't report usage data.
|
|
20
|
-
#
|
|
21
|
-
# Usage:
|
|
22
|
-
# ruby examples/19_token_tracking.rb
|
|
23
|
-
|
|
24
|
-
require_relative "common"
|
|
25
|
-
|
|
26
|
-
# Cost model. A local LM Studio model bills nothing, so the interesting number is
|
|
27
|
-
# what the same traffic WOULD have cost on a hosted model — set RATE_INPUT_CPM
|
|
28
|
-
# and RATE_OUTPUT_CPM to your provider's $-per-1M-token rates to see it.
|
|
29
|
-
# Defaults are zero: local inference is free.
|
|
30
|
-
RATE_INPUT_CPM = ENV.fetch("RATE_INPUT_CPM", "0").to_f
|
|
31
|
-
RATE_OUTPUT_CPM = ENV.fetch("RATE_OUTPUT_CPM", "0").to_f
|
|
32
|
-
|
|
33
|
-
def token_summary(input, output)
|
|
34
|
-
"in=#{input} out=#{output} total=#{input + output}"
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def run_cost(input, output)
|
|
38
|
-
return "$0.00000 (local)" if RATE_INPUT_CPM.zero? && RATE_OUTPUT_CPM.zero?
|
|
39
|
-
|
|
40
|
-
dollars = (input * RATE_INPUT_CPM + output * RATE_OUTPUT_CPM) / 1_000_000.0
|
|
41
|
-
"$#{"%.5f" % dollars}"
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def first_line(text)
|
|
45
|
-
text&.strip&.lines&.first&.strip || ""
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
banner "Per-Robot Token & Cost Tracking"
|
|
49
|
-
|
|
50
|
-
puts "Model: #{LLM[:default].provider}/#{LLM[:default].model}"
|
|
51
|
-
if RATE_INPUT_CPM.zero? && RATE_OUTPUT_CPM.zero?
|
|
52
|
-
puts "Rates: none set — local inference is free."
|
|
53
|
-
puts " Set RATE_INPUT_CPM / RATE_OUTPUT_CPM to price the same traffic"
|
|
54
|
-
puts " against a hosted provider."
|
|
55
|
-
else
|
|
56
|
-
puts "Rates: $#{RATE_INPUT_CPM}/1M in, $#{RATE_OUTPUT_CPM}/1M out"
|
|
57
|
-
end
|
|
58
|
-
puts
|
|
59
|
-
|
|
60
|
-
robot = RobotLab.build(
|
|
61
|
-
**llm_opts,
|
|
62
|
-
name: "analyst",
|
|
63
|
-
system_prompt: "You are a concise technical analyst. Keep every reply under 40 words."
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
prompts = [
|
|
67
|
-
"What is the difference between a stack and a queue?",
|
|
68
|
-
"Name three Ruby gems that every Rails project should consider.",
|
|
69
|
-
"Why does database indexing improve query performance?"
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
puts "Running #{prompts.size} prompts...\n\n"
|
|
73
|
-
|
|
74
|
-
prompts.each_with_index do |prompt, i|
|
|
75
|
-
result = robot.run(prompt)
|
|
76
|
-
|
|
77
|
-
puts "Run #{i + 1}: #{prompt}"
|
|
78
|
-
puts " Reply: #{first_line(result.reply)}"
|
|
79
|
-
puts " This run: #{token_summary(result.input_tokens, result.output_tokens)}"
|
|
80
|
-
puts " Cost: #{run_cost(result.input_tokens, result.output_tokens)}"
|
|
81
|
-
puts " Cumulative: #{token_summary(robot.total_input_tokens, robot.total_output_tokens)}"
|
|
82
|
-
puts
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
hr
|
|
86
|
-
puts "After #{prompts.size} runs:"
|
|
87
|
-
puts " Total tokens: #{token_summary(robot.total_input_tokens, robot.total_output_tokens)}"
|
|
88
|
-
puts " Total cost: #{run_cost(robot.total_input_tokens, robot.total_output_tokens)}"
|
|
89
|
-
puts
|
|
90
|
-
|
|
91
|
-
# ---------------------------------------------------------------
|
|
92
|
-
# reset_token_totals: resets the accounting counter only.
|
|
93
|
-
# Useful when you want to measure cost for a specific task batch
|
|
94
|
-
# while keeping the robot alive for the next batch.
|
|
95
|
-
#
|
|
96
|
-
# Note: input_tokens will still reflect the full chat history sent
|
|
97
|
-
# to the API — that's the real cost, not an error.
|
|
98
|
-
# ---------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
puts "Resetting token totals (batch 1 complete, starting batch 2)..."
|
|
101
|
-
robot.reset_token_totals
|
|
102
|
-
puts " Counter now: #{token_summary(robot.total_input_tokens, robot.total_output_tokens)}"
|
|
103
|
-
puts
|
|
104
|
-
puts " Note: the next run's input_tokens will be larger than run 1's"
|
|
105
|
-
puts " because the LLM receives the full accumulated chat context."
|
|
106
|
-
puts " The counter reset tracks *accounting* — it doesn't clear the chat."
|
|
107
|
-
puts
|
|
108
|
-
|
|
109
|
-
result = robot.run("What is memoization?")
|
|
110
|
-
puts "Batch 2 — Run 1: What is memoization?"
|
|
111
|
-
puts " Reply: #{first_line(result.reply)}"
|
|
112
|
-
puts " This run: #{token_summary(result.input_tokens, result.output_tokens)}"
|
|
113
|
-
puts " Batch total: #{token_summary(robot.total_input_tokens, robot.total_output_tokens)}"
|
|
114
|
-
puts
|
|
115
|
-
|
|
116
|
-
# ---------------------------------------------------------------
|
|
117
|
-
# To start truly fresh (new context, new counter), build a new robot.
|
|
118
|
-
# ---------------------------------------------------------------
|
|
119
|
-
|
|
120
|
-
hr
|
|
121
|
-
puts "Fresh robot — genuinely zero context:"
|
|
122
|
-
puts
|
|
123
|
-
|
|
124
|
-
fresh = RobotLab.build(
|
|
125
|
-
**llm_opts,
|
|
126
|
-
name: "analyst2",
|
|
127
|
-
system_prompt: "You are a concise technical analyst. Keep every reply under 40 words."
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
result = fresh.run("What is memoization?")
|
|
131
|
-
puts "Run 1 (fresh robot): What is memoization?"
|
|
132
|
-
puts " Reply: #{first_line(result.reply)}"
|
|
133
|
-
puts " This run: #{token_summary(result.input_tokens, result.output_tokens)}"
|
|
134
|
-
puts " Cumulative: #{token_summary(fresh.total_input_tokens, fresh.total_output_tokens)}"
|
|
135
|
-
puts
|
|
136
|
-
|
|
137
|
-
hr
|
|
138
|
-
puts "Token tracking demo complete."
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Example 20: Tool Loop Circuit Breaker
|
|
5
|
-
#
|
|
6
|
-
# Demonstrates max_tool_rounds to prevent runaway tool call loops.
|
|
7
|
-
#
|
|
8
|
-
# A "process runner" robot is given a step tool whose return value always
|
|
9
|
-
# instructs the LLM to call it again — it would loop forever without a guard.
|
|
10
|
-
# The circuit breaker fires after max_tool_rounds tool calls and raises
|
|
11
|
-
# RobotLab::ToolLoopError.
|
|
12
|
-
#
|
|
13
|
-
# Key behaviour when the breaker fires:
|
|
14
|
-
# The chat history contains a dangling tool_use with no tool_result.
|
|
15
|
-
# Most providers reject any subsequent request with that
|
|
16
|
-
# broken history. You MUST call robot.clear_messages before reusing the
|
|
17
|
-
# same robot instance — or simply build a fresh robot.
|
|
18
|
-
#
|
|
19
|
-
# Demonstrates:
|
|
20
|
-
# - max_tool_rounds: N on RobotLab.build
|
|
21
|
-
# - ToolLoopError raised when the limit is exceeded
|
|
22
|
-
# - clear_messages to flush the corrupted chat and recover the robot
|
|
23
|
-
# - Contrast: robot without a circuit breaker on a task that terminates
|
|
24
|
-
#
|
|
25
|
-
# Usage:
|
|
26
|
-
# ruby examples/20_circuit_breaker.rb
|
|
27
|
-
|
|
28
|
-
require_relative "common"
|
|
29
|
-
|
|
30
|
-
# -------------------------------------------------------------------------
|
|
31
|
-
# A tool that always says "more steps remain" — designed to induce looping
|
|
32
|
-
# -------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
class MultiStepProcessor < RubyLLM::Tool
|
|
35
|
-
description <<~DESC
|
|
36
|
-
Executes one step of a sequential batch process.
|
|
37
|
-
Returns the result of that step and whether more steps remain.
|
|
38
|
-
You MUST call this tool again for each remaining step until
|
|
39
|
-
the status is "complete".
|
|
40
|
-
DESC
|
|
41
|
-
|
|
42
|
-
parameter :step_number,
|
|
43
|
-
type: "integer",
|
|
44
|
-
description: "Which step to execute. Start at 1, increment by 1 each call."
|
|
45
|
-
|
|
46
|
-
TOTAL_STEPS = 50 # far more than any sensible max_tool_rounds
|
|
47
|
-
|
|
48
|
-
def execute(step_number:)
|
|
49
|
-
remaining = TOTAL_STEPS - step_number
|
|
50
|
-
if remaining <= 0
|
|
51
|
-
{ status: "complete", step: step_number, message: "All steps finished." }
|
|
52
|
-
else
|
|
53
|
-
{
|
|
54
|
-
status: "in_progress",
|
|
55
|
-
step_completed: step_number,
|
|
56
|
-
remaining_steps: remaining,
|
|
57
|
-
instruction: "Call this tool again with step_number: #{step_number + 1}"
|
|
58
|
-
}
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
banner "Tool Loop Circuit Breaker"
|
|
64
|
-
|
|
65
|
-
TASK = "Run the batch process from step 1 using the MultiStepProcessor tool. " \
|
|
66
|
-
"Execute every step sequentially until the process reports 'complete'."
|
|
67
|
-
|
|
68
|
-
# -------------------------------------------------------------------------
|
|
69
|
-
# Part 1: Circuit breaker fires — rescue ToolLoopError, then recover
|
|
70
|
-
#
|
|
71
|
-
# After ToolLoopError the chat contains a dangling tool_use block with no
|
|
72
|
-
# matching tool_result. Providers reject any follow-up request with that
|
|
73
|
-
# history. Call clear_messages to flush the broken context before reuse.
|
|
74
|
-
# -------------------------------------------------------------------------
|
|
75
|
-
|
|
76
|
-
section "Part 1: Circuit Breaker Fires (max_tool_rounds: 5)"
|
|
77
|
-
|
|
78
|
-
robot = RobotLab.build(
|
|
79
|
-
**llm_opts,
|
|
80
|
-
name: "process_runner",
|
|
81
|
-
system_prompt: "You are a process runner. Execute tasks exactly as instructed.",
|
|
82
|
-
local_tools: [MultiStepProcessor],
|
|
83
|
-
max_tool_rounds: 5
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
begin
|
|
87
|
-
# tools: :inherit — run() defaults to :none, which sends the provider an
|
|
88
|
-
# empty tool list. With no tool to loop on there would be nothing for the
|
|
89
|
-
# breaker to trip.
|
|
90
|
-
robot.run(TASK, tools: :inherit)
|
|
91
|
-
puts "Process completed (unexpected for this demo)."
|
|
92
|
-
rescue RobotLab::ToolLoopError => e
|
|
93
|
-
puts "Circuit breaker fired!"
|
|
94
|
-
puts " #{e.message}"
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
puts
|
|
98
|
-
|
|
99
|
-
# -------------------------------------------------------------------------
|
|
100
|
-
# Part 2: Recover by flushing the corrupted chat with clear_messages
|
|
101
|
-
#
|
|
102
|
-
# The robot retains its config (system prompt, tools, max_tool_rounds).
|
|
103
|
-
# Only the conversation history is cleared — the robot is then reusable.
|
|
104
|
-
# -------------------------------------------------------------------------
|
|
105
|
-
|
|
106
|
-
section "Part 2: Recover with clear_messages"
|
|
107
|
-
|
|
108
|
-
robot.clear_messages
|
|
109
|
-
puts "Chat flushed. Robot config (tools, max_tool_rounds) is unchanged."
|
|
110
|
-
puts " max_tool_rounds still: #{robot.config.max_tool_rounds}"
|
|
111
|
-
puts
|
|
112
|
-
|
|
113
|
-
result = robot.run("What is 3 + 4? Answer in one sentence.")
|
|
114
|
-
puts "Follow-up after recovery: #{result.reply&.strip}"
|
|
115
|
-
puts
|
|
116
|
-
|
|
117
|
-
# -------------------------------------------------------------------------
|
|
118
|
-
# Part 3: Robot without a circuit breaker on a task that terminates quickly
|
|
119
|
-
#
|
|
120
|
-
# Normal tool use works fine with no guard — the breaker is a safety net,
|
|
121
|
-
# not a tax on well-behaved tool interactions.
|
|
122
|
-
# -------------------------------------------------------------------------
|
|
123
|
-
|
|
124
|
-
section "Part 3: No Circuit Breaker — Task Terminates Naturally"
|
|
125
|
-
|
|
126
|
-
class SingleStep < RubyLLM::Tool
|
|
127
|
-
description "Doubles a number and returns the result immediately."
|
|
128
|
-
parameter :value, type: "integer", description: "The number to double"
|
|
129
|
-
|
|
130
|
-
def execute(value:)
|
|
131
|
-
{ result: value * 2, status: "complete" }
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
unguarded = RobotLab.build(
|
|
136
|
-
**llm_opts,
|
|
137
|
-
name: "calculator",
|
|
138
|
-
system_prompt: "Use the provided tool to answer questions.",
|
|
139
|
-
local_tools: [SingleStep]
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
result = unguarded.run("Double the number 21 using the tool.", tools: :inherit)
|
|
143
|
-
puts "Result: #{result.reply&.strip}"
|
|
144
|
-
puts
|
|
145
|
-
|
|
146
|
-
hr
|
|
147
|
-
puts "Circuit breaker demo complete."
|