robot_lab 0.0.1
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 +7 -0
- data/.envrc +1 -0
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.github/workflows/deploy-yard-docs.yml +52 -0
- data/CHANGELOG.md +55 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +332 -0
- data/Rakefile +67 -0
- data/docs/api/adapters/anthropic.md +121 -0
- data/docs/api/adapters/gemini.md +133 -0
- data/docs/api/adapters/index.md +104 -0
- data/docs/api/adapters/openai.md +134 -0
- data/docs/api/core/index.md +113 -0
- data/docs/api/core/memory.md +314 -0
- data/docs/api/core/network.md +291 -0
- data/docs/api/core/robot.md +273 -0
- data/docs/api/core/state.md +273 -0
- data/docs/api/core/tool.md +353 -0
- data/docs/api/history/active-record-adapter.md +195 -0
- data/docs/api/history/config.md +191 -0
- data/docs/api/history/index.md +132 -0
- data/docs/api/history/thread-manager.md +144 -0
- data/docs/api/index.md +82 -0
- data/docs/api/mcp/client.md +221 -0
- data/docs/api/mcp/index.md +111 -0
- data/docs/api/mcp/server.md +225 -0
- data/docs/api/mcp/transports.md +264 -0
- data/docs/api/messages/index.md +67 -0
- data/docs/api/messages/text-message.md +102 -0
- data/docs/api/messages/tool-call-message.md +144 -0
- data/docs/api/messages/tool-result-message.md +154 -0
- data/docs/api/messages/user-message.md +171 -0
- data/docs/api/streaming/context.md +174 -0
- data/docs/api/streaming/events.md +237 -0
- data/docs/api/streaming/index.md +108 -0
- data/docs/architecture/core-concepts.md +243 -0
- data/docs/architecture/index.md +138 -0
- data/docs/architecture/message-flow.md +320 -0
- data/docs/architecture/network-orchestration.md +216 -0
- data/docs/architecture/robot-execution.md +243 -0
- data/docs/architecture/state-management.md +323 -0
- data/docs/assets/css/custom.css +56 -0
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +216 -0
- data/docs/examples/basic-chat.md +193 -0
- data/docs/examples/index.md +129 -0
- data/docs/examples/mcp-server.md +290 -0
- data/docs/examples/multi-robot-network.md +312 -0
- data/docs/examples/rails-application.md +420 -0
- data/docs/examples/tool-usage.md +310 -0
- data/docs/getting-started/configuration.md +230 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +179 -0
- data/docs/getting-started/quick-start.md +203 -0
- data/docs/guides/building-robots.md +376 -0
- data/docs/guides/creating-networks.md +366 -0
- data/docs/guides/history.md +359 -0
- data/docs/guides/index.md +68 -0
- data/docs/guides/mcp-integration.md +356 -0
- data/docs/guides/memory.md +309 -0
- data/docs/guides/rails-integration.md +432 -0
- data/docs/guides/streaming.md +314 -0
- data/docs/guides/using-tools.md +394 -0
- data/docs/index.md +160 -0
- data/examples/01_simple_robot.rb +38 -0
- data/examples/02_tools.rb +106 -0
- data/examples/03_network.rb +103 -0
- data/examples/04_mcp.rb +219 -0
- data/examples/05_streaming.rb +124 -0
- data/examples/06_prompt_templates.rb +324 -0
- data/examples/07_network_memory.rb +329 -0
- data/examples/prompts/assistant/system.txt.erb +2 -0
- data/examples/prompts/assistant/user.txt.erb +1 -0
- data/examples/prompts/billing/system.txt.erb +7 -0
- data/examples/prompts/billing/user.txt.erb +1 -0
- data/examples/prompts/classifier/system.txt.erb +4 -0
- data/examples/prompts/classifier/user.txt.erb +1 -0
- data/examples/prompts/entity_extractor/system.txt.erb +11 -0
- data/examples/prompts/entity_extractor/user.txt.erb +3 -0
- data/examples/prompts/escalation/system.txt.erb +35 -0
- data/examples/prompts/escalation/user.txt.erb +34 -0
- data/examples/prompts/general/system.txt.erb +4 -0
- data/examples/prompts/general/user.txt.erb +1 -0
- data/examples/prompts/github_assistant/system.txt.erb +6 -0
- data/examples/prompts/github_assistant/user.txt.erb +1 -0
- data/examples/prompts/helper/system.txt.erb +1 -0
- data/examples/prompts/helper/user.txt.erb +1 -0
- data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
- data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
- data/examples/prompts/order_support/system.txt.erb +27 -0
- data/examples/prompts/order_support/user.txt.erb +22 -0
- data/examples/prompts/product_support/system.txt.erb +30 -0
- data/examples/prompts/product_support/user.txt.erb +32 -0
- data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
- data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
- data/examples/prompts/synthesizer/system.txt.erb +14 -0
- data/examples/prompts/synthesizer/user.txt.erb +15 -0
- data/examples/prompts/technical/system.txt.erb +7 -0
- data/examples/prompts/technical/user.txt.erb +1 -0
- data/examples/prompts/triage/system.txt.erb +16 -0
- data/examples/prompts/triage/user.txt.erb +17 -0
- data/lib/generators/robot_lab/install_generator.rb +78 -0
- data/lib/generators/robot_lab/robot_generator.rb +55 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
- data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
- data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
- data/lib/robot_lab/adapters/anthropic.rb +163 -0
- data/lib/robot_lab/adapters/base.rb +85 -0
- data/lib/robot_lab/adapters/gemini.rb +193 -0
- data/lib/robot_lab/adapters/openai.rb +159 -0
- data/lib/robot_lab/adapters/registry.rb +81 -0
- data/lib/robot_lab/configuration.rb +143 -0
- data/lib/robot_lab/error.rb +32 -0
- data/lib/robot_lab/errors.rb +70 -0
- data/lib/robot_lab/history/active_record_adapter.rb +146 -0
- data/lib/robot_lab/history/config.rb +115 -0
- data/lib/robot_lab/history/thread_manager.rb +93 -0
- data/lib/robot_lab/mcp/client.rb +210 -0
- data/lib/robot_lab/mcp/server.rb +84 -0
- data/lib/robot_lab/mcp/transports/base.rb +56 -0
- data/lib/robot_lab/mcp/transports/sse.rb +117 -0
- data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
- data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
- data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
- data/lib/robot_lab/memory.rb +882 -0
- data/lib/robot_lab/memory_change.rb +123 -0
- data/lib/robot_lab/message.rb +357 -0
- data/lib/robot_lab/network.rb +350 -0
- data/lib/robot_lab/rails/engine.rb +29 -0
- data/lib/robot_lab/rails/railtie.rb +42 -0
- data/lib/robot_lab/robot.rb +560 -0
- data/lib/robot_lab/robot_result.rb +205 -0
- data/lib/robot_lab/robotic_model.rb +324 -0
- data/lib/robot_lab/state_proxy.rb +188 -0
- data/lib/robot_lab/streaming/context.rb +144 -0
- data/lib/robot_lab/streaming/events.rb +95 -0
- data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
- data/lib/robot_lab/task.rb +117 -0
- data/lib/robot_lab/tool.rb +223 -0
- data/lib/robot_lab/tool_config.rb +112 -0
- data/lib/robot_lab/tool_manifest.rb +234 -0
- data/lib/robot_lab/user_message.rb +118 -0
- data/lib/robot_lab/version.rb +5 -0
- data/lib/robot_lab/waiter.rb +73 -0
- data/lib/robot_lab.rb +195 -0
- data/mkdocs.yml +214 -0
- data/sig/robot_lab.rbs +4 -0
- metadata +442 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Core Concepts
|
|
2
|
+
|
|
3
|
+
This page provides an in-depth look at RobotLab's fundamental building blocks.
|
|
4
|
+
|
|
5
|
+
## Robot
|
|
6
|
+
|
|
7
|
+
A Robot is the primary unit of computation in RobotLab. It wraps an LLM with:
|
|
8
|
+
|
|
9
|
+
- A unique identity (name, description)
|
|
10
|
+
- A personality (system prompt/template)
|
|
11
|
+
- Capabilities (tools, MCP connections)
|
|
12
|
+
- A specific model configuration
|
|
13
|
+
|
|
14
|
+
### Robot Anatomy
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
robot = RobotLab.build do
|
|
18
|
+
name "support_agent" # Unique identifier
|
|
19
|
+
description "Handles support requests" # Used for routing hints
|
|
20
|
+
model "claude-sonnet-4" # LLM model
|
|
21
|
+
|
|
22
|
+
# System prompt - defines personality
|
|
23
|
+
template <<~PROMPT
|
|
24
|
+
You are a friendly customer support agent for Acme Corp.
|
|
25
|
+
Always be polite and helpful. If you don't know something,
|
|
26
|
+
say so honestly.
|
|
27
|
+
PROMPT
|
|
28
|
+
|
|
29
|
+
# Tools - extend capabilities
|
|
30
|
+
tool :lookup_account do
|
|
31
|
+
description "Look up customer account"
|
|
32
|
+
parameter :email, type: :string, required: true
|
|
33
|
+
handler { |email:, **_| Account.find_by_email(email)&.to_h }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# MCP - external tool servers
|
|
37
|
+
mcp :inherit # Use network's MCP servers
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Robot Lifecycle
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
stateDiagram-v2
|
|
45
|
+
[*] --> Created: RobotLab.build
|
|
46
|
+
Created --> Running: robot.run(state)
|
|
47
|
+
Running --> ToolExecution: tool_call
|
|
48
|
+
ToolExecution --> Running: result
|
|
49
|
+
Running --> Completed: stop_reason
|
|
50
|
+
Completed --> [*]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Robot Properties
|
|
54
|
+
|
|
55
|
+
| Property | Type | Description |
|
|
56
|
+
|----------|------|-------------|
|
|
57
|
+
| `name` | String | Unique identifier within network |
|
|
58
|
+
| `description` | String | What the robot does |
|
|
59
|
+
| `model` | String | LLM model to use |
|
|
60
|
+
| `template` | String | System prompt |
|
|
61
|
+
| `local_tools` | Array | Defined tools |
|
|
62
|
+
| `mcp_clients` | Array | Connected MCP clients |
|
|
63
|
+
|
|
64
|
+
## Tool
|
|
65
|
+
|
|
66
|
+
Tools give robots the ability to interact with external systems.
|
|
67
|
+
|
|
68
|
+
### Tool Structure
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
tool = RobotLab::Tool.new(
|
|
72
|
+
name: "get_weather",
|
|
73
|
+
description: "Get current weather for a location",
|
|
74
|
+
parameters: {
|
|
75
|
+
location: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: "City name",
|
|
78
|
+
required: true
|
|
79
|
+
},
|
|
80
|
+
unit: {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: ["celsius", "fahrenheit"],
|
|
83
|
+
default: "celsius"
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
handler: ->(location:, unit: "celsius", **_context) {
|
|
87
|
+
WeatherAPI.current(location, unit: unit)
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Tool Execution
|
|
93
|
+
|
|
94
|
+
When an LLM decides to use a tool:
|
|
95
|
+
|
|
96
|
+
1. LLM generates a `ToolCallMessage` with tool name and arguments
|
|
97
|
+
2. RobotLab validates arguments against the tool's schema
|
|
98
|
+
3. Tool handler is called with validated arguments
|
|
99
|
+
4. Result is wrapped in a `ToolResultMessage`
|
|
100
|
+
5. Result is sent back to the LLM for continued processing
|
|
101
|
+
|
|
102
|
+
### Handler Context
|
|
103
|
+
|
|
104
|
+
Tool handlers receive context about the current execution:
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
handler: ->(param:, robot:, network:, state:) {
|
|
108
|
+
# robot - The Robot instance executing the tool
|
|
109
|
+
# network - The Network (or NetworkRun) context
|
|
110
|
+
# state - Current State with data, results, memory
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
!!! tip "Ignoring Context"
|
|
115
|
+
Use `**_context` to accept but ignore context parameters:
|
|
116
|
+
```ruby
|
|
117
|
+
handler: ->(location:, **_context) { ... }
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## ToolManifest
|
|
121
|
+
|
|
122
|
+
When you need to modify tool metadata without changing functionality:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
manifest = RobotLab::ToolManifest.new(
|
|
126
|
+
tool: existing_tool,
|
|
127
|
+
name: "custom_name", # Override name
|
|
128
|
+
description: "New description" # Override description
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Message Types
|
|
133
|
+
|
|
134
|
+
RobotLab uses a type hierarchy for messages:
|
|
135
|
+
|
|
136
|
+
```mermaid
|
|
137
|
+
classDiagram
|
|
138
|
+
Message <|-- TextMessage
|
|
139
|
+
Message <|-- ToolMessage
|
|
140
|
+
ToolMessage <|-- ToolCallMessage
|
|
141
|
+
ToolMessage <|-- ToolResultMessage
|
|
142
|
+
|
|
143
|
+
class Message {
|
|
144
|
+
+String type
|
|
145
|
+
+String role
|
|
146
|
+
+String content
|
|
147
|
+
+String stop_reason
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class TextMessage {
|
|
151
|
+
+text?()
|
|
152
|
+
+user?()
|
|
153
|
+
+assistant?()
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
class ToolMessage {
|
|
157
|
+
+String id
|
|
158
|
+
+String name
|
|
159
|
+
+Hash input
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
class ToolCallMessage {
|
|
163
|
+
+Array~ToolMessage~ tools
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
class ToolResultMessage {
|
|
167
|
+
+ToolMessage tool
|
|
168
|
+
+Hash content
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Message Roles
|
|
173
|
+
|
|
174
|
+
| Role | Description |
|
|
175
|
+
|------|-------------|
|
|
176
|
+
| `user` | Input from the user |
|
|
177
|
+
| `assistant` | Response from the LLM |
|
|
178
|
+
| `system` | System instructions |
|
|
179
|
+
| `tool` | Tool call or result |
|
|
180
|
+
|
|
181
|
+
### Stop Reasons
|
|
182
|
+
|
|
183
|
+
| Reason | Description |
|
|
184
|
+
|--------|-------------|
|
|
185
|
+
| `stop` | Natural completion |
|
|
186
|
+
| `tool` | Tool call requested |
|
|
187
|
+
| `max_tokens` | Token limit reached |
|
|
188
|
+
|
|
189
|
+
## RobotResult
|
|
190
|
+
|
|
191
|
+
The output from a robot execution:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
result = robot.run(state: state, network: network)
|
|
195
|
+
|
|
196
|
+
result.robot_name # => "support_agent"
|
|
197
|
+
result.output # => [TextMessage, ...]
|
|
198
|
+
result.tool_calls # => [ToolMessage, ...]
|
|
199
|
+
result.stop_reason # => "stop"
|
|
200
|
+
result.created_at # => Time
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Accessing Response Content
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
# Get text response
|
|
207
|
+
text = result.output.select(&:text?).map(&:content).join
|
|
208
|
+
|
|
209
|
+
# Check if tools were called
|
|
210
|
+
has_tools = result.tool_calls.any?
|
|
211
|
+
|
|
212
|
+
# Get all tool names called
|
|
213
|
+
tool_names = result.tool_calls.map(&:name)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Configuration Hierarchy
|
|
217
|
+
|
|
218
|
+
RobotLab uses a cascading configuration system:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
Global (RobotLab.configure)
|
|
222
|
+
│
|
|
223
|
+
├── mcp: [server1, server2]
|
|
224
|
+
├── tools: [tool1, tool2]
|
|
225
|
+
│
|
|
226
|
+
└── Network
|
|
227
|
+
│
|
|
228
|
+
├── mcp: :inherit | :none | [servers]
|
|
229
|
+
├── tools: :inherit | :none | [tools]
|
|
230
|
+
│
|
|
231
|
+
└── Robot
|
|
232
|
+
│
|
|
233
|
+
├── mcp: :inherit | :none | [servers]
|
|
234
|
+
└── tools: :inherit | :none | [tools]
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The `:inherit` value pulls from the parent level.
|
|
238
|
+
|
|
239
|
+
## Next Steps
|
|
240
|
+
|
|
241
|
+
- [Robot Execution](robot-execution.md) - Detailed execution flow
|
|
242
|
+
- [Network Orchestration](network-orchestration.md) - Multi-robot coordination
|
|
243
|
+
- [State Management](state-management.md) - Managing conversation state
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Architecture Overview
|
|
2
|
+
|
|
3
|
+
RobotLab is designed around a few core architectural principles that enable flexible, composable AI workflows.
|
|
4
|
+
|
|
5
|
+
## Design Philosophy
|
|
6
|
+
|
|
7
|
+
### 1. Separation of Concerns
|
|
8
|
+
|
|
9
|
+
Each component has a single, well-defined responsibility:
|
|
10
|
+
|
|
11
|
+
- **Robot**: Encapsulates LLM interaction logic and personality
|
|
12
|
+
- **Network**: Orchestrates robot execution and routing
|
|
13
|
+
- **State**: Manages conversation and workflow data
|
|
14
|
+
- **Tool**: Provides external capabilities to robots
|
|
15
|
+
|
|
16
|
+
### 2. Composability
|
|
17
|
+
|
|
18
|
+
Components are designed to be mixed and matched:
|
|
19
|
+
|
|
20
|
+
- Robots can be reused across multiple networks
|
|
21
|
+
- Tools can be shared or robot-specific
|
|
22
|
+
- Networks can be nested or chained
|
|
23
|
+
- State can be persisted and restored
|
|
24
|
+
|
|
25
|
+
### 3. Provider Agnostic
|
|
26
|
+
|
|
27
|
+
RobotLab abstracts away LLM provider differences:
|
|
28
|
+
|
|
29
|
+
- Unified message format across providers
|
|
30
|
+
- Consistent tool calling interface
|
|
31
|
+
- Automatic provider detection from model names
|
|
32
|
+
- Easy switching between providers
|
|
33
|
+
|
|
34
|
+
## System Architecture
|
|
35
|
+
|
|
36
|
+
```mermaid
|
|
37
|
+
graph TB
|
|
38
|
+
subgraph "Application Layer"
|
|
39
|
+
A[Your Application]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
subgraph "RobotLab Core"
|
|
43
|
+
B[Network]
|
|
44
|
+
C[Router]
|
|
45
|
+
D[Robot]
|
|
46
|
+
E[State]
|
|
47
|
+
F[Memory]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
subgraph "Integration Layer"
|
|
51
|
+
G[Adapters]
|
|
52
|
+
H[MCP Client]
|
|
53
|
+
I[Tools]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
subgraph "Provider Layer"
|
|
57
|
+
J[Anthropic]
|
|
58
|
+
K[OpenAI]
|
|
59
|
+
L[Gemini]
|
|
60
|
+
M[MCP Servers]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
A --> B
|
|
64
|
+
B --> C
|
|
65
|
+
B --> D
|
|
66
|
+
B --> E
|
|
67
|
+
E --> F
|
|
68
|
+
D --> G
|
|
69
|
+
D --> H
|
|
70
|
+
D --> I
|
|
71
|
+
G --> J
|
|
72
|
+
G --> K
|
|
73
|
+
G --> L
|
|
74
|
+
H --> M
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Core Components
|
|
78
|
+
|
|
79
|
+
| Component | Description | Documentation |
|
|
80
|
+
|-----------|-------------|---------------|
|
|
81
|
+
| **Robot** | LLM-powered agent with personality and tools | [Core Concepts](core-concepts.md) |
|
|
82
|
+
| **Network** | Orchestrates multiple robots | [Network Orchestration](network-orchestration.md) |
|
|
83
|
+
| **State** | Conversation and workflow data | [State Management](state-management.md) |
|
|
84
|
+
| **Router** | Determines robot execution order | [Network Orchestration](network-orchestration.md) |
|
|
85
|
+
| **Memory** | Shared key-value store | [State Management](state-management.md) |
|
|
86
|
+
| **Adapter** | Provider-specific message conversion | [Message Flow](message-flow.md) |
|
|
87
|
+
|
|
88
|
+
## Data Flow
|
|
89
|
+
|
|
90
|
+
1. **Input**: User message enters via State
|
|
91
|
+
2. **Routing**: Router selects robot(s) to execute
|
|
92
|
+
3. **Execution**: Robot processes message with LLM
|
|
93
|
+
4. **Tools**: Robot may call tools during execution
|
|
94
|
+
5. **Result**: Robot returns RobotResult
|
|
95
|
+
6. **Iteration**: Router checks for next robot
|
|
96
|
+
7. **Output**: Final results returned to application
|
|
97
|
+
|
|
98
|
+
## Key Patterns
|
|
99
|
+
|
|
100
|
+
### Builder Pattern
|
|
101
|
+
|
|
102
|
+
Robots and networks are constructed using a fluent DSL:
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
robot = RobotLab.build do
|
|
106
|
+
name "assistant"
|
|
107
|
+
model "claude-sonnet-4"
|
|
108
|
+
template "You are helpful."
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Strategy Pattern
|
|
113
|
+
|
|
114
|
+
Routers implement custom selection logic:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
router = ->(args) {
|
|
118
|
+
# Custom routing strategy
|
|
119
|
+
args.call_count.zero? ? :first_robot : nil
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Adapter Pattern
|
|
124
|
+
|
|
125
|
+
Provider adapters normalize LLM interfaces:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
adapter = Adapters::Registry.for(:anthropic)
|
|
129
|
+
messages = adapter.format_messages(robot_lab_messages)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Next Steps
|
|
133
|
+
|
|
134
|
+
- [Core Concepts](core-concepts.md) - Deep dive into robots and tools
|
|
135
|
+
- [Robot Execution](robot-execution.md) - How robots process messages
|
|
136
|
+
- [Network Orchestration](network-orchestration.md) - Multi-robot workflows
|
|
137
|
+
- [State Management](state-management.md) - Managing conversation state
|
|
138
|
+
- [Message Flow](message-flow.md) - How messages move through the system
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# Message Flow
|
|
2
|
+
|
|
3
|
+
This page explains how messages move through RobotLab, from user input to LLM response.
|
|
4
|
+
|
|
5
|
+
## Message Types
|
|
6
|
+
|
|
7
|
+
RobotLab uses four primary message types:
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
classDiagram
|
|
11
|
+
class Message {
|
|
12
|
+
<<abstract>>
|
|
13
|
+
+type: String
|
|
14
|
+
+role: String
|
|
15
|
+
+content: String
|
|
16
|
+
+stop_reason: String
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
class TextMessage {
|
|
20
|
+
+text?() bool
|
|
21
|
+
+user?() bool
|
|
22
|
+
+assistant?() bool
|
|
23
|
+
+system?() bool
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
class ToolMessage {
|
|
27
|
+
+id: String
|
|
28
|
+
+name: String
|
|
29
|
+
+input: Hash
|
|
30
|
+
+tool_call?() bool
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class ToolCallMessage {
|
|
34
|
+
+tools: Array~ToolMessage~
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class ToolResultMessage {
|
|
38
|
+
+tool: ToolMessage
|
|
39
|
+
+content: Hash
|
|
40
|
+
+tool_result?() bool
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
Message <|-- TextMessage
|
|
44
|
+
Message <|-- ToolMessage
|
|
45
|
+
ToolMessage <|-- ToolCallMessage
|
|
46
|
+
ToolMessage <|-- ToolResultMessage
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### TextMessage
|
|
50
|
+
|
|
51
|
+
Regular text content from users or assistants:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
TextMessage.new(
|
|
55
|
+
role: "user",
|
|
56
|
+
content: "What's the weather in Paris?"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
TextMessage.new(
|
|
60
|
+
role: "assistant",
|
|
61
|
+
content: "The weather in Paris is sunny and 22°C.",
|
|
62
|
+
stop_reason: "stop"
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### ToolMessage
|
|
67
|
+
|
|
68
|
+
Base class for tool-related messages:
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
ToolMessage.new(
|
|
72
|
+
id: "tool_123",
|
|
73
|
+
name: "get_weather",
|
|
74
|
+
input: { location: "Paris" }
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### ToolCallMessage
|
|
79
|
+
|
|
80
|
+
LLM's request to execute tools:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
ToolCallMessage.new(
|
|
84
|
+
role: "assistant",
|
|
85
|
+
content: nil,
|
|
86
|
+
stop_reason: "tool",
|
|
87
|
+
tools: [
|
|
88
|
+
ToolMessage.new(id: "call_1", name: "get_weather", input: { location: "Paris" })
|
|
89
|
+
]
|
|
90
|
+
)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### ToolResultMessage
|
|
94
|
+
|
|
95
|
+
Result from tool execution:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
ToolResultMessage.new(
|
|
99
|
+
tool: tool_message,
|
|
100
|
+
content: { data: { temp: 22, condition: "sunny" } }
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Message Flow Diagram
|
|
105
|
+
|
|
106
|
+
```mermaid
|
|
107
|
+
sequenceDiagram
|
|
108
|
+
participant User
|
|
109
|
+
participant State
|
|
110
|
+
participant Robot
|
|
111
|
+
participant Adapter
|
|
112
|
+
participant LLM
|
|
113
|
+
|
|
114
|
+
User->>State: UserMessage
|
|
115
|
+
State->>State: Format messages
|
|
116
|
+
State->>Robot: state.messages
|
|
117
|
+
Robot->>Adapter: Convert messages
|
|
118
|
+
Adapter->>LLM: Provider-specific format
|
|
119
|
+
|
|
120
|
+
LLM-->>Adapter: Response
|
|
121
|
+
Adapter-->>Robot: Parse response
|
|
122
|
+
Robot->>Robot: Execute tools (if any)
|
|
123
|
+
Robot-->>State: RobotResult
|
|
124
|
+
State->>State: Append result
|
|
125
|
+
State-->>User: Response
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Adapter Layer
|
|
129
|
+
|
|
130
|
+
Adapters convert between RobotLab's message format and provider-specific formats.
|
|
131
|
+
|
|
132
|
+
### Anthropic Adapter
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# RobotLab format
|
|
136
|
+
messages = [
|
|
137
|
+
TextMessage.new(role: "user", content: "Hello"),
|
|
138
|
+
TextMessage.new(role: "assistant", content: "Hi there!")
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
# Converted to Anthropic format
|
|
142
|
+
[
|
|
143
|
+
{ role: "user", content: "Hello" },
|
|
144
|
+
{ role: "assistant", content: "Hi there!" }
|
|
145
|
+
]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### System Message Handling
|
|
149
|
+
|
|
150
|
+
System messages are handled specially:
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# RobotLab
|
|
154
|
+
messages = [
|
|
155
|
+
TextMessage.new(role: "system", content: "You are helpful."),
|
|
156
|
+
TextMessage.new(role: "user", content: "Hello")
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
# Anthropic: system extracted separately
|
|
160
|
+
# OpenAI: system message stays in array
|
|
161
|
+
# Gemini: converted to context
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Tool Message Conversion
|
|
165
|
+
|
|
166
|
+
Tool calls are provider-specific:
|
|
167
|
+
|
|
168
|
+
=== "Anthropic"
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
{
|
|
172
|
+
type: "tool_use",
|
|
173
|
+
id: "tool_123",
|
|
174
|
+
name: "get_weather",
|
|
175
|
+
input: { location: "Paris" }
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
=== "OpenAI"
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
{
|
|
183
|
+
type: "function",
|
|
184
|
+
function: {
|
|
185
|
+
name: "get_weather",
|
|
186
|
+
arguments: '{"location":"Paris"}'
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Conversation Building
|
|
192
|
+
|
|
193
|
+
### Initial State
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
state = RobotLab.create_state(
|
|
197
|
+
message: "What's the weather?",
|
|
198
|
+
data: { location: "Paris" }
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
state.messages
|
|
202
|
+
# => [TextMessage(role: "user", content: "What's the weather?")]
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### After Robot Execution
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
result = robot.run(state: state, network: network)
|
|
209
|
+
state.append_result(result)
|
|
210
|
+
|
|
211
|
+
state.messages
|
|
212
|
+
# => [
|
|
213
|
+
# TextMessage(role: "user", content: "What's the weather?"),
|
|
214
|
+
# TextMessage(role: "assistant", content: "The weather is sunny.")
|
|
215
|
+
# ]
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### With Tool Calls
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
state.messages
|
|
222
|
+
# => [
|
|
223
|
+
# TextMessage(role: "user", content: "What's the weather?"),
|
|
224
|
+
# ToolCallMessage(tools: [ToolMessage(name: "get_weather", ...)]),
|
|
225
|
+
# ToolResultMessage(content: { temp: 22 }),
|
|
226
|
+
# TextMessage(role: "assistant", content: "It's 22°C and sunny.")
|
|
227
|
+
# ]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Format History
|
|
231
|
+
|
|
232
|
+
The `format_history` method prepares messages for LLM:
|
|
233
|
+
|
|
234
|
+
```ruby
|
|
235
|
+
# Includes results from previous robots
|
|
236
|
+
formatted = state.format_history
|
|
237
|
+
|
|
238
|
+
# Returns alternating user/assistant messages
|
|
239
|
+
# with tool calls/results properly interleaved
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Message Predicates
|
|
243
|
+
|
|
244
|
+
Check message types easily:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
message.text? # Is it a TextMessage?
|
|
248
|
+
message.tool_call? # Is it a ToolCallMessage?
|
|
249
|
+
message.tool_result? # Is it a ToolResultMessage?
|
|
250
|
+
|
|
251
|
+
message.user? # Is role "user"?
|
|
252
|
+
message.assistant? # Is role "assistant"?
|
|
253
|
+
message.system? # Is role "system"?
|
|
254
|
+
|
|
255
|
+
message.stopped? # Is stop_reason "stop"?
|
|
256
|
+
message.tool_stop? # Is stop_reason "tool"?
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Creating Messages
|
|
260
|
+
|
|
261
|
+
### From Strings
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
TextMessage.new(role: "user", content: "Hello")
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### From Hashes
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
Message.from_hash(
|
|
271
|
+
type: "text",
|
|
272
|
+
role: "user",
|
|
273
|
+
content: "Hello"
|
|
274
|
+
)
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### From UserMessage
|
|
278
|
+
|
|
279
|
+
```ruby
|
|
280
|
+
user_msg = UserMessage.new("Hello", thread_id: "123")
|
|
281
|
+
text_msg = user_msg.to_message
|
|
282
|
+
# => TextMessage(role: "user", content: "Hello")
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Serialization
|
|
286
|
+
|
|
287
|
+
Messages can be serialized:
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
# To hash
|
|
291
|
+
hash = message.to_h
|
|
292
|
+
# => { type: "text", role: "user", content: "Hello" }
|
|
293
|
+
|
|
294
|
+
# To JSON
|
|
295
|
+
json = message.to_json
|
|
296
|
+
# => '{"type":"text","role":"user","content":"Hello"}'
|
|
297
|
+
|
|
298
|
+
# From hash
|
|
299
|
+
message = Message.from_hash(hash)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Provider Registry
|
|
303
|
+
|
|
304
|
+
The adapter registry maps providers to adapters:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
Adapters::Registry.for(:anthropic) # => Adapters::Anthropic
|
|
308
|
+
Adapters::Registry.for(:openai) # => Adapters::OpenAI
|
|
309
|
+
Adapters::Registry.for(:gemini) # => Adapters::Gemini
|
|
310
|
+
|
|
311
|
+
# Aliases
|
|
312
|
+
Adapters::Registry.for(:azure_openai) # => Adapters::OpenAI
|
|
313
|
+
Adapters::Registry.for(:bedrock) # => Adapters::Anthropic
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## Next Steps
|
|
317
|
+
|
|
318
|
+
- [Building Robots](../guides/building-robots.md) - Creating custom robots
|
|
319
|
+
- [Using Tools](../guides/using-tools.md) - Tool message handling
|
|
320
|
+
- [API Reference: Messages](../api/messages/index.md) - Detailed message API
|