robot_lab 0.0.7 → 0.0.9
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 +99 -3
- data/README.md +125 -26
- data/Rakefile +39 -1
- data/docs/api/core/robot.md +284 -8
- data/docs/api/core/tool.md +28 -2
- data/docs/api/mcp/client.md +1 -0
- data/docs/api/mcp/server.md +27 -8
- data/docs/api/mcp/transports.md +21 -6
- data/docs/architecture/core-concepts.md +1 -1
- data/docs/architecture/robot-execution.md +20 -2
- data/docs/concepts.md +11 -3
- data/docs/examples/index.md +1 -1
- data/docs/examples/rails-application.md +1 -1
- data/docs/guides/building-robots.md +245 -11
- data/docs/guides/creating-networks.md +18 -0
- data/docs/guides/mcp-integration.md +74 -2
- data/docs/guides/rails-integration.md +145 -17
- data/docs/guides/using-tools.md +45 -4
- data/docs/index.md +62 -6
- data/examples/05_streaming.rb +113 -94
- data/examples/14_rusty_circuit/.gitignore +1 -0
- data/examples/14_rusty_circuit/open_mic.rb +1 -1
- data/examples/17_skills.rb +242 -0
- data/examples/18_rails/.envrc +3 -0
- data/examples/18_rails/.gitignore +5 -0
- data/examples/18_rails/Gemfile +10 -0
- data/examples/18_rails/README.md +48 -0
- data/examples/18_rails/Rakefile +4 -0
- data/examples/18_rails/app/controllers/application_controller.rb +4 -0
- data/examples/18_rails/app/controllers/chat_controller.rb +46 -0
- data/examples/18_rails/app/jobs/application_job.rb +4 -0
- data/examples/18_rails/app/jobs/robot_run_job.rb +79 -0
- data/examples/18_rails/app/models/application_record.rb +5 -0
- data/examples/18_rails/app/models/robot_lab_result.rb +36 -0
- data/examples/18_rails/app/models/robot_lab_thread.rb +23 -0
- data/examples/18_rails/app/robots/chat_robot.rb +14 -0
- data/examples/18_rails/app/tools/time_tool.rb +9 -0
- data/examples/18_rails/app/views/chat/_user_message.html.erb +1 -0
- data/examples/18_rails/app/views/chat/index.html.erb +67 -0
- data/examples/18_rails/app/views/layouts/application.html.erb +49 -0
- data/examples/18_rails/bin/dev +7 -0
- data/examples/18_rails/bin/rails +6 -0
- data/examples/18_rails/bin/setup +15 -0
- data/examples/18_rails/config/application.rb +33 -0
- data/examples/18_rails/config/cable.yml +2 -0
- data/examples/18_rails/config/database.yml +5 -0
- data/examples/18_rails/config/environment.rb +4 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +3 -0
- data/examples/18_rails/config/routes.rb +6 -0
- data/examples/18_rails/config.ru +4 -0
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +32 -0
- data/examples/README.md +30 -0
- data/examples/prompts/audit_trail.md +8 -0
- data/examples/prompts/incident_responder.md +18 -0
- data/examples/prompts/parameterized_main_test.md +6 -0
- data/examples/prompts/pii_redactor.md +7 -0
- data/examples/prompts/runbook_protocol.md +12 -0
- data/examples/prompts/skill_a_test.md +4 -0
- data/examples/prompts/skill_b_test.md +4 -0
- data/examples/prompts/skill_config_test.md +5 -0
- data/examples/prompts/skill_cycle_a_test.md +6 -0
- data/examples/prompts/skill_cycle_b_test.md +6 -0
- data/examples/prompts/skill_description_test.md +4 -0
- data/examples/prompts/skill_leaf_test.md +4 -0
- data/examples/prompts/skill_nested_test.md +6 -0
- data/examples/prompts/skill_refs_main_test.md +6 -0
- data/examples/prompts/skill_self_ref_test.md +6 -0
- data/examples/prompts/skill_with_params_test.md +6 -0
- data/examples/prompts/sre_compliance.md +10 -0
- data/examples/prompts/structured_output.md +7 -0
- data/examples/prompts/template_with_skills_test.md +6 -0
- data/lib/generators/robot_lab/install_generator.rb +12 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +36 -22
- data/lib/generators/robot_lab/templates/job.rb.tt +92 -0
- data/lib/generators/robot_lab/templates/robot.rb.tt +6 -21
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +5 -3
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +41 -35
- data/lib/robot_lab/mcp/client.rb +6 -4
- data/lib/robot_lab/mcp/server.rb +21 -3
- data/lib/robot_lab/mcp/transports/base.rb +10 -2
- data/lib/robot_lab/mcp/transports/stdio.rb +52 -26
- data/lib/robot_lab/{rails → rails_integration}/engine.rb +1 -1
- data/lib/robot_lab/{rails → rails_integration}/railtie.rb +1 -1
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +72 -0
- data/lib/robot_lab/robot/mcp_management.rb +61 -4
- data/lib/robot_lab/robot/template_rendering.rb +181 -4
- data/lib/robot_lab/robot.rb +196 -33
- data/lib/robot_lab/robot_result.rb +3 -1
- data/lib/robot_lab/run_config.rb +1 -1
- data/lib/robot_lab/tool.rb +26 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +6 -4
- metadata +55 -19
- data/examples/14_rusty_circuit/scout_notes.md +0 -89
|
@@ -0,0 +1,15 @@
|
|
|
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."
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
|
@@ -0,0 +1,32 @@
|
|
|
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
|
data/examples/README.md
CHANGED
|
@@ -45,6 +45,15 @@ examples/
|
|
|
45
45
|
scout.rb # Talent scout with analyst spawning
|
|
46
46
|
display.rb # Terminal formatting (color, wrapping, file output)
|
|
47
47
|
prompts/ # Templates for comic, heckler, and scout
|
|
48
|
+
18_rails/ # Minimal Rails 8 demo app (full integration)
|
|
49
|
+
app/robots/chat_robot.rb # Robot factory with system prompt + TimeTool
|
|
50
|
+
app/tools/time_tool.rb # Custom RobotLab::Tool subclass
|
|
51
|
+
app/jobs/robot_run_job.rb # Background job with Turbo Stream callbacks
|
|
52
|
+
app/controllers/ # Chat controller (index + create)
|
|
53
|
+
app/views/ # Layout with CDN importmap, chat view with streaming
|
|
54
|
+
app/models/ # RobotLabThread, RobotLabResult
|
|
55
|
+
config/ # Minimal Rails 8 config (async adapters, no asset pipeline)
|
|
56
|
+
db/migrate/ # Migration from generator template
|
|
48
57
|
prompts/ # Prompt templates (.md with YAML front matter)
|
|
49
58
|
```
|
|
50
59
|
|
|
@@ -152,6 +161,27 @@ Demonstrates: Robot subclasses, self-modification via tool side effects, dynamic
|
|
|
152
161
|
|
|
153
162
|
**Requires:** LLM API key
|
|
154
163
|
|
|
164
|
+
### 18 — Rails Integration Demo
|
|
165
|
+
|
|
166
|
+
A minimal, hand-built Rails 8 app that exercises every piece of RobotLab's Rails integration end-to-end. No `rails new` — every file is hand-crafted for minimum size.
|
|
167
|
+
|
|
168
|
+
**What it demonstrates:**
|
|
169
|
+
- **ChatRobot** with a custom `TimeTool` (`app/robots/`, `app/tools/`)
|
|
170
|
+
- **RobotRunJob** — background execution via `ActiveJob` async adapter
|
|
171
|
+
- **Turbo Stream token streaming** — real-time content chunks broadcast to the browser over ActionCable
|
|
172
|
+
- **Persistence** — `RobotLabThread` + `RobotLabResult` with conversation history
|
|
173
|
+
- **Auto-scrolling chat** — MutationObserver keeps the view pinned to the latest streaming content
|
|
174
|
+
|
|
175
|
+
**No Redis, no Solid Queue, no asset pipeline.** Uses `:async` adapters for both ActiveJob and ActionCable. Turbo JS loaded via importmap from CDN (`@hotwired/turbo-rails`).
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
cd examples/18_rails
|
|
179
|
+
bin/setup # bundle install + db:create + db:migrate
|
|
180
|
+
bin/dev # starts Puma on http://localhost:3000
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Requires:** LLM API key, Ruby 3.2+
|
|
184
|
+
|
|
155
185
|
## Prompt Templates
|
|
156
186
|
|
|
157
187
|
Templates live in `examples/prompts/` as `.md` files with YAML front matter. Each template defines a robot's personality and behavior:
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Include audit metadata in every response
|
|
3
|
+
---
|
|
4
|
+
Include an "audit" field in every response with:
|
|
5
|
+
- "timestamp": the current date/time you were asked (use ISO 8601)
|
|
6
|
+
- "confidence": your confidence level from 0.0 to 1.0
|
|
7
|
+
- "sources": list of data sources or signals you used for your assessment
|
|
8
|
+
- "assumptions": any assumptions you made due to missing information
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Incident response analyst for production systems
|
|
3
|
+
parameters:
|
|
4
|
+
team_name: null
|
|
5
|
+
services: null
|
|
6
|
+
on_call_runbook_url: null
|
|
7
|
+
---
|
|
8
|
+
You are the on-call incident response analyst for the <%= team_name %> team.
|
|
9
|
+
|
|
10
|
+
Your team owns these services: <%= Array(services).join(", ") %>.
|
|
11
|
+
|
|
12
|
+
When an incident is reported, analyze the provided alert data, logs, and metrics
|
|
13
|
+
to help the on-call engineer understand what is happening and what to do next.
|
|
14
|
+
|
|
15
|
+
Reference the team runbook at: <%= on_call_runbook_url %>
|
|
16
|
+
|
|
17
|
+
Prioritize reducing customer impact over finding root cause.
|
|
18
|
+
If you are uncertain, say so and recommend escalation.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Redact personally identifiable information from output
|
|
3
|
+
---
|
|
4
|
+
Never include real personally identifiable information (PII) in your responses.
|
|
5
|
+
If log data contains email addresses, IP addresses, usernames, or account IDs,
|
|
6
|
+
replace them with redacted placeholders like [EMAIL], [IP], [USER], [ACCOUNT_ID].
|
|
7
|
+
This applies to all fields in your output including explanations.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Follow structured runbook steps for incident response
|
|
3
|
+
---
|
|
4
|
+
When analyzing an incident, follow this protocol:
|
|
5
|
+
|
|
6
|
+
1. ASSESS — Identify the affected service, severity, and blast radius.
|
|
7
|
+
2. DIAGNOSE — Examine symptoms, recent changes, and correlated events.
|
|
8
|
+
3. MITIGATE — Recommend immediate actions to reduce impact.
|
|
9
|
+
4. RESOLVE — Propose a root-cause fix and verification steps.
|
|
10
|
+
5. FOLLOW-UP — Suggest post-incident review items and preventive measures.
|
|
11
|
+
|
|
12
|
+
Always label each section clearly. Never skip a step.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: SRE compliance bundle — PII redaction and audit trail
|
|
3
|
+
skills:
|
|
4
|
+
- pii_redactor
|
|
5
|
+
- audit_trail
|
|
6
|
+
---
|
|
7
|
+
You operate under SRE compliance requirements.
|
|
8
|
+
All responses must be safe for inclusion in incident tickets visible to external stakeholders.
|
|
9
|
+
Do not speculate about blame or assign fault to individuals.
|
|
10
|
+
Focus on systems, processes, and technical root causes.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Respond with structured JSON output
|
|
3
|
+
temperature: 0.2
|
|
4
|
+
---
|
|
5
|
+
Always respond with valid JSON. Structure your response as a JSON object.
|
|
6
|
+
Do not include any text outside the JSON block.
|
|
7
|
+
If you need to explain something, include it in an "explanation" field.
|
|
@@ -17,6 +17,8 @@ module RobotLab
|
|
|
17
17
|
|
|
18
18
|
class_option :skip_migration, type: :boolean, default: false,
|
|
19
19
|
desc: "Skip database migration generation"
|
|
20
|
+
class_option :skip_job, type: :boolean, default: false,
|
|
21
|
+
desc: "Skip background job generation"
|
|
20
22
|
|
|
21
23
|
# Returns the next migration number for ActiveRecord migrations.
|
|
22
24
|
#
|
|
@@ -52,6 +54,15 @@ module RobotLab
|
|
|
52
54
|
template "result_model.rb.tt", "app/models/robot_lab_result.rb"
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# Creates the background job for robot execution.
|
|
58
|
+
#
|
|
59
|
+
# @return [void]
|
|
60
|
+
def create_job
|
|
61
|
+
return if options[:skip_job]
|
|
62
|
+
|
|
63
|
+
template "job.rb.tt", "app/jobs/robot_run_job.rb"
|
|
64
|
+
end
|
|
65
|
+
|
|
55
66
|
# Creates the robots and tools directories.
|
|
56
67
|
#
|
|
57
68
|
# @return [void]
|
|
@@ -71,6 +82,7 @@ module RobotLab
|
|
|
71
82
|
say " 1. Run migrations: rails db:migrate"
|
|
72
83
|
say " 2. Configure your LLM API keys in config/initializers/robot_lab.rb"
|
|
73
84
|
say " 3. Generate your first robot: rails g robot_lab:robot MyRobot"
|
|
85
|
+
say " 4. Enqueue robot runs via RobotRunJob (app/jobs/robot_run_job.rb)" unless options[:skip_job]
|
|
74
86
|
say ""
|
|
75
87
|
end
|
|
76
88
|
end
|
|
@@ -2,27 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
# RobotLab Configuration
|
|
4
4
|
#
|
|
5
|
-
#
|
|
5
|
+
# RobotLab uses MywayConfig for configuration. Settings are loaded from:
|
|
6
6
|
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# Configure Ruby LLM (required for LLM interactions)
|
|
7
|
+
# 1. Bundled defaults (lib/robot_lab/config/defaults.yml)
|
|
8
|
+
# 2. Environment-specific overrides (development, test, production)
|
|
9
|
+
# 3. XDG user config (~/.config/robot_lab/config.yml)
|
|
10
|
+
# 4. Project config (./config/robot_lab.yml)
|
|
11
|
+
# 5. Environment variables (ROBOT_LAB_* prefix)
|
|
12
|
+
#
|
|
13
|
+
# Create config/robot_lab.yml for project-specific settings:
|
|
14
|
+
#
|
|
15
|
+
# defaults:
|
|
16
|
+
# ruby_llm:
|
|
17
|
+
# anthropic_api_key: <%%= ENV['ANTHROPIC_API_KEY'] %>
|
|
18
|
+
# openai_api_key: <%%= ENV['OPENAI_API_KEY'] %>
|
|
19
|
+
# model: claude-sonnet-4
|
|
20
|
+
# request_timeout: 180
|
|
23
21
|
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
22
|
+
# development:
|
|
23
|
+
# ruby_llm:
|
|
24
|
+
# log_level: :debug
|
|
25
|
+
#
|
|
26
|
+
# test:
|
|
27
|
+
# streaming_enabled: false
|
|
28
|
+
# ruby_llm:
|
|
29
|
+
# model: claude-3-haiku-20240307
|
|
30
|
+
# request_timeout: 30
|
|
31
|
+
#
|
|
32
|
+
# Or use environment variables with double underscores for nested keys:
|
|
33
|
+
#
|
|
34
|
+
# ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
35
|
+
# ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
36
|
+
#
|
|
37
|
+
# RobotLab also falls back to standard provider env vars
|
|
38
|
+
# (e.g. ANTHROPIC_API_KEY, OPENAI_API_KEY) when the prefixed
|
|
39
|
+
# versions are not set.
|
|
40
|
+
|
|
41
|
+
# Set the RobotLab logger to use Rails.logger
|
|
42
|
+
RobotLab.config.logger = Rails.logger
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Background job for executing robot runs asynchronously.
|
|
4
|
+
#
|
|
5
|
+
# Resolves a robot class, optionally wires Turbo Stream callbacks for
|
|
6
|
+
# real-time token streaming, persists the result, and broadcasts
|
|
7
|
+
# completion/error events.
|
|
8
|
+
#
|
|
9
|
+
# @example Enqueue from a controller
|
|
10
|
+
# RobotRunJob.perform_later(
|
|
11
|
+
# robot_class: "SupportRobot",
|
|
12
|
+
# message: params[:message],
|
|
13
|
+
# thread_id: session_id
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
class RobotRunJob < ApplicationJob
|
|
17
|
+
queue_as :default
|
|
18
|
+
|
|
19
|
+
retry_on StandardError, wait: 5.seconds, attempts: 3
|
|
20
|
+
discard_on ActiveJob::DeserializationError
|
|
21
|
+
|
|
22
|
+
def perform(robot_class:, message:, thread_id:, **context)
|
|
23
|
+
thread = RobotLabThread.find_or_create_by_session_id(thread_id)
|
|
24
|
+
thread.update!(last_user_message: message, last_user_message_at: Time.current)
|
|
25
|
+
|
|
26
|
+
robot = resolve_robot(robot_class, thread_id)
|
|
27
|
+
result = robot.run(message, **context)
|
|
28
|
+
|
|
29
|
+
persist_result(thread, result)
|
|
30
|
+
broadcast_completion(thread_id)
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
broadcast_error(thread_id, e)
|
|
33
|
+
raise
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def resolve_robot(robot_class, thread_id)
|
|
39
|
+
klass = robot_class.to_s.constantize
|
|
40
|
+
stream_name = "robot_lab_thread_#{thread_id}"
|
|
41
|
+
|
|
42
|
+
if turbo_available?
|
|
43
|
+
on_content = RobotLab::RailsIntegration::TurboStreamCallbacks.build_content_callback(
|
|
44
|
+
stream_name: stream_name
|
|
45
|
+
)
|
|
46
|
+
on_tool_call = RobotLab::RailsIntegration::TurboStreamCallbacks.build_tool_call_callback(
|
|
47
|
+
stream_name: stream_name
|
|
48
|
+
)
|
|
49
|
+
klass.build(on_content: on_content, on_tool_call: on_tool_call)
|
|
50
|
+
else
|
|
51
|
+
klass.build
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def persist_result(thread, result)
|
|
56
|
+
sequence = thread.results.maximum(:sequence_number).to_i + 1
|
|
57
|
+
exported = result.export
|
|
58
|
+
|
|
59
|
+
thread.results.create!(
|
|
60
|
+
robot_name: result.robot_name,
|
|
61
|
+
sequence_number: sequence,
|
|
62
|
+
output_data: exported[:output],
|
|
63
|
+
tool_calls_data: exported[:tool_calls],
|
|
64
|
+
stop_reason: result.stop_reason,
|
|
65
|
+
checksum: result.checksum
|
|
66
|
+
)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def broadcast_completion(thread_id)
|
|
70
|
+
return unless turbo_available?
|
|
71
|
+
|
|
72
|
+
Turbo::StreamsChannel.broadcast_replace_to(
|
|
73
|
+
"robot_lab_thread_#{thread_id}",
|
|
74
|
+
target: "robot_status",
|
|
75
|
+
html: "<span class=\"robot-status-complete\">Complete</span>"
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def broadcast_error(thread_id, error)
|
|
80
|
+
return unless turbo_available?
|
|
81
|
+
|
|
82
|
+
Turbo::StreamsChannel.broadcast_append_to(
|
|
83
|
+
"robot_lab_thread_#{thread_id}",
|
|
84
|
+
target: "robot_errors",
|
|
85
|
+
html: "<div class=\"robot-error\">#{ERB::Util.html_escape(error.message)}</div>"
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def turbo_available?
|
|
90
|
+
defined?(Turbo::StreamsChannel)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -5,42 +5,27 @@
|
|
|
5
5
|
# <%= robot_description %>
|
|
6
6
|
#
|
|
7
7
|
class <%= class_name %>Robot
|
|
8
|
-
include RobotLab::Lifecycle
|
|
9
|
-
|
|
10
8
|
SYSTEM_PROMPT = <<~PROMPT
|
|
11
9
|
You are a helpful <%= class_name.titleize %> assistant.
|
|
12
10
|
|
|
13
11
|
Your role is to assist users with their requests in a friendly and efficient manner.
|
|
14
12
|
PROMPT
|
|
15
13
|
|
|
16
|
-
def self.build
|
|
17
|
-
RobotLab.
|
|
14
|
+
def self.build(**options)
|
|
15
|
+
RobotLab.build(
|
|
18
16
|
name: "<%= file_name %>",
|
|
19
17
|
description: "<%= robot_description %>",
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
system_prompt: SYSTEM_PROMPT,
|
|
19
|
+
local_tools: tools,
|
|
20
|
+
**options
|
|
23
21
|
)
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
def self.tools
|
|
27
25
|
[
|
|
28
26
|
<%- robot_tools.each do |tool| -%>
|
|
29
|
-
# <%= tool.camelize
|
|
27
|
+
# <%= tool.camelize %>,
|
|
30
28
|
<%- end -%>
|
|
31
29
|
]
|
|
32
30
|
end
|
|
33
|
-
|
|
34
|
-
def self.lifecycle_hooks
|
|
35
|
-
RobotLab::Lifecycle::Hooks.new(
|
|
36
|
-
on_start: ->(robot:, network:, prompt:, history:) {
|
|
37
|
-
# Called before robot execution
|
|
38
|
-
{ prompt: prompt, history: history }
|
|
39
|
-
},
|
|
40
|
-
on_finish: ->(robot:, network:, result:) {
|
|
41
|
-
# Called after robot execution
|
|
42
|
-
result
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
end
|
|
46
31
|
end
|
|
@@ -16,7 +16,7 @@ class <%= class_name %>RobotTest < ActiveSupport::TestCase
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
test "robot has system prompt" do
|
|
19
|
-
assert_not_nil @robot.
|
|
19
|
+
assert_not_nil @robot.system_prompt
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Add more tests as needed:
|
|
@@ -24,9 +24,11 @@ class <%= class_name %>RobotTest < ActiveSupport::TestCase
|
|
|
24
24
|
# test "robot runs successfully" do
|
|
25
25
|
# result = @robot.run("Hello, I need help")
|
|
26
26
|
# assert result.is_a?(RobotLab::RobotResult)
|
|
27
|
+
# assert_not_nil result.last_text_content
|
|
27
28
|
# end
|
|
28
29
|
#
|
|
29
|
-
# test "robot
|
|
30
|
-
#
|
|
30
|
+
# test "robot has expected tools" do
|
|
31
|
+
# tool_names = @robot.local_tools.map(&:name)
|
|
32
|
+
# assert_includes tool_names, "expected_tool_name"
|
|
31
33
|
# end
|
|
32
34
|
end
|