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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/README.md +64 -6
  4. data/Rakefile +2 -1
  5. data/docs/api/core/index.md +41 -46
  6. data/docs/api/core/memory.md +200 -154
  7. data/docs/api/core/network.md +13 -3
  8. data/docs/api/core/robot.md +38 -26
  9. data/docs/api/core/state.md +55 -73
  10. data/docs/api/index.md +7 -28
  11. data/docs/api/messages/index.md +35 -20
  12. data/docs/api/messages/text-message.md +67 -21
  13. data/docs/api/messages/tool-call-message.md +80 -41
  14. data/docs/api/messages/tool-result-message.md +119 -50
  15. data/docs/api/messages/user-message.md +48 -24
  16. data/docs/architecture/core-concepts.md +10 -15
  17. data/docs/concepts.md +5 -7
  18. data/docs/examples/index.md +2 -2
  19. data/docs/getting-started/configuration.md +80 -0
  20. data/docs/guides/building-robots.md +10 -9
  21. data/docs/guides/creating-networks.md +49 -0
  22. data/docs/guides/index.md +0 -5
  23. data/docs/guides/rails-integration.md +244 -162
  24. data/docs/guides/streaming.md +118 -138
  25. data/docs/index.md +0 -8
  26. data/examples/03_network.rb +10 -7
  27. data/examples/08_llm_config.rb +40 -11
  28. data/examples/09_chaining.rb +45 -6
  29. data/examples/11_network_introspection.rb +30 -7
  30. data/examples/12_message_bus.rb +1 -1
  31. data/examples/14_rusty_circuit/heckler.rb +14 -8
  32. data/examples/14_rusty_circuit/open_mic.rb +5 -3
  33. data/examples/14_rusty_circuit/scout.rb +14 -31
  34. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
  35. data/examples/16_writers_room/display.rb +158 -0
  36. data/examples/16_writers_room/output/.gitignore +4 -0
  37. data/examples/16_writers_room/output/README.md +69 -0
  38. data/examples/16_writers_room/output/opus_001.md +263 -0
  39. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  40. data/examples/16_writers_room/output/opus_002.md +245 -0
  41. data/examples/16_writers_room/output/opus_002_notes.log +546 -0
  42. data/examples/16_writers_room/output/opus_002_screenplay.md +7989 -0
  43. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +993 -0
  44. data/examples/16_writers_room/prompts/screenplay_writer.md +66 -0
  45. data/examples/16_writers_room/prompts/writer.md +37 -0
  46. data/examples/16_writers_room/room.rb +186 -0
  47. data/examples/16_writers_room/tools.rb +173 -0
  48. data/examples/16_writers_room/writer.rb +121 -0
  49. data/examples/16_writers_room/writers_room.rb +256 -0
  50. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  51. data/lib/robot_lab/memory.rb +8 -32
  52. data/lib/robot_lab/network.rb +13 -20
  53. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  54. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  55. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  56. data/lib/robot_lab/robot.rb +56 -420
  57. data/lib/robot_lab/run_config.rb +184 -0
  58. data/lib/robot_lab/state_proxy.rb +2 -12
  59. data/lib/robot_lab/task.rb +8 -1
  60. data/lib/robot_lab/utils.rb +39 -0
  61. data/lib/robot_lab/version.rb +1 -1
  62. data/lib/robot_lab.rb +29 -8
  63. data/mkdocs.yml +0 -11
  64. metadata +21 -20
  65. data/docs/api/adapters/anthropic.md +0 -121
  66. data/docs/api/adapters/gemini.md +0 -133
  67. data/docs/api/adapters/index.md +0 -104
  68. data/docs/api/adapters/openai.md +0 -134
  69. data/docs/api/history/active-record-adapter.md +0 -275
  70. data/docs/api/history/config.md +0 -284
  71. data/docs/api/history/index.md +0 -128
  72. data/docs/api/history/thread-manager.md +0 -194
  73. data/docs/guides/history.md +0 -359
  74. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  75. data/lib/robot_lab/adapters/base.rb +0 -85
  76. data/lib/robot_lab/adapters/gemini.rb +0 -193
  77. data/lib/robot_lab/adapters/openai.rb +0 -160
  78. data/lib/robot_lab/adapters/registry.rb +0 -81
  79. data/lib/robot_lab/errors.rb +0 -70
  80. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  81. data/lib/robot_lab/history/config.rb +0 -115
  82. data/lib/robot_lab/history/thread_manager.rb +0 -93
  83. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -1,24 +1,26 @@
1
1
  # TextMessage
2
2
 
3
- Assistant text response.
3
+ Text message from system, user, or assistant.
4
4
 
5
5
  ## Class: `RobotLab::TextMessage`
6
6
 
7
7
  ```ruby
8
- message = TextMessage.new("Hello! How can I help you today?")
8
+ message = TextMessage.new(role: "assistant", content: "Hello! How can I help you today?")
9
9
  ```
10
10
 
11
11
  ## Constructor
12
12
 
13
13
  ```ruby
14
- TextMessage.new(content)
14
+ TextMessage.new(role:, content:, stop_reason: nil)
15
15
  ```
16
16
 
17
17
  **Parameters:**
18
18
 
19
19
  | Name | Type | Description |
20
20
  |------|------|-------------|
21
- | `content` | `String` | Response text |
21
+ | `role` | `String` | Message role ("system", "user", or "assistant") |
22
+ | `content` | `String` | The text content |
23
+ | `stop_reason` | `String`, `nil` | Stop reason ("stop" or "tool") |
22
24
 
23
25
  ## Attributes
24
26
 
@@ -28,15 +30,31 @@ TextMessage.new(content)
28
30
  message.content # => String
29
31
  ```
30
32
 
31
- The response text.
33
+ The text content.
32
34
 
33
35
  ### role
34
36
 
35
37
  ```ruby
36
- message.role # => :assistant
38
+ message.role # => "assistant"
37
39
  ```
38
40
 
39
- Always returns `:assistant`.
41
+ Returns a String: `"system"`, `"user"`, or `"assistant"`.
42
+
43
+ ### type
44
+
45
+ ```ruby
46
+ message.type # => "text"
47
+ ```
48
+
49
+ Always returns `"text"`.
50
+
51
+ ### stop_reason
52
+
53
+ ```ruby
54
+ message.stop_reason # => "stop" or nil
55
+ ```
56
+
57
+ The stop reason, if any.
40
58
 
41
59
  ## Methods
42
60
 
@@ -52,8 +70,10 @@ Hash representation.
52
70
 
53
71
  ```ruby
54
72
  {
55
- role: :assistant,
56
- content: "Hello! How can I help you today?"
73
+ type: "text",
74
+ role: "assistant",
75
+ content: "Hello! How can I help you today?",
76
+ stop_reason: "stop"
57
77
  }
58
78
  ```
59
79
 
@@ -65,34 +85,60 @@ message.to_json # => String
65
85
 
66
86
  JSON representation.
67
87
 
88
+ ### Predicates
89
+
90
+ ```ruby
91
+ message.text? # => true
92
+ message.tool_call? # => false
93
+ message.assistant? # => true (if role is "assistant")
94
+ message.user? # => false
95
+ message.stopped? # => true (if stop_reason is "stop")
96
+ ```
97
+
68
98
  ## Examples
69
99
 
70
- ### Basic Response
100
+ ### System Message
101
+
102
+ ```ruby
103
+ message = TextMessage.new(role: "system", content: "You are a helpful assistant")
104
+ message.system? # => true
105
+ ```
106
+
107
+ ### User Message
108
+
109
+ ```ruby
110
+ message = TextMessage.new(role: "user", content: "What's the weather?")
111
+ message.user? # => true
112
+ ```
113
+
114
+ ### Assistant Response
71
115
 
72
116
  ```ruby
73
- message = TextMessage.new("Your order has shipped!")
117
+ message = TextMessage.new(
118
+ role: "assistant",
119
+ content: "Your order has shipped!",
120
+ stop_reason: "stop"
121
+ )
122
+ message.assistant? # => true
123
+ message.stopped? # => true
74
124
  ```
75
125
 
76
126
  ### In Robot Results
77
127
 
78
128
  ```ruby
79
- result = robot.run(state: state)
129
+ result = robot.run("Tell me a joke")
80
130
 
81
- # Extract text messages
82
- result.output.each do |msg|
83
- if msg.is_a?(TextMessage)
84
- puts msg.content
85
- end
131
+ # The result is a TextMessage when the assistant replies with text
132
+ if result.text?
133
+ puts result.content
86
134
  end
87
135
  ```
88
136
 
89
137
  ### Filtering Text Content
90
138
 
91
139
  ```ruby
92
- # Get only text responses from results
93
- text_responses = state.results.flat_map(&:output).select do |msg|
94
- msg.is_a?(TextMessage)
95
- end.map(&:content)
140
+ # Get only text messages from memory
141
+ text_messages = memory.messages.select(&:text?).map(&:content)
96
142
  ```
97
143
 
98
144
  ## See Also
@@ -6,20 +6,35 @@ Tool invocation request from the LLM.
6
6
 
7
7
  ```ruby
8
8
  message = ToolCallMessage.new(
9
- id: "call_abc123",
10
- name: "get_weather",
11
- input: { city: "New York", units: "fahrenheit" }
9
+ role: "assistant",
10
+ tools: [
11
+ ToolMessage.new(id: "call_abc123", name: "get_weather", input: { city: "New York" })
12
+ ]
12
13
  )
13
14
  ```
14
15
 
15
16
  ## Constructor
16
17
 
17
18
  ```ruby
18
- ToolCallMessage.new(id:, name:, input:)
19
+ ToolCallMessage.new(role:, tools:, stop_reason: nil)
19
20
  ```
20
21
 
21
22
  **Parameters:**
22
23
 
24
+ | Name | Type | Description |
25
+ |------|------|-------------|
26
+ | `role` | `String` | Message role (typically "assistant") |
27
+ | `tools` | `Array<ToolMessage>` | Array of tool call objects |
28
+ | `stop_reason` | `String`, `nil` | Stop reason (defaults to "tool") |
29
+
30
+ ## ToolMessage
31
+
32
+ Each tool call is represented by a standalone `ToolMessage` object:
33
+
34
+ ```ruby
35
+ ToolMessage.new(id:, name:, input:)
36
+ ```
37
+
23
38
  | Name | Type | Description |
24
39
  |------|------|-------------|
25
40
  | `id` | `String` | Unique call identifier |
@@ -28,37 +43,45 @@ ToolCallMessage.new(id:, name:, input:)
28
43
 
29
44
  ## Attributes
30
45
 
31
- ### id
46
+ ### tools
32
47
 
33
48
  ```ruby
34
- message.id # => String
49
+ message.tools # => Array<ToolMessage>
35
50
  ```
36
51
 
37
- Unique identifier for this tool call. Used to match with `ToolResultMessage`.
52
+ Array of `ToolMessage` objects representing the tool calls.
38
53
 
39
- ### name
54
+ ### role
40
55
 
41
56
  ```ruby
42
- message.name # => String
57
+ message.role # => "assistant"
43
58
  ```
44
59
 
45
- Name of the tool being invoked.
60
+ Returns a String. The LLM initiates tool calls, so this is typically `"assistant"`.
46
61
 
47
- ### input
62
+ ### type
48
63
 
49
64
  ```ruby
50
- message.input # => Hash
65
+ message.type # => "tool_call"
51
66
  ```
52
67
 
53
- Parameters passed to the tool.
68
+ Always returns `"tool_call"`.
54
69
 
55
- ### role
70
+ ### content
71
+
72
+ ```ruby
73
+ message.content # => nil
74
+ ```
75
+
76
+ Always `nil` for tool call messages (the tool data is in `tools`).
77
+
78
+ ### stop_reason
56
79
 
57
80
  ```ruby
58
- message.role # => :assistant
81
+ message.stop_reason # => "tool"
59
82
  ```
60
83
 
61
- Always returns `:assistant` (the LLM initiates tool calls).
84
+ Defaults to `"tool"` indicating the conversation stopped for tool execution.
62
85
 
63
86
  ## Methods
64
87
 
@@ -74,12 +97,12 @@ Hash representation.
74
97
 
75
98
  ```ruby
76
99
  {
77
- role: :assistant,
78
- tool_call: {
79
- id: "call_abc123",
80
- name: "get_weather",
81
- input: { city: "New York", units: "fahrenheit" }
82
- }
100
+ type: "tool_call",
101
+ role: "assistant",
102
+ tools: [
103
+ { type: "tool", id: "call_abc123", name: "get_weather", input: { city: "New York" } }
104
+ ],
105
+ stop_reason: "tool"
83
106
  }
84
107
  ```
85
108
 
@@ -91,28 +114,48 @@ message.to_json # => String
91
114
 
92
115
  JSON representation.
93
116
 
117
+ ### Predicates
118
+
119
+ ```ruby
120
+ message.tool_call? # => true
121
+ message.text? # => false
122
+ message.assistant? # => true
123
+ message.tool_stop? # => true
124
+ ```
125
+
94
126
  ## Examples
95
127
 
96
- ### Basic Tool Call
128
+ ### Single Tool Call
97
129
 
98
130
  ```ruby
99
- call = ToolCallMessage.new(
131
+ tool = ToolMessage.new(
100
132
  id: "call_1",
101
133
  name: "search_orders",
102
134
  input: { user_id: "123", status: "pending" }
103
135
  )
136
+
137
+ call = ToolCallMessage.new(role: "assistant", tools: [tool])
138
+ ```
139
+
140
+ ### Multiple Tool Calls
141
+
142
+ ```ruby
143
+ tools = [
144
+ ToolMessage.new(id: "call_1", name: "get_weather", input: { city: "NYC" }),
145
+ ToolMessage.new(id: "call_2", name: "get_time", input: { timezone: "EST" })
146
+ ]
147
+
148
+ call = ToolCallMessage.new(role: "assistant", tools: tools)
149
+ call.tools.length # => 2
104
150
  ```
105
151
 
106
152
  ### Processing Tool Calls
107
153
 
108
154
  ```ruby
109
- result.output.each do |msg|
110
- case msg
111
- when ToolCallMessage
112
- puts "Tool called: #{msg.name}"
113
- puts "Parameters: #{msg.input.inspect}"
114
- when ToolResultMessage
115
- puts "Result for #{msg.id}: #{msg.result}"
155
+ if message.tool_call?
156
+ message.tools.each do |tool|
157
+ puts "Tool called: #{tool.name}"
158
+ puts "Parameters: #{tool.input.inspect}"
116
159
  end
117
160
  end
118
161
  ```
@@ -121,19 +164,15 @@ end
121
164
 
122
165
  ```ruby
123
166
  # LLM returns a tool call
124
- tool_call = ToolCallMessage.new(
125
- id: "call_weather_1",
126
- name: "get_weather",
127
- input: { city: "Seattle" }
128
- )
167
+ tool = ToolMessage.new(id: "call_weather_1", name: "get_weather", input: { city: "Seattle" })
168
+ tool_call = ToolCallMessage.new(role: "assistant", tools: [tool])
129
169
 
130
- # Tool is executed
131
- result = tool.call(tool_call.input, state: state)
170
+ # Execute the tool and record the result
171
+ result_data = execute_tool(tool.name, tool.input)
132
172
 
133
- # Result is recorded
134
173
  tool_result = ToolResultMessage.new(
135
- id: tool_call.id,
136
- result: result
174
+ tool: tool,
175
+ content: { data: result_data }
137
176
  )
138
177
  ```
139
178
 
@@ -5,53 +5,104 @@ Result from tool execution.
5
5
  ## Class: `RobotLab::ToolResultMessage`
6
6
 
7
7
  ```ruby
8
+ tool = ToolMessage.new(id: "call_abc123", name: "get_weather", input: { city: "NYC" })
9
+
8
10
  message = ToolResultMessage.new(
9
- id: "call_abc123",
10
- result: { temperature: 72, conditions: "sunny" }
11
+ tool: tool,
12
+ content: { data: { temperature: 72, conditions: "sunny" } }
11
13
  )
12
14
  ```
13
15
 
14
16
  ## Constructor
15
17
 
16
18
  ```ruby
17
- ToolResultMessage.new(id:, result:)
19
+ ToolResultMessage.new(tool:, content:, stop_reason: nil)
18
20
  ```
19
21
 
20
22
  **Parameters:**
21
23
 
22
24
  | Name | Type | Description |
23
25
  |------|------|-------------|
24
- | `id` | `String` | Matching tool call ID |
25
- | `result` | `Object` | Tool execution result |
26
+ | `tool` | `ToolMessage` | The tool call that was executed |
27
+ | `content` | `Hash` | Result with `:data` key (success) or `:error` key (failure) |
28
+ | `stop_reason` | `String`, `nil` | Stop reason (defaults to "tool") |
26
29
 
27
30
  ## Attributes
28
31
 
29
- ### id
32
+ ### tool
30
33
 
31
34
  ```ruby
32
- message.id # => String
35
+ message.tool # => ToolMessage
33
36
  ```
34
37
 
35
- Identifier matching the corresponding `ToolCallMessage`.
38
+ The `ToolMessage` representing the tool call that produced this result. Provides access to `tool.id`, `tool.name`, and `tool.input`.
36
39
 
37
- ### result
40
+ ### content
38
41
 
39
42
  ```ruby
40
- message.result # => Object
43
+ message.content # => Hash
41
44
  ```
42
45
 
43
- The result returned by the tool. Can be any serializable object.
46
+ The result content. Contains either a `:data` key (success) or an `:error` key (failure).
44
47
 
45
48
  ### role
46
49
 
47
50
  ```ruby
48
- message.role # => :tool
51
+ message.role # => "tool_result"
52
+ ```
53
+
54
+ Always returns `"tool_result"`.
55
+
56
+ ### type
57
+
58
+ ```ruby
59
+ message.type # => "tool_result"
49
60
  ```
50
61
 
51
- Always returns `:tool`.
62
+ Always returns `"tool_result"`.
63
+
64
+ ### stop_reason
65
+
66
+ ```ruby
67
+ message.stop_reason # => "tool"
68
+ ```
69
+
70
+ Defaults to `"tool"`.
52
71
 
53
72
  ## Methods
54
73
 
74
+ ### success?
75
+
76
+ ```ruby
77
+ message.success? # => Boolean
78
+ ```
79
+
80
+ Returns `true` if the content contains a `:data` key.
81
+
82
+ ### error?
83
+
84
+ ```ruby
85
+ message.error? # => Boolean
86
+ ```
87
+
88
+ Returns `true` if the content contains an `:error` key.
89
+
90
+ ### data
91
+
92
+ ```ruby
93
+ message.data # => Object | nil
94
+ ```
95
+
96
+ Returns the result data if successful, `nil` otherwise.
97
+
98
+ ### error
99
+
100
+ ```ruby
101
+ message.error # => String | nil
102
+ ```
103
+
104
+ Returns the error message if there was an error, `nil` otherwise.
105
+
55
106
  ### to_h
56
107
 
57
108
  ```ruby
@@ -64,11 +115,11 @@ Hash representation.
64
115
 
65
116
  ```ruby
66
117
  {
67
- role: :tool,
68
- tool_result: {
69
- id: "call_abc123",
70
- result: { temperature: 72, conditions: "sunny" }
71
- }
118
+ type: "tool_result",
119
+ role: "tool_result",
120
+ tool: { type: "tool", id: "call_abc123", name: "get_weather", input: { city: "NYC" } },
121
+ content: { data: { temperature: 72, conditions: "sunny" } },
122
+ stop_reason: "tool"
72
123
  }
73
124
  ```
74
125
 
@@ -80,70 +131,88 @@ message.to_json # => String
80
131
 
81
132
  JSON representation.
82
133
 
83
- ## Examples
84
-
85
- ### Basic Result
134
+ ### Predicates
86
135
 
87
136
  ```ruby
88
- result = ToolResultMessage.new(
89
- id: "call_1",
90
- result: { success: true, order_id: "ord_123" }
91
- )
137
+ message.tool_result? # => true
138
+ message.tool_call? # => false
139
+ message.text? # => false
140
+ message.tool_stop? # => true
92
141
  ```
93
142
 
94
- ### String Result
143
+ ## Examples
144
+
145
+ ### Successful Result
95
146
 
96
147
  ```ruby
148
+ tool = ToolMessage.new(id: "call_1", name: "search_orders", input: { user_id: "123" })
149
+
97
150
  result = ToolResultMessage.new(
98
- id: "call_time",
99
- result: "2024-01-15T10:30:00Z"
151
+ tool: tool,
152
+ content: { data: { order_id: "ord_123", status: "shipped" } }
100
153
  )
154
+
155
+ result.success? # => true
156
+ result.data # => { order_id: "ord_123", status: "shipped" }
101
157
  ```
102
158
 
103
- ### Array Result
159
+ ### Error Result
104
160
 
105
161
  ```ruby
162
+ tool = ToolMessage.new(id: "call_order", name: "get_order", input: { id: "bad" })
163
+
106
164
  result = ToolResultMessage.new(
107
- id: "call_search",
108
- result: [
109
- { id: 1, name: "Product A" },
110
- { id: 2, name: "Product B" }
111
- ]
165
+ tool: tool,
166
+ content: { error: "Order not found" }
112
167
  )
168
+
169
+ result.error? # => true
170
+ result.error # => "Order not found"
171
+ result.data # => nil
113
172
  ```
114
173
 
115
- ### Error Result
174
+ ### Accessing Tool Information
116
175
 
117
176
  ```ruby
118
177
  result = ToolResultMessage.new(
119
- id: "call_order",
120
- result: { success: false, error: "Order not found" }
178
+ tool: ToolMessage.new(id: "call_1", name: "get_weather", input: { city: "Berlin" }),
179
+ content: { data: { temperature: 15, unit: "celsius" } }
121
180
  )
181
+
182
+ result.tool.name # => "get_weather"
183
+ result.tool.id # => "call_1"
184
+ result.tool.input # => { city: "Berlin" }
185
+ result.data # => { temperature: 15, unit: "celsius" }
122
186
  ```
123
187
 
124
- ### Matching with Tool Calls
188
+ ### Matching Tool Calls with Results
125
189
 
126
190
  ```ruby
127
- # Process all tool interactions
128
- result.output.each_cons(2) do |a, b|
129
- if a.is_a?(ToolCallMessage) && b.is_a?(ToolResultMessage)
130
- if a.id == b.id
131
- puts "#{a.name}(#{a.input}) => #{b.result}"
132
- end
191
+ # Given a ToolCallMessage and its results
192
+ tool_call_msg.tools.each do |tool|
193
+ # Find the matching result
194
+ matching_result = results.find { |r| r.tool.id == tool.id }
195
+
196
+ if matching_result&.success?
197
+ puts "#{tool.name}(#{tool.input}) => #{matching_result.data}"
198
+ elsif matching_result&.error?
199
+ puts "#{tool.name} failed: #{matching_result.error}"
133
200
  end
134
201
  end
135
202
  ```
136
203
 
137
- ### In Result History
204
+ ### In Memory History
138
205
 
139
206
  ```ruby
140
- # Find all tool results from execution
141
- tool_results = state.results
142
- .flat_map(&:output)
143
- .select { |m| m.is_a?(ToolResultMessage) }
207
+ # Find all tool results from memory
208
+ tool_results = memory.messages.select(&:tool_result?)
144
209
 
145
210
  tool_results.each do |tr|
146
- puts "Tool result: #{tr.result}"
211
+ if tr.success?
212
+ puts "#{tr.tool.name}: #{tr.data}"
213
+ else
214
+ puts "#{tr.tool.name} error: #{tr.error}"
215
+ end
147
216
  end
148
217
  ```
149
218