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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
# Typed message envelope for bus-based robot communication.
|
|
5
|
+
#
|
|
6
|
+
# RobotMessage is a Data class (immutable value object) that wraps
|
|
7
|
+
# content sent between robots via a TypedBus channel.
|
|
8
|
+
#
|
|
9
|
+
# @example Creating a new message
|
|
10
|
+
# msg = RobotMessage.build(id: 1, from: "alice", content: "Hello")
|
|
11
|
+
# msg.key #=> "alice:1"
|
|
12
|
+
# msg.reply? #=> false
|
|
13
|
+
#
|
|
14
|
+
# @example Creating a reply
|
|
15
|
+
# reply = RobotMessage.build(
|
|
16
|
+
# id: 2, from: "bob",
|
|
17
|
+
# content: "Hi back",
|
|
18
|
+
# in_reply_to: "alice:1"
|
|
19
|
+
# )
|
|
20
|
+
# reply.reply? #=> true
|
|
21
|
+
#
|
|
22
|
+
RobotMessage = Data.define(:id, :from, :content, :in_reply_to) do
|
|
23
|
+
# Build a RobotMessage with in_reply_to defaulting to nil.
|
|
24
|
+
#
|
|
25
|
+
# @param id [Integer] per-robot message counter
|
|
26
|
+
# @param from [String] sender's robot name (= channel name)
|
|
27
|
+
# @param content [String, Hash] message payload
|
|
28
|
+
# @param in_reply_to [String, nil] composite key of the message being replied to
|
|
29
|
+
# @return [RobotMessage]
|
|
30
|
+
def self.build(id:, from:, content:, in_reply_to: nil)
|
|
31
|
+
new(id: id, from: from, content: content, in_reply_to: in_reply_to)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Composite identity key: "from:id"
|
|
35
|
+
#
|
|
36
|
+
# @return [String]
|
|
37
|
+
def key = "#{from}:#{id}"
|
|
38
|
+
|
|
39
|
+
# Whether this message is a reply to another message.
|
|
40
|
+
#
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def reply? = !in_reply_to.nil?
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/robot_lab/tool.rb
CHANGED
|
@@ -1,222 +1,158 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RobotLab
|
|
4
|
-
#
|
|
4
|
+
# A tool that robots can use, built on RubyLLM::Tool.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
7
|
-
# They have a name, description, parameter schema, and handler.
|
|
6
|
+
# Provides two patterns for defining tools:
|
|
8
7
|
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# description
|
|
13
|
-
#
|
|
14
|
-
# )
|
|
8
|
+
# 1. **Subclass pattern** — for reusable, robot-aware tools:
|
|
9
|
+
#
|
|
10
|
+
# class GetWeather < RobotLab::Tool
|
|
11
|
+
# description "Get weather for a location"
|
|
12
|
+
# param :location, type: "string", desc: "City name"
|
|
15
13
|
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# string :unit, enum: %w[celsius fahrenheit], required: false
|
|
14
|
+
# def execute(location:)
|
|
15
|
+
# WeatherService.fetch(location)
|
|
16
|
+
# end
|
|
20
17
|
# end
|
|
21
18
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# fetch_weather(input[:location], input[:unit] || "celsius")
|
|
29
|
-
# }
|
|
30
|
-
# )
|
|
19
|
+
# 2. **Factory pattern** — for dynamic/inline tools:
|
|
20
|
+
#
|
|
21
|
+
# tool = RobotLab::Tool.create(
|
|
22
|
+
# name: "get_time",
|
|
23
|
+
# description: "Get the current time"
|
|
24
|
+
# ) { |args| Time.now.to_s }
|
|
31
25
|
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# @!attribute [
|
|
38
|
-
# @return [
|
|
39
|
-
|
|
40
|
-
|
|
26
|
+
# Subclasses have access to the owning +robot+ via an accessor,
|
|
27
|
+
# enabling tools that modify their robot's state (temperature,
|
|
28
|
+
# system prompt, spawning, etc.).
|
|
29
|
+
#
|
|
30
|
+
class Tool < RubyLLM::Tool
|
|
31
|
+
# @!attribute [rw] robot
|
|
32
|
+
# @return [Robot, nil] the robot that owns this tool
|
|
33
|
+
attr_accessor :robot
|
|
34
|
+
|
|
41
35
|
# @!attribute [r] mcp
|
|
42
36
|
# @return [String, nil] the MCP server name if this is an MCP-provided tool
|
|
43
|
-
|
|
44
|
-
# @return [Boolean, nil] whether strict mode is enabled
|
|
45
|
-
attr_reader :name, :description, :parameters, :handler, :mcp, :strict
|
|
37
|
+
attr_reader :mcp
|
|
46
38
|
|
|
47
39
|
# Creates a new Tool instance.
|
|
48
40
|
#
|
|
49
|
-
# @param
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
# @param mcp [String, nil] MCP server name if this is an MCP tool
|
|
54
|
-
# @param strict [Boolean, nil] whether strict mode is enabled
|
|
55
|
-
# @yield [input, **opts] optional block as handler
|
|
56
|
-
#
|
|
57
|
-
# @example Tool with block handler
|
|
58
|
-
# Tool.new(name: "greet", description: "Greet user") do |input, **opts|
|
|
59
|
-
# "Hello, #{input[:name]}!"
|
|
60
|
-
# end
|
|
61
|
-
def initialize(name:, description: nil, parameters: nil, handler: nil, mcp: nil, strict: nil, &block)
|
|
62
|
-
@name = name.to_s
|
|
63
|
-
@description = description
|
|
64
|
-
@parameters = parameters
|
|
65
|
-
@handler = handler || block
|
|
66
|
-
@mcp = mcp
|
|
67
|
-
@strict = strict
|
|
41
|
+
# @param robot [Robot, nil] the owning robot
|
|
42
|
+
def initialize(robot: nil)
|
|
43
|
+
super()
|
|
44
|
+
@robot = robot
|
|
68
45
|
end
|
|
69
46
|
|
|
70
|
-
#
|
|
47
|
+
# Override name to support explicit names for dynamic/MCP tools.
|
|
71
48
|
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
#
|
|
78
|
-
# @param network [NetworkRun, nil] The network context if running in a network
|
|
79
|
-
# @param step [Object, nil] Durable execution step context
|
|
80
|
-
# @return [Object] The tool's output
|
|
49
|
+
# @return [String] the tool name
|
|
50
|
+
def name
|
|
51
|
+
defined?(@custom_name) && @custom_name ? @custom_name : super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if this is an MCP-provided tool.
|
|
81
55
|
#
|
|
82
|
-
|
|
83
|
-
|
|
56
|
+
# @return [Boolean]
|
|
57
|
+
def mcp?
|
|
58
|
+
!@mcp.nil?
|
|
59
|
+
end
|
|
84
60
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
61
|
+
# Factory for dynamic tools (MCP wrappers, inline tools).
|
|
62
|
+
#
|
|
63
|
+
# @param name [String, Symbol] the tool name
|
|
64
|
+
# @param description [String, nil] what the tool does
|
|
65
|
+
# @param parameters [Hash, nil] JSON Schema parameter definition
|
|
66
|
+
# @param mcp [String, nil] MCP server name
|
|
67
|
+
# @param robot [Robot, nil] the owning robot
|
|
68
|
+
# @yield [args] block that executes the tool logic
|
|
69
|
+
# @return [Tool] a new tool instance
|
|
70
|
+
#
|
|
71
|
+
# @example Simple factory tool
|
|
72
|
+
# tool = RobotLab::Tool.create(
|
|
73
|
+
# name: "get_time",
|
|
74
|
+
# description: "Get the current time"
|
|
75
|
+
# ) { |args| Time.now.to_s }
|
|
76
|
+
#
|
|
77
|
+
# @example MCP tool wrapper
|
|
78
|
+
# tool = RobotLab::Tool.create(
|
|
79
|
+
# name: "search",
|
|
80
|
+
# description: "Search the web",
|
|
81
|
+
# parameters: { type: "object", properties: { q: { type: "string" } }, required: ["q"] },
|
|
82
|
+
# mcp: "brave_search"
|
|
83
|
+
# ) { |args| mcp_client.call_tool("search", args) }
|
|
84
|
+
#
|
|
85
|
+
def self.create(name:, description: nil, parameters: nil, mcp: nil, robot: nil, &handler)
|
|
86
|
+
desc_text = description
|
|
87
|
+
params_hash = parameters
|
|
88
|
+
block = handler
|
|
89
|
+
|
|
90
|
+
tool_class = Class.new(self) do
|
|
91
|
+
description(desc_text) if desc_text
|
|
92
|
+
|
|
93
|
+
if params_hash.is_a?(Hash) && params_hash[:properties]
|
|
94
|
+
required_list = Array(params_hash[:required]).map(&:to_s)
|
|
95
|
+
params_hash[:properties].each do |pname, pdef|
|
|
96
|
+
param pname.to_sym,
|
|
97
|
+
type: pdef[:type] || "string",
|
|
98
|
+
desc: pdef[:description],
|
|
99
|
+
required: required_list.include?(pname.to_s)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
define_method(:execute) do |**args|
|
|
104
|
+
block.call(args)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
instance = tool_class.new(robot: robot)
|
|
109
|
+
instance.instance_variable_set(:@custom_name, name.to_s)
|
|
110
|
+
instance.instance_variable_set(:@mcp, mcp)
|
|
111
|
+
instance
|
|
91
112
|
end
|
|
92
113
|
|
|
93
|
-
# Convert to JSON Schema for LLM function calling
|
|
114
|
+
# Convert to JSON Schema for LLM function calling.
|
|
115
|
+
# Used by RobotLab adapters for provider-specific formatting.
|
|
94
116
|
#
|
|
95
117
|
# @return [Hash] JSON Schema representation
|
|
96
|
-
#
|
|
97
118
|
def to_json_schema
|
|
98
|
-
schema =
|
|
99
|
-
# ruby_llm-schema class
|
|
100
|
-
parameters.new.to_json_schema[:schema]
|
|
101
|
-
elsif parameters.is_a?(Hash)
|
|
102
|
-
# Raw JSON schema
|
|
103
|
-
parameters
|
|
104
|
-
else
|
|
105
|
-
# No parameters
|
|
106
|
-
{ type: "object", properties: {}, required: [] }
|
|
107
|
-
end
|
|
108
|
-
|
|
119
|
+
schema = params_schema || { "type" => "object", "properties" => {}, "required" => [] }
|
|
109
120
|
{
|
|
110
121
|
name: name,
|
|
111
122
|
description: description,
|
|
112
|
-
parameters: schema
|
|
123
|
+
parameters: deep_symbolize_keys(schema)
|
|
113
124
|
}.compact
|
|
114
125
|
end
|
|
115
126
|
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# @return [Class] A RubyLLM::Tool subclass
|
|
119
|
-
#
|
|
120
|
-
def to_ruby_llm_tool
|
|
121
|
-
tool = self
|
|
122
|
-
Class.new(RubyLLM::Tool) do
|
|
123
|
-
description tool.description
|
|
124
|
-
|
|
125
|
-
# Define parameters from schema
|
|
126
|
-
if tool.parameters.respond_to?(:to_json_schema)
|
|
127
|
-
schema = tool.parameters.new.to_json_schema[:schema]
|
|
128
|
-
schema[:properties]&.each do |prop_name, prop_def|
|
|
129
|
-
param prop_name.to_sym,
|
|
130
|
-
type: prop_def[:type],
|
|
131
|
-
desc: prop_def[:description],
|
|
132
|
-
required: schema[:required]&.include?(prop_name.to_s)
|
|
133
|
-
end
|
|
134
|
-
elsif tool.parameters.is_a?(Hash) && tool.parameters[:properties]
|
|
135
|
-
tool.parameters[:properties].each do |prop_name, prop_def|
|
|
136
|
-
param prop_name.to_sym,
|
|
137
|
-
type: prop_def[:type] || "string",
|
|
138
|
-
desc: prop_def[:description],
|
|
139
|
-
required: tool.parameters[:required]&.include?(prop_name.to_s)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
define_method(:execute) do |**kwargs|
|
|
144
|
-
# This will be overridden at runtime with proper context
|
|
145
|
-
kwargs
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Converts the tool to a hash representation.
|
|
127
|
+
# Hash representation.
|
|
151
128
|
#
|
|
152
|
-
# @return [Hash]
|
|
129
|
+
# @return [Hash]
|
|
153
130
|
def to_h
|
|
154
131
|
{
|
|
155
132
|
name: name,
|
|
156
133
|
description: description,
|
|
157
|
-
|
|
158
|
-
mcp: mcp,
|
|
159
|
-
strict: strict
|
|
134
|
+
mcp: mcp
|
|
160
135
|
}.compact
|
|
161
136
|
end
|
|
162
137
|
|
|
163
|
-
#
|
|
138
|
+
# JSON representation.
|
|
164
139
|
#
|
|
165
140
|
# @param args [Array] arguments passed to to_json
|
|
166
|
-
# @return [String]
|
|
141
|
+
# @return [String]
|
|
167
142
|
def to_json(*args)
|
|
168
143
|
to_h.to_json(*args)
|
|
169
144
|
end
|
|
170
145
|
|
|
171
|
-
# Check if this is an MCP-provided tool
|
|
172
|
-
#
|
|
173
|
-
# @return [Boolean]
|
|
174
|
-
#
|
|
175
|
-
def mcp?
|
|
176
|
-
!mcp.nil?
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Return parameters schema for ruby_llm compatibility
|
|
180
|
-
#
|
|
181
|
-
# @return [Hash, nil] JSON Schema for tool parameters
|
|
182
|
-
#
|
|
183
|
-
def params_schema
|
|
184
|
-
if parameters.respond_to?(:to_json_schema)
|
|
185
|
-
parameters.new.to_json_schema[:schema]
|
|
186
|
-
elsif parameters.is_a?(Hash)
|
|
187
|
-
parameters
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# Provider-specific parameters for ruby_llm compatibility
|
|
192
|
-
#
|
|
193
|
-
# @return [Hash] Empty hash (no provider-specific params)
|
|
194
|
-
#
|
|
195
|
-
def provider_params
|
|
196
|
-
{}
|
|
197
|
-
end
|
|
198
|
-
|
|
199
146
|
private
|
|
200
147
|
|
|
201
|
-
def
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
# Validate with ruby_llm-schema (if available)
|
|
208
|
-
# For now, just pass through
|
|
209
|
-
input
|
|
148
|
+
def deep_symbolize_keys(obj)
|
|
149
|
+
case obj
|
|
150
|
+
when Hash
|
|
151
|
+
obj.each_with_object({}) { |(k, v), h| h[k.to_sym] = deep_symbolize_keys(v) }
|
|
152
|
+
when Array
|
|
153
|
+
obj.map { |v| deep_symbolize_keys(v) }
|
|
210
154
|
else
|
|
211
|
-
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
def parameters_to_hash
|
|
216
|
-
if parameters.respond_to?(:to_json_schema)
|
|
217
|
-
parameters.new.to_json_schema
|
|
218
|
-
elsif parameters.is_a?(Hash)
|
|
219
|
-
parameters
|
|
155
|
+
obj
|
|
220
156
|
end
|
|
221
157
|
end
|
|
222
158
|
end
|
|
@@ -4,7 +4,7 @@ module RobotLab
|
|
|
4
4
|
# Handles hierarchical MCP and tools configuration resolution
|
|
5
5
|
#
|
|
6
6
|
# Configuration hierarchy (each level overrides the previous):
|
|
7
|
-
# 1. RobotLab.
|
|
7
|
+
# 1. RobotLab.config (global)
|
|
8
8
|
# 2. Network.new (network scope)
|
|
9
9
|
# 3. Robot.new (robot definition scope)
|
|
10
10
|
# 4. robot.run (runtime scope)
|
|
@@ -183,22 +183,6 @@ module RobotLab
|
|
|
183
183
|
self
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
-
# Convert to hash for JSON Schema
|
|
187
|
-
#
|
|
188
|
-
# @return [Hash] Map of tool names to their JSON schemas
|
|
189
|
-
#
|
|
190
|
-
def to_json_schema
|
|
191
|
-
@tools.transform_values(&:to_json_schema)
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Convert to array of ruby_llm Tool classes
|
|
195
|
-
#
|
|
196
|
-
# @return [Array<Class>]
|
|
197
|
-
#
|
|
198
|
-
def to_ruby_llm_tools
|
|
199
|
-
@tools.values.map(&:to_ruby_llm_tool)
|
|
200
|
-
end
|
|
201
|
-
|
|
202
186
|
# Converts the manifest to a hash representation.
|
|
203
187
|
#
|
|
204
188
|
# @return [Hash<String, Hash>] map of tool names to their hash representations
|
|
@@ -221,11 +205,11 @@ module RobotLab
|
|
|
221
205
|
#
|
|
222
206
|
def self.from_hash(hash)
|
|
223
207
|
tools = hash.map do |name, config|
|
|
224
|
-
Tool.
|
|
208
|
+
Tool.create(
|
|
225
209
|
name: name,
|
|
226
210
|
description: config[:description],
|
|
227
211
|
parameters: config[:parameters],
|
|
228
|
-
|
|
212
|
+
&config[:handler]
|
|
229
213
|
)
|
|
230
214
|
end
|
|
231
215
|
new(tools)
|
data/lib/robot_lab/version.rb
CHANGED
data/lib/robot_lab.rb
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
require 'digest'
|
|
7
7
|
|
|
8
8
|
# Core dependencies
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
# ActiveSupport delegation is required by ruby_llm (RubyLLM::Agent uses delegate)
|
|
10
|
+
# but not declared in ruby_llm's gemspec. Load it before ruby_llm.
|
|
11
|
+
require 'active_support/core_ext/module/delegation'
|
|
12
|
+
require 'ruby_llm'
|
|
13
|
+
require 'prompt_manager'
|
|
14
|
+
require 'async'
|
|
15
|
+
require 'typed_bus'
|
|
11
16
|
|
|
12
17
|
# Define the module first so Zeitwerk can populate it
|
|
13
18
|
#
|
|
@@ -29,10 +34,16 @@ require "async"
|
|
|
29
34
|
# result = network.run(message: "Process this document")
|
|
30
35
|
#
|
|
31
36
|
# @example Configuration
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
37
|
+
# # Via environment variables (ROBOT_LAB_* prefix)
|
|
38
|
+
# # ROBOT_LAB_DEFAULT_MODEL=gpt-4
|
|
39
|
+
# # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
|
|
40
|
+
#
|
|
41
|
+
# # Or via config files (~/.config/robot_lab/config.yml or ./config/robot_lab.yml)
|
|
42
|
+
# # See lib/robot_lab/config/defaults.yml for all options
|
|
43
|
+
#
|
|
44
|
+
# # Access configuration values:
|
|
45
|
+
# RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
46
|
+
# RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
36
47
|
#
|
|
37
48
|
module RobotLab
|
|
38
49
|
end
|
|
@@ -43,16 +54,16 @@ loader.ignore("#{__dir__}/robot_lab/rails")
|
|
|
43
54
|
|
|
44
55
|
# Custom inflections for classes that don't follow Zeitwerk naming conventions
|
|
45
56
|
loader.inflector.inflect(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
'robot_lab' => 'RobotLab',
|
|
58
|
+
'robotic_model' => 'RoboticModel',
|
|
59
|
+
'mcp' => 'MCP',
|
|
60
|
+
'openai' => 'OpenAI',
|
|
61
|
+
'sse' => 'SSE',
|
|
62
|
+
'streamable_http' => 'StreamableHTTP',
|
|
63
|
+
'websocket' => 'WebSocket'
|
|
53
64
|
)
|
|
54
65
|
|
|
55
|
-
#
|
|
66
|
+
# NOTE: adapters/ is NOT collapsed since files define RobotLab::Adapters::* classes
|
|
56
67
|
|
|
57
68
|
loader.setup
|
|
58
69
|
|
|
@@ -63,40 +74,51 @@ module RobotLab
|
|
|
63
74
|
# Error classes are defined in lib/robot_lab/error.rb
|
|
64
75
|
|
|
65
76
|
class << self
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
#
|
|
77
|
+
# Returns the Config object (MywayConfig-based).
|
|
78
|
+
#
|
|
79
|
+
# Configuration is automatically loaded from:
|
|
80
|
+
# - Bundled defaults (lib/robot_lab/config/defaults.yml)
|
|
81
|
+
# - Environment-specific overrides (development, test, production)
|
|
82
|
+
# - XDG config files (~/.config/robot_lab/config.yml)
|
|
83
|
+
# - Project config (./config/robot_lab.yml)
|
|
84
|
+
# - Environment variables (ROBOT_LAB_*)
|
|
71
85
|
#
|
|
72
|
-
# @return [
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
# @return [Config] the config instance
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
|
|
90
|
+
# RobotLab.config.ruby_llm.request_timeout #=> 120
|
|
91
|
+
# RobotLab.config.development? #=> true
|
|
92
|
+
def config
|
|
93
|
+
@config ||= Config.new.tap(&:after_load)
|
|
75
94
|
end
|
|
76
95
|
|
|
77
|
-
|
|
96
|
+
|
|
97
|
+
# Reload configuration from all sources.
|
|
78
98
|
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
99
|
+
# Clears the cached Config instance, forcing it to be
|
|
100
|
+
# reloaded on next access.
|
|
81
101
|
#
|
|
82
|
-
# @
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def configure
|
|
87
|
-
yield(configuration)
|
|
102
|
+
# @return [Config] the new config instance
|
|
103
|
+
def reload_config!
|
|
104
|
+
@config = nil
|
|
105
|
+
config
|
|
88
106
|
end
|
|
89
107
|
|
|
108
|
+
|
|
90
109
|
# Factory method to create a new Robot instance.
|
|
91
110
|
#
|
|
92
|
-
# @param name [String] the unique identifier for the robot
|
|
93
|
-
# @param template [Symbol, nil] the
|
|
111
|
+
# @param name [String, nil] the unique identifier for the robot (auto-generated if nil)
|
|
112
|
+
# @param template [Symbol, nil] the prompt_manager template for the robot's prompt
|
|
94
113
|
# @param system_prompt [String, nil] inline system prompt (can be used alone or with template)
|
|
95
114
|
# @param context [Hash] variables to pass to the template
|
|
96
115
|
# @param enable_cache [Boolean] whether to enable semantic caching (default: true)
|
|
97
116
|
# @param options [Hash] additional options passed to Robot.new
|
|
98
117
|
# @return [Robot] a new Robot instance
|
|
99
|
-
#
|
|
118
|
+
#
|
|
119
|
+
# @example Bare robot (no template or prompt)
|
|
120
|
+
# robot = RobotLab.build
|
|
121
|
+
# robot.with_instructions("You are helpful.").run("Hello!")
|
|
100
122
|
#
|
|
101
123
|
# @example Robot with template
|
|
102
124
|
# robot = RobotLab.build(
|
|
@@ -110,31 +132,19 @@ module RobotLab
|
|
|
110
132
|
# name: "helper",
|
|
111
133
|
# system_prompt: "You are a helpful assistant."
|
|
112
134
|
# )
|
|
113
|
-
|
|
114
|
-
# @example Robot with both template and system prompt
|
|
115
|
-
# robot = RobotLab.build(
|
|
116
|
-
# name: "support",
|
|
117
|
-
# template: :support_agent,
|
|
118
|
-
# system_prompt: "Today's date is #{Date.today}."
|
|
119
|
-
# )
|
|
120
|
-
#
|
|
121
|
-
# @example Robot with caching disabled
|
|
122
|
-
# robot = RobotLab.build(
|
|
123
|
-
# name: "simple",
|
|
124
|
-
# system_prompt: "You are helpful.",
|
|
125
|
-
# enable_cache: false
|
|
126
|
-
# )
|
|
127
|
-
def build(name:, template: nil, system_prompt: nil, context: {}, enable_cache: true, **options)
|
|
135
|
+
def build(name: "robot", template: nil, system_prompt: nil, context: {}, enable_cache: true, bus: nil, **options)
|
|
128
136
|
Robot.new(
|
|
129
137
|
name: name,
|
|
130
138
|
template: template,
|
|
131
139
|
system_prompt: system_prompt,
|
|
132
140
|
context: context,
|
|
133
141
|
enable_cache: enable_cache,
|
|
142
|
+
bus: bus,
|
|
134
143
|
**options
|
|
135
144
|
)
|
|
136
145
|
end
|
|
137
146
|
|
|
147
|
+
|
|
138
148
|
# Factory method to create a new Network of robots.
|
|
139
149
|
#
|
|
140
150
|
# @param name [String] the unique identifier for the network
|
|
@@ -166,6 +176,7 @@ module RobotLab
|
|
|
166
176
|
Network.new(name: name, concurrency: concurrency, &block)
|
|
167
177
|
end
|
|
168
178
|
|
|
179
|
+
|
|
169
180
|
# Factory method to create a new Memory object.
|
|
170
181
|
#
|
|
171
182
|
# @param data [Hash] initial runtime data
|
|
@@ -190,6 +201,6 @@ end
|
|
|
190
201
|
|
|
191
202
|
# Load Rails integration if Rails is defined
|
|
192
203
|
if defined?(Rails::Engine)
|
|
193
|
-
require
|
|
194
|
-
require
|
|
204
|
+
require 'robot_lab/rails/engine'
|
|
205
|
+
require 'robot_lab/rails/railtie'
|
|
195
206
|
end
|