robot_lab 0.0.4 → 0.0.7
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 +76 -0
- data/README.md +64 -6
- data/Rakefile +2 -1
- data/docs/api/core/index.md +41 -46
- data/docs/api/core/memory.md +200 -154
- data/docs/api/core/network.md +13 -3
- data/docs/api/core/robot.md +38 -26
- data/docs/api/core/state.md +55 -73
- data/docs/api/index.md +7 -28
- data/docs/api/messages/index.md +35 -20
- data/docs/api/messages/text-message.md +67 -21
- data/docs/api/messages/tool-call-message.md +80 -41
- data/docs/api/messages/tool-result-message.md +119 -50
- data/docs/api/messages/user-message.md +48 -24
- data/docs/architecture/core-concepts.md +10 -15
- data/docs/concepts.md +5 -7
- data/docs/examples/index.md +2 -2
- data/docs/getting-started/configuration.md +80 -0
- data/docs/guides/building-robots.md +10 -9
- data/docs/guides/creating-networks.md +49 -0
- data/docs/guides/index.md +0 -5
- data/docs/guides/rails-integration.md +244 -162
- data/docs/guides/streaming.md +118 -138
- data/docs/index.md +0 -8
- data/examples/03_network.rb +10 -7
- data/examples/08_llm_config.rb +40 -11
- data/examples/09_chaining.rb +45 -6
- data/examples/11_network_introspection.rb +30 -7
- data/examples/12_message_bus.rb +1 -1
- data/examples/14_rusty_circuit/heckler.rb +14 -8
- data/examples/14_rusty_circuit/open_mic.rb +5 -3
- data/examples/14_rusty_circuit/scout.rb +14 -31
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
- data/examples/16_writers_room/display.rb +158 -0
- data/examples/16_writers_room/output/.gitignore +4 -0
- data/examples/16_writers_room/output/README.md +69 -0
- data/examples/16_writers_room/output/opus_001.md +263 -0
- data/examples/16_writers_room/output/opus_001_notes.log +470 -0
- data/examples/16_writers_room/output/opus_002.md +245 -0
- data/examples/16_writers_room/output/opus_002_notes.log +546 -0
- data/examples/16_writers_room/output/opus_002_screenplay.md +7989 -0
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +993 -0
- data/examples/16_writers_room/prompts/screenplay_writer.md +66 -0
- data/examples/16_writers_room/prompts/writer.md +37 -0
- data/examples/16_writers_room/room.rb +186 -0
- data/examples/16_writers_room/tools.rb +173 -0
- data/examples/16_writers_room/writer.rb +121 -0
- data/examples/16_writers_room/writers_room.rb +256 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
- data/lib/robot_lab/memory.rb +8 -32
- data/lib/robot_lab/network.rb +13 -20
- data/lib/robot_lab/robot/bus_messaging.rb +239 -0
- data/lib/robot_lab/robot/mcp_management.rb +88 -0
- data/lib/robot_lab/robot/template_rendering.rb +130 -0
- data/lib/robot_lab/robot.rb +56 -420
- data/lib/robot_lab/run_config.rb +184 -0
- data/lib/robot_lab/state_proxy.rb +2 -12
- data/lib/robot_lab/task.rb +8 -1
- data/lib/robot_lab/utils.rb +39 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +29 -8
- data/mkdocs.yml +0 -11
- metadata +21 -20
- data/docs/api/adapters/anthropic.md +0 -121
- data/docs/api/adapters/gemini.md +0 -133
- data/docs/api/adapters/index.md +0 -104
- data/docs/api/adapters/openai.md +0 -134
- data/docs/api/history/active-record-adapter.md +0 -275
- data/docs/api/history/config.md +0 -284
- data/docs/api/history/index.md +0 -128
- data/docs/api/history/thread-manager.md +0 -194
- data/docs/guides/history.md +0 -359
- data/lib/robot_lab/adapters/anthropic.rb +0 -163
- data/lib/robot_lab/adapters/base.rb +0 -85
- data/lib/robot_lab/adapters/gemini.rb +0 -193
- data/lib/robot_lab/adapters/openai.rb +0 -160
- data/lib/robot_lab/adapters/registry.rb +0 -81
- data/lib/robot_lab/errors.rb +0 -70
- data/lib/robot_lab/history/active_record_adapter.rb +0 -146
- data/lib/robot_lab/history/config.rb +0 -115
- data/lib/robot_lab/history/thread_manager.rb +0 -93
- data/lib/robot_lab/robotic_model.rb +0 -324
|
@@ -13,7 +13,7 @@ rails generate robot_lab:install
|
|
|
13
13
|
This creates:
|
|
14
14
|
|
|
15
15
|
```
|
|
16
|
-
config/initializers/robot_lab.rb #
|
|
16
|
+
config/initializers/robot_lab.rb # Logger setup
|
|
17
17
|
db/migrate/*_create_robot_lab_tables.rb # Database tables
|
|
18
18
|
app/models/robot_lab_thread.rb # Thread model
|
|
19
19
|
app/models/robot_lab_result.rb # Result model
|
|
@@ -29,52 +29,75 @@ rails db:migrate
|
|
|
29
29
|
|
|
30
30
|
## Configuration
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for configuration. There is no `RobotLab.configure` block. Instead, settings are loaded from YAML files and environment variables in the following priority order:
|
|
33
|
+
|
|
34
|
+
1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
|
|
35
|
+
2. **Environment-specific overrides** (development, test, production sections)
|
|
36
|
+
3. **XDG user config** (`~/.config/robot_lab/config.yml`)
|
|
37
|
+
4. **Project config** (`./config/robot_lab.yml`)
|
|
38
|
+
5. **Environment variables** (`ROBOT_LAB_*` prefix)
|
|
39
|
+
|
|
40
|
+
### Project Config File
|
|
41
|
+
|
|
42
|
+
```yaml title="config/robot_lab.yml"
|
|
43
|
+
defaults:
|
|
44
|
+
ruby_llm:
|
|
45
|
+
anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
|
|
46
|
+
openai_api_key: <%= ENV['OPENAI_API_KEY'] %>
|
|
47
|
+
model: claude-sonnet-4
|
|
48
|
+
request_timeout: 180
|
|
49
|
+
|
|
50
|
+
# Template path auto-detected as app/prompts in Rails
|
|
51
|
+
# template_path: app/prompts
|
|
52
|
+
|
|
53
|
+
development:
|
|
54
|
+
ruby_llm:
|
|
55
|
+
model: claude-haiku-3
|
|
56
|
+
log_level: :debug
|
|
57
|
+
|
|
58
|
+
test:
|
|
59
|
+
streaming_enabled: false
|
|
60
|
+
ruby_llm:
|
|
61
|
+
model: claude-3-haiku-20240307
|
|
62
|
+
request_timeout: 30
|
|
63
|
+
|
|
64
|
+
production:
|
|
65
|
+
ruby_llm:
|
|
66
|
+
request_timeout: 180
|
|
67
|
+
max_retries: 5
|
|
68
|
+
```
|
|
39
69
|
|
|
40
|
-
|
|
41
|
-
config.default_provider = :anthropic
|
|
42
|
-
config.default_model = "claude-sonnet-4"
|
|
70
|
+
### Environment Variables
|
|
43
71
|
|
|
44
|
-
|
|
45
|
-
config.logger = Rails.logger
|
|
72
|
+
Environment variables use the `ROBOT_LAB_` prefix with double underscores for nested keys:
|
|
46
73
|
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
```bash
|
|
75
|
+
ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
76
|
+
ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
|
|
77
|
+
ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
|
|
49
78
|
```
|
|
50
79
|
|
|
51
|
-
|
|
80
|
+
RobotLab also falls back to standard provider environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) when the prefixed versions are not set.
|
|
52
81
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
config.streaming_enabled = false
|
|
63
|
-
when "production"
|
|
64
|
-
config.default_model = "claude-sonnet-4"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
82
|
+
### Initializer (Logger Only)
|
|
83
|
+
|
|
84
|
+
The only runtime-writable config attribute is the logger. The generated initializer sets it to the Rails logger:
|
|
85
|
+
|
|
86
|
+
```ruby title="config/initializers/robot_lab.rb"
|
|
87
|
+
# frozen_string_literal: true
|
|
88
|
+
|
|
89
|
+
# Set the RobotLab logger to use Rails.logger
|
|
90
|
+
RobotLab.config.logger = Rails.logger
|
|
67
91
|
```
|
|
68
92
|
|
|
69
|
-
###
|
|
93
|
+
### Accessing Configuration
|
|
70
94
|
|
|
71
|
-
```ruby
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
95
|
+
```ruby
|
|
96
|
+
# Read configuration values
|
|
97
|
+
RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
98
|
+
RobotLab.config.ruby_llm.anthropic_api_key #=> "sk-ant-..."
|
|
99
|
+
RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
100
|
+
RobotLab.config.streaming_enabled #=> true
|
|
78
101
|
```
|
|
79
102
|
|
|
80
103
|
## Creating Robots
|
|
@@ -89,22 +112,45 @@ rails generate robot_lab:robot Router --routing
|
|
|
89
112
|
|
|
90
113
|
### Robot Class
|
|
91
114
|
|
|
115
|
+
Robots are plain Ruby classes with a `.build` factory method that calls `RobotLab.build` with keyword arguments:
|
|
116
|
+
|
|
92
117
|
```ruby title="app/robots/support_robot.rb"
|
|
118
|
+
# frozen_string_literal: true
|
|
119
|
+
|
|
93
120
|
class SupportRobot
|
|
94
121
|
def self.build
|
|
95
|
-
RobotLab.build
|
|
96
|
-
name "support"
|
|
97
|
-
description "Handles customer support inquiries"
|
|
98
|
-
model "claude-sonnet-4"
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
122
|
+
RobotLab.build(
|
|
123
|
+
name: "support",
|
|
124
|
+
description: "Handles customer support inquiries",
|
|
125
|
+
model: "claude-sonnet-4",
|
|
126
|
+
template: :support,
|
|
127
|
+
local_tools: [OrderLookup.new]
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Custom Tool
|
|
134
|
+
|
|
135
|
+
Tools subclass `RobotLab::Tool` (which extends `RubyLLM::Tool`):
|
|
136
|
+
|
|
137
|
+
```ruby title="app/tools/order_lookup.rb"
|
|
138
|
+
# frozen_string_literal: true
|
|
139
|
+
|
|
140
|
+
class OrderLookup < RobotLab::Tool
|
|
141
|
+
description "Look up an order by ID"
|
|
142
|
+
param :order_id, type: "string", desc: "The order ID to look up"
|
|
143
|
+
|
|
144
|
+
def execute(order_id:)
|
|
145
|
+
order = Order.find_by(id: order_id)
|
|
146
|
+
return "Order not found" unless order
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
id: order.id,
|
|
150
|
+
status: order.status,
|
|
151
|
+
total: order.total.to_s,
|
|
152
|
+
created_at: order.created_at.iso8601
|
|
153
|
+
}.to_json
|
|
108
154
|
end
|
|
109
155
|
end
|
|
110
156
|
```
|
|
@@ -114,37 +160,40 @@ end
|
|
|
114
160
|
```ruby title="app/controllers/chat_controller.rb"
|
|
115
161
|
class ChatController < ApplicationController
|
|
116
162
|
def create
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
message: params[:message],
|
|
120
|
-
data: { user_id: current_user.id }
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
result = network.run(state: state)
|
|
163
|
+
robot = SupportRobot.build
|
|
164
|
+
result = robot.run(params[:message])
|
|
124
165
|
|
|
125
166
|
render json: {
|
|
126
|
-
response: result.
|
|
127
|
-
|
|
167
|
+
response: result.last_text_content,
|
|
168
|
+
robot_name: result.robot_name
|
|
128
169
|
}
|
|
129
170
|
end
|
|
171
|
+
end
|
|
172
|
+
```
|
|
130
173
|
|
|
131
|
-
|
|
174
|
+
### Using a Network in Controllers
|
|
132
175
|
|
|
133
|
-
|
|
134
|
-
RobotLab.create_network do
|
|
135
|
-
name "customer_service"
|
|
136
|
-
add_robot SupportRobot.build
|
|
137
|
-
add_robot BillingRobot.build
|
|
176
|
+
Networks use `RobotLab.create_network` with a block DSL that defines tasks. Each task wraps a robot with dependency declarations:
|
|
138
177
|
|
|
139
|
-
|
|
178
|
+
```ruby title="app/controllers/chat_controller.rb"
|
|
179
|
+
class ChatController < ApplicationController
|
|
180
|
+
def create
|
|
181
|
+
support_robot = SupportRobot.build
|
|
182
|
+
billing_robot = BillingRobot.build
|
|
183
|
+
|
|
184
|
+
network = RobotLab.create_network(name: "customer_service") do
|
|
185
|
+
task :support, support_robot, depends_on: :none
|
|
186
|
+
task :billing, billing_robot, depends_on: :optional
|
|
140
187
|
end
|
|
141
|
-
end
|
|
142
188
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
189
|
+
result = network.run(message: params[:message], user_id: current_user.id)
|
|
190
|
+
|
|
191
|
+
# result is a SimpleFlow::Result
|
|
192
|
+
# result.value is a RobotResult from the last robot
|
|
193
|
+
render json: {
|
|
194
|
+
response: result.value.last_text_content,
|
|
195
|
+
robot_name: result.value.robot_name
|
|
196
|
+
}
|
|
148
197
|
end
|
|
149
198
|
end
|
|
150
199
|
```
|
|
@@ -153,26 +202,28 @@ end
|
|
|
153
202
|
|
|
154
203
|
### Template Location
|
|
155
204
|
|
|
205
|
+
Templates are `.md` files with YAML front matter, stored in `app/prompts/` (auto-configured for Rails):
|
|
206
|
+
|
|
156
207
|
```
|
|
157
208
|
app/prompts/
|
|
158
|
-
├── support
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
└── billing/
|
|
162
|
-
└── system_prompt.erb
|
|
209
|
+
├── support.md
|
|
210
|
+
├── billing.md
|
|
211
|
+
└── router.md
|
|
163
212
|
```
|
|
164
213
|
|
|
165
|
-
### Template
|
|
166
|
-
|
|
167
|
-
```ruby
|
|
168
|
-
robot = RobotLab.build do
|
|
169
|
-
name "support"
|
|
170
|
-
template "support/system_prompt", company: "Acme Corp"
|
|
171
|
-
end
|
|
172
|
-
```
|
|
214
|
+
### Template Format
|
|
173
215
|
|
|
174
|
-
```
|
|
175
|
-
|
|
216
|
+
```markdown title="app/prompts/support.md"
|
|
217
|
+
---
|
|
218
|
+
description: Customer support assistant
|
|
219
|
+
parameters:
|
|
220
|
+
company_name: null
|
|
221
|
+
tone: friendly
|
|
222
|
+
model: claude-sonnet-4
|
|
223
|
+
temperature: 0.7
|
|
224
|
+
---
|
|
225
|
+
You are a support agent for <%= company_name %>.
|
|
226
|
+
Respond in a <%= tone %> manner.
|
|
176
227
|
|
|
177
228
|
Your responsibilities:
|
|
178
229
|
- Answer product questions
|
|
@@ -180,6 +231,21 @@ Your responsibilities:
|
|
|
180
231
|
- Provide friendly assistance
|
|
181
232
|
```
|
|
182
233
|
|
|
234
|
+
### Template Usage
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
# Pass context to fill template parameters
|
|
238
|
+
robot = RobotLab.build(
|
|
239
|
+
name: "support",
|
|
240
|
+
template: :support,
|
|
241
|
+
context: { company_name: "Acme Corp" }
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Parameters with defaults (like `tone: friendly`) are optional.
|
|
245
|
+
# Parameters set to null are required and must be provided via context.
|
|
246
|
+
result = robot.run("I need help with my order")
|
|
247
|
+
```
|
|
248
|
+
|
|
183
249
|
## Action Cable Integration
|
|
184
250
|
|
|
185
251
|
### Channel
|
|
@@ -187,29 +253,25 @@ Your responsibilities:
|
|
|
187
253
|
```ruby title="app/channels/chat_channel.rb"
|
|
188
254
|
class ChatChannel < ApplicationCable::Channel
|
|
189
255
|
def subscribed
|
|
190
|
-
stream_from "chat_#{params[:
|
|
256
|
+
stream_from "chat_#{params[:session_id]}"
|
|
191
257
|
end
|
|
192
258
|
|
|
193
259
|
def receive(data)
|
|
194
|
-
message
|
|
195
|
-
|
|
260
|
+
message = data["message"]
|
|
261
|
+
session_id = data["session_id"]
|
|
196
262
|
|
|
197
|
-
|
|
198
|
-
|
|
263
|
+
robot = SupportRobot.build
|
|
264
|
+
result = robot.run(message)
|
|
199
265
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
266
|
+
ActionCable.server.broadcast(
|
|
267
|
+
"chat_#{session_id}",
|
|
268
|
+
{
|
|
269
|
+
event: "complete",
|
|
270
|
+
response: result.last_text_content,
|
|
271
|
+
robot_name: result.robot_name
|
|
204
272
|
}
|
|
205
273
|
)
|
|
206
274
|
end
|
|
207
|
-
|
|
208
|
-
private
|
|
209
|
-
|
|
210
|
-
def network
|
|
211
|
-
@network ||= ChatNetwork.build
|
|
212
|
-
end
|
|
213
275
|
end
|
|
214
276
|
```
|
|
215
277
|
|
|
@@ -217,17 +279,17 @@ end
|
|
|
217
279
|
|
|
218
280
|
```javascript
|
|
219
281
|
const channel = consumer.subscriptions.create(
|
|
220
|
-
{ channel: "ChatChannel",
|
|
282
|
+
{ channel: "ChatChannel", session_id: sessionId },
|
|
221
283
|
{
|
|
222
284
|
received(data) {
|
|
223
|
-
if (data.event === "
|
|
224
|
-
|
|
285
|
+
if (data.event === "complete") {
|
|
286
|
+
displayMessage(data.response);
|
|
225
287
|
}
|
|
226
288
|
}
|
|
227
289
|
}
|
|
228
290
|
);
|
|
229
291
|
|
|
230
|
-
channel.send({ message: "Hello!",
|
|
292
|
+
channel.send({ message: "Hello!", session_id: sessionId });
|
|
231
293
|
```
|
|
232
294
|
|
|
233
295
|
## Background Jobs
|
|
@@ -238,27 +300,20 @@ channel.send({ message: "Hello!", thread_id: threadId });
|
|
|
238
300
|
class ProcessMessageJob < ApplicationJob
|
|
239
301
|
queue_as :default
|
|
240
302
|
|
|
241
|
-
def perform(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
data: { user_id: user_id }
|
|
245
|
-
)
|
|
246
|
-
state.thread_id = thread_id
|
|
247
|
-
|
|
248
|
-
result = network.run(state: state)
|
|
303
|
+
def perform(session_id:, message:, user_id:)
|
|
304
|
+
robot = SupportRobot.build
|
|
305
|
+
result = robot.run(message)
|
|
249
306
|
|
|
250
|
-
# Notify user of completion
|
|
307
|
+
# Notify user of completion via Action Cable
|
|
251
308
|
ActionCable.server.broadcast(
|
|
252
|
-
"chat_#{
|
|
253
|
-
{
|
|
309
|
+
"chat_#{session_id}",
|
|
310
|
+
{
|
|
311
|
+
event: "complete",
|
|
312
|
+
response: result.last_text_content,
|
|
313
|
+
robot_name: result.robot_name
|
|
314
|
+
}
|
|
254
315
|
)
|
|
255
316
|
end
|
|
256
|
-
|
|
257
|
-
private
|
|
258
|
-
|
|
259
|
-
def network
|
|
260
|
-
ChatNetwork.build
|
|
261
|
-
end
|
|
262
317
|
end
|
|
263
318
|
```
|
|
264
319
|
|
|
@@ -266,7 +321,7 @@ end
|
|
|
266
321
|
|
|
267
322
|
```ruby
|
|
268
323
|
ProcessMessageJob.perform_later(
|
|
269
|
-
|
|
324
|
+
session_id: params[:session_id],
|
|
270
325
|
message: params[:message],
|
|
271
326
|
user_id: current_user.id
|
|
272
327
|
)
|
|
@@ -278,10 +333,16 @@ render json: { status: "processing" }
|
|
|
278
333
|
|
|
279
334
|
### Test Configuration
|
|
280
335
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
336
|
+
Use `config/robot_lab.yml` to configure the test environment with a faster, cheaper model:
|
|
337
|
+
|
|
338
|
+
```yaml title="config/robot_lab.yml"
|
|
339
|
+
test:
|
|
340
|
+
max_iterations: 3
|
|
341
|
+
streaming_enabled: false
|
|
342
|
+
ruby_llm:
|
|
343
|
+
model: claude-3-haiku-20240307
|
|
344
|
+
request_timeout: 30
|
|
345
|
+
max_retries: 1
|
|
285
346
|
```
|
|
286
347
|
|
|
287
348
|
### Robot Tests
|
|
@@ -293,7 +354,17 @@ class SupportRobotTest < ActiveSupport::TestCase
|
|
|
293
354
|
test "builds valid robot" do
|
|
294
355
|
robot = SupportRobot.build
|
|
295
356
|
assert_equal "support", robot.name
|
|
296
|
-
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
test "robot has correct model" do
|
|
360
|
+
robot = SupportRobot.build
|
|
361
|
+
assert_equal "claude-sonnet-4", robot.model
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
test "robot has local tools" do
|
|
365
|
+
robot = SupportRobot.build
|
|
366
|
+
tool_names = robot.local_tools.map(&:name)
|
|
367
|
+
assert_includes tool_names, "order_lookup"
|
|
297
368
|
end
|
|
298
369
|
end
|
|
299
370
|
```
|
|
@@ -311,7 +382,6 @@ class ChatTest < ActionDispatch::IntegrationTest
|
|
|
311
382
|
|
|
312
383
|
json = JSON.parse(response.body)
|
|
313
384
|
assert json["response"].present?
|
|
314
|
-
assert json["thread_id"].present?
|
|
315
385
|
end
|
|
316
386
|
end
|
|
317
387
|
end
|
|
@@ -323,11 +393,21 @@ end
|
|
|
323
393
|
|
|
324
394
|
```ruby title="app/models/robot_lab_thread.rb"
|
|
325
395
|
class RobotLabThread < ApplicationRecord
|
|
326
|
-
has_many :results,
|
|
327
|
-
|
|
396
|
+
has_many :results,
|
|
397
|
+
class_name: "RobotLabResult",
|
|
398
|
+
foreign_key: :session_id,
|
|
399
|
+
primary_key: :session_id,
|
|
400
|
+
dependent: :destroy
|
|
328
401
|
|
|
329
|
-
|
|
330
|
-
|
|
402
|
+
validates :session_id, presence: true, uniqueness: true
|
|
403
|
+
|
|
404
|
+
def self.find_or_create_by_session_id(id)
|
|
405
|
+
find_or_create_by(session_id: id)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def last_result
|
|
409
|
+
results.order(sequence_number: :desc).first
|
|
410
|
+
end
|
|
331
411
|
end
|
|
332
412
|
```
|
|
333
413
|
|
|
@@ -335,23 +415,24 @@ end
|
|
|
335
415
|
|
|
336
416
|
```ruby title="app/models/robot_lab_result.rb"
|
|
337
417
|
class RobotLabResult < ApplicationRecord
|
|
338
|
-
belongs_to :thread,
|
|
418
|
+
belongs_to :thread,
|
|
419
|
+
class_name: "RobotLabThread",
|
|
420
|
+
foreign_key: :session_id,
|
|
421
|
+
primary_key: :session_id
|
|
422
|
+
|
|
423
|
+
validates :session_id, presence: true
|
|
424
|
+
validates :robot_name, presence: true
|
|
425
|
+
|
|
426
|
+
default_scope { order(sequence_number: :asc) }
|
|
339
427
|
|
|
340
428
|
def to_robot_result
|
|
341
429
|
RobotLab::RobotResult.new(
|
|
342
430
|
robot_name: robot_name,
|
|
343
|
-
output:
|
|
344
|
-
tool_calls:
|
|
431
|
+
output: (output_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
|
|
432
|
+
tool_calls: (tool_calls_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
|
|
345
433
|
stop_reason: stop_reason
|
|
346
434
|
)
|
|
347
435
|
end
|
|
348
|
-
|
|
349
|
-
private
|
|
350
|
-
|
|
351
|
-
def deserialize_messages(data)
|
|
352
|
-
return [] unless data
|
|
353
|
-
data.map { |h| RobotLab::Message.from_hash(h.symbolize_keys) }
|
|
354
|
-
end
|
|
355
436
|
end
|
|
356
437
|
```
|
|
357
438
|
|
|
@@ -365,29 +446,31 @@ class ChatService
|
|
|
365
446
|
@user = user
|
|
366
447
|
end
|
|
367
448
|
|
|
368
|
-
def process(message
|
|
369
|
-
|
|
370
|
-
result =
|
|
449
|
+
def process(message)
|
|
450
|
+
robot = SupportRobot.build
|
|
451
|
+
result = robot.run(message)
|
|
371
452
|
|
|
372
453
|
{
|
|
373
|
-
response: result.
|
|
374
|
-
|
|
454
|
+
response: result.last_text_content,
|
|
455
|
+
robot_name: result.robot_name
|
|
375
456
|
}
|
|
376
457
|
end
|
|
377
458
|
|
|
378
|
-
|
|
459
|
+
def process_with_network(message)
|
|
460
|
+
support_robot = SupportRobot.build
|
|
461
|
+
billing_robot = BillingRobot.build
|
|
379
462
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
state
|
|
387
|
-
end
|
|
463
|
+
network = RobotLab.create_network(name: "customer_service") do
|
|
464
|
+
task :support, support_robot, depends_on: :none
|
|
465
|
+
task :billing, billing_robot, depends_on: :optional
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
result = network.run(message: message, user_id: @user.id)
|
|
388
469
|
|
|
389
|
-
|
|
390
|
-
|
|
470
|
+
{
|
|
471
|
+
response: result.value.last_text_content,
|
|
472
|
+
robot_name: result.value.robot_name
|
|
473
|
+
}
|
|
391
474
|
end
|
|
392
475
|
end
|
|
393
476
|
```
|
|
@@ -429,4 +512,3 @@ end
|
|
|
429
512
|
|
|
430
513
|
- [Building Robots](building-robots.md) - Robot patterns
|
|
431
514
|
- [Creating Networks](creating-networks.md) - Network configuration
|
|
432
|
-
- [History Guide](history.md) - Conversation persistence
|