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
data/docs/index.md CHANGED
@@ -11,7 +11,7 @@
11
11
  </td>
12
12
  <td width="50%" valign="top">
13
13
  RobotLab is a Ruby gem that enables you to build sophisticated AI applications using multiple specialized robots (LLM agents) that work together to accomplish complex tasks.<br><br>
14
- Each robot has its own system prompt, tools, and capabilities. Robots can be orchestrated through networks with customizable routing logic, share information through a hierarchical memory system, and connect to external tools via the Model Context Protocol (MCP).
14
+ Each robot is backed by a persistent LLM chat, configured with keyword arguments, and run with a simple positional message. Robots can be orchestrated through networks with task-based pipelines, share information through a reactive memory system, and connect to external tools via the Model Context Protocol (MCP).
15
15
  </td>
16
16
  </tr>
17
17
  </table>
@@ -24,7 +24,7 @@ Each robot has its own system prompt, tools, and capabilities. Robots can be orc
24
24
 
25
25
  ---
26
26
 
27
- Build applications with multiple specialized AI agents, each with unique capabilities and personalities.
27
+ Build applications with multiple specialized AI agents, each inheriting from `RubyLLM::Agent` with persistent chat and memory.
28
28
 
29
29
  [:octicons-arrow-right-24: Learn more](architecture/core-concepts.md)
30
30
 
@@ -32,7 +32,7 @@ Each robot has its own system prompt, tools, and capabilities. Robots can be orc
32
32
 
33
33
  ---
34
34
 
35
- Connect robots in networks with flexible routing to handle complex, multi-step workflows.
35
+ Connect robots in task-based pipelines using SimpleFlow with sequential, parallel, and conditional execution.
36
36
 
37
37
  [:octicons-arrow-right-24: Creating Networks](guides/creating-networks.md)
38
38
 
@@ -40,7 +40,7 @@ Each robot has its own system prompt, tools, and capabilities. Robots can be orc
40
40
 
41
41
  ---
42
42
 
43
- Give robots custom tools to interact with external systems, databases, and APIs.
43
+ Give robots tools via `RubyLLM::Tool` subclasses or `RobotLab::Tool.new` with block handlers.
44
44
 
45
45
  [:octicons-arrow-right-24: Using Tools](guides/using-tools.md)
46
46
 
@@ -52,11 +52,19 @@ Each robot has its own system prompt, tools, and capabilities. Robots can be orc
52
52
 
53
53
  [:octicons-arrow-right-24: MCP Guide](guides/mcp-integration.md)
54
54
 
55
- - :material-memory:{ .lg .middle } **Shared Memory**
55
+ - :material-message-arrow-right-outline:{ .lg .middle } **Message Bus**
56
56
 
57
57
  ---
58
58
 
59
- Robots can share information through a hierarchical memory system with namespaced scopes.
59
+ Enable bidirectional, cyclic communication between robots via TypedBus for negotiation loops and convergence patterns.
60
+
61
+ [:octicons-arrow-right-24: Message Bus](architecture/core-concepts.md#message-bus)
62
+
63
+ - :material-memory:{ .lg .middle } **Reactive Memory**
64
+
65
+ ---
66
+
67
+ Robots share data through a reactive key-value memory system with subscriptions, blocking reads, and optional Redis backend.
60
68
 
61
69
  [:octicons-arrow-right-24: Memory System](guides/memory.md)
62
70
 
@@ -75,33 +83,34 @@ Each robot has its own system prompt, tools, and capabilities. Robots can be orc
75
83
  ```ruby
76
84
  require "robot_lab"
77
85
 
78
- # Configure RobotLab
79
- RobotLab.configure do |config|
80
- config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
81
- config.default_model = "claude-sonnet-4"
82
- end
83
-
84
- # Create a simple robot
85
- robot = RobotLab.build do
86
- name "assistant"
87
- description "A helpful AI assistant"
88
- template <<~PROMPT
89
- You are a helpful assistant. Answer questions clearly and concisely.
90
- PROMPT
91
- end
92
-
93
- # Create a network with the robot
94
- network = RobotLab.create_network do
95
- name "simple_network"
96
- add_robot robot
97
- end
98
-
99
- # Run the network
100
- state = RobotLab.create_state(message: "What is the capital of France?")
101
- result = network.run(state: state)
102
-
103
- puts result.last_result.output.first.content
86
+ # Configuration is automatic via environment variables, YAML files, or defaults.
87
+ # Set API keys via env vars:
88
+ # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
89
+ #
90
+ # Or place a config file at ~/.config/robot_lab/config.yml
91
+ # Access config values: RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
92
+
93
+ # Create a robot with keyword arguments
94
+ robot = RobotLab.build(
95
+ name: "assistant",
96
+ system_prompt: "You are a helpful assistant. Answer questions clearly and concisely.",
97
+ model: "claude-sonnet-4"
98
+ )
99
+
100
+ # Run the robot with a positional string argument
101
+ result = robot.run("What is the capital of France?")
102
+
103
+ puts result.last_text_content
104
104
  # => "The capital of France is Paris."
105
+
106
+ # Memory persists across runs
107
+ robot.run("Remember that my favorite color is blue.")
108
+ result = robot.run("What is my favorite color?")
109
+ puts result.last_text_content
110
+ # => "Your favorite color is blue."
111
+
112
+ # Chaining configuration
113
+ robot.with_instructions("Be extra concise.").with_temperature(0.3).run("Explain Ruby in one sentence.")
105
114
  ```
106
115
 
107
116
  ## Supported LLM Providers
@@ -110,12 +119,16 @@ RobotLab supports multiple LLM providers through the [ruby_llm](https://github.c
110
119
 
111
120
  | Provider | Models |
112
121
  |----------|--------|
113
- | **Anthropic** | Claude 4, Claude Sonnet, Claude Haiku |
114
- | **OpenAI** | GPT-4o, GPT-4, GPT-3.5 Turbo |
115
- | **Google** | Gemini Pro, Gemini Ultra |
116
- | **Azure OpenAI** | All Azure-hosted OpenAI models |
117
- | **Bedrock** | Claude models via AWS Bedrock |
122
+ | **Anthropic** | Claude Opus 4, Claude Sonnet 4, Claude Haiku 3.5 |
123
+ | **OpenAI** | GPT-4o, GPT-4, o1, o3 |
124
+ | **Google** | Gemini 2.5 Pro, Gemini 2.5 Flash |
125
+ | **DeepSeek** | DeepSeek V3, DeepSeek R1 |
126
+ | **AWS Bedrock** | Claude models via AWS Bedrock |
127
+ | **Google Vertex AI** | Gemini models via Vertex AI |
118
128
  | **Ollama** | Local models via Ollama |
129
+ | **OpenRouter** | Multi-provider routing |
130
+ | **Mistral** | Mistral Large, Mistral Medium |
131
+ | **xAI** | Grok models |
119
132
 
120
133
  ## Installation
121
134
 
@@ -8,26 +8,23 @@
8
8
  # Usage:
9
9
  # ANTHROPIC_API_KEY=your_key ruby examples/01_simple_robot.rb
10
10
 
11
- require_relative "../lib/robot_lab"
11
+ # Configure template path before loading (MywayConfig reads env vars on init)
12
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
12
13
 
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
14
+ require_relative "../lib/robot_lab"
18
15
 
19
16
  # Create a simple robot using a template
20
17
  robot = RobotLab.build(
21
18
  name: "helper",
22
19
  template: :helper,
23
- model: "claude-sonnet-4"
20
+ model: "claude-3-haiku-20240307"
24
21
  )
25
22
 
26
23
  puts "Running simple robot..."
27
24
  puts "-" * 40
28
25
 
29
- # Run the robot with a simple query
30
- result = robot.run(message: "What is 2 + 2? Please explain your reasoning briefly.")
26
+ # Run the robot
27
+ result = robot.run("What is 2 + 2? Please explain your reasoning briefly.")
31
28
 
32
29
  # Display the result
33
30
  puts "Robot: #{robot.name}"
data/examples/02_tools.rb CHANGED
@@ -8,13 +8,10 @@
8
8
  # Usage:
9
9
  # ANTHROPIC_API_KEY=your_key ruby examples/02_tools.rb
10
10
 
11
- require_relative "../lib/robot_lab"
11
+ # Configure template path before loading (MywayConfig reads env vars on init)
12
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
12
13
 
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
14
+ require_relative "../lib/robot_lab"
18
15
 
19
16
  # Define tools using RubyLLM::Tool
20
17
  class Calculator < RubyLLM::Tool
@@ -86,15 +83,15 @@ end
86
83
  robot = RobotLab.build(
87
84
  name: "assistant",
88
85
  template: :assistant,
89
- tools: [Calculator, FortuneCookie],
90
- model: "claude-sonnet-4"
86
+ local_tools: [Calculator, FortuneCookie],
87
+ model: "claude-3-haiku-20240307"
91
88
  )
92
89
 
93
90
  puts "Running robot with tools..."
94
91
  puts "-" * 40
95
92
 
96
93
  # Run the robot
97
- result = robot.run(message: "What is 15 multiplied by 7? Also, give me a fortune about my career.")
94
+ result = robot.run("What is 15 multiplied by 7? Also, give me a fortune about my career.")
98
95
 
99
96
  # Display results
100
97
  puts "Robot: #{robot.name}"
@@ -9,25 +9,24 @@
9
9
  # Usage:
10
10
  # ANTHROPIC_API_KEY=your_key ruby examples/03_network.rb
11
11
 
12
- require_relative "../lib/robot_lab"
12
+ # Configure template path before loading (MywayConfig reads env vars on init)
13
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
13
14
 
14
- # Configure RobotLab
15
- RobotLab.configure do |config|
16
- config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
17
- config.template_path = File.join(__dir__, "prompts")
18
- end
15
+ require_relative "../lib/robot_lab"
19
16
 
20
17
  # Classifier robot that activates the appropriate specialist
21
18
  class ClassifierRobot < RobotLab::Robot
22
19
  def call(result)
23
- robot_result = run(**extract_run_context(result))
20
+ run_context = extract_run_context(result)
21
+ message = run_context.delete(:message)
22
+ robot_result = run(message, **run_context)
24
23
 
25
24
  new_result = result
26
25
  .with_context(@name.to_sym, robot_result)
27
26
  .continue(robot_result)
28
27
 
29
28
  # Examine LLM output and activate appropriate specialist
30
- category = robot_result.last_text_content.to_s.strip.downcase
29
+ category = robot_result.reply.to_s.strip.downcase
31
30
 
32
31
  case category
33
32
  when /billing/
@@ -44,25 +43,25 @@ end
44
43
  classifier = ClassifierRobot.new(
45
44
  name: "classifier",
46
45
  template: :classifier,
47
- model: "claude-sonnet-4"
46
+ model: "claude-3-haiku-20240307"
48
47
  )
49
48
 
50
49
  billing_robot = RobotLab.build(
51
50
  name: "billing",
52
51
  template: :billing,
53
- model: "claude-sonnet-4"
52
+ model: "claude-3-haiku-20240307"
54
53
  )
55
54
 
56
55
  technical_robot = RobotLab.build(
57
56
  name: "technical",
58
57
  template: :technical,
59
- model: "claude-sonnet-4"
58
+ model: "claude-3-haiku-20240307"
60
59
  )
61
60
 
62
61
  general_robot = RobotLab.build(
63
62
  name: "general",
64
63
  template: :general,
65
- model: "claude-sonnet-4"
64
+ model: "claude-3-haiku-20240307"
66
65
  )
67
66
 
68
67
  # Create network with optional task routing
@@ -90,13 +89,13 @@ puts "\nConversation flow:"
90
89
  if result.context[:classifier]
91
90
  classifier_result = result.context[:classifier]
92
91
  puts "\n1. Robot: classifier"
93
- puts " Classification: #{classifier_result.last_text_content}"
92
+ puts " Classification: #{classifier_result.reply}"
94
93
  end
95
94
 
96
95
  # Show specialist result (the final value)
97
96
  if result.value.is_a?(RobotLab::RobotResult)
98
97
  puts "\n2. Robot: #{result.value.robot_name}"
99
- content = result.value.last_text_content
98
+ content = result.value.reply
100
99
  puts " Response: #{content[0..200]}..." if content
101
100
  end
102
101
 
data/examples/04_mcp.rb CHANGED
@@ -21,13 +21,10 @@
21
21
  # - Reading file contents and repository information
22
22
  # - Managing branches and commits
23
23
 
24
- require_relative "../lib/robot_lab"
24
+ # Configure template path before loading (MywayConfig reads env vars on init)
25
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
25
26
 
26
- # Configure RobotLab
27
- RobotLab.configure do |config|
28
- config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
29
- config.template_path = File.join(__dir__, "prompts")
30
- end
27
+ require_relative "../lib/robot_lab"
31
28
 
32
29
  # GitHub MCP server configuration using StdIO transport
33
30
  github_server = {
@@ -151,7 +148,7 @@ begin
151
148
  name: "github_assistant",
152
149
  template: :github_assistant,
153
150
  mcp_servers: [github_server],
154
- model: "claude-sonnet-4-20250514"
151
+ model: "claude-3-haiku-20240307"
155
152
  )
156
153
 
157
154
  puts "Robot created: #{robot.name}"
@@ -175,7 +172,7 @@ begin
175
172
  puts "Query: 'What are the top 3 most starred Ruby web frameworks on GitHub?'"
176
173
  puts "-" * 40
177
174
 
178
- result = robot.run(message: "What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.")
175
+ result = robot.run("What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.")
179
176
 
180
177
  puts
181
178
  puts "Robot Response:"
@@ -8,13 +8,10 @@
8
8
  # Usage:
9
9
  # ANTHROPIC_API_KEY=your_key ruby examples/05_streaming.rb
10
10
 
11
- require_relative "../lib/robot_lab"
11
+ # Configure template path before loading (MywayConfig reads env vars on init)
12
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
12
13
 
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
14
+ require_relative "../lib/robot_lab"
18
15
 
19
16
  # Create a streaming handler
20
17
  streaming_handler = lambda do |event|
@@ -97,11 +94,11 @@ puts <<~CODE
97
94
  robot = RobotLab.build(
98
95
  name: "streamer",
99
96
  template: :helper,
100
- model: "claude-sonnet-4"
97
+ model: "claude-3-haiku-20240307"
101
98
  )
102
99
 
103
100
  # Run with streaming callback
104
- result = robot.run(message: "Tell me a story") do |event|
101
+ result = robot.run("Tell me a story") do |event|
105
102
  case event[:event]
106
103
  when "text.delta"
107
104
  print event[:data][:delta]
@@ -1,29 +1,24 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Example 6: Prompt Templates with ruby_llm-template
4
+ # Example 6: Prompt Templates with prompt_manager
5
5
  #
6
- # Demonstrates using ruby_llm-template for organized, reusable prompts
6
+ # Demonstrates using prompt_manager for organized, reusable prompts
7
7
  # within a RobotLab network. This example shows an e-commerce support
8
8
  # system with dynamic context injection using SimpleFlow's optional task routing.
9
9
  #
10
10
  # Usage:
11
11
  # ANTHROPIC_API_KEY=your_key ruby examples/06_prompt_templates.rb
12
12
  #
13
- # Template Structure:
13
+ # Template Structure (prompt_manager .md files with YAML front matter):
14
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
15
+ # ├── triage.md
16
+ # ├── order_support.md
17
+ # ├── product_support.md
18
+ # └── escalation.md
19
+
20
+ # Configure template path before loading (MywayConfig reads env vars on init)
21
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
27
22
 
28
23
  require_relative "../lib/robot_lab"
29
24
 
@@ -31,8 +26,14 @@ require_relative "../lib/robot_lab"
31
26
  # Sample Data
32
27
  # =============================================================================
33
28
  # Simulated customer and business data that would come from your database.
29
+ # company_name is a template parameter (declared null in all four templates).
30
+ # AskUser gathers it from the user before building robots.
34
31
 
35
- COMPANY_NAME = "TechGear Pro"
32
+ ask = RobotLab::AskUser.new
33
+ COMPANY_NAME = ask.call(
34
+ "question" => "What is your company name?",
35
+ "default" => "TechGear Pro"
36
+ )
36
37
 
37
38
  SAMPLE_CUSTOMER = {
38
39
  name: "Sarah Johnson",
@@ -149,14 +150,16 @@ ESCALATION_AUTHORITIES = [
149
150
  # Custom triage robot that classifies and activates appropriate specialist
150
151
  class TriageRobot < RobotLab::Robot
151
152
  def call(result)
152
- robot_result = run(**extract_run_context(result))
153
+ run_context = extract_run_context(result)
154
+ message = run_context.delete(:message)
155
+ robot_result = run(message, **run_context)
153
156
 
154
157
  new_result = result
155
158
  .with_context(@name.to_sym, robot_result)
156
159
  .continue(robot_result)
157
160
 
158
161
  # Examine LLM output and activate appropriate specialist
159
- classification = robot_result.last_text_content.to_s.strip.downcase
162
+ classification = robot_result.reply.to_s.strip.downcase
160
163
 
161
164
  case classification
162
165
  when /order/
@@ -182,11 +185,7 @@ puts "E-Commerce Support Network with Dynamic Context"
182
185
  puts "=" * 70
183
186
  puts
184
187
 
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
188
+ # Template path is set via ROBOT_LAB_TEMPLATE_PATH env var (see top of file)
190
189
 
191
190
  # -----------------------------------------------------------------------------
192
191
  # Build Robots with Template-Based Prompts
@@ -197,11 +196,12 @@ triage_robot = TriageRobot.new(
197
196
  name: "triage",
198
197
  description: "Classifies incoming requests to route to specialists",
199
198
  template: :triage,
199
+ local_tools: [RobotLab::AskUser],
200
200
  context: {
201
201
  company_name: COMPANY_NAME,
202
202
  categories: CATEGORIES
203
203
  },
204
- model: "claude-sonnet-4"
204
+ model: "claude-3-haiku-20240307"
205
205
  )
206
206
 
207
207
  # Order Support Robot
@@ -209,12 +209,13 @@ order_robot = RobotLab.build(
209
209
  name: "order",
210
210
  description: "Handles order-related inquiries with full order history",
211
211
  template: :order_support,
212
+ local_tools: [RobotLab::AskUser],
212
213
  context: {
213
214
  company_name: COMPANY_NAME,
214
215
  policies: POLICIES,
215
216
  capabilities: ORDER_CAPABILITIES
216
217
  },
217
- model: "claude-sonnet-4"
218
+ model: "claude-3-haiku-20240307"
218
219
  )
219
220
 
220
221
  # Product Support Robot
@@ -222,13 +223,14 @@ product_robot = RobotLab.build(
222
223
  name: "product",
223
224
  description: "Answers product questions with catalog knowledge",
224
225
  template: :product_support,
226
+ local_tools: [RobotLab::AskUser],
225
227
  context: {
226
228
  company_name: COMPANY_NAME,
227
229
  products: PRODUCTS,
228
230
  promotions: PROMOTIONS,
229
231
  product_categories: PRODUCT_CATEGORIES
230
232
  },
231
- model: "claude-sonnet-4"
233
+ model: "claude-3-haiku-20240307"
232
234
  )
233
235
 
234
236
  # Escalation Robot
@@ -236,11 +238,12 @@ escalation_robot = RobotLab.build(
236
238
  name: "escalation",
237
239
  description: "Handles complex cases requiring special authority",
238
240
  template: :escalation,
241
+ local_tools: [RobotLab::AskUser],
239
242
  context: {
240
243
  company_name: COMPANY_NAME,
241
244
  authorities: ESCALATION_AUTHORITIES
242
245
  },
243
- model: "claude-sonnet-4"
246
+ model: "claude-3-haiku-20240307"
244
247
  )
245
248
 
246
249
  # -----------------------------------------------------------------------------
@@ -291,14 +294,14 @@ demo_queries.each_with_index do |query, index|
291
294
  # Display triage classification
292
295
  if result.context[:triage]
293
296
  triage_result = result.context[:triage]
294
- puts "Classification: #{triage_result.last_text_content}"
297
+ puts "Classification: #{triage_result.reply}"
295
298
  puts
296
299
  end
297
300
 
298
301
  # Display specialist response (the final value)
299
302
  if result.value.is_a?(RobotLab::RobotResult)
300
303
  puts "Routed to: #{result.value.robot_name.upcase}"
301
- content = result.value.last_text_content.to_s
304
+ content = result.value.reply.to_s
302
305
  # Truncate long responses for display
303
306
  if content.length > 300
304
307
  puts "Response: #{content[0..300]}..."
@@ -314,11 +317,13 @@ end
314
317
  puts
315
318
  puts "Demo Complete!"
316
319
  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')}"
320
+ puts <<~FOOTER
321
+ This example demonstrates:
322
+ - prompt_manager templates (.md with YAML front matter)
323
+ - Build-time context (robot identity/capabilities)
324
+ - Run-time context (customer data, order history)
325
+ - Multi-robot network with optional task routing
326
+ - SimpleFlow::Result for passing context between robots
327
+
328
+ Template files are located in: #{File.join(__dir__, 'prompts')}
329
+ FOOTER
@@ -41,15 +41,12 @@
41
41
  # Usage:
42
42
  # ANTHROPIC_API_KEY=your_key ruby examples/07_network_memory.rb
43
43
 
44
+ # Configure template path before loading (MywayConfig reads env vars on init)
45
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
46
+
44
47
  require_relative "../lib/robot_lab"
45
48
  require "json"
46
49
 
47
- # Configure RobotLab
48
- RobotLab.configure do |config|
49
- config.anthropic_api_key = ENV.fetch("ANTHROPIC_API_KEY", nil)
50
- config.template_path = File.join(__dir__, "prompts")
51
- end
52
-
53
50
  puts "=" * 60
54
51
  puts "Example 7: Network Memory with Concurrent Robots"
55
52
  puts "=" * 60
@@ -69,12 +66,13 @@ class AnalysisRobot < RobotLab::Robot
69
66
  def call(result)
70
67
  run_context = extract_run_context(result)
71
68
  network_memory = run_context.delete(:network_memory)
69
+ message = run_context.delete(:message)
72
70
 
73
- robot_result = run(network_memory: network_memory, **run_context)
71
+ robot_result = run(message, network_memory: network_memory, **run_context)
74
72
 
75
73
  # Parse the JSON response and write to shared memory
76
74
  if network_memory
77
- content = robot_result.last_text_content.to_s
75
+ content = robot_result.reply.to_s
78
76
 
79
77
  # Set writer before writing to memory
80
78
  network_memory.current_writer = @name
@@ -101,6 +99,7 @@ class SynthesizerRobot < RobotLab::Robot
101
99
  def call(result)
102
100
  run_context = extract_run_context(result)
103
101
  network_memory = run_context.delete(:network_memory)
102
+ message = run_context.delete(:message)
104
103
 
105
104
  puts " [#{@name}] Reading analysis results from memory..."
106
105
 
@@ -125,7 +124,7 @@ class SynthesizerRobot < RobotLab::Robot
125
124
  run_context[:keywords] = "Not available"
126
125
  end
127
126
 
128
- robot_result = run(network_memory: network_memory, **run_context)
127
+ robot_result = run(message, network_memory: network_memory, **run_context)
129
128
 
130
129
  result
131
130
  .with_context(@name.to_sym, robot_result)
@@ -154,27 +153,27 @@ sentiment_robot = AnalysisRobot.new(
154
153
  name: "sentiment_analyzer",
155
154
  template: :sentiment_analyzer,
156
155
  memory_key: :sentiment,
157
- model: "claude-sonnet-4"
156
+ model: "claude-3-haiku-20240307"
158
157
  )
159
158
 
160
159
  entity_robot = AnalysisRobot.new(
161
160
  name: "entity_extractor",
162
161
  template: :entity_extractor,
163
162
  memory_key: :entities,
164
- model: "claude-sonnet-4"
163
+ model: "claude-3-haiku-20240307"
165
164
  )
166
165
 
167
166
  keyword_robot = AnalysisRobot.new(
168
167
  name: "keyword_extractor",
169
168
  template: :keyword_extractor,
170
169
  memory_key: :keywords,
171
- model: "claude-sonnet-4"
170
+ model: "claude-3-haiku-20240307"
172
171
  )
173
172
 
174
173
  synthesizer = SynthesizerRobot.new(
175
174
  name: "synthesizer",
176
175
  template: :synthesizer,
177
- model: "claude-sonnet-4"
176
+ model: "claude-3-haiku-20240307"
178
177
  )
179
178
 
180
179
  # -----------------------------------------------------------------------------
@@ -267,7 +266,7 @@ puts "=" * 60
267
266
  puts
268
267
 
269
268
  if result.value.is_a?(RobotLab::RobotResult)
270
- puts result.value.last_text_content
269
+ puts result.value.reply
271
270
  end
272
271
 
273
272
  puts