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.
Files changed (153) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/.github/workflows/deploy-yard-docs.yml +52 -0
  5. data/CHANGELOG.md +55 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +332 -0
  9. data/Rakefile +67 -0
  10. data/docs/api/adapters/anthropic.md +121 -0
  11. data/docs/api/adapters/gemini.md +133 -0
  12. data/docs/api/adapters/index.md +104 -0
  13. data/docs/api/adapters/openai.md +134 -0
  14. data/docs/api/core/index.md +113 -0
  15. data/docs/api/core/memory.md +314 -0
  16. data/docs/api/core/network.md +291 -0
  17. data/docs/api/core/robot.md +273 -0
  18. data/docs/api/core/state.md +273 -0
  19. data/docs/api/core/tool.md +353 -0
  20. data/docs/api/history/active-record-adapter.md +195 -0
  21. data/docs/api/history/config.md +191 -0
  22. data/docs/api/history/index.md +132 -0
  23. data/docs/api/history/thread-manager.md +144 -0
  24. data/docs/api/index.md +82 -0
  25. data/docs/api/mcp/client.md +221 -0
  26. data/docs/api/mcp/index.md +111 -0
  27. data/docs/api/mcp/server.md +225 -0
  28. data/docs/api/mcp/transports.md +264 -0
  29. data/docs/api/messages/index.md +67 -0
  30. data/docs/api/messages/text-message.md +102 -0
  31. data/docs/api/messages/tool-call-message.md +144 -0
  32. data/docs/api/messages/tool-result-message.md +154 -0
  33. data/docs/api/messages/user-message.md +171 -0
  34. data/docs/api/streaming/context.md +174 -0
  35. data/docs/api/streaming/events.md +237 -0
  36. data/docs/api/streaming/index.md +108 -0
  37. data/docs/architecture/core-concepts.md +243 -0
  38. data/docs/architecture/index.md +138 -0
  39. data/docs/architecture/message-flow.md +320 -0
  40. data/docs/architecture/network-orchestration.md +216 -0
  41. data/docs/architecture/robot-execution.md +243 -0
  42. data/docs/architecture/state-management.md +323 -0
  43. data/docs/assets/css/custom.css +56 -0
  44. data/docs/assets/images/robot_lab.jpg +0 -0
  45. data/docs/concepts.md +216 -0
  46. data/docs/examples/basic-chat.md +193 -0
  47. data/docs/examples/index.md +129 -0
  48. data/docs/examples/mcp-server.md +290 -0
  49. data/docs/examples/multi-robot-network.md +312 -0
  50. data/docs/examples/rails-application.md +420 -0
  51. data/docs/examples/tool-usage.md +310 -0
  52. data/docs/getting-started/configuration.md +230 -0
  53. data/docs/getting-started/index.md +56 -0
  54. data/docs/getting-started/installation.md +179 -0
  55. data/docs/getting-started/quick-start.md +203 -0
  56. data/docs/guides/building-robots.md +376 -0
  57. data/docs/guides/creating-networks.md +366 -0
  58. data/docs/guides/history.md +359 -0
  59. data/docs/guides/index.md +68 -0
  60. data/docs/guides/mcp-integration.md +356 -0
  61. data/docs/guides/memory.md +309 -0
  62. data/docs/guides/rails-integration.md +432 -0
  63. data/docs/guides/streaming.md +314 -0
  64. data/docs/guides/using-tools.md +394 -0
  65. data/docs/index.md +160 -0
  66. data/examples/01_simple_robot.rb +38 -0
  67. data/examples/02_tools.rb +106 -0
  68. data/examples/03_network.rb +103 -0
  69. data/examples/04_mcp.rb +219 -0
  70. data/examples/05_streaming.rb +124 -0
  71. data/examples/06_prompt_templates.rb +324 -0
  72. data/examples/07_network_memory.rb +329 -0
  73. data/examples/prompts/assistant/system.txt.erb +2 -0
  74. data/examples/prompts/assistant/user.txt.erb +1 -0
  75. data/examples/prompts/billing/system.txt.erb +7 -0
  76. data/examples/prompts/billing/user.txt.erb +1 -0
  77. data/examples/prompts/classifier/system.txt.erb +4 -0
  78. data/examples/prompts/classifier/user.txt.erb +1 -0
  79. data/examples/prompts/entity_extractor/system.txt.erb +11 -0
  80. data/examples/prompts/entity_extractor/user.txt.erb +3 -0
  81. data/examples/prompts/escalation/system.txt.erb +35 -0
  82. data/examples/prompts/escalation/user.txt.erb +34 -0
  83. data/examples/prompts/general/system.txt.erb +4 -0
  84. data/examples/prompts/general/user.txt.erb +1 -0
  85. data/examples/prompts/github_assistant/system.txt.erb +6 -0
  86. data/examples/prompts/github_assistant/user.txt.erb +1 -0
  87. data/examples/prompts/helper/system.txt.erb +1 -0
  88. data/examples/prompts/helper/user.txt.erb +1 -0
  89. data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
  90. data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
  91. data/examples/prompts/order_support/system.txt.erb +27 -0
  92. data/examples/prompts/order_support/user.txt.erb +22 -0
  93. data/examples/prompts/product_support/system.txt.erb +30 -0
  94. data/examples/prompts/product_support/user.txt.erb +32 -0
  95. data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
  96. data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
  97. data/examples/prompts/synthesizer/system.txt.erb +14 -0
  98. data/examples/prompts/synthesizer/user.txt.erb +15 -0
  99. data/examples/prompts/technical/system.txt.erb +7 -0
  100. data/examples/prompts/technical/user.txt.erb +1 -0
  101. data/examples/prompts/triage/system.txt.erb +16 -0
  102. data/examples/prompts/triage/user.txt.erb +17 -0
  103. data/lib/generators/robot_lab/install_generator.rb +78 -0
  104. data/lib/generators/robot_lab/robot_generator.rb +55 -0
  105. data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
  106. data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
  107. data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
  108. data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
  109. data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
  110. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
  111. data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
  112. data/lib/robot_lab/adapters/anthropic.rb +163 -0
  113. data/lib/robot_lab/adapters/base.rb +85 -0
  114. data/lib/robot_lab/adapters/gemini.rb +193 -0
  115. data/lib/robot_lab/adapters/openai.rb +159 -0
  116. data/lib/robot_lab/adapters/registry.rb +81 -0
  117. data/lib/robot_lab/configuration.rb +143 -0
  118. data/lib/robot_lab/error.rb +32 -0
  119. data/lib/robot_lab/errors.rb +70 -0
  120. data/lib/robot_lab/history/active_record_adapter.rb +146 -0
  121. data/lib/robot_lab/history/config.rb +115 -0
  122. data/lib/robot_lab/history/thread_manager.rb +93 -0
  123. data/lib/robot_lab/mcp/client.rb +210 -0
  124. data/lib/robot_lab/mcp/server.rb +84 -0
  125. data/lib/robot_lab/mcp/transports/base.rb +56 -0
  126. data/lib/robot_lab/mcp/transports/sse.rb +117 -0
  127. data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
  128. data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
  129. data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
  130. data/lib/robot_lab/memory.rb +882 -0
  131. data/lib/robot_lab/memory_change.rb +123 -0
  132. data/lib/robot_lab/message.rb +357 -0
  133. data/lib/robot_lab/network.rb +350 -0
  134. data/lib/robot_lab/rails/engine.rb +29 -0
  135. data/lib/robot_lab/rails/railtie.rb +42 -0
  136. data/lib/robot_lab/robot.rb +560 -0
  137. data/lib/robot_lab/robot_result.rb +205 -0
  138. data/lib/robot_lab/robotic_model.rb +324 -0
  139. data/lib/robot_lab/state_proxy.rb +188 -0
  140. data/lib/robot_lab/streaming/context.rb +144 -0
  141. data/lib/robot_lab/streaming/events.rb +95 -0
  142. data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
  143. data/lib/robot_lab/task.rb +117 -0
  144. data/lib/robot_lab/tool.rb +223 -0
  145. data/lib/robot_lab/tool_config.rb +112 -0
  146. data/lib/robot_lab/tool_manifest.rb +234 -0
  147. data/lib/robot_lab/user_message.rb +118 -0
  148. data/lib/robot_lab/version.rb +5 -0
  149. data/lib/robot_lab/waiter.rb +73 -0
  150. data/lib/robot_lab.rb +195 -0
  151. data/mkdocs.yml +214 -0
  152. data/sig/robot_lab.rbs +4 -0
  153. metadata +442 -0
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 5: Streaming Events
5
+ #
6
+ # Demonstrates real-time streaming of robot responses.
7
+ #
8
+ # Usage:
9
+ # ANTHROPIC_API_KEY=your_key ruby examples/05_streaming.rb
10
+
11
+ require_relative "../lib/robot_lab"
12
+
13
+ # Configure RobotLab
14
+ RobotLab.configure do |config|
15
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
16
+ config.template_path = File.join(__dir__, "prompts")
17
+ end
18
+
19
+ # Create a streaming handler
20
+ streaming_handler = lambda do |event|
21
+ case event[:event]
22
+ when RobotLab::Streaming::Events::RUN_STARTED
23
+ puts "[#{event[:data][:scope]}] Run started: #{event[:data][:run_id]}"
24
+ puts "-" * 40
25
+
26
+ when RobotLab::Streaming::Events::TEXT_DELTA
27
+ # Print text deltas without newline for streaming effect
28
+ print event[:data][:delta]
29
+ $stdout.flush
30
+
31
+ when RobotLab::Streaming::Events::TOOL_CALL_ARGUMENTS_DELTA
32
+ puts "[Tool call] #{event[:data][:tool_name]}: #{event[:data][:delta]}"
33
+
34
+ when RobotLab::Streaming::Events::TOOL_CALL_OUTPUT_DELTA
35
+ puts "[Tool output] #{event[:data][:delta]}"
36
+
37
+ when RobotLab::Streaming::Events::RUN_COMPLETED
38
+ puts ""
39
+ puts "-" * 40
40
+ puts "[#{event[:data][:scope]}] Run completed"
41
+
42
+ when RobotLab::Streaming::Events::RUN_FAILED
43
+ puts ""
44
+ puts "[ERROR] Run failed: #{event[:data][:error]}"
45
+
46
+ else
47
+ # Log other events at debug level
48
+ # puts "[DEBUG] #{event[:event]}: #{event[:data].keys.join(', ')}"
49
+ end
50
+ end
51
+
52
+ # Create streaming context for testing
53
+ context = RobotLab::Streaming::Context.new(
54
+ run_id: SecureRandom.uuid,
55
+ message_id: SecureRandom.uuid,
56
+ scope: "robot",
57
+ publish: streaming_handler
58
+ )
59
+
60
+ puts "Streaming Events Example"
61
+ puts "=" * 40
62
+ puts ""
63
+
64
+ # Simulate streaming events
65
+ puts "Simulating streaming events:"
66
+ puts ""
67
+
68
+ # Simulate run started
69
+ context.publish_event(
70
+ event: RobotLab::Streaming::Events::RUN_STARTED,
71
+ data: {}
72
+ )
73
+
74
+ # Simulate text streaming
75
+ text = "Hello! I'm demonstrating streaming output. Each word appears as it's generated, creating a real-time effect."
76
+ text.split(" ").each do |word|
77
+ context.publish_event(
78
+ event: RobotLab::Streaming::Events::TEXT_DELTA,
79
+ data: { delta: word + " " }
80
+ )
81
+ sleep 0.1 # Simulate generation delay
82
+ end
83
+
84
+ # Simulate completion
85
+ context.publish_event(
86
+ event: RobotLab::Streaming::Events::RUN_COMPLETED,
87
+ data: {}
88
+ )
89
+
90
+ puts ""
91
+ puts "=" * 40
92
+ puts ""
93
+ puts "Using streaming with a robot:"
94
+ puts ""
95
+ puts <<~CODE
96
+ # Create robot with template
97
+ robot = RobotLab.build(
98
+ name: "streamer",
99
+ template: :helper,
100
+ model: "claude-sonnet-4"
101
+ )
102
+
103
+ # Run with streaming callback
104
+ result = robot.run(message: "Tell me a story") do |event|
105
+ case event[:event]
106
+ when "text.delta"
107
+ print event[:data][:delta]
108
+ when "run.completed"
109
+ puts "\\nDone!"
110
+ end
111
+ end
112
+ CODE
113
+
114
+ puts ""
115
+ puts "Or with a network:"
116
+ puts ""
117
+ puts <<~CODE
118
+ streaming_handler = ->(event) { broadcast_to_websocket(event) }
119
+
120
+ network.run(
121
+ message: "Process this request",
122
+ streaming: streaming_handler
123
+ )
124
+ CODE
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 6: Prompt Templates with ruby_llm-template
5
+ #
6
+ # Demonstrates using ruby_llm-template for organized, reusable prompts
7
+ # within a RobotLab network. This example shows an e-commerce support
8
+ # system with dynamic context injection using SimpleFlow's optional task routing.
9
+ #
10
+ # Usage:
11
+ # ANTHROPIC_API_KEY=your_key ruby examples/06_prompt_templates.rb
12
+ #
13
+ # Template Structure:
14
+ # examples/prompts/
15
+ # ├── triage/
16
+ # │ ├── system.txt.erb
17
+ # │ └── user.txt.erb
18
+ # ├── order_support/
19
+ # │ ├── system.txt.erb
20
+ # │ └── user.txt.erb
21
+ # ├── product_support/
22
+ # │ ├── system.txt.erb
23
+ # │ └── user.txt.erb
24
+ # └── escalation/
25
+ # ├── system.txt.erb
26
+ # └── user.txt.erb
27
+
28
+ require_relative "../lib/robot_lab"
29
+
30
+ # =============================================================================
31
+ # Sample Data
32
+ # =============================================================================
33
+ # Simulated customer and business data that would come from your database.
34
+
35
+ COMPANY_NAME = "TechGear Pro"
36
+
37
+ SAMPLE_CUSTOMER = {
38
+ name: "Sarah Johnson",
39
+ email: "sarah.johnson@example.com",
40
+ account_type: "Premium",
41
+ member_since: "2022-03-15",
42
+ vip: true,
43
+ lifetime_value: 4_250.00,
44
+ recent_orders: [
45
+ { id: "ORD-2024-1847", date: "2024-01-10", status: "Delivered", total: 299.99 },
46
+ { id: "ORD-2024-1923", date: "2024-01-15", status: "Processing", total: 149.50 }
47
+ ],
48
+ open_tickets: [
49
+ { id: "TKT-5521", subject: "Delayed shipment inquiry", status: "Open" }
50
+ ],
51
+ purchase_history: [
52
+ { category: "Electronics" },
53
+ { category: "Accessories" },
54
+ { category: "Audio" }
55
+ ],
56
+ escalation_history: []
57
+ }
58
+
59
+ CATEGORIES = [
60
+ { name: "order", description: "Order status, shipping, returns, refunds" },
61
+ { name: "product", description: "Product questions, specifications, recommendations" },
62
+ { name: "escalation", description: "Complex issues, complaints, special requests" }
63
+ ]
64
+
65
+ ORDERS = [
66
+ {
67
+ id: "ORD-2024-1847",
68
+ date: "2024-01-10",
69
+ status: "Delivered",
70
+ total: 299.99,
71
+ tracking: "1Z999AA10123456784",
72
+ items: [
73
+ { name: "Wireless Noise-Canceling Headphones", quantity: 1, price: 249.99 },
74
+ { name: "Premium Carrying Case", quantity: 1, price: 49.99 }
75
+ ]
76
+ },
77
+ {
78
+ id: "ORD-2024-1923",
79
+ date: "2024-01-15",
80
+ status: "Processing",
81
+ total: 149.50,
82
+ tracking: nil,
83
+ items: [
84
+ { name: "USB-C Hub Pro", quantity: 1, price: 89.50 },
85
+ { name: "Braided USB-C Cable 2m", quantity: 2, price: 30.00 }
86
+ ]
87
+ }
88
+ ]
89
+
90
+ PRODUCTS = [
91
+ {
92
+ name: "Wireless Noise-Canceling Headphones XR500",
93
+ sku: "AUDIO-XR500",
94
+ price: 249.99,
95
+ category: "Audio",
96
+ in_stock: true,
97
+ quantity: 45,
98
+ features: ["40hr battery life", "Active noise cancellation", "Bluetooth 5.2", "Hi-Res Audio"],
99
+ compatible_with: ["All Bluetooth devices", "3.5mm audio jack"]
100
+ },
101
+ {
102
+ name: "USB-C Hub Pro 7-in-1",
103
+ sku: "ACC-HUB7",
104
+ price: 89.50,
105
+ category: "Accessories",
106
+ in_stock: true,
107
+ quantity: 120,
108
+ features: ["4K HDMI output", "100W Power Delivery", "SD/MicroSD slots", "USB 3.0 ports"],
109
+ compatible_with: ["MacBook Pro", "MacBook Air", "iPad Pro", "Windows laptops"]
110
+ }
111
+ ]
112
+
113
+ PROMOTIONS = [
114
+ { name: "New Year Sale", description: "15% off all audio products", code: "AUDIO15" },
115
+ { name: "Free Shipping", description: "Free shipping on orders over $75", code: "FREESHIP" }
116
+ ]
117
+
118
+ PRODUCT_CATEGORIES = [
119
+ { name: "Electronics", description: "Laptops, tablets, smartphones" },
120
+ { name: "Audio", description: "Headphones, speakers, microphones" },
121
+ { name: "Accessories", description: "Cables, hubs, cases, chargers" }
122
+ ]
123
+
124
+ POLICIES = {
125
+ refund_window: 30,
126
+ free_shipping_threshold: 75,
127
+ express_fee: "$12.99"
128
+ }
129
+
130
+ ORDER_CAPABILITIES = [
131
+ "Check order status and tracking",
132
+ "Process returns and exchanges",
133
+ "Issue refunds (up to $500 without manager approval)",
134
+ "Modify pending orders",
135
+ "Apply shipping upgrades"
136
+ ]
137
+
138
+ ESCALATION_AUTHORITIES = [
139
+ { name: "Courtesy Credit", description: "Account credit for inconvenience", limit: "$50" },
140
+ { name: "Expedited Shipping", description: "Free upgrade to express shipping", limit: "Unlimited" },
141
+ { name: "Extended Return Window", description: "Extend return period", limit: "60 days" },
142
+ { name: "Price Match", description: "Match competitor pricing", limit: "20% max discount" }
143
+ ]
144
+
145
+ # =============================================================================
146
+ # Triage Robot with Routing Logic
147
+ # =============================================================================
148
+
149
+ # Custom triage robot that classifies and activates appropriate specialist
150
+ class TriageRobot < RobotLab::Robot
151
+ def call(result)
152
+ robot_result = run(**extract_run_context(result))
153
+
154
+ new_result = result
155
+ .with_context(@name.to_sym, robot_result)
156
+ .continue(robot_result)
157
+
158
+ # Examine LLM output and activate appropriate specialist
159
+ classification = robot_result.last_text_content.to_s.strip.downcase
160
+
161
+ case classification
162
+ when /order/
163
+ new_result.activate(:order)
164
+ when /product/
165
+ new_result.activate(:product)
166
+ when /escalat/
167
+ new_result.activate(:escalation)
168
+ else
169
+ # Default to escalation for unclear cases
170
+ new_result.activate(:escalation)
171
+ end
172
+ end
173
+ end
174
+
175
+ # =============================================================================
176
+ # Main Demo
177
+ # =============================================================================
178
+
179
+ puts "=" * 70
180
+ puts "RobotLab + Prompt Templates Demo"
181
+ puts "E-Commerce Support Network with Dynamic Context"
182
+ puts "=" * 70
183
+ puts
184
+
185
+ # Configure RobotLab
186
+ RobotLab.configure do |config|
187
+ config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
188
+ config.template_path = File.join(__dir__, "prompts")
189
+ end
190
+
191
+ # -----------------------------------------------------------------------------
192
+ # Build Robots with Template-Based Prompts
193
+ # -----------------------------------------------------------------------------
194
+
195
+ # Triage Robot - Classifies incoming requests
196
+ triage_robot = TriageRobot.new(
197
+ name: "triage",
198
+ description: "Classifies incoming requests to route to specialists",
199
+ template: :triage,
200
+ context: {
201
+ company_name: COMPANY_NAME,
202
+ categories: CATEGORIES
203
+ },
204
+ model: "claude-sonnet-4"
205
+ )
206
+
207
+ # Order Support Robot
208
+ order_robot = RobotLab.build(
209
+ name: "order",
210
+ description: "Handles order-related inquiries with full order history",
211
+ template: :order_support,
212
+ context: {
213
+ company_name: COMPANY_NAME,
214
+ policies: POLICIES,
215
+ capabilities: ORDER_CAPABILITIES
216
+ },
217
+ model: "claude-sonnet-4"
218
+ )
219
+
220
+ # Product Support Robot
221
+ product_robot = RobotLab.build(
222
+ name: "product",
223
+ description: "Answers product questions with catalog knowledge",
224
+ template: :product_support,
225
+ context: {
226
+ company_name: COMPANY_NAME,
227
+ products: PRODUCTS,
228
+ promotions: PROMOTIONS,
229
+ product_categories: PRODUCT_CATEGORIES
230
+ },
231
+ model: "claude-sonnet-4"
232
+ )
233
+
234
+ # Escalation Robot
235
+ escalation_robot = RobotLab.build(
236
+ name: "escalation",
237
+ description: "Handles complex cases requiring special authority",
238
+ template: :escalation,
239
+ context: {
240
+ company_name: COMPANY_NAME,
241
+ authorities: ESCALATION_AUTHORITIES
242
+ },
243
+ model: "claude-sonnet-4"
244
+ )
245
+
246
+ # -----------------------------------------------------------------------------
247
+ # Create Network with Optional Task Routing
248
+ # -----------------------------------------------------------------------------
249
+
250
+ network = RobotLab.create_network(name: "ecommerce_support") do
251
+ task :triage, triage_robot, depends_on: :none
252
+ task :order, order_robot, depends_on: :optional
253
+ task :product, product_robot, depends_on: :optional
254
+ task :escalation, escalation_robot, depends_on: :optional
255
+ end
256
+
257
+ # -----------------------------------------------------------------------------
258
+ # Run Demo Scenarios
259
+ # -----------------------------------------------------------------------------
260
+
261
+ demo_queries = [
262
+ {
263
+ label: "Order Inquiry",
264
+ message: "Where is my order ORD-2024-1923? It's been 5 days and still shows processing."
265
+ },
266
+ {
267
+ label: "Product Question",
268
+ message: "Are the XR500 headphones compatible with my iPhone? What's the battery life?"
269
+ },
270
+ {
271
+ label: "Escalation Case",
272
+ message: "This is ridiculous! I've been waiting 2 weeks for my order and nobody can help me. I want a refund AND compensation for this terrible experience!"
273
+ }
274
+ ]
275
+
276
+ demo_queries.each_with_index do |query, index|
277
+ puts
278
+ puts "-" * 70
279
+ puts "Scenario #{index + 1}: #{query[:label]}"
280
+ puts "-" * 70
281
+ puts "Customer: #{query[:message]}"
282
+ puts
283
+
284
+ # Run the network with context
285
+ result = network.run(
286
+ message: query[:message],
287
+ customer: SAMPLE_CUSTOMER,
288
+ orders: ORDERS
289
+ )
290
+
291
+ # Display triage classification
292
+ if result.context[:triage]
293
+ triage_result = result.context[:triage]
294
+ puts "Classification: #{triage_result.last_text_content}"
295
+ puts
296
+ end
297
+
298
+ # Display specialist response (the final value)
299
+ if result.value.is_a?(RobotLab::RobotResult)
300
+ puts "Routed to: #{result.value.robot_name.upcase}"
301
+ content = result.value.last_text_content.to_s
302
+ # Truncate long responses for display
303
+ if content.length > 300
304
+ puts "Response: #{content[0..300]}..."
305
+ else
306
+ puts "Response: #{content}"
307
+ end
308
+ end
309
+
310
+ puts
311
+ puts "=" * 70
312
+ end
313
+
314
+ puts
315
+ puts "Demo Complete!"
316
+ puts
317
+ puts "This example demonstrates:"
318
+ puts " - ruby_llm-template for organized, reusable prompts"
319
+ puts " - Build-time context (robot identity/capabilities)"
320
+ puts " - Run-time context (customer data, order history)"
321
+ puts " - Multi-robot network with optional task routing"
322
+ puts " - SimpleFlow::Result for passing context between robots"
323
+ puts
324
+ puts "Template files are located in: #{File.join(__dir__, 'prompts')}"