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
data/docs/index.md
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# RobotLab
|
|
2
|
+
|
|
3
|
+
> [!CAUTION]
|
|
4
|
+
> This gem is under active development. APIs and features may change without notice. See the [CHANGELOG](https://github.com/MadBomber/robot_lab/blob/main/CHANGELOG.md) for details.
|
|
5
|
+
|
|
6
|
+
<table>
|
|
7
|
+
<tr>
|
|
8
|
+
<td width="50%" align="center" valign="top">
|
|
9
|
+
<img src="assets/images/robot_lab.jpg" alt="RobotLab"><br>
|
|
10
|
+
<em>"Build robots. Solve problems."</em>
|
|
11
|
+
</td>
|
|
12
|
+
<td width="50%" valign="top">
|
|
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).
|
|
15
|
+
</td>
|
|
16
|
+
</tr>
|
|
17
|
+
</table>
|
|
18
|
+
|
|
19
|
+
## Key Features
|
|
20
|
+
|
|
21
|
+
<div class="grid cards" markdown>
|
|
22
|
+
|
|
23
|
+
- :material-robot:{ .lg .middle } **Multi-Robot Architecture**
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Build applications with multiple specialized AI agents, each with unique capabilities and personalities.
|
|
28
|
+
|
|
29
|
+
[:octicons-arrow-right-24: Learn more](architecture/core-concepts.md)
|
|
30
|
+
|
|
31
|
+
- :material-transit-connection-variant:{ .lg .middle } **Network Orchestration**
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
Connect robots in networks with flexible routing to handle complex, multi-step workflows.
|
|
36
|
+
|
|
37
|
+
[:octicons-arrow-right-24: Creating Networks](guides/creating-networks.md)
|
|
38
|
+
|
|
39
|
+
- :material-tools:{ .lg .middle } **Extensible Tools**
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
Give robots custom tools to interact with external systems, databases, and APIs.
|
|
44
|
+
|
|
45
|
+
[:octicons-arrow-right-24: Using Tools](guides/using-tools.md)
|
|
46
|
+
|
|
47
|
+
- :material-server-network:{ .lg .middle } **MCP Integration**
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Connect to Model Context Protocol servers to extend robot capabilities with external tools.
|
|
52
|
+
|
|
53
|
+
[:octicons-arrow-right-24: MCP Guide](guides/mcp-integration.md)
|
|
54
|
+
|
|
55
|
+
- :material-memory:{ .lg .middle } **Shared Memory**
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
Robots can share information through a hierarchical memory system with namespaced scopes.
|
|
60
|
+
|
|
61
|
+
[:octicons-arrow-right-24: Memory System](guides/memory.md)
|
|
62
|
+
|
|
63
|
+
- :material-history:{ .lg .middle } **Conversation History**
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
Persist and restore conversation threads for long-running interactions.
|
|
68
|
+
|
|
69
|
+
[:octicons-arrow-right-24: History Guide](guides/history.md)
|
|
70
|
+
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
## Quick Example
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
require "robot_lab"
|
|
77
|
+
|
|
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
|
|
104
|
+
# => "The capital of France is Paris."
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Supported LLM Providers
|
|
108
|
+
|
|
109
|
+
RobotLab supports multiple LLM providers through the [ruby_llm](https://github.com/crmne/ruby_llm) library:
|
|
110
|
+
|
|
111
|
+
| Provider | Models |
|
|
112
|
+
|----------|--------|
|
|
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 |
|
|
118
|
+
| **Ollama** | Local models via Ollama |
|
|
119
|
+
|
|
120
|
+
## Installation
|
|
121
|
+
|
|
122
|
+
Add RobotLab to your Gemfile:
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
gem "robot_lab"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or install directly:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
gem install robot_lab
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
[:octicons-arrow-right-24: Full Installation Guide](getting-started/installation.md)
|
|
135
|
+
|
|
136
|
+
## Next Steps
|
|
137
|
+
|
|
138
|
+
<div class="grid cards" markdown>
|
|
139
|
+
|
|
140
|
+
- [:octicons-rocket-24: **Quick Start**](getting-started/quick-start.md)
|
|
141
|
+
|
|
142
|
+
Get up and running in 5 minutes
|
|
143
|
+
|
|
144
|
+
- [:octicons-book-24: **Concepts**](concepts.md)
|
|
145
|
+
|
|
146
|
+
Understand the core concepts
|
|
147
|
+
|
|
148
|
+
- [:octicons-code-24: **Examples**](examples/index.md)
|
|
149
|
+
|
|
150
|
+
See RobotLab in action
|
|
151
|
+
|
|
152
|
+
- [:octicons-gear-24: **API Reference**](api/index.md)
|
|
153
|
+
|
|
154
|
+
Detailed API documentation
|
|
155
|
+
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
RobotLab is released under the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 1: Simple Robot
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates creating and running a basic robot with a template.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ANTHROPIC_API_KEY=your_key ruby examples/01_simple_robot.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 simple robot using a template
|
|
20
|
+
robot = RobotLab.build(
|
|
21
|
+
name: "helper",
|
|
22
|
+
template: :helper,
|
|
23
|
+
model: "claude-sonnet-4"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
puts "Running simple robot..."
|
|
27
|
+
puts "-" * 40
|
|
28
|
+
|
|
29
|
+
# Run the robot with a simple query
|
|
30
|
+
result = robot.run(message: "What is 2 + 2? Please explain your reasoning briefly.")
|
|
31
|
+
|
|
32
|
+
# Display the result
|
|
33
|
+
puts "Robot: #{robot.name}"
|
|
34
|
+
puts "Output:"
|
|
35
|
+
result.output.each do |message|
|
|
36
|
+
puts " #{message.content}" if message.respond_to?(:content)
|
|
37
|
+
end
|
|
38
|
+
puts "-" * 40
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 2: Robot with Tools
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates creating a robot with custom tools using RubyLLM::Tool.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# ANTHROPIC_API_KEY=your_key ruby examples/02_tools.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
|
+
# Define tools using RubyLLM::Tool
|
|
20
|
+
class Calculator < RubyLLM::Tool
|
|
21
|
+
description "Performs basic arithmetic operations"
|
|
22
|
+
|
|
23
|
+
param :operation,
|
|
24
|
+
type: "string",
|
|
25
|
+
desc: "The operation to perform (add, subtract, multiply, divide)"
|
|
26
|
+
|
|
27
|
+
param :a,
|
|
28
|
+
type: "number",
|
|
29
|
+
desc: "First operand"
|
|
30
|
+
|
|
31
|
+
param :b,
|
|
32
|
+
type: "number",
|
|
33
|
+
desc: "Second operand"
|
|
34
|
+
|
|
35
|
+
def execute(operation:, a:, b:)
|
|
36
|
+
case operation
|
|
37
|
+
when "add" then a + b
|
|
38
|
+
when "subtract" then a - b
|
|
39
|
+
when "multiply" then a * b
|
|
40
|
+
when "divide" then a.to_f / b
|
|
41
|
+
else "Unknown operation: #{operation}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class FortuneCookie < RubyLLM::Tool
|
|
47
|
+
description "Get a fortune cookie message with wisdom and lucky numbers"
|
|
48
|
+
|
|
49
|
+
param :category,
|
|
50
|
+
type: "string",
|
|
51
|
+
desc: "The category of fortune (wisdom, love, career, adventure)"
|
|
52
|
+
|
|
53
|
+
FORTUNES = {
|
|
54
|
+
"wisdom" => [
|
|
55
|
+
"The obstacle in the path becomes the path.",
|
|
56
|
+
"A journey of a thousand miles begins with a single step.",
|
|
57
|
+
"The best time to plant a tree was 20 years ago. The second best time is now."
|
|
58
|
+
],
|
|
59
|
+
"love" => [
|
|
60
|
+
"The heart that loves is always young.",
|
|
61
|
+
"To love and be loved is to feel the sun from both sides.",
|
|
62
|
+
"Love is not about finding the right person, but being the right person."
|
|
63
|
+
],
|
|
64
|
+
"career" => [
|
|
65
|
+
"Opportunity dances with those already on the dance floor.",
|
|
66
|
+
"Your work is your signature. Sign it with excellence.",
|
|
67
|
+
"The expert in anything was once a beginner."
|
|
68
|
+
],
|
|
69
|
+
"adventure" => [
|
|
70
|
+
"Life shrinks or expands in proportion to one's courage.",
|
|
71
|
+
"Not all who wander are lost.",
|
|
72
|
+
"The biggest adventure you can take is to live the life of your dreams."
|
|
73
|
+
]
|
|
74
|
+
}.freeze
|
|
75
|
+
|
|
76
|
+
def execute(category:)
|
|
77
|
+
{
|
|
78
|
+
category: category,
|
|
79
|
+
fortune: FORTUNES.fetch(category, FORTUNES["wisdom"]).sample,
|
|
80
|
+
lucky_numbers: Array.new(6) { rand(1..49) }.sort
|
|
81
|
+
}
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Create robot with tools
|
|
86
|
+
robot = RobotLab.build(
|
|
87
|
+
name: "assistant",
|
|
88
|
+
template: :assistant,
|
|
89
|
+
tools: [Calculator, FortuneCookie],
|
|
90
|
+
model: "claude-sonnet-4"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
puts "Running robot with tools..."
|
|
94
|
+
puts "-" * 40
|
|
95
|
+
|
|
96
|
+
# Run the robot
|
|
97
|
+
result = robot.run(message: "What is 15 multiplied by 7? Also, give me a fortune about my career.")
|
|
98
|
+
|
|
99
|
+
# Display results
|
|
100
|
+
puts "Robot: #{robot.name}"
|
|
101
|
+
puts "\nOutput:"
|
|
102
|
+
result.output.each do |message|
|
|
103
|
+
puts " #{message.content}" if message.respond_to?(:content)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
puts "-" * 40
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 3: Multi-Robot Network
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates creating a network of robots with conditional routing
|
|
7
|
+
# using SimpleFlow's optional step activation.
|
|
8
|
+
#
|
|
9
|
+
# Usage:
|
|
10
|
+
# ANTHROPIC_API_KEY=your_key ruby examples/03_network.rb
|
|
11
|
+
|
|
12
|
+
require_relative "../lib/robot_lab"
|
|
13
|
+
|
|
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
|
|
19
|
+
|
|
20
|
+
# Classifier robot that activates the appropriate specialist
|
|
21
|
+
class ClassifierRobot < RobotLab::Robot
|
|
22
|
+
def call(result)
|
|
23
|
+
robot_result = run(**extract_run_context(result))
|
|
24
|
+
|
|
25
|
+
new_result = result
|
|
26
|
+
.with_context(@name.to_sym, robot_result)
|
|
27
|
+
.continue(robot_result)
|
|
28
|
+
|
|
29
|
+
# Examine LLM output and activate appropriate specialist
|
|
30
|
+
category = robot_result.last_text_content.to_s.strip.downcase
|
|
31
|
+
|
|
32
|
+
case category
|
|
33
|
+
when /billing/
|
|
34
|
+
new_result.activate(:billing)
|
|
35
|
+
when /technical/
|
|
36
|
+
new_result.activate(:technical)
|
|
37
|
+
else
|
|
38
|
+
new_result.activate(:general)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Create specialized robots
|
|
44
|
+
classifier = ClassifierRobot.new(
|
|
45
|
+
name: "classifier",
|
|
46
|
+
template: :classifier,
|
|
47
|
+
model: "claude-sonnet-4"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
billing_robot = RobotLab.build(
|
|
51
|
+
name: "billing",
|
|
52
|
+
template: :billing,
|
|
53
|
+
model: "claude-sonnet-4"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
technical_robot = RobotLab.build(
|
|
57
|
+
name: "technical",
|
|
58
|
+
template: :technical,
|
|
59
|
+
model: "claude-sonnet-4"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
general_robot = RobotLab.build(
|
|
63
|
+
name: "general",
|
|
64
|
+
template: :general,
|
|
65
|
+
model: "claude-sonnet-4"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
# Create network with optional task routing
|
|
69
|
+
network = RobotLab.create_network(name: "support_network") do
|
|
70
|
+
task :classifier, classifier, depends_on: :none
|
|
71
|
+
task :billing, billing_robot, depends_on: :optional
|
|
72
|
+
task :technical, technical_robot, depends_on: :optional
|
|
73
|
+
task :general, general_robot, depends_on: :optional
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
puts "Running multi-robot network..."
|
|
77
|
+
puts "-" * 40
|
|
78
|
+
puts "Network structure:"
|
|
79
|
+
puts network.visualize
|
|
80
|
+
puts "-" * 40
|
|
81
|
+
|
|
82
|
+
# Run the network with a billing question
|
|
83
|
+
result = network.run(message: "I was charged twice for my subscription last month. Can you help?")
|
|
84
|
+
|
|
85
|
+
# Display results
|
|
86
|
+
puts "Network: #{network.name}"
|
|
87
|
+
puts "\nConversation flow:"
|
|
88
|
+
|
|
89
|
+
# Show classifier result
|
|
90
|
+
if result.context[:classifier]
|
|
91
|
+
classifier_result = result.context[:classifier]
|
|
92
|
+
puts "\n1. Robot: classifier"
|
|
93
|
+
puts " Classification: #{classifier_result.last_text_content}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Show specialist result (the final value)
|
|
97
|
+
if result.value.is_a?(RobotLab::RobotResult)
|
|
98
|
+
puts "\n2. Robot: #{result.value.robot_name}"
|
|
99
|
+
content = result.value.last_text_content
|
|
100
|
+
puts " Response: #{content[0..200]}..." if content
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
puts "-" * 40
|
data/examples/04_mcp.rb
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Example 4: MCP (Model Context Protocol) Integration with GitHub
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates connecting to the GitHub MCP server and using its tools
|
|
7
|
+
# to interact with GitHub repositories.
|
|
8
|
+
#
|
|
9
|
+
# Prerequisites:
|
|
10
|
+
# 1. Install the GitHub MCP server: brew install github-mcp-server
|
|
11
|
+
# 2. Set environment variables:
|
|
12
|
+
# - ANTHROPIC_API_KEY: Your Anthropic API key
|
|
13
|
+
# - GITHUB_PERSONAL_ACCESS_TOKEN: Your GitHub personal access token
|
|
14
|
+
#
|
|
15
|
+
# Usage:
|
|
16
|
+
# ANTHROPIC_API_KEY=your_key GITHUB_PERSONAL_ACCESS_TOKEN=your_token ruby examples/04_mcp.rb
|
|
17
|
+
#
|
|
18
|
+
# The GitHub MCP server provides tools for:
|
|
19
|
+
# - Searching repositories, code, issues, and users
|
|
20
|
+
# - Creating and managing issues and pull requests
|
|
21
|
+
# - Reading file contents and repository information
|
|
22
|
+
# - Managing branches and commits
|
|
23
|
+
|
|
24
|
+
require_relative "../lib/robot_lab"
|
|
25
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
# GitHub MCP server configuration using StdIO transport
|
|
33
|
+
github_server = {
|
|
34
|
+
name: "github",
|
|
35
|
+
transport: {
|
|
36
|
+
type: "stdio",
|
|
37
|
+
command: "github-mcp-server",
|
|
38
|
+
args: ["stdio"],
|
|
39
|
+
env: {
|
|
40
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN" => ENV.fetch("GITHUB_PERSONAL_ACCESS_TOKEN", "")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
puts <<~HEADER
|
|
46
|
+
MCP Integration Example: GitHub
|
|
47
|
+
#{"=" * 40}
|
|
48
|
+
|
|
49
|
+
This example demonstrates using the GitHub MCP server to interact
|
|
50
|
+
with GitHub repositories through the Model Context Protocol.
|
|
51
|
+
|
|
52
|
+
HEADER
|
|
53
|
+
|
|
54
|
+
# Verify prerequisites
|
|
55
|
+
unless ENV["GITHUB_PERSONAL_ACCESS_TOKEN"]
|
|
56
|
+
puts <<~ERROR
|
|
57
|
+
ERROR: GITHUB_PERSONAL_ACCESS_TOKEN environment variable not set.
|
|
58
|
+
|
|
59
|
+
To use this example:
|
|
60
|
+
1. Create a GitHub Personal Access Token at https://github.com/settings/tokens
|
|
61
|
+
2. Grant appropriate permissions (repo, read:user, etc.)
|
|
62
|
+
3. Run: GITHUB_PERSONAL_ACCESS_TOKEN=your_token ruby examples/04_mcp.rb
|
|
63
|
+
ERROR
|
|
64
|
+
exit 1
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# ============================================================================
|
|
68
|
+
# Part 1: Direct MCP Client Usage
|
|
69
|
+
# ============================================================================
|
|
70
|
+
|
|
71
|
+
puts "PART 1: Direct MCP Client Usage"
|
|
72
|
+
puts "-" * 40
|
|
73
|
+
puts
|
|
74
|
+
puts "Connecting to GitHub MCP server..."
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
# Create MCP client and connect
|
|
78
|
+
client = RobotLab::MCP::Client.new(github_server)
|
|
79
|
+
client.connect
|
|
80
|
+
|
|
81
|
+
unless client.connected?
|
|
82
|
+
puts <<~ERROR
|
|
83
|
+
|
|
84
|
+
ERROR: Failed to connect to the GitHub MCP server.
|
|
85
|
+
|
|
86
|
+
Make sure you have installed it:
|
|
87
|
+
brew install github-mcp-server
|
|
88
|
+
ERROR
|
|
89
|
+
exit 1
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
puts "Connected successfully!"
|
|
93
|
+
puts
|
|
94
|
+
|
|
95
|
+
# List available tools
|
|
96
|
+
puts "Available GitHub Tools:"
|
|
97
|
+
puts "-" * 40
|
|
98
|
+
|
|
99
|
+
tools = client.list_tools
|
|
100
|
+
if tools.empty?
|
|
101
|
+
puts " (No tools returned - check server connection)"
|
|
102
|
+
else
|
|
103
|
+
tools.each do |tool|
|
|
104
|
+
puts " #{tool[:name]}"
|
|
105
|
+
puts " #{tool[:description]}" if tool[:description]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
puts "-" * 40
|
|
110
|
+
puts "Total: #{tools.size} tools available"
|
|
111
|
+
puts
|
|
112
|
+
|
|
113
|
+
# Demonstrate a simple tool call: search for repositories
|
|
114
|
+
puts "Demo: Searching for popular Ruby repositories..."
|
|
115
|
+
puts
|
|
116
|
+
|
|
117
|
+
result = client.call_tool("search_repositories", {
|
|
118
|
+
query: "language:ruby stars:>1000",
|
|
119
|
+
per_page: 5
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Extract and pretty print the JSON result
|
|
123
|
+
if result.is_a?(Array) && result.first.is_a?(Hash) && result.first[:text]
|
|
124
|
+
data = JSON.parse(result.first[:text])
|
|
125
|
+
puts JSON.pretty_generate(data)
|
|
126
|
+
else
|
|
127
|
+
puts JSON.pretty_generate(result)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
puts
|
|
131
|
+
|
|
132
|
+
# Clean up direct client
|
|
133
|
+
client.disconnect
|
|
134
|
+
puts "Disconnected from direct MCP client."
|
|
135
|
+
|
|
136
|
+
# ============================================================================
|
|
137
|
+
# Part 2: Robot + MCP Integration
|
|
138
|
+
# ============================================================================
|
|
139
|
+
|
|
140
|
+
puts
|
|
141
|
+
puts "=" * 40
|
|
142
|
+
puts "PART 2: Robot + MCP Integration"
|
|
143
|
+
puts "-" * 40
|
|
144
|
+
puts
|
|
145
|
+
|
|
146
|
+
puts "Creating Robot with MCP server integration..."
|
|
147
|
+
puts
|
|
148
|
+
|
|
149
|
+
# Create a Robot with MCP server - tools are automatically discovered
|
|
150
|
+
robot = RobotLab.build(
|
|
151
|
+
name: "github_assistant",
|
|
152
|
+
template: :github_assistant,
|
|
153
|
+
mcp_servers: [github_server],
|
|
154
|
+
model: "claude-sonnet-4-20250514"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
puts "Robot created: #{robot.name}"
|
|
158
|
+
puts " Model: #{robot.model}"
|
|
159
|
+
puts " MCP Servers: #{robot.mcp_clients.keys.join(", ")}"
|
|
160
|
+
puts " MCP Tools discovered: #{robot.mcp_tools.size}"
|
|
161
|
+
puts
|
|
162
|
+
|
|
163
|
+
# Show discovered MCP tools
|
|
164
|
+
puts "Discovered MCP Tools:"
|
|
165
|
+
puts "-" * 40
|
|
166
|
+
robot.mcp_tools.first(10).each do |tool|
|
|
167
|
+
puts " #{tool.name}"
|
|
168
|
+
puts " #{tool.description&.slice(0, 60)}..." if tool.description
|
|
169
|
+
end
|
|
170
|
+
puts " ... and #{robot.mcp_tools.size - 10} more" if robot.mcp_tools.size > 10
|
|
171
|
+
puts
|
|
172
|
+
|
|
173
|
+
# Run the robot with a query that will use MCP tools
|
|
174
|
+
puts "Running Robot with a GitHub query..."
|
|
175
|
+
puts "Query: 'What are the top 3 most starred Ruby web frameworks on GitHub?'"
|
|
176
|
+
puts "-" * 40
|
|
177
|
+
|
|
178
|
+
result = robot.run(message: "What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.")
|
|
179
|
+
|
|
180
|
+
puts
|
|
181
|
+
puts "Robot Response:"
|
|
182
|
+
puts "-" * 40
|
|
183
|
+
result.output.each do |msg|
|
|
184
|
+
puts msg.content if msg.respond_to?(:content)
|
|
185
|
+
end
|
|
186
|
+
puts
|
|
187
|
+
|
|
188
|
+
# Show tool calls if any were made
|
|
189
|
+
if result.tool_calls.any?
|
|
190
|
+
puts "Tool Calls Made:"
|
|
191
|
+
puts "-" * 40
|
|
192
|
+
result.tool_calls.each do |tc|
|
|
193
|
+
tool_info = tc.respond_to?(:tool) ? tc.tool : tc
|
|
194
|
+
puts " #{tool_info[:name] || tool_info}"
|
|
195
|
+
end
|
|
196
|
+
puts
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Clean up robot's MCP connections
|
|
200
|
+
robot.disconnect
|
|
201
|
+
puts "Robot MCP connections disconnected."
|
|
202
|
+
|
|
203
|
+
rescue RobotLab::MCPError => e
|
|
204
|
+
puts "MCP Error: #{e.message}"
|
|
205
|
+
exit 1
|
|
206
|
+
rescue Errno::ENOENT
|
|
207
|
+
puts <<~ERROR
|
|
208
|
+
|
|
209
|
+
ERROR: Could not find the github-mcp-server command.
|
|
210
|
+
|
|
211
|
+
Install it with:
|
|
212
|
+
brew install github-mcp-server
|
|
213
|
+
ERROR
|
|
214
|
+
exit 1
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
puts
|
|
218
|
+
puts "=" * 40
|
|
219
|
+
puts "Example complete!"
|