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.
- checksums.yaml +4 -4
- data/.github/workflows/deploy-github-pages.yml +9 -9
- data/.irbrc +6 -0
- data/CHANGELOG.md +90 -0
- data/README.md +203 -46
- data/Rakefile +70 -1
- data/docs/api/core/index.md +12 -0
- data/docs/api/core/robot.md +478 -130
- data/docs/api/core/tool.md +205 -209
- data/docs/api/history/active-record-adapter.md +174 -94
- data/docs/api/history/config.md +186 -93
- data/docs/api/history/index.md +57 -61
- data/docs/api/history/thread-manager.md +123 -73
- data/docs/api/mcp/client.md +119 -48
- data/docs/api/mcp/index.md +75 -60
- data/docs/api/mcp/server.md +120 -136
- data/docs/api/mcp/transports.md +172 -184
- data/docs/api/streaming/context.md +157 -74
- data/docs/api/streaming/events.md +114 -166
- data/docs/api/streaming/index.md +74 -72
- data/docs/architecture/core-concepts.md +361 -112
- data/docs/architecture/index.md +97 -59
- data/docs/architecture/message-flow.md +138 -129
- data/docs/architecture/network-orchestration.md +197 -50
- data/docs/architecture/robot-execution.md +199 -146
- data/docs/architecture/state-management.md +255 -187
- data/docs/concepts.md +312 -48
- data/docs/examples/basic-chat.md +89 -77
- data/docs/examples/index.md +222 -47
- data/docs/examples/mcp-server.md +207 -203
- data/docs/examples/multi-robot-network.md +129 -35
- data/docs/examples/rails-application.md +159 -160
- data/docs/examples/tool-usage.md +295 -204
- data/docs/getting-started/configuration.md +275 -162
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/installation.md +22 -13
- data/docs/getting-started/quick-start.md +166 -121
- data/docs/guides/building-robots.md +417 -212
- data/docs/guides/creating-networks.md +94 -24
- data/docs/guides/mcp-integration.md +152 -113
- data/docs/guides/memory.md +220 -164
- data/docs/guides/streaming.md +80 -110
- data/docs/guides/using-tools.md +259 -212
- data/docs/index.md +50 -37
- data/examples/01_simple_robot.rb +6 -9
- data/examples/02_tools.rb +6 -9
- data/examples/03_network.rb +13 -14
- data/examples/04_mcp.rb +5 -8
- data/examples/05_streaming.rb +5 -8
- data/examples/06_prompt_templates.rb +42 -37
- data/examples/07_network_memory.rb +13 -14
- data/examples/08_llm_config.rb +140 -0
- data/examples/09_chaining.rb +223 -0
- data/examples/10_memory.rb +331 -0
- data/examples/11_network_introspection.rb +230 -0
- data/examples/12_message_bus.rb +74 -0
- data/examples/13_spawn.rb +90 -0
- data/examples/14_rusty_circuit/comic.rb +143 -0
- data/examples/14_rusty_circuit/display.rb +203 -0
- data/examples/14_rusty_circuit/heckler.rb +57 -0
- data/examples/14_rusty_circuit/open_mic.rb +121 -0
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
- data/examples/14_rusty_circuit/scout.rb +173 -0
- data/examples/14_rusty_circuit/scout_notes.md +89 -0
- data/examples/14_rusty_circuit/show.log +234 -0
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
- data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
- data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
- data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
- data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
- data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
- data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
- data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
- data/examples/15_memory_network_and_bus/output/memory.json +13 -0
- data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
- data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
- data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
- data/examples/README.md +197 -0
- data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
- data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
- data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
- data/examples/prompts/comedian.md +6 -0
- data/examples/prompts/comedy_critic.md +10 -0
- data/examples/prompts/configurable.md +9 -0
- data/examples/prompts/dispatcher.md +12 -0
- data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
- data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
- data/examples/prompts/frontmatter_mcp_test.md +9 -0
- data/examples/prompts/frontmatter_named_test.md +5 -0
- data/examples/prompts/frontmatter_tools_test.md +6 -0
- data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
- data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
- data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
- data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
- data/examples/prompts/llm_config_demo.md +20 -0
- data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
- data/examples/prompts/os_advocate.md +13 -0
- data/examples/prompts/os_chief.md +13 -0
- data/examples/prompts/os_editor.md +13 -0
- data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
- data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
- data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
- data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
- data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +1 -1
- data/lib/robot_lab/adapters/openai.rb +2 -1
- data/lib/robot_lab/ask_user.rb +75 -0
- data/lib/robot_lab/config/defaults.yml +121 -0
- data/lib/robot_lab/config.rb +183 -0
- data/lib/robot_lab/error.rb +6 -0
- data/lib/robot_lab/mcp/client.rb +1 -1
- data/lib/robot_lab/memory.rb +2 -2
- data/lib/robot_lab/robot.rb +523 -249
- data/lib/robot_lab/robot_message.rb +44 -0
- data/lib/robot_lab/robot_result.rb +1 -0
- data/lib/robot_lab/robotic_model.rb +1 -1
- data/lib/robot_lab/streaming/context.rb +1 -1
- data/lib/robot_lab/tool.rb +108 -172
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +2 -18
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +66 -55
- metadata +107 -116
- data/examples/prompts/assistant/user.txt.erb +0 -1
- data/examples/prompts/billing/user.txt.erb +0 -1
- data/examples/prompts/classifier/user.txt.erb +0 -1
- data/examples/prompts/entity_extractor/user.txt.erb +0 -3
- data/examples/prompts/escalation/user.txt.erb +0 -34
- data/examples/prompts/general/user.txt.erb +0 -1
- data/examples/prompts/github_assistant/user.txt.erb +0 -1
- data/examples/prompts/helper/user.txt.erb +0 -1
- data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
- data/examples/prompts/order_support/user.txt.erb +0 -22
- data/examples/prompts/product_support/user.txt.erb +0 -32
- data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
- data/examples/prompts/synthesizer/user.txt.erb +0 -15
- data/examples/prompts/technical/user.txt.erb +0 -1
- data/examples/prompts/triage/user.txt.erb +0 -17
- data/lib/robot_lab/configuration.rb +0 -143
data/docs/examples/tool-usage.md
CHANGED
|
@@ -1,102 +1,222 @@
|
|
|
1
1
|
# Tool Usage
|
|
2
2
|
|
|
3
|
-
Robots with external
|
|
3
|
+
Robots with external capabilities through tools.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This example demonstrates how to give robots access to external systems through tools
|
|
7
|
+
This example demonstrates how to give robots access to external systems through tools. Tools are defined as `RubyLLM::Tool` subclasses or `RobotLab::Tool` instances and passed to robots via the `local_tools:` parameter.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## RubyLLM::Tool Subclass Pattern
|
|
10
|
+
|
|
11
|
+
The primary way to define tools is by subclassing `RubyLLM::Tool`:
|
|
10
12
|
|
|
11
13
|
```ruby
|
|
12
14
|
#!/usr/bin/env ruby
|
|
13
|
-
# examples/
|
|
15
|
+
# examples/tool_usage.rb
|
|
14
16
|
|
|
15
17
|
require "bundler/setup"
|
|
16
18
|
require "robot_lab"
|
|
17
|
-
require "http"
|
|
18
|
-
require "json"
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
# Define tools as RubyLLM::Tool subclasses
|
|
21
|
+
class Calculator < RubyLLM::Tool
|
|
22
|
+
description "Performs basic arithmetic operations"
|
|
23
|
+
|
|
24
|
+
param :operation,
|
|
25
|
+
type: "string",
|
|
26
|
+
desc: "The operation to perform (add, subtract, multiply, divide)"
|
|
27
|
+
|
|
28
|
+
param :a,
|
|
29
|
+
type: "number",
|
|
30
|
+
desc: "First operand"
|
|
31
|
+
|
|
32
|
+
param :b,
|
|
33
|
+
type: "number",
|
|
34
|
+
desc: "Second operand"
|
|
35
|
+
|
|
36
|
+
def execute(operation:, a:, b:)
|
|
37
|
+
case operation
|
|
38
|
+
when "add" then a + b
|
|
39
|
+
when "subtract" then a - b
|
|
40
|
+
when "multiply" then a * b
|
|
41
|
+
when "divide" then a.to_f / b
|
|
42
|
+
else "Unknown operation: #{operation}"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
22
45
|
end
|
|
23
46
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
class FortuneCookie < RubyLLM::Tool
|
|
48
|
+
description "Get a fortune cookie message with wisdom and lucky numbers"
|
|
49
|
+
|
|
50
|
+
param :category,
|
|
51
|
+
type: "string",
|
|
52
|
+
desc: "The category of fortune (wisdom, love, career, adventure)"
|
|
53
|
+
|
|
54
|
+
FORTUNES = {
|
|
55
|
+
"wisdom" => [
|
|
56
|
+
"The obstacle in the path becomes the path.",
|
|
57
|
+
"A journey of a thousand miles begins with a single step."
|
|
58
|
+
],
|
|
59
|
+
"career" => [
|
|
60
|
+
"Opportunity dances with those already on the dance floor.",
|
|
61
|
+
"Your work is your signature. Sign it with excellence."
|
|
62
|
+
]
|
|
63
|
+
}.freeze
|
|
64
|
+
|
|
65
|
+
def execute(category:)
|
|
66
|
+
{
|
|
67
|
+
category: category,
|
|
68
|
+
fortune: FORTUNES.fetch(category, FORTUNES["wisdom"]).sample,
|
|
69
|
+
lucky_numbers: Array.new(6) { rand(1..49) }.sort
|
|
70
|
+
}
|
|
71
|
+
end
|
|
72
|
+
end
|
|
28
73
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
74
|
+
# Create robot with tools via local_tools
|
|
75
|
+
robot = RobotLab.build(
|
|
76
|
+
name: "assistant",
|
|
77
|
+
system_prompt: "You help with math and dispense fortune cookies.",
|
|
78
|
+
local_tools: [Calculator, FortuneCookie],
|
|
79
|
+
model: "claude-sonnet-4"
|
|
80
|
+
)
|
|
33
81
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
PROMPT
|
|
82
|
+
# Run the robot
|
|
83
|
+
result = robot.run("What is 15 multiplied by 7? Also, give me a career fortune.")
|
|
37
84
|
|
|
38
|
-
|
|
39
|
-
|
|
85
|
+
# Display results
|
|
86
|
+
puts "Response: #{result.last_text_content}"
|
|
40
87
|
|
|
41
|
-
|
|
42
|
-
|
|
88
|
+
if result.tool_calls.any?
|
|
89
|
+
puts "\nTool calls made:"
|
|
90
|
+
result.tool_calls.each do |tc|
|
|
91
|
+
tool_info = tc.respond_to?(:tool) ? tc.tool : tc
|
|
92
|
+
puts " #{tool_info[:name] || tool_info}"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
```
|
|
43
96
|
|
|
44
|
-
|
|
45
|
-
# Using wttr.in API (free, no key required)
|
|
46
|
-
response = HTTP.get("https://wttr.in/#{URI.encode_www_form_component(city)}?format=j1")
|
|
97
|
+
## RobotLab::Tool.create Pattern
|
|
47
98
|
|
|
48
|
-
|
|
49
|
-
data = JSON.parse(response.body)
|
|
50
|
-
current = data["current_condition"].first
|
|
99
|
+
For simpler tools that do not need their own class, use `RobotLab::Tool.create`:
|
|
51
100
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
```ruby
|
|
102
|
+
require "robot_lab"
|
|
103
|
+
|
|
104
|
+
# Define an inline tool
|
|
105
|
+
get_time = RobotLab::Tool.create(
|
|
106
|
+
name: "get_time",
|
|
107
|
+
description: "Get the current time"
|
|
108
|
+
) { |_args| Time.now.to_s }
|
|
109
|
+
|
|
110
|
+
# Define a tool with parameters (JSON Schema)
|
|
111
|
+
weather_tool = RobotLab::Tool.create(
|
|
112
|
+
name: "get_weather",
|
|
113
|
+
description: "Get weather for a city",
|
|
114
|
+
parameters: {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
city: { type: "string", description: "City name" }
|
|
118
|
+
},
|
|
119
|
+
required: ["city"]
|
|
120
|
+
}
|
|
121
|
+
) { |args| { city: args[:city], temperature: "72F", condition: "sunny" } }
|
|
122
|
+
|
|
123
|
+
robot = RobotLab.build(
|
|
124
|
+
name: "weather_bot",
|
|
125
|
+
system_prompt: "You provide weather and time information.",
|
|
126
|
+
local_tools: [get_time, weather_tool],
|
|
127
|
+
model: "claude-sonnet-4"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
result = robot.run("What time is it and what's the weather in New York?")
|
|
131
|
+
puts result.last_text_content
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Weather API Integration
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
#!/usr/bin/env ruby
|
|
138
|
+
# examples/weather_assistant.rb
|
|
139
|
+
|
|
140
|
+
require "bundler/setup"
|
|
141
|
+
require "robot_lab"
|
|
142
|
+
require "http"
|
|
143
|
+
require "json"
|
|
144
|
+
|
|
145
|
+
class GetWeather < RubyLLM::Tool
|
|
146
|
+
description "Get current weather for a city"
|
|
147
|
+
|
|
148
|
+
param :city,
|
|
149
|
+
type: "string",
|
|
150
|
+
desc: "City name (e.g., 'New York', 'London')"
|
|
151
|
+
|
|
152
|
+
def execute(city:)
|
|
153
|
+
response = HTTP.get(
|
|
154
|
+
"https://wttr.in/#{URI.encode_www_form_component(city)}?format=j1"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
if response.status.success?
|
|
158
|
+
data = JSON.parse(response.body)
|
|
159
|
+
current = data["current_condition"].first
|
|
160
|
+
|
|
161
|
+
{
|
|
162
|
+
city: city,
|
|
163
|
+
temperature_f: current["temp_F"],
|
|
164
|
+
temperature_c: current["temp_C"],
|
|
165
|
+
condition: current["weatherDesc"].first["value"],
|
|
166
|
+
humidity: current["humidity"],
|
|
167
|
+
wind_mph: current["windspeedMiles"]
|
|
168
|
+
}
|
|
169
|
+
else
|
|
170
|
+
{ error: "Could not fetch weather for #{city}" }
|
|
67
171
|
end
|
|
172
|
+
rescue HTTP::Error => e
|
|
173
|
+
{ error: "Network error: #{e.message}" }
|
|
68
174
|
end
|
|
175
|
+
end
|
|
69
176
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
parameter :city, type: :string, required: true
|
|
74
|
-
parameter :days, type: :integer, default: 3
|
|
177
|
+
class GetForecast < RubyLLM::Tool
|
|
178
|
+
description "Get weather forecast for upcoming days"
|
|
75
179
|
|
|
76
|
-
|
|
77
|
-
|
|
180
|
+
param :city, type: "string", desc: "City name"
|
|
181
|
+
param :days, type: "integer", desc: "Number of days (default 3)"
|
|
78
182
|
|
|
79
|
-
|
|
80
|
-
|
|
183
|
+
def execute(city:, days: 3)
|
|
184
|
+
response = HTTP.get(
|
|
185
|
+
"https://wttr.in/#{URI.encode_www_form_component(city)}?format=j1"
|
|
186
|
+
)
|
|
81
187
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{ error: "Could not fetch forecast" }
|
|
188
|
+
if response.status.success?
|
|
189
|
+
data = JSON.parse(response.body)
|
|
190
|
+
data["weather"].take(days).map do |day|
|
|
191
|
+
{
|
|
192
|
+
date: day["date"],
|
|
193
|
+
high_f: day["maxtempF"],
|
|
194
|
+
low_f: day["mintempF"],
|
|
195
|
+
condition: day["hourly"].first["weatherDesc"].first["value"]
|
|
196
|
+
}
|
|
92
197
|
end
|
|
93
|
-
|
|
94
|
-
{ error: "
|
|
198
|
+
else
|
|
199
|
+
{ error: "Could not fetch forecast" }
|
|
95
200
|
end
|
|
201
|
+
rescue HTTP::Error => e
|
|
202
|
+
{ error: "Network error: #{e.message}" }
|
|
96
203
|
end
|
|
97
204
|
end
|
|
98
205
|
|
|
99
|
-
#
|
|
206
|
+
# Create weather assistant
|
|
207
|
+
weather_bot = RobotLab.build(
|
|
208
|
+
name: "weather_assistant",
|
|
209
|
+
description: "Provides weather information",
|
|
210
|
+
system_prompt: <<~PROMPT,
|
|
211
|
+
You are a helpful weather assistant. Use your tools to look up weather.
|
|
212
|
+
Always provide temperatures in both Fahrenheit and Celsius.
|
|
213
|
+
Include relevant advice based on conditions (umbrella, sunscreen, etc).
|
|
214
|
+
PROMPT
|
|
215
|
+
local_tools: [GetWeather, GetForecast],
|
|
216
|
+
model: "claude-sonnet-4"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Interactive session
|
|
100
220
|
puts "Weather Assistant (type 'quit' to exit)"
|
|
101
221
|
puts "-" * 50
|
|
102
222
|
|
|
@@ -107,18 +227,8 @@ loop do
|
|
|
107
227
|
break if input.nil? || input.downcase == "quit"
|
|
108
228
|
next if input.empty?
|
|
109
229
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
print "\nAssistant: "
|
|
113
|
-
weather_bot.run(state: state) do |event|
|
|
114
|
-
case event.type
|
|
115
|
-
when :text_delta
|
|
116
|
-
print event.text
|
|
117
|
-
when :tool_call
|
|
118
|
-
puts "\n[Checking weather for #{event.input[:city]}...]"
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
puts
|
|
230
|
+
result = weather_bot.run(input)
|
|
231
|
+
puts "\nAssistant: #{result.last_text_content}"
|
|
122
232
|
end
|
|
123
233
|
|
|
124
234
|
puts "\nGoodbye!"
|
|
@@ -137,174 +247,155 @@ ORDERS = {
|
|
|
137
247
|
"ORD002" => { id: "ORD002", status: "processing", items: ["Gadget", "Gizmo"], total: 89.99 }
|
|
138
248
|
}
|
|
139
249
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
template "You help customers check their orders."
|
|
143
|
-
|
|
144
|
-
tool :get_order do
|
|
145
|
-
description "Look up an order by ID"
|
|
146
|
-
parameter :order_id, type: :string, required: true
|
|
250
|
+
class GetOrder < RubyLLM::Tool
|
|
251
|
+
description "Look up an order by ID"
|
|
147
252
|
|
|
148
|
-
|
|
149
|
-
# Verify user owns this order
|
|
150
|
-
user_id = state.data[:user_id]
|
|
151
|
-
order = ORDERS[order_id.upcase]
|
|
253
|
+
param :order_id, type: "string", desc: "The order ID to look up"
|
|
152
254
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
{ error: "Order not found" }
|
|
157
|
-
end
|
|
158
|
-
end
|
|
255
|
+
def execute(order_id:)
|
|
256
|
+
order = ORDERS[order_id.upcase]
|
|
257
|
+
order || { error: "Order not found" }
|
|
159
258
|
end
|
|
259
|
+
end
|
|
160
260
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
parameter :limit, type: :integer, default: 5
|
|
261
|
+
class ListOrders < RubyLLM::Tool
|
|
262
|
+
description "List recent orders"
|
|
164
263
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
end
|
|
264
|
+
param :limit, type: "integer", desc: "Maximum number of orders to return"
|
|
265
|
+
|
|
266
|
+
def execute(limit: 5)
|
|
267
|
+
ORDERS.values.take(limit)
|
|
170
268
|
end
|
|
269
|
+
end
|
|
171
270
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
271
|
+
class CancelOrder < RubyLLM::Tool
|
|
272
|
+
description "Cancel an order"
|
|
273
|
+
|
|
274
|
+
param :order_id, type: "string", desc: "The order ID to cancel"
|
|
275
|
+
param :reason, type: "string", desc: "Reason for cancellation"
|
|
276
|
+
|
|
277
|
+
def execute(order_id:, reason: nil)
|
|
278
|
+
order = ORDERS[order_id.upcase]
|
|
279
|
+
|
|
280
|
+
if order.nil?
|
|
281
|
+
{ success: false, error: "Order not found" }
|
|
282
|
+
elsif order[:status] == "shipped"
|
|
283
|
+
{ success: false, error: "Cannot cancel shipped orders" }
|
|
284
|
+
else
|
|
285
|
+
order[:status] = "cancelled"
|
|
286
|
+
order[:cancel_reason] = reason
|
|
287
|
+
{ success: true, message: "Order #{order_id} cancelled" }
|
|
189
288
|
end
|
|
190
289
|
end
|
|
191
290
|
end
|
|
192
291
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
292
|
+
order_bot = RobotLab.build(
|
|
293
|
+
name: "order_assistant",
|
|
294
|
+
system_prompt: "You help customers check and manage their orders.",
|
|
295
|
+
local_tools: [GetOrder, ListOrders, CancelOrder],
|
|
296
|
+
model: "claude-sonnet-4"
|
|
197
297
|
)
|
|
198
298
|
|
|
199
|
-
|
|
200
|
-
|
|
299
|
+
# Run with a question
|
|
300
|
+
result = order_bot.run("What's the status of order ORD001?")
|
|
301
|
+
puts result.last_text_content
|
|
201
302
|
```
|
|
202
303
|
|
|
203
|
-
##
|
|
304
|
+
## Tool Call Callbacks
|
|
204
305
|
|
|
205
|
-
|
|
206
|
-
# examples/math_assistant.rb
|
|
306
|
+
Use `on_tool_call` and `on_tool_result` to monitor tool execution:
|
|
207
307
|
|
|
208
|
-
|
|
209
|
-
|
|
308
|
+
```ruby
|
|
309
|
+
robot = RobotLab.build(
|
|
310
|
+
name: "monitored_bot",
|
|
311
|
+
system_prompt: "You help with calculations.",
|
|
312
|
+
local_tools: [Calculator],
|
|
313
|
+
model: "claude-sonnet-4",
|
|
314
|
+
on_tool_call: ->(tool_call) {
|
|
315
|
+
puts "[Tool Call] #{tool_call.name}: #{tool_call.arguments}"
|
|
316
|
+
},
|
|
317
|
+
on_tool_result: ->(tool_call, result) {
|
|
318
|
+
puts "[Tool Result] #{tool_call.name}: #{result}"
|
|
319
|
+
}
|
|
320
|
+
)
|
|
210
321
|
|
|
211
|
-
|
|
322
|
+
result = robot.run("What is 42 * 17?")
|
|
323
|
+
```
|
|
212
324
|
|
|
213
|
-
|
|
214
|
-
name "math_assistant"
|
|
215
|
-
template "You help with mathematical calculations."
|
|
325
|
+
## Running
|
|
216
326
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
parameter :expression, type: :string, required: true,
|
|
220
|
-
description: "Math expression like '2 + 2' or 'sqrt(16)'"
|
|
327
|
+
```bash
|
|
328
|
+
export ANTHROPIC_API_KEY="your-key"
|
|
221
329
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
{ expression: expression, result: result }
|
|
225
|
-
rescue => e
|
|
226
|
-
{ error: "Invalid expression: #{e.message}" }
|
|
227
|
-
end
|
|
228
|
-
end
|
|
330
|
+
# Tool usage example
|
|
331
|
+
ruby examples/tool_usage.rb
|
|
229
332
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
parameter :equation, type: :string, required: true
|
|
233
|
-
parameter :variable, type: :string, required: true
|
|
333
|
+
# Weather assistant
|
|
334
|
+
ruby examples/weather_assistant.rb
|
|
234
335
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
{ equation: equation, variable: variable, solutions: result }
|
|
238
|
-
rescue => e
|
|
239
|
-
{ error: "Could not solve: #{e.message}" }
|
|
240
|
-
end
|
|
241
|
-
end
|
|
242
|
-
end
|
|
336
|
+
# Order lookup
|
|
337
|
+
ruby examples/order_assistant.rb
|
|
243
338
|
```
|
|
244
339
|
|
|
245
|
-
##
|
|
340
|
+
## Interactive User Input
|
|
246
341
|
|
|
247
|
-
|
|
248
|
-
# examples/research_assistant.rb
|
|
342
|
+
Use the built-in `RobotLab::AskUser` tool to let robots ask the user questions during execution:
|
|
249
343
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
template "You help with research tasks."
|
|
344
|
+
```ruby
|
|
345
|
+
require "robot_lab"
|
|
253
346
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
347
|
+
robot = RobotLab.build(
|
|
348
|
+
name: "interviewer",
|
|
349
|
+
system_prompt: <<~PROMPT,
|
|
350
|
+
You are a project setup assistant. Interview the user to understand their
|
|
351
|
+
needs, then summarize the project plan. Use the ask_user tool to gather
|
|
352
|
+
information one question at a time.
|
|
353
|
+
PROMPT
|
|
354
|
+
local_tools: [RobotLab::AskUser],
|
|
355
|
+
model: "claude-sonnet-4"
|
|
356
|
+
)
|
|
259
357
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
handler { |url:, **_| HTTP.get(url).body.to_s }
|
|
264
|
-
end
|
|
358
|
+
result = robot.run("Help me plan a new web application")
|
|
359
|
+
puts "\nProject Plan:\n#{result.last_text_content}"
|
|
360
|
+
```
|
|
265
361
|
|
|
266
|
-
|
|
267
|
-
description "Summarize text"
|
|
268
|
-
parameter :text, type: :string, required: true
|
|
269
|
-
parameter :length, type: :string, enum: %w[short medium long], default: "medium"
|
|
270
|
-
handler { |text:, length:, **_| Summarizer.summarize(text, length) }
|
|
271
|
-
end
|
|
362
|
+
The robot will ask questions interactively:
|
|
272
363
|
|
|
273
|
-
tool :save_note do
|
|
274
|
-
description "Save a research note"
|
|
275
|
-
parameter :title, type: :string, required: true
|
|
276
|
-
parameter :content, type: :string, required: true
|
|
277
|
-
handler do |title:, content:, state:, **_|
|
|
278
|
-
notes = state.memory.recall("notes") || []
|
|
279
|
-
notes << { title: title, content: content, created: Time.now }
|
|
280
|
-
state.memory.remember("notes", notes)
|
|
281
|
-
{ saved: true, total_notes: notes.size }
|
|
282
|
-
end
|
|
283
|
-
end
|
|
284
|
-
end
|
|
285
364
|
```
|
|
365
|
+
[interviewer] What programming language would you like to use?
|
|
366
|
+
1. Ruby
|
|
367
|
+
2. Python
|
|
368
|
+
3. TypeScript
|
|
369
|
+
> 1
|
|
286
370
|
|
|
287
|
-
|
|
371
|
+
[interviewer] Will you need a database?
|
|
372
|
+
> [yes]
|
|
288
373
|
|
|
289
|
-
|
|
290
|
-
|
|
374
|
+
[interviewer] What's the main purpose of the application?
|
|
375
|
+
> Customer support portal
|
|
376
|
+
```
|
|
291
377
|
|
|
292
|
-
|
|
293
|
-
ruby examples/weather_assistant.rb
|
|
378
|
+
For testing, inject `StringIO` objects:
|
|
294
379
|
|
|
295
|
-
|
|
296
|
-
|
|
380
|
+
```ruby
|
|
381
|
+
robot.input = StringIO.new("Ruby\nyes\nCustomer portal\n")
|
|
382
|
+
robot.output = StringIO.new
|
|
297
383
|
```
|
|
298
384
|
|
|
299
385
|
## Key Concepts
|
|
300
386
|
|
|
301
|
-
1. **Tool
|
|
302
|
-
2. **
|
|
303
|
-
3. **
|
|
304
|
-
4. **
|
|
387
|
+
1. **RubyLLM::Tool subclass**: Define a class with `description`, `param`, and `execute` method
|
|
388
|
+
2. **RobotLab::Tool subclass**: Same DSL plus `robot` accessor for robot-aware tools
|
|
389
|
+
3. **RobotLab::Tool.create**: Use `RobotLab::Tool.create(name:, description:, &block)` for dynamic tools
|
|
390
|
+
4. **Built-in tools**: `RobotLab::AskUser` for interactive terminal input
|
|
391
|
+
5. **local_tools**: Pass tool classes/instances via `local_tools:` parameter to `RobotLab.build` or `Robot.new`
|
|
392
|
+
6. **Frontmatter tools**: Declare tool class names in template YAML front matter (`tools: [Calculator]`) for self-contained templates
|
|
393
|
+
7. **Error Handling**: Return error hashes (e.g., `{ error: "message" }`) for graceful failures
|
|
394
|
+
8. **Callbacks**: Use `on_tool_call:` and `on_tool_result:` for monitoring
|
|
395
|
+
9. **Result Access**: Check `result.tool_calls` for tool call history, `result.last_text_content` for the final response
|
|
305
396
|
|
|
306
397
|
## See Also
|
|
307
398
|
|
|
308
399
|
- [Using Tools Guide](../guides/using-tools.md)
|
|
309
400
|
- [Tool API](../api/core/tool.md)
|
|
310
|
-
- [
|
|
401
|
+
- [Robot API](../api/core/robot.md)
|