robot_lab 0.0.1 → 0.0.4
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/.github/workflows/deploy-github-pages.yml +9 -9
- data/.irbrc +6 -0
- data/CHANGELOG.md +90 -0
- data/README.md +203 -46
- data/Rakefile +70 -1
- data/docs/api/core/index.md +12 -0
- data/docs/api/core/robot.md +478 -130
- data/docs/api/core/tool.md +205 -209
- data/docs/api/history/active-record-adapter.md +174 -94
- data/docs/api/history/config.md +186 -93
- data/docs/api/history/index.md +57 -61
- data/docs/api/history/thread-manager.md +123 -73
- data/docs/api/mcp/client.md +119 -48
- data/docs/api/mcp/index.md +75 -60
- data/docs/api/mcp/server.md +120 -136
- data/docs/api/mcp/transports.md +172 -184
- data/docs/api/streaming/context.md +157 -74
- data/docs/api/streaming/events.md +114 -166
- data/docs/api/streaming/index.md +74 -72
- data/docs/architecture/core-concepts.md +361 -112
- data/docs/architecture/index.md +97 -59
- data/docs/architecture/message-flow.md +138 -129
- data/docs/architecture/network-orchestration.md +197 -50
- data/docs/architecture/robot-execution.md +199 -146
- data/docs/architecture/state-management.md +255 -187
- data/docs/concepts.md +312 -48
- data/docs/examples/basic-chat.md +89 -77
- data/docs/examples/index.md +222 -47
- data/docs/examples/mcp-server.md +207 -203
- data/docs/examples/multi-robot-network.md +129 -35
- data/docs/examples/rails-application.md +159 -160
- data/docs/examples/tool-usage.md +295 -204
- data/docs/getting-started/configuration.md +275 -162
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/installation.md +22 -13
- data/docs/getting-started/quick-start.md +166 -121
- data/docs/guides/building-robots.md +417 -212
- data/docs/guides/creating-networks.md +94 -24
- data/docs/guides/mcp-integration.md +152 -113
- data/docs/guides/memory.md +220 -164
- data/docs/guides/streaming.md +80 -110
- data/docs/guides/using-tools.md +259 -212
- data/docs/index.md +50 -37
- data/examples/01_simple_robot.rb +6 -9
- data/examples/02_tools.rb +6 -9
- data/examples/03_network.rb +13 -14
- data/examples/04_mcp.rb +5 -8
- data/examples/05_streaming.rb +5 -8
- data/examples/06_prompt_templates.rb +42 -37
- data/examples/07_network_memory.rb +13 -14
- data/examples/08_llm_config.rb +140 -0
- data/examples/09_chaining.rb +223 -0
- data/examples/10_memory.rb +331 -0
- data/examples/11_network_introspection.rb +230 -0
- data/examples/12_message_bus.rb +74 -0
- data/examples/13_spawn.rb +90 -0
- data/examples/14_rusty_circuit/comic.rb +143 -0
- data/examples/14_rusty_circuit/display.rb +203 -0
- data/examples/14_rusty_circuit/heckler.rb +57 -0
- data/examples/14_rusty_circuit/open_mic.rb +121 -0
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
- data/examples/14_rusty_circuit/scout.rb +173 -0
- data/examples/14_rusty_circuit/scout_notes.md +89 -0
- data/examples/14_rusty_circuit/show.log +234 -0
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
- data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
- data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
- data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
- data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
- data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
- data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
- data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
- data/examples/15_memory_network_and_bus/output/memory.json +13 -0
- data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
- data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
- data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
- data/examples/README.md +197 -0
- data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
- data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
- data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
- data/examples/prompts/comedian.md +6 -0
- data/examples/prompts/comedy_critic.md +10 -0
- data/examples/prompts/configurable.md +9 -0
- data/examples/prompts/dispatcher.md +12 -0
- data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
- data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
- data/examples/prompts/frontmatter_mcp_test.md +9 -0
- data/examples/prompts/frontmatter_named_test.md +5 -0
- data/examples/prompts/frontmatter_tools_test.md +6 -0
- data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
- data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
- data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
- data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
- data/examples/prompts/llm_config_demo.md +20 -0
- data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
- data/examples/prompts/os_advocate.md +13 -0
- data/examples/prompts/os_chief.md +13 -0
- data/examples/prompts/os_editor.md +13 -0
- data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
- data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
- data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
- data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
- data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
- data/lib/robot_lab/adapters/openai.rb +2 -1
- data/lib/robot_lab/ask_user.rb +75 -0
- data/lib/robot_lab/config/defaults.yml +121 -0
- data/lib/robot_lab/config.rb +183 -0
- data/lib/robot_lab/error.rb +6 -0
- data/lib/robot_lab/mcp/client.rb +1 -1
- data/lib/robot_lab/memory.rb +2 -2
- data/lib/robot_lab/robot.rb +523 -249
- data/lib/robot_lab/robot_message.rb +44 -0
- data/lib/robot_lab/robot_result.rb +1 -0
- data/lib/robot_lab/robotic_model.rb +1 -1
- data/lib/robot_lab/streaming/context.rb +1 -1
- data/lib/robot_lab/tool.rb +108 -172
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +2 -18
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +66 -55
- metadata +107 -116
- data/examples/prompts/assistant/user.txt.erb +0 -1
- data/examples/prompts/billing/user.txt.erb +0 -1
- data/examples/prompts/classifier/user.txt.erb +0 -1
- data/examples/prompts/entity_extractor/user.txt.erb +0 -3
- data/examples/prompts/escalation/user.txt.erb +0 -34
- data/examples/prompts/general/user.txt.erb +0 -1
- data/examples/prompts/github_assistant/user.txt.erb +0 -1
- data/examples/prompts/helper/user.txt.erb +0 -1
- data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
- data/examples/prompts/order_support/user.txt.erb +0 -22
- data/examples/prompts/product_support/user.txt.erb +0 -32
- data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
- data/examples/prompts/synthesizer/user.txt.erb +0 -15
- data/examples/prompts/technical/user.txt.erb +0 -1
- data/examples/prompts/triage/user.txt.erb +0 -17
- data/lib/robot_lab/configuration.rb +0 -143
|
@@ -25,7 +25,8 @@ This creates:
|
|
|
25
25
|
|
|
26
26
|
- `config/initializers/robot_lab.rb`
|
|
27
27
|
- `app/robots/` directory
|
|
28
|
-
-
|
|
28
|
+
- `app/tools/` directory
|
|
29
|
+
- Database migrations for conversation history
|
|
29
30
|
|
|
30
31
|
### 3. Run Migrations
|
|
31
32
|
|
|
@@ -35,17 +36,60 @@ rails db:migrate
|
|
|
35
36
|
|
|
36
37
|
## Configuration
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
# config/initializers/robot_lab.rb
|
|
39
|
+
RobotLab uses MywayConfig for configuration. There is no `RobotLab.configure` block. Instead, configuration is loaded automatically from multiple sources in priority order:
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
|
|
42
|
+
2. Environment-specific overrides (development, test, production)
|
|
43
|
+
3. XDG user config (`~/.config/robot_lab/config.yml`)
|
|
44
|
+
4. Project config (`./config/robot_lab.yml`)
|
|
45
|
+
5. Environment variables (`ROBOT_LAB_*` prefix)
|
|
43
46
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
### Config File
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
# config/robot_lab.yml
|
|
51
|
+
defaults:
|
|
52
|
+
ruby_llm:
|
|
53
|
+
model: claude-sonnet-4
|
|
54
|
+
anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
|
|
55
|
+
|
|
56
|
+
development:
|
|
57
|
+
ruby_llm:
|
|
58
|
+
log_level: :debug
|
|
59
|
+
|
|
60
|
+
production:
|
|
61
|
+
ruby_llm:
|
|
62
|
+
request_timeout: 180
|
|
63
|
+
max_retries: 5
|
|
47
64
|
```
|
|
48
65
|
|
|
66
|
+
### Environment Variables
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Provider API keys
|
|
70
|
+
ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
71
|
+
ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
|
|
72
|
+
|
|
73
|
+
# Model configuration
|
|
74
|
+
ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
75
|
+
ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=120
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Accessing Configuration
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
# Read configuration values at runtime
|
|
82
|
+
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
83
|
+
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
84
|
+
|
|
85
|
+
# The logger defaults to Rails.logger when running in Rails
|
|
86
|
+
RobotLab.config.logger #=> Rails.logger
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Rails Engine and Railtie
|
|
90
|
+
|
|
91
|
+
RobotLab provides both a Rails Engine (`RobotLab::Rails::Engine`) and a Railtie (`RobotLab::Rails::Railtie`). These are loaded automatically when Rails is detected. The Engine isolates the RobotLab namespace and adds `app/robots` and `app/tools` to the autoload paths. The Railtie loads rake tasks and generators.
|
|
92
|
+
|
|
49
93
|
## Models
|
|
50
94
|
|
|
51
95
|
```ruby
|
|
@@ -70,116 +114,114 @@ class ConversationMessage < ApplicationRecord
|
|
|
70
114
|
validates :content, presence: true
|
|
71
115
|
|
|
72
116
|
scope :ordered, -> { order(:position) }
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Robot Definitions
|
|
121
|
+
|
|
122
|
+
Robots are built using `RobotLab.build` with named parameters. Tools are Ruby classes that inherit from `RubyLLM::Tool`.
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
# app/tools/get_user_info_tool.rb
|
|
126
|
+
class GetUserInfoTool < RubyLLM::Tool
|
|
127
|
+
description "Get information about the current user"
|
|
128
|
+
|
|
129
|
+
param :user_id, type: :integer, desc: "The user ID to look up"
|
|
130
|
+
|
|
131
|
+
def execute(user_id:)
|
|
132
|
+
user = User.find(user_id)
|
|
133
|
+
{
|
|
134
|
+
name: user.name,
|
|
135
|
+
email: user.email,
|
|
136
|
+
plan: user.subscription&.plan || "free",
|
|
137
|
+
member_since: user.created_at.to_date.to_s
|
|
138
|
+
}
|
|
139
|
+
rescue ActiveRecord::RecordNotFound
|
|
140
|
+
{ error: "User not found" }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# app/tools/get_orders_tool.rb
|
|
145
|
+
class GetOrdersTool < RubyLLM::Tool
|
|
146
|
+
description "Get user's recent orders"
|
|
147
|
+
|
|
148
|
+
param :user_id, type: :integer, desc: "The user ID"
|
|
149
|
+
param :limit, type: :integer, desc: "Number of orders to return", default: 5
|
|
150
|
+
|
|
151
|
+
def execute(user_id:, limit: 5)
|
|
152
|
+
orders = Order.where(user_id: user_id)
|
|
153
|
+
.order(created_at: :desc)
|
|
154
|
+
.limit(limit)
|
|
73
155
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
156
|
+
orders.map do |order|
|
|
157
|
+
{
|
|
158
|
+
id: order.external_id,
|
|
159
|
+
status: order.status,
|
|
160
|
+
total: order.total.to_f,
|
|
161
|
+
created_at: order.created_at.iso8601
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# app/tools/create_ticket_tool.rb
|
|
168
|
+
class CreateTicketTool < RubyLLM::Tool
|
|
169
|
+
description "Create a support ticket"
|
|
170
|
+
|
|
171
|
+
param :user_id, type: :integer, desc: "The user ID"
|
|
172
|
+
param :subject, type: :string, desc: "Ticket subject"
|
|
173
|
+
param :description, type: :string, desc: "Ticket description"
|
|
174
|
+
param :priority, type: :string, desc: "Priority level", enum: %w[low medium high]
|
|
175
|
+
|
|
176
|
+
def execute(user_id:, subject:, description:, priority: "medium")
|
|
177
|
+
ticket = SupportTicket.create!(
|
|
178
|
+
user_id: user_id,
|
|
179
|
+
subject: subject,
|
|
180
|
+
description: description,
|
|
181
|
+
priority: priority
|
|
79
182
|
)
|
|
183
|
+
|
|
184
|
+
{
|
|
185
|
+
success: true,
|
|
186
|
+
ticket_id: ticket.external_id,
|
|
187
|
+
message: "Ticket created successfully"
|
|
188
|
+
}
|
|
189
|
+
rescue => e
|
|
190
|
+
{ success: false, error: e.message }
|
|
80
191
|
end
|
|
81
192
|
end
|
|
82
193
|
```
|
|
83
194
|
|
|
84
|
-
## Robot Definitions
|
|
85
|
-
|
|
86
195
|
```ruby
|
|
87
196
|
# app/robots/support_robot.rb
|
|
88
197
|
class SupportRobot
|
|
89
|
-
def self.build
|
|
90
|
-
RobotLab.build
|
|
91
|
-
name "support"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
template <<~PROMPT
|
|
198
|
+
def self.build(user_id:)
|
|
199
|
+
RobotLab.build(
|
|
200
|
+
name: "support",
|
|
201
|
+
system_prompt: <<~PROMPT,
|
|
95
202
|
You are a helpful customer support assistant for our company.
|
|
96
203
|
Be friendly, professional, and thorough in your responses.
|
|
97
204
|
If you need to look up information, use the available tools.
|
|
205
|
+
The current user ID is #{user_id}.
|
|
98
206
|
PROMPT
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
description "Get information about the current user"
|
|
102
|
-
|
|
103
|
-
handler do |state:, **_|
|
|
104
|
-
user_id = state.data[:user_id]
|
|
105
|
-
user = User.find(user_id)
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
name: user.name,
|
|
109
|
-
email: user.email,
|
|
110
|
-
plan: user.subscription&.plan || "free",
|
|
111
|
-
member_since: user.created_at.to_date.to_s
|
|
112
|
-
}
|
|
113
|
-
rescue ActiveRecord::RecordNotFound
|
|
114
|
-
{ error: "User not found" }
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
tool :get_orders do
|
|
119
|
-
description "Get user's recent orders"
|
|
120
|
-
parameter :limit, type: :integer, default: 5
|
|
121
|
-
|
|
122
|
-
handler do |limit:, state:, **_|
|
|
123
|
-
user_id = state.data[:user_id]
|
|
124
|
-
orders = Order.where(user_id: user_id)
|
|
125
|
-
.order(created_at: :desc)
|
|
126
|
-
.limit(limit)
|
|
127
|
-
|
|
128
|
-
orders.map do |order|
|
|
129
|
-
{
|
|
130
|
-
id: order.external_id,
|
|
131
|
-
status: order.status,
|
|
132
|
-
total: order.total.to_f,
|
|
133
|
-
created_at: order.created_at.iso8601
|
|
134
|
-
}
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
tool :create_ticket do
|
|
140
|
-
description "Create a support ticket"
|
|
141
|
-
parameter :subject, type: :string, required: true
|
|
142
|
-
parameter :description, type: :string, required: true
|
|
143
|
-
parameter :priority, type: :string, enum: %w[low medium high], default: "medium"
|
|
144
|
-
|
|
145
|
-
handler do |subject:, description:, priority:, state:, **_|
|
|
146
|
-
ticket = SupportTicket.create!(
|
|
147
|
-
user_id: state.data[:user_id],
|
|
148
|
-
subject: subject,
|
|
149
|
-
description: description,
|
|
150
|
-
priority: priority
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
{
|
|
154
|
-
success: true,
|
|
155
|
-
ticket_id: ticket.external_id,
|
|
156
|
-
message: "Ticket created successfully"
|
|
157
|
-
}
|
|
158
|
-
rescue => e
|
|
159
|
-
{ success: false, error: e.message }
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
end
|
|
207
|
+
local_tools: [GetUserInfoTool, GetOrdersTool, CreateTicketTool]
|
|
208
|
+
)
|
|
163
209
|
end
|
|
164
210
|
end
|
|
165
211
|
```
|
|
166
212
|
|
|
167
213
|
## Network Configuration
|
|
168
214
|
|
|
215
|
+
Networks use `create_network` with a block DSL that defines tasks and their dependencies:
|
|
216
|
+
|
|
169
217
|
```ruby
|
|
170
218
|
# app/robots/support_network.rb
|
|
171
219
|
class SupportNetwork
|
|
172
|
-
def self.build
|
|
173
|
-
|
|
174
|
-
name "support_network"
|
|
175
|
-
default_model "claude-sonnet-4"
|
|
220
|
+
def self.build(user_id:)
|
|
221
|
+
support = SupportRobot.build(user_id: user_id)
|
|
176
222
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
result_model: ConversationMessage
|
|
180
|
-
).to_config
|
|
181
|
-
|
|
182
|
-
add_robot SupportRobot.build
|
|
223
|
+
RobotLab.create_network(name: "support_network") do
|
|
224
|
+
task :support, support, depends_on: :none
|
|
183
225
|
end
|
|
184
226
|
end
|
|
185
227
|
end
|
|
@@ -193,44 +235,17 @@ class ChatService
|
|
|
193
235
|
def initialize(user:, thread_id: nil)
|
|
194
236
|
@user = user
|
|
195
237
|
@thread_id = thread_id
|
|
196
|
-
@network = SupportNetwork.build
|
|
197
238
|
end
|
|
198
239
|
|
|
199
|
-
def call(message
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
result = @network.run(state: state, user_id: @user.id) do |event|
|
|
204
|
-
streaming_callback&.call(event)
|
|
205
|
-
end
|
|
240
|
+
def call(message:)
|
|
241
|
+
robot = SupportRobot.build(user_id: @user.id)
|
|
242
|
+
result = robot.run(message)
|
|
206
243
|
|
|
207
244
|
{
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
messages: result.new_results
|
|
245
|
+
response: result.last_text_content,
|
|
246
|
+
has_tool_calls: result.has_tool_calls?
|
|
211
247
|
}
|
|
212
248
|
end
|
|
213
|
-
|
|
214
|
-
private
|
|
215
|
-
|
|
216
|
-
def build_message(content)
|
|
217
|
-
if @thread_id
|
|
218
|
-
RobotLab::UserMessage.new(content, thread_id: @thread_id)
|
|
219
|
-
else
|
|
220
|
-
content
|
|
221
|
-
end
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def build_state(message)
|
|
225
|
-
RobotLab.create_state(
|
|
226
|
-
message: message,
|
|
227
|
-
data: { user_id: @user.id }
|
|
228
|
-
)
|
|
229
|
-
end
|
|
230
|
-
|
|
231
|
-
def extract_response(result)
|
|
232
|
-
result.last_result&.output&.find { |m| m.is_a?(RobotLab::TextMessage) }&.content
|
|
233
|
-
end
|
|
234
249
|
end
|
|
235
250
|
```
|
|
236
251
|
|
|
@@ -251,7 +266,6 @@ module Api
|
|
|
251
266
|
result = service.call(message: params[:message])
|
|
252
267
|
|
|
253
268
|
render json: {
|
|
254
|
-
thread_id: result[:thread_id],
|
|
255
269
|
response: result[:response]
|
|
256
270
|
}
|
|
257
271
|
end
|
|
@@ -283,25 +297,15 @@ class ChatJob < ApplicationJob
|
|
|
283
297
|
|
|
284
298
|
def perform(user_id:, thread_id:, message:)
|
|
285
299
|
user = User.find(user_id)
|
|
300
|
+
robot = SupportRobot.build(user_id: user.id)
|
|
286
301
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
service.call(message: message) do |event|
|
|
290
|
-
case event.type
|
|
291
|
-
when :text_delta
|
|
292
|
-
broadcast_to_user(user, type: "text", content: event.text)
|
|
293
|
-
when :tool_call
|
|
294
|
-
broadcast_to_user(user, type: "tool", name: event.name)
|
|
295
|
-
when :complete
|
|
296
|
-
broadcast_to_user(user, type: "complete")
|
|
297
|
-
end
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
private
|
|
302
|
+
result = robot.run(message)
|
|
302
303
|
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
ChatChannel.broadcast_to(
|
|
305
|
+
user,
|
|
306
|
+
type: "complete",
|
|
307
|
+
content: result.last_text_content
|
|
308
|
+
)
|
|
305
309
|
end
|
|
306
310
|
end
|
|
307
311
|
```
|
|
@@ -347,14 +351,8 @@ export default class extends Controller {
|
|
|
347
351
|
|
|
348
352
|
handleMessage(data) {
|
|
349
353
|
switch (data.type) {
|
|
350
|
-
case "text":
|
|
351
|
-
this.currentResponse.textContent += data.content
|
|
352
|
-
break
|
|
353
|
-
case "tool":
|
|
354
|
-
// Show tool indicator
|
|
355
|
-
break
|
|
356
354
|
case "complete":
|
|
357
|
-
this.
|
|
355
|
+
this.currentResponse.textContent = data.content
|
|
358
356
|
break
|
|
359
357
|
}
|
|
360
358
|
}
|
|
@@ -397,7 +395,7 @@ yarn install
|
|
|
397
395
|
# Setup database
|
|
398
396
|
rails db:migrate
|
|
399
397
|
|
|
400
|
-
# Set API key
|
|
398
|
+
# Set API key (or configure via config/robot_lab.yml)
|
|
401
399
|
export ANTHROPIC_API_KEY="your-key"
|
|
402
400
|
|
|
403
401
|
# Start server
|
|
@@ -406,15 +404,16 @@ bin/dev
|
|
|
406
404
|
|
|
407
405
|
## Key Concepts
|
|
408
406
|
|
|
409
|
-
1. **Robot
|
|
410
|
-
2. **
|
|
411
|
-
3.
|
|
412
|
-
4.
|
|
413
|
-
5. **
|
|
414
|
-
6. **
|
|
407
|
+
1. **Robot Factory**: `RobotLab.build(name:, system_prompt:, local_tools:, ...)` creates robot instances
|
|
408
|
+
2. **MywayConfig**: Configuration via YAML files and environment variables, not a configure block
|
|
409
|
+
3. **`robot.run("message")`**: Send a message as a positional string argument
|
|
410
|
+
4. **`result.last_text_content`**: Extract the response text from a `RobotResult`
|
|
411
|
+
5. **Memory**: Robots have `robot.memory` for key-value storage; networks share memory
|
|
412
|
+
6. **Tools**: Ruby classes inheriting from `RubyLLM::Tool`, passed via `local_tools:`
|
|
413
|
+
7. **Action Cable**: Real-time streaming to browser
|
|
414
|
+
8. **Background Jobs**: Non-blocking processing
|
|
415
415
|
|
|
416
416
|
## See Also
|
|
417
417
|
|
|
418
418
|
- [Rails Integration Guide](../guides/rails-integration.md)
|
|
419
419
|
- [Streaming Guide](../guides/streaming.md)
|
|
420
|
-
- [History Guide](../guides/history.md)
|