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.
Files changed (145) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +90 -0
  5. data/README.md +203 -46
  6. data/Rakefile +70 -1
  7. data/docs/api/core/index.md +12 -0
  8. data/docs/api/core/robot.md +478 -130
  9. data/docs/api/core/tool.md +205 -209
  10. data/docs/api/history/active-record-adapter.md +174 -94
  11. data/docs/api/history/config.md +186 -93
  12. data/docs/api/history/index.md +57 -61
  13. data/docs/api/history/thread-manager.md +123 -73
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/streaming/context.md +157 -74
  19. data/docs/api/streaming/events.md +114 -166
  20. data/docs/api/streaming/index.md +74 -72
  21. data/docs/architecture/core-concepts.md +361 -112
  22. data/docs/architecture/index.md +97 -59
  23. data/docs/architecture/message-flow.md +138 -129
  24. data/docs/architecture/network-orchestration.md +197 -50
  25. data/docs/architecture/robot-execution.md +199 -146
  26. data/docs/architecture/state-management.md +255 -187
  27. data/docs/concepts.md +312 -48
  28. data/docs/examples/basic-chat.md +89 -77
  29. data/docs/examples/index.md +222 -47
  30. data/docs/examples/mcp-server.md +207 -203
  31. data/docs/examples/multi-robot-network.md +129 -35
  32. data/docs/examples/rails-application.md +159 -160
  33. data/docs/examples/tool-usage.md +295 -204
  34. data/docs/getting-started/configuration.md +275 -162
  35. data/docs/getting-started/index.md +1 -1
  36. data/docs/getting-started/installation.md +22 -13
  37. data/docs/getting-started/quick-start.md +166 -121
  38. data/docs/guides/building-robots.md +417 -212
  39. data/docs/guides/creating-networks.md +94 -24
  40. data/docs/guides/mcp-integration.md +152 -113
  41. data/docs/guides/memory.md +220 -164
  42. data/docs/guides/streaming.md +80 -110
  43. data/docs/guides/using-tools.md +259 -212
  44. data/docs/index.md +50 -37
  45. data/examples/01_simple_robot.rb +6 -9
  46. data/examples/02_tools.rb +6 -9
  47. data/examples/03_network.rb +13 -14
  48. data/examples/04_mcp.rb +5 -8
  49. data/examples/05_streaming.rb +5 -8
  50. data/examples/06_prompt_templates.rb +42 -37
  51. data/examples/07_network_memory.rb +13 -14
  52. data/examples/08_llm_config.rb +140 -0
  53. data/examples/09_chaining.rb +223 -0
  54. data/examples/10_memory.rb +331 -0
  55. data/examples/11_network_introspection.rb +230 -0
  56. data/examples/12_message_bus.rb +74 -0
  57. data/examples/13_spawn.rb +90 -0
  58. data/examples/14_rusty_circuit/comic.rb +143 -0
  59. data/examples/14_rusty_circuit/display.rb +203 -0
  60. data/examples/14_rusty_circuit/heckler.rb +57 -0
  61. data/examples/14_rusty_circuit/open_mic.rb +121 -0
  62. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  63. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  64. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  65. data/examples/14_rusty_circuit/scout.rb +173 -0
  66. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  67. data/examples/14_rusty_circuit/show.log +234 -0
  68. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  69. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  70. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  71. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  72. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  73. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  74. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  75. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  76. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  77. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  78. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  79. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  80. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  81. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  82. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  83. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  84. data/examples/README.md +197 -0
  85. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  86. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  87. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  88. data/examples/prompts/comedian.md +6 -0
  89. data/examples/prompts/comedy_critic.md +10 -0
  90. data/examples/prompts/configurable.md +9 -0
  91. data/examples/prompts/dispatcher.md +12 -0
  92. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  93. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  94. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  95. data/examples/prompts/frontmatter_named_test.md +5 -0
  96. data/examples/prompts/frontmatter_tools_test.md +6 -0
  97. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  98. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  99. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  100. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  101. data/examples/prompts/llm_config_demo.md +20 -0
  102. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  103. data/examples/prompts/os_advocate.md +13 -0
  104. data/examples/prompts/os_chief.md +13 -0
  105. data/examples/prompts/os_editor.md +13 -0
  106. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  107. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  108. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  109. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  110. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  111. data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
  112. data/lib/robot_lab/adapters/openai.rb +2 -1
  113. data/lib/robot_lab/ask_user.rb +75 -0
  114. data/lib/robot_lab/config/defaults.yml +121 -0
  115. data/lib/robot_lab/config.rb +183 -0
  116. data/lib/robot_lab/error.rb +6 -0
  117. data/lib/robot_lab/mcp/client.rb +1 -1
  118. data/lib/robot_lab/memory.rb +2 -2
  119. data/lib/robot_lab/robot.rb +523 -249
  120. data/lib/robot_lab/robot_message.rb +44 -0
  121. data/lib/robot_lab/robot_result.rb +1 -0
  122. data/lib/robot_lab/robotic_model.rb +1 -1
  123. data/lib/robot_lab/streaming/context.rb +1 -1
  124. data/lib/robot_lab/tool.rb +108 -172
  125. data/lib/robot_lab/tool_config.rb +1 -1
  126. data/lib/robot_lab/tool_manifest.rb +2 -18
  127. data/lib/robot_lab/version.rb +1 -1
  128. data/lib/robot_lab.rb +66 -55
  129. metadata +107 -116
  130. data/examples/prompts/assistant/user.txt.erb +0 -1
  131. data/examples/prompts/billing/user.txt.erb +0 -1
  132. data/examples/prompts/classifier/user.txt.erb +0 -1
  133. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  134. data/examples/prompts/escalation/user.txt.erb +0 -34
  135. data/examples/prompts/general/user.txt.erb +0 -1
  136. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  137. data/examples/prompts/helper/user.txt.erb +0 -1
  138. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  139. data/examples/prompts/order_support/user.txt.erb +0 -22
  140. data/examples/prompts/product_support/user.txt.erb +0 -32
  141. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  142. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  143. data/examples/prompts/technical/user.txt.erb +0 -1
  144. data/examples/prompts/triage/user.txt.erb +0 -17
  145. data/lib/robot_lab/configuration.rb +0 -143
@@ -2,93 +2,67 @@
2
2
 
3
3
  Build your first RobotLab application in 5 minutes.
4
4
 
5
- ## Step 1: Configure RobotLab
5
+ ## Step 1: Set Up API Keys
6
6
 
7
- First, set up your API credentials:
7
+ RobotLab reads configuration from environment variables automatically. Set your API key before running any code:
8
8
 
9
- ```ruby
10
- require "robot_lab"
11
-
12
- RobotLab.configure do |config|
13
- config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
14
- config.default_model = "claude-sonnet-4"
15
- end
9
+ ```bash
10
+ export ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY="sk-ant-..."
16
11
  ```
17
12
 
18
- ## Step 2: Create a Robot
19
-
20
- Build a simple assistant robot:
21
-
22
- ```ruby
23
- assistant = RobotLab.build do
24
- name "assistant"
25
- description "A helpful AI assistant"
13
+ Or create a config file at `./config/robot_lab.yml`:
26
14
 
27
- template <<~PROMPT
28
- You are a helpful AI assistant. You provide clear, accurate,
29
- and concise answers to questions. Be friendly but professional.
30
- PROMPT
31
- end
15
+ ```yaml
16
+ defaults:
17
+ ruby_llm:
18
+ anthropic_api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
32
19
  ```
33
20
 
34
- ## Step 3: Create a Network
21
+ See [Configuration](configuration.md) for all configuration options.
22
+
23
+ ## Step 2: Create a Robot
35
24
 
36
- Add your robot to a network:
25
+ Build a simple assistant robot using keyword arguments:
37
26
 
38
27
  ```ruby
39
- network = RobotLab.create_network do
40
- name "my_first_network"
41
- add_robot assistant
42
- end
28
+ require "robot_lab"
29
+
30
+ assistant = RobotLab.build(
31
+ name: "assistant",
32
+ system_prompt: "You are a helpful AI assistant. You provide clear, accurate, and concise answers."
33
+ )
43
34
  ```
44
35
 
45
- ## Step 4: Run It!
36
+ ## Step 3: Run It
46
37
 
47
- Execute the network with a message:
38
+ Send a message and get a response:
48
39
 
49
40
  ```ruby
50
- # Create state with your message
51
- state = RobotLab.create_state(message: "What is Ruby on Rails?")
41
+ result = assistant.run("What is Ruby on Rails?")
52
42
 
53
- # Run the network
54
- result = network.run(state: state)
55
-
56
- # Get the response
57
- response = result.last_result.output.first.content
58
- puts response
43
+ puts result.last_text_content
59
44
  ```
60
45
 
46
+ The `run` method takes a positional string message and returns a `RobotResult`. Use `last_text_content` to extract the response text.
47
+
61
48
  ## Complete Example
62
49
 
63
- Here's everything together in one file:
50
+ Here is everything together in one file:
64
51
 
65
52
  ```ruby title="hello_robot.rb"
66
53
  require "robot_lab"
67
54
 
68
- # Configure
69
- RobotLab.configure do |config|
70
- config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
71
- config.default_model = "claude-sonnet-4"
72
- end
73
-
74
- # Build robot
75
- assistant = RobotLab.build do
76
- name "assistant"
77
- description "A helpful AI assistant"
78
- template "You are a helpful AI assistant."
79
- end
80
-
81
- # Create network
82
- network = RobotLab.create_network do
83
- name "hello_network"
84
- add_robot assistant
85
- end
55
+ # Build a robot with an inline system prompt
56
+ assistant = RobotLab.build(
57
+ name: "assistant",
58
+ system_prompt: "You are a helpful AI assistant. Be concise and friendly."
59
+ )
86
60
 
87
- # Run
88
- state = RobotLab.create_state(message: "Hello! What can you help me with?")
89
- result = network.run(state: state)
61
+ # Run the robot with a message
62
+ result = assistant.run("Hello! What can you help me with?")
90
63
 
91
- puts result.last_result.output.first.content
64
+ # Print the response
65
+ puts result.last_text_content
92
66
  ```
93
67
 
94
68
  Run it:
@@ -97,90 +71,161 @@ Run it:
97
71
  ruby hello_robot.rb
98
72
  ```
99
73
 
100
- ## Adding a Tool
74
+ ## Using Templates
75
+
76
+ Instead of inline prompts, you can use template files managed by `prompt_manager`. Templates are `.md` files with YAML front matter:
77
+
78
+ ```markdown title="prompts/helper.md"
79
+ ---
80
+ description: A helpful assistant
81
+ parameters:
82
+ company_name: null
83
+ ---
84
+ You are a helpful assistant for <%= company_name %>.
85
+ Be concise and friendly in your responses.
86
+ ```
101
87
 
102
- Make your robot more capable with tools:
88
+ Create the robot with a template reference and context:
103
89
 
104
90
  ```ruby
105
- assistant = RobotLab.build do
106
- name "assistant"
107
- description "An assistant that can tell time"
91
+ robot = RobotLab.build(
92
+ name: "helper",
93
+ template: :helper,
94
+ context: { company_name: "Acme Corp" }
95
+ )
96
+
97
+ result = robot.run("What services do you offer?")
98
+ puts result.last_text_content
99
+ ```
108
100
 
109
- template <<~PROMPT
110
- You are a helpful assistant. Use the current_time tool
111
- when users ask about the time.
112
- PROMPT
101
+ Templates are loaded from the `prompts/` directory by default (or `app/prompts/` in Rails). You can change this in your config.
113
102
 
114
- tool :current_time do
115
- description "Get the current date and time"
116
- handler { Time.now.strftime("%Y-%m-%d %H:%M:%S") }
103
+ ## Adding a Tool
104
+
105
+ Give your robot custom capabilities by defining a `RubyLLM::Tool` subclass:
106
+
107
+ ```ruby title="hello_tools.rb"
108
+ require "robot_lab"
109
+
110
+ class CurrentTime < RubyLLM::Tool
111
+ description "Get the current date and time"
112
+
113
+ param :timezone,
114
+ type: "string",
115
+ desc: "Timezone name (e.g., 'UTC', 'US/Eastern')",
116
+ required: false
117
+
118
+ def execute(timezone: "UTC")
119
+ Time.now.getlocal(timezone_offset(timezone)).strftime("%Y-%m-%d %H:%M:%S %Z")
120
+ rescue => e
121
+ Time.now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")
122
+ end
123
+
124
+ private
125
+
126
+ def timezone_offset(tz)
127
+ case tz
128
+ when "UTC" then "+00:00"
129
+ when "US/Eastern" then "-05:00"
130
+ when "US/Pacific" then "-08:00"
131
+ else "+00:00"
132
+ end
117
133
  end
118
134
  end
135
+
136
+ # Pass tools via the local_tools: parameter
137
+ assistant = RobotLab.build(
138
+ name: "time_bot",
139
+ system_prompt: "You are a helpful assistant. Use the current_time tool when users ask about the time.",
140
+ local_tools: [CurrentTime]
141
+ )
142
+
143
+ result = assistant.run("What time is it right now?")
144
+ puts result.last_text_content
119
145
  ```
120
146
 
121
- Now the robot can respond to "What time is it?" by calling the tool.
147
+ Tools are passed to the robot via the `local_tools:` keyword argument as an array of `RubyLLM::Tool` subclasses.
122
148
 
123
- ## Multi-Robot Example
149
+ ## Method Chaining
124
150
 
125
- Create a network with multiple specialized robots:
151
+ Robots support a chaining API for runtime adjustments:
126
152
 
127
153
  ```ruby
128
- # Classifier robot
129
- classifier = RobotLab.build do
130
- name "classifier"
131
- description "Classifies incoming requests"
132
- template <<~PROMPT
133
- Classify the user's request into one category:
134
- - QUESTION: General knowledge questions
135
- - MATH: Mathematical calculations
136
- - OTHER: Everything else
137
-
138
- Respond with only the category name.
154
+ robot = RobotLab.build(name: "writer")
155
+
156
+ result = robot
157
+ .with_instructions("You are a creative fiction writer.")
158
+ .with_temperature(0.9)
159
+ .with_model("claude-sonnet-4")
160
+ .run("Write a haiku about programming.")
161
+
162
+ puts result.last_text_content
163
+ ```
164
+
165
+ Available chaining methods include `with_instructions`, `with_temperature`, `with_model`, `with_max_tokens`, `with_top_p`, `with_tools`, and more.
166
+
167
+ ## Multi-Robot Network
168
+
169
+ Create a pipeline of robots using `RobotLab.create_network`. Networks use `SimpleFlow::Pipeline` under the hood with `task` definitions and dependency tracking:
170
+
171
+ ```ruby title="hello_network.rb"
172
+ require "robot_lab"
173
+
174
+ # Build specialized robots
175
+ analyst = RobotLab.build(
176
+ name: "analyst",
177
+ system_prompt: <<~PROMPT
178
+ You are a text analyst. Analyze the given text and provide a brief
179
+ summary of its key themes and sentiment. Be concise -- 2-3 sentences max.
139
180
  PROMPT
140
- end
181
+ )
182
+
183
+ writer = RobotLab.build(
184
+ name: "writer",
185
+ system_prompt: <<~PROMPT
186
+ You are a professional copywriter. Based on the analysis you receive,
187
+ write a short, engaging summary suitable for a newsletter. Keep it
188
+ to one paragraph.
189
+ PROMPT
190
+ )
141
191
 
142
- # Question answerer
143
- answerer = RobotLab.build do
144
- name "answerer"
145
- description "Answers general questions"
146
- template "You answer general knowledge questions accurately."
192
+ # Create a sequential pipeline
193
+ network = RobotLab.create_network(name: "content_pipeline") do
194
+ task :analyst, analyst, depends_on: :none
195
+ task :writer, writer, depends_on: [:analyst]
147
196
  end
148
197
 
149
- # Calculator
150
- calculator = RobotLab.build do
151
- name "calculator"
152
- description "Handles math problems"
153
- template "You solve mathematical problems step by step."
198
+ # Run the network
199
+ result = network.run(
200
+ message: "Ruby 3.4 was released with significant performance improvements..."
201
+ )
202
+
203
+ # The final result is from the last robot in the pipeline
204
+ if result.value.is_a?(RobotLab::RobotResult)
205
+ puts result.value.last_text_content
154
206
  end
155
207
 
156
- # Network with routing
157
- network = RobotLab.create_network do
158
- name "smart_assistant"
159
- add_robot classifier
160
- add_robot answerer
161
- add_robot calculator
162
-
163
- router ->(args) {
164
- case args.call_count
165
- when 0
166
- :classifier
167
- when 1
168
- result = args.last_result&.output&.first&.content&.strip
169
- case result
170
- when "QUESTION" then :answerer
171
- when "MATH" then :calculator
172
- else :answerer
173
- end
174
- else
175
- nil
176
- end
177
- }
208
+ # Access intermediate results by task name
209
+ if result.context[:analyst]
210
+ puts "\nAnalysis: #{result.context[:analyst].last_text_content}"
178
211
  end
179
212
  ```
180
213
 
214
+ ### Network Task Dependencies
215
+
216
+ Tasks declare their dependencies to control execution order:
217
+
218
+ | Dependency | Meaning |
219
+ |-----------|---------|
220
+ | `depends_on: :none` | Entry point -- runs first with no dependencies |
221
+ | `depends_on: [:task_name]` | Runs after the named task(s) complete |
222
+ | `depends_on: :optional` | Only runs if explicitly activated by a preceding task |
223
+
224
+ Tasks with non-overlapping dependencies can execute in parallel automatically.
225
+
181
226
  ## What's Next?
182
227
 
183
- You've built your first RobotLab application! Here's where to go next:
228
+ You have built your first RobotLab application. Here is where to go next:
184
229
 
185
230
  <div class="grid cards" markdown>
186
231