robot_lab 0.0.1 → 0.0.6
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 +140 -0
- data/README.md +263 -48
- data/Rakefile +71 -1
- data/docs/api/core/index.md +53 -46
- data/docs/api/core/memory.md +200 -154
- data/docs/api/core/network.md +13 -3
- data/docs/api/core/robot.md +490 -130
- data/docs/api/core/state.md +55 -73
- data/docs/api/core/tool.md +205 -209
- data/docs/api/index.md +7 -28
- 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/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/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 +360 -116
- 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 +311 -49
- 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 +347 -154
- 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 +418 -212
- data/docs/guides/creating-networks.md +143 -24
- data/docs/guides/index.md +0 -5
- data/docs/guides/mcp-integration.md +152 -113
- data/docs/guides/memory.md +220 -164
- data/docs/guides/rails-integration.md +244 -162
- data/docs/guides/streaming.md +137 -187
- data/docs/guides/using-tools.md +259 -212
- data/docs/index.md +46 -41
- data/examples/01_simple_robot.rb +6 -9
- data/examples/02_tools.rb +6 -9
- data/examples/03_network.rb +19 -17
- 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 +169 -0
- data/examples/09_chaining.rb +262 -0
- data/examples/10_memory.rb +331 -0
- data/examples/11_network_introspection.rb +253 -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 +63 -0
- data/examples/14_rusty_circuit/open_mic.rb +123 -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 +156 -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/16_writers_room/display.rb +158 -0
- data/examples/16_writers_room/output/.gitignore +2 -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/prompts/writer.md +37 -0
- data/examples/16_writers_room/room.rb +150 -0
- data/examples/16_writers_room/tools.rb +162 -0
- data/examples/16_writers_room/writer.rb +121 -0
- data/examples/16_writers_room/writers_room.rb +162 -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 +0 -13
- 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 +10 -34
- 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 +240 -330
- data/lib/robot_lab/robot_message.rb +44 -0
- data/lib/robot_lab/robot_result.rb +1 -0
- data/lib/robot_lab/run_config.rb +184 -0
- data/lib/robot_lab/state_proxy.rb +2 -12
- data/lib/robot_lab/streaming/context.rb +1 -1
- data/lib/robot_lab/task.rb +8 -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/utils.rb +39 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +89 -57
- data/mkdocs.yml +0 -11
- metadata +121 -135
- 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 -195
- data/docs/api/history/config.md +0 -191
- data/docs/api/history/index.md +0 -132
- data/docs/api/history/thread-manager.md +0 -144
- data/docs/guides/history.md +0 -359
- 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/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 -159
- data/lib/robot_lab/adapters/registry.rb +0 -81
- data/lib/robot_lab/configuration.rb +0 -143
- 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
data/docs/examples/basic-chat.md
CHANGED
|
@@ -4,7 +4,7 @@ A simple conversational robot example.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This example demonstrates the minimal setup for a conversational robot that can respond to user messages.
|
|
7
|
+
This example demonstrates the minimal setup for a conversational robot that can respond to user messages using `robot.run("message")`.
|
|
8
8
|
|
|
9
9
|
## Complete Example
|
|
10
10
|
|
|
@@ -15,21 +15,16 @@ This example demonstrates the minimal setup for a conversational robot that can
|
|
|
15
15
|
require "bundler/setup"
|
|
16
16
|
require "robot_lab"
|
|
17
17
|
|
|
18
|
-
# Configure RobotLab
|
|
19
|
-
RobotLab.configure do |config|
|
|
20
|
-
config.default_model = "claude-sonnet-4"
|
|
21
|
-
end
|
|
22
|
-
|
|
23
18
|
# Build a simple assistant
|
|
24
|
-
assistant = RobotLab.build
|
|
25
|
-
name "assistant"
|
|
26
|
-
description "A helpful conversational assistant"
|
|
27
|
-
|
|
28
|
-
template <<~PROMPT
|
|
19
|
+
assistant = RobotLab.build(
|
|
20
|
+
name: "assistant",
|
|
21
|
+
description: "A helpful conversational assistant",
|
|
22
|
+
system_prompt: <<~PROMPT,
|
|
29
23
|
You are a helpful, friendly assistant. You provide clear,
|
|
30
24
|
concise answers to questions. Be conversational but informative.
|
|
31
25
|
PROMPT
|
|
32
|
-
|
|
26
|
+
model: "claude-sonnet-4"
|
|
27
|
+
)
|
|
33
28
|
|
|
34
29
|
# Simple REPL
|
|
35
30
|
puts "Chat with the assistant (type 'quit' to exit)"
|
|
@@ -42,13 +37,11 @@ loop do
|
|
|
42
37
|
break if input.nil? || input.downcase == "quit"
|
|
43
38
|
next if input.empty?
|
|
44
39
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
result = assistant.run(state: state)
|
|
40
|
+
# Run the robot with the user's message
|
|
41
|
+
result = assistant.run(input)
|
|
48
42
|
|
|
49
43
|
# Display response
|
|
50
|
-
|
|
51
|
-
puts "\nAssistant: #{response}"
|
|
44
|
+
puts "\nAssistant: #{result.last_text_content}"
|
|
52
45
|
end
|
|
53
46
|
|
|
54
47
|
puts "\nGoodbye!"
|
|
@@ -63,14 +56,11 @@ puts "\nGoodbye!"
|
|
|
63
56
|
require "bundler/setup"
|
|
64
57
|
require "robot_lab"
|
|
65
58
|
|
|
66
|
-
RobotLab.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
name "assistant"
|
|
72
|
-
template "You are a helpful assistant."
|
|
73
|
-
end
|
|
59
|
+
assistant = RobotLab.build(
|
|
60
|
+
name: "assistant",
|
|
61
|
+
system_prompt: "You are a helpful assistant.",
|
|
62
|
+
model: "claude-sonnet-4"
|
|
63
|
+
)
|
|
74
64
|
|
|
75
65
|
puts "Chat with streaming (type 'quit' to exit)"
|
|
76
66
|
puts "-" * 50
|
|
@@ -82,11 +72,9 @@ loop do
|
|
|
82
72
|
break if input.nil? || input.downcase == "quit"
|
|
83
73
|
next if input.empty?
|
|
84
74
|
|
|
85
|
-
state = RobotLab.create_state(message: input)
|
|
86
|
-
|
|
87
75
|
print "\nAssistant: "
|
|
88
|
-
assistant.run(
|
|
89
|
-
print event.text if event.
|
|
76
|
+
result = assistant.run(input) do |event|
|
|
77
|
+
print event.text if event.respond_to?(:text)
|
|
90
78
|
end
|
|
91
79
|
puts
|
|
92
80
|
end
|
|
@@ -94,51 +82,61 @@ end
|
|
|
94
82
|
puts "\nGoodbye!"
|
|
95
83
|
```
|
|
96
84
|
|
|
97
|
-
## With
|
|
85
|
+
## With Template
|
|
98
86
|
|
|
99
87
|
```ruby
|
|
100
88
|
#!/usr/bin/env ruby
|
|
101
|
-
# examples/
|
|
89
|
+
# examples/template_chat.rb
|
|
102
90
|
|
|
103
91
|
require "bundler/setup"
|
|
104
92
|
require "robot_lab"
|
|
105
93
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
# Build a robot using a prompt template file
|
|
95
|
+
# Template: prompts/assistant.md (Markdown with YAML front matter)
|
|
96
|
+
assistant = RobotLab.build(
|
|
97
|
+
name: "assistant",
|
|
98
|
+
template: :assistant,
|
|
99
|
+
context: { tone: "friendly", domain: "general" },
|
|
100
|
+
model: "claude-sonnet-4"
|
|
101
|
+
)
|
|
109
102
|
|
|
110
|
-
assistant
|
|
111
|
-
|
|
112
|
-
template "You are a helpful assistant with memory of our conversation."
|
|
113
|
-
end
|
|
103
|
+
puts "Chat with template-based assistant (type 'quit' to exit)"
|
|
104
|
+
puts "-" * 50
|
|
114
105
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
HISTORY[id] = []
|
|
122
|
-
{ id: id }
|
|
123
|
-
},
|
|
124
|
-
get: ->(thread_id:, **) {
|
|
125
|
-
HISTORY[thread_id] || []
|
|
126
|
-
},
|
|
127
|
-
append_results: ->(thread_id:, new_results:, **) {
|
|
128
|
-
HISTORY[thread_id].concat(new_results.map(&:to_h))
|
|
129
|
-
}
|
|
130
|
-
)
|
|
106
|
+
loop do
|
|
107
|
+
print "\nYou: "
|
|
108
|
+
input = gets&.chomp
|
|
109
|
+
|
|
110
|
+
break if input.nil? || input.downcase == "quit"
|
|
111
|
+
next if input.empty?
|
|
131
112
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
history history_config
|
|
135
|
-
add_robot assistant
|
|
113
|
+
result = assistant.run(input)
|
|
114
|
+
puts "\nAssistant: #{result.last_text_content}"
|
|
136
115
|
end
|
|
137
116
|
|
|
117
|
+
puts "\nGoodbye!"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## With Memory
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
#!/usr/bin/env ruby
|
|
124
|
+
# examples/chat_with_memory.rb
|
|
125
|
+
|
|
126
|
+
require "bundler/setup"
|
|
127
|
+
require "robot_lab"
|
|
128
|
+
|
|
129
|
+
assistant = RobotLab.build(
|
|
130
|
+
name: "assistant",
|
|
131
|
+
system_prompt: "You are a helpful assistant. Use the user's name when you know it.",
|
|
132
|
+
model: "claude-sonnet-4"
|
|
133
|
+
)
|
|
134
|
+
|
|
138
135
|
puts "Chat with memory (type 'quit' to exit)"
|
|
139
136
|
puts "-" * 50
|
|
140
137
|
|
|
141
|
-
|
|
138
|
+
# Store user info in the robot's inherent memory
|
|
139
|
+
assistant.memory[:user_name] = "Alice"
|
|
142
140
|
|
|
143
141
|
loop do
|
|
144
142
|
print "\nYou: "
|
|
@@ -147,20 +145,34 @@ loop do
|
|
|
147
145
|
break if input.nil? || input.downcase == "quit"
|
|
148
146
|
next if input.empty?
|
|
149
147
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
# The robot's persistent @chat maintains conversation history automatically
|
|
149
|
+
result = assistant.run(input)
|
|
150
|
+
puts "\nAssistant: #{result.last_text_content}"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
puts "\nGoodbye!"
|
|
154
|
+
```
|
|
153
155
|
|
|
154
|
-
|
|
155
|
-
result = network.run(state: state)
|
|
156
|
+
## Bare Robot with Chaining
|
|
156
157
|
|
|
157
|
-
|
|
158
|
+
```ruby
|
|
159
|
+
#!/usr/bin/env ruby
|
|
160
|
+
# examples/bare_robot.rb
|
|
158
161
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
end
|
|
162
|
+
require "bundler/setup"
|
|
163
|
+
require "robot_lab"
|
|
162
164
|
|
|
163
|
-
|
|
165
|
+
# Build a bare robot with no template or prompt
|
|
166
|
+
robot = RobotLab.build(name: "bot")
|
|
167
|
+
|
|
168
|
+
# Configure via chaining
|
|
169
|
+
result = robot
|
|
170
|
+
.with_model("claude-sonnet-4")
|
|
171
|
+
.with_temperature(0.7)
|
|
172
|
+
.with_instructions("You are a pirate. Respond in pirate speak.")
|
|
173
|
+
.run("What is the weather like today?")
|
|
174
|
+
|
|
175
|
+
puts result.last_text_content
|
|
164
176
|
```
|
|
165
177
|
|
|
166
178
|
## Running
|
|
@@ -174,20 +186,20 @@ ruby examples/basic_chat.rb
|
|
|
174
186
|
|
|
175
187
|
# Run with streaming
|
|
176
188
|
ruby examples/streaming_chat.rb
|
|
177
|
-
|
|
178
|
-
# Run with memory
|
|
179
|
-
ruby examples/chat_with_memory.rb
|
|
180
189
|
```
|
|
181
190
|
|
|
182
191
|
## Key Concepts
|
|
183
192
|
|
|
184
|
-
1. **Robot Building**: Use `RobotLab.build`
|
|
185
|
-
2. **
|
|
186
|
-
3. **
|
|
187
|
-
4. **
|
|
193
|
+
1. **Robot Building**: Use `RobotLab.build(name:, system_prompt:)` or `RobotLab.build(name:, template:)` to create a robot
|
|
194
|
+
2. **Execution**: Call `robot.run("message")` to send a message and get a response
|
|
195
|
+
3. **Response**: Access the text via `result.last_text_content`
|
|
196
|
+
4. **Streaming**: Pass a block to `robot.run("message") { |event| ... }`
|
|
197
|
+
5. **Memory**: Access inherent memory via `robot.memory[:key]`
|
|
198
|
+
6. **Chaining**: Configure with `with_*` methods that return `self`
|
|
199
|
+
7. **Conversation History**: The persistent `@chat` maintains history across multiple `run` calls
|
|
188
200
|
|
|
189
201
|
## See Also
|
|
190
202
|
|
|
191
203
|
- [Building Robots Guide](../guides/building-robots.md)
|
|
192
204
|
- [Streaming Guide](../guides/streaming.md)
|
|
193
|
-
- [
|
|
205
|
+
- [Robot API Reference](../api/core/robot.md)
|
data/docs/examples/index.md
CHANGED
|
@@ -15,6 +15,8 @@ These examples show how to use RobotLab for common scenarios, from simple chatbo
|
|
|
15
15
|
| [Tool Usage](tool-usage.md) | External API integration |
|
|
16
16
|
| [MCP Server](mcp-server.md) | Creating an MCP tool server |
|
|
17
17
|
| [Rails Application](rails-application.md) | Full Rails integration |
|
|
18
|
+
| [Message Bus](#message-bus) | Bidirectional robot communication with convergence |
|
|
19
|
+
| [Spawning Robots](#spawning-robots) | Dynamic specialist creation at runtime |
|
|
18
20
|
|
|
19
21
|
## Quick Links
|
|
20
22
|
|
|
@@ -29,80 +31,117 @@ These examples show how to use RobotLab for common scenarios, from simple chatbo
|
|
|
29
31
|
- [Streaming Responses](basic-chat.md#with-streaming)
|
|
30
32
|
- [Persistent Conversations](basic-chat.md#with-conversation-history)
|
|
31
33
|
- [MCP Integration](mcp-server.md)
|
|
34
|
+
- [Message Bus Communication](#message-bus)
|
|
35
|
+
- [Spawning Robots](#spawning-robots)
|
|
32
36
|
|
|
33
37
|
## Hello World
|
|
34
38
|
|
|
35
39
|
```ruby
|
|
36
40
|
require "robot_lab"
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
# Configuration is handled automatically via MywayConfig.
|
|
43
|
+
# Set API keys via environment variables:
|
|
44
|
+
# ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
45
|
+
# Or via config files (~/.config/robot_lab/config.yml)
|
|
41
46
|
|
|
42
|
-
robot = RobotLab.build
|
|
43
|
-
name "greeter"
|
|
44
|
-
|
|
45
|
-
|
|
47
|
+
robot = RobotLab.build(
|
|
48
|
+
name: "greeter",
|
|
49
|
+
system_prompt: "You are a friendly greeter. Say hello warmly."
|
|
50
|
+
)
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
result = robot.run(state: state)
|
|
52
|
+
result = robot.run("Hi there!")
|
|
49
53
|
|
|
50
|
-
puts result.
|
|
54
|
+
puts result.last_text_content
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
## Robot with Tools
|
|
54
58
|
|
|
55
59
|
```ruby
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
handler { |expression:, **_| eval(expression).to_s }
|
|
60
|
+
class CalculatorTool < RubyLLM::Tool
|
|
61
|
+
description "Perform a calculation"
|
|
62
|
+
|
|
63
|
+
param :expression, type: :string, desc: "Math expression to evaluate"
|
|
64
|
+
|
|
65
|
+
def execute(expression:)
|
|
66
|
+
eval(expression).to_s
|
|
64
67
|
end
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
robot = RobotLab.build(
|
|
71
|
+
name: "calculator",
|
|
72
|
+
system_prompt: "You help with calculations.",
|
|
73
|
+
local_tools: [CalculatorTool]
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
result = robot.run("What's 25 * 4?")
|
|
77
|
+
puts result.last_text_content
|
|
69
78
|
```
|
|
70
79
|
|
|
71
80
|
## Network with Routing
|
|
72
81
|
|
|
73
82
|
```ruby
|
|
74
|
-
classifier = RobotLab.build
|
|
75
|
-
name "classifier"
|
|
76
|
-
|
|
83
|
+
classifier = RobotLab.build(
|
|
84
|
+
name: "classifier",
|
|
85
|
+
system_prompt: "Classify the request as BILLING or TECHNICAL. Respond with only the category."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
billing = RobotLab.build(
|
|
89
|
+
name: "billing",
|
|
90
|
+
system_prompt: "You handle billing questions."
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
tech = RobotLab.build(
|
|
94
|
+
name: "tech",
|
|
95
|
+
system_prompt: "You handle technical issues."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
network = RobotLab.create_network(name: "support") do
|
|
99
|
+
task :classifier, classifier, depends_on: :none
|
|
100
|
+
task :billing, billing, depends_on: :optional
|
|
101
|
+
task :tech, tech, depends_on: :optional
|
|
77
102
|
end
|
|
78
103
|
|
|
79
|
-
|
|
80
|
-
name "billing"
|
|
81
|
-
template "You handle billing questions."
|
|
82
|
-
end
|
|
104
|
+
result = network.run(message: "I was charged twice for my subscription")
|
|
83
105
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
# Access individual robot results via context
|
|
107
|
+
classifier_result = result.context[:classifier]
|
|
108
|
+
puts classifier_result.last_text_content
|
|
109
|
+
```
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
111
|
+
## Chaining Configuration
|
|
112
|
+
|
|
113
|
+
Robots support `with_*` methods that return `self` for chaining:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
robot = RobotLab.build(name: "assistant")
|
|
117
|
+
.with_instructions("You are a helpful coding assistant.")
|
|
118
|
+
.with_temperature(0.3)
|
|
119
|
+
.with_model("gpt-4o")
|
|
120
|
+
|
|
121
|
+
result = robot.run("Explain Ruby blocks.")
|
|
122
|
+
puts result.last_text_content
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Using Templates
|
|
126
|
+
|
|
127
|
+
Templates are `.md` files with optional YAML front matter, managed by prompt_manager:
|
|
104
128
|
|
|
105
|
-
|
|
129
|
+
```ruby
|
|
130
|
+
# Template file: prompts/support.md
|
|
131
|
+
# ---
|
|
132
|
+
# model: claude-sonnet-4
|
|
133
|
+
# temperature: 0.5
|
|
134
|
+
# ---
|
|
135
|
+
# You are a support assistant for {{ company_name }}.
|
|
136
|
+
|
|
137
|
+
robot = RobotLab.build(
|
|
138
|
+
name: "support",
|
|
139
|
+
template: :support,
|
|
140
|
+
context: { company_name: "Acme Corp" }
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
result = robot.run("How do I reset my password?")
|
|
144
|
+
puts result.last_text_content
|
|
106
145
|
```
|
|
107
146
|
|
|
108
147
|
## Running Examples
|
|
@@ -122,6 +161,142 @@ result = network.run(state: state)
|
|
|
122
161
|
ruby examples/basic_chat.rb
|
|
123
162
|
```
|
|
124
163
|
|
|
164
|
+
Or use the provided rake tasks:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
bundle exec rake examples:all # Run all examples
|
|
168
|
+
bundle exec rake examples:run[1] # Run specific example by number
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Message Bus
|
|
172
|
+
|
|
173
|
+
Robots can communicate bidirectionally via a message bus, enabling convergence loops and negotiation patterns. This example demonstrates a comedy critic tasking a comedian to generate jokes until one passes:
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
177
|
+
require "robot_lab"
|
|
178
|
+
|
|
179
|
+
MAX_ATTEMPTS = 5
|
|
180
|
+
|
|
181
|
+
class Comedian < RobotLab::Robot
|
|
182
|
+
TEMP_START = 0.2
|
|
183
|
+
TEMP_STEP = 0.2
|
|
184
|
+
|
|
185
|
+
def initialize(bus:)
|
|
186
|
+
super(name: "bob", template: :comedian, bus: bus, temperature: TEMP_START)
|
|
187
|
+
@attempts = 0
|
|
188
|
+
on_message do |message|
|
|
189
|
+
@attempts += 1
|
|
190
|
+
temp = [TEMP_START + TEMP_STEP * (@attempts - 1), 1.0].min
|
|
191
|
+
with_temperature(temp)
|
|
192
|
+
joke = run(message.content.to_s).last_text_content.strip
|
|
193
|
+
send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
attr_reader :attempts
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
class ComedyCritic < RobotLab::Robot
|
|
201
|
+
def initialize(bus:)
|
|
202
|
+
super(name: "alice", template: :comedy_critic, bus: bus)
|
|
203
|
+
@accepted = false
|
|
204
|
+
on_message do |message|
|
|
205
|
+
verdict = run("Evaluate this joke:\n\n#{message.content}").last_text_content.strip
|
|
206
|
+
@accepted = verdict.start_with?("FUNNY")
|
|
207
|
+
send_message(to: :bob, content: "Not funny enough. Try again.") unless @accepted
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
attr_reader :accepted
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
bus = TypedBus::MessageBus.new
|
|
215
|
+
bob = Comedian.new(bus: bus)
|
|
216
|
+
alice = ComedyCritic.new(bus: bus)
|
|
217
|
+
|
|
218
|
+
alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
|
|
219
|
+
puts "Attempts: #{bob.attempts} / #{MAX_ATTEMPTS}"
|
|
220
|
+
puts "Accepted: #{alice.accepted}"
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Key patterns demonstrated:
|
|
224
|
+
|
|
225
|
+
- **Robot subclasses** with templates for prompt management
|
|
226
|
+
- **Auto-ack** via 1-arg `on_message` blocks
|
|
227
|
+
- **`send_reply(to:, content:, in_reply_to:)`** for correlated responses
|
|
228
|
+
- **Temperature ramping** (0.2 → 1.0) for increasing creativity
|
|
229
|
+
- **Convergence loop** that terminates when the critic approves
|
|
230
|
+
|
|
231
|
+
Run: `bundle exec ruby examples/12_message_bus.rb`
|
|
232
|
+
|
|
233
|
+
## Spawning Robots
|
|
234
|
+
|
|
235
|
+
Robots can create new specialist robots at runtime using `spawn`. A dispatcher receives questions, decides what kind of specialist is needed, and spawns one on the fly. The bus is created lazily — no explicit setup required:
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
239
|
+
require "robot_lab"
|
|
240
|
+
|
|
241
|
+
QUESTIONS = [
|
|
242
|
+
"Why did the Roman Empire fall?",
|
|
243
|
+
"Write a haiku about recursion.",
|
|
244
|
+
"What is the square root of 144?",
|
|
245
|
+
].freeze
|
|
246
|
+
|
|
247
|
+
class Dispatcher < RobotLab::Robot
|
|
248
|
+
attr_reader :spawned
|
|
249
|
+
|
|
250
|
+
def initialize(bus: nil)
|
|
251
|
+
super(name: "dispatcher", template: :dispatcher, bus: bus)
|
|
252
|
+
@spawned = {}
|
|
253
|
+
@pending = {}
|
|
254
|
+
|
|
255
|
+
on_message do |message|
|
|
256
|
+
puts " Dispatcher <- :#{message.from} replied"
|
|
257
|
+
puts " | #{message.content.to_s.lines.first&.strip}"
|
|
258
|
+
@pending.delete(message.from)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def dispatch(question)
|
|
263
|
+
plan = run(question).last_text_content.strip
|
|
264
|
+
role, instruction = plan.split("\n", 2)
|
|
265
|
+
role = role.strip.downcase.gsub(/\s+/, "_")
|
|
266
|
+
instruction = instruction&.strip || "You are a helpful #{role}."
|
|
267
|
+
|
|
268
|
+
specialist = @spawned[role] ||= spawn(
|
|
269
|
+
name: role,
|
|
270
|
+
system_prompt: instruction
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
@pending[role] = question
|
|
274
|
+
|
|
275
|
+
specialist.send_message(to: :dispatcher, content:
|
|
276
|
+
specialist.run(question).last_text_content.strip
|
|
277
|
+
)
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
dispatcher = Dispatcher.new
|
|
282
|
+
|
|
283
|
+
QUESTIONS.each_with_index do |question, i|
|
|
284
|
+
puts "\nQuestion #{i + 1}: #{question}"
|
|
285
|
+
dispatcher.dispatch(question)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
puts "\nSpecialists spawned: #{dispatcher.spawned.keys.join(', ')}"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Key patterns demonstrated:
|
|
292
|
+
|
|
293
|
+
- **`spawn`** for dynamic robot creation (bus created lazily)
|
|
294
|
+
- **`on_message`** for reply handling
|
|
295
|
+
- **LLM-driven delegation** — the dispatcher asks its LLM what specialist to create
|
|
296
|
+
- **Specialist reuse** — spawned robots are cached and reused across questions
|
|
297
|
+
|
|
298
|
+
Run: `bundle exec ruby examples/13_spawn.rb`
|
|
299
|
+
|
|
125
300
|
## See Also
|
|
126
301
|
|
|
127
302
|
- [Getting Started](../getting-started/index.md)
|