robot_lab 0.0.4 → 0.0.7
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/CHANGELOG.md +76 -0
- data/README.md +64 -6
- data/Rakefile +2 -1
- data/docs/api/core/index.md +41 -46
- data/docs/api/core/memory.md +200 -154
- data/docs/api/core/network.md +13 -3
- data/docs/api/core/robot.md +38 -26
- data/docs/api/core/state.md +55 -73
- data/docs/api/index.md +7 -28
- data/docs/api/messages/index.md +35 -20
- data/docs/api/messages/text-message.md +67 -21
- data/docs/api/messages/tool-call-message.md +80 -41
- data/docs/api/messages/tool-result-message.md +119 -50
- data/docs/api/messages/user-message.md +48 -24
- data/docs/architecture/core-concepts.md +10 -15
- data/docs/concepts.md +5 -7
- data/docs/examples/index.md +2 -2
- data/docs/getting-started/configuration.md +80 -0
- data/docs/guides/building-robots.md +10 -9
- data/docs/guides/creating-networks.md +49 -0
- data/docs/guides/index.md +0 -5
- data/docs/guides/rails-integration.md +244 -162
- data/docs/guides/streaming.md +118 -138
- data/docs/index.md +0 -8
- data/examples/03_network.rb +10 -7
- data/examples/08_llm_config.rb +40 -11
- data/examples/09_chaining.rb +45 -6
- data/examples/11_network_introspection.rb +30 -7
- data/examples/12_message_bus.rb +1 -1
- data/examples/14_rusty_circuit/heckler.rb +14 -8
- data/examples/14_rusty_circuit/open_mic.rb +5 -3
- data/examples/14_rusty_circuit/scout.rb +14 -31
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
- data/examples/16_writers_room/display.rb +158 -0
- data/examples/16_writers_room/output/.gitignore +4 -0
- data/examples/16_writers_room/output/README.md +69 -0
- data/examples/16_writers_room/output/opus_001.md +263 -0
- data/examples/16_writers_room/output/opus_001_notes.log +470 -0
- data/examples/16_writers_room/output/opus_002.md +245 -0
- data/examples/16_writers_room/output/opus_002_notes.log +546 -0
- data/examples/16_writers_room/output/opus_002_screenplay.md +7989 -0
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +993 -0
- data/examples/16_writers_room/prompts/screenplay_writer.md +66 -0
- data/examples/16_writers_room/prompts/writer.md +37 -0
- data/examples/16_writers_room/room.rb +186 -0
- data/examples/16_writers_room/tools.rb +173 -0
- data/examples/16_writers_room/writer.rb +121 -0
- data/examples/16_writers_room/writers_room.rb +256 -0
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
- data/lib/robot_lab/memory.rb +8 -32
- data/lib/robot_lab/network.rb +13 -20
- data/lib/robot_lab/robot/bus_messaging.rb +239 -0
- data/lib/robot_lab/robot/mcp_management.rb +88 -0
- data/lib/robot_lab/robot/template_rendering.rb +130 -0
- data/lib/robot_lab/robot.rb +56 -420
- data/lib/robot_lab/run_config.rb +184 -0
- data/lib/robot_lab/state_proxy.rb +2 -12
- data/lib/robot_lab/task.rb +8 -1
- data/lib/robot_lab/utils.rb +39 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +29 -8
- data/mkdocs.yml +0 -11
- metadata +21 -20
- data/docs/api/adapters/anthropic.md +0 -121
- data/docs/api/adapters/gemini.md +0 -133
- data/docs/api/adapters/index.md +0 -104
- data/docs/api/adapters/openai.md +0 -134
- data/docs/api/history/active-record-adapter.md +0 -275
- data/docs/api/history/config.md +0 -284
- data/docs/api/history/index.md +0 -128
- data/docs/api/history/thread-manager.md +0 -194
- data/docs/guides/history.md +0 -359
- data/lib/robot_lab/adapters/anthropic.rb +0 -163
- data/lib/robot_lab/adapters/base.rb +0 -85
- data/lib/robot_lab/adapters/gemini.rb +0 -193
- data/lib/robot_lab/adapters/openai.rb +0 -160
- data/lib/robot_lab/adapters/registry.rb +0 -81
- data/lib/robot_lab/errors.rb +0 -70
- data/lib/robot_lab/history/active_record_adapter.rb +0 -146
- data/lib/robot_lab/history/config.rb +0 -115
- data/lib/robot_lab/history/thread_manager.rb +0 -93
- data/lib/robot_lab/robotic_model.rb +0 -324
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
class Robot < RubyLLM::Agent
|
|
5
|
+
# MCP client lifecycle and hierarchical tool/MCP resolution.
|
|
6
|
+
#
|
|
7
|
+
# Expects the including class to provide:
|
|
8
|
+
# @mcp_config, @tools_config, @mcp_clients, @mcp_tools,
|
|
9
|
+
# @mcp_initialized, @name, @chat, @local_tools
|
|
10
|
+
module MCPManagement
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Resolve MCP hierarchy: runtime -> robot build -> network -> config
|
|
14
|
+
def resolve_mcp_hierarchy(runtime_value, network: nil, network_config: nil)
|
|
15
|
+
parent_value = network_config&.mcp || network&.network&.mcp || RobotLab.config.mcp
|
|
16
|
+
build_resolved = ToolConfig.resolve_mcp(@mcp_config, parent_value: parent_value)
|
|
17
|
+
ToolConfig.resolve_mcp(runtime_value, parent_value: build_resolved)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Resolve tools hierarchy: runtime -> robot build -> network -> config
|
|
22
|
+
def resolve_tools_hierarchy(runtime_value, network: nil, network_config: nil)
|
|
23
|
+
parent_value = network_config&.tools || network&.network&.tools || RobotLab.config.tools
|
|
24
|
+
build_resolved = ToolConfig.resolve_tools(@tools_config, parent_value: parent_value)
|
|
25
|
+
ToolConfig.resolve_tools(runtime_value, parent_value: build_resolved)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Ensure MCP clients are initialized for the given server configs
|
|
30
|
+
def ensure_mcp_clients(mcp_servers)
|
|
31
|
+
return if mcp_servers.empty?
|
|
32
|
+
|
|
33
|
+
needed_servers = mcp_servers.map { |s| s.is_a?(Hash) ? s[:name] : s.to_s }.compact
|
|
34
|
+
return if @mcp_initialized && (@mcp_clients.keys.sort == needed_servers.sort)
|
|
35
|
+
|
|
36
|
+
disconnect if @mcp_initialized
|
|
37
|
+
|
|
38
|
+
@mcp_clients = {}
|
|
39
|
+
@mcp_tools = []
|
|
40
|
+
|
|
41
|
+
mcp_servers.each do |server_config|
|
|
42
|
+
init_mcp_client(server_config)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
@mcp_initialized = true
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def init_mcp_client(server_config)
|
|
50
|
+
client = MCP::Client.new(server_config)
|
|
51
|
+
client.connect
|
|
52
|
+
|
|
53
|
+
if client.connected?
|
|
54
|
+
server_name = client.server.name
|
|
55
|
+
@mcp_clients[server_name] = client
|
|
56
|
+
discover_mcp_tools(client, server_name)
|
|
57
|
+
else
|
|
58
|
+
RobotLab.config.logger.warn(
|
|
59
|
+
"Robot '#{@name}' failed to connect to MCP server: #{server_config[:name] || server_config}"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def discover_mcp_tools(client, server_name)
|
|
66
|
+
tools = client.list_tools
|
|
67
|
+
|
|
68
|
+
tools.each do |tool_def|
|
|
69
|
+
tool_name = tool_def[:name]
|
|
70
|
+
mcp_client = client
|
|
71
|
+
|
|
72
|
+
tool = Tool.create(
|
|
73
|
+
name: tool_name,
|
|
74
|
+
description: tool_def[:description],
|
|
75
|
+
parameters: tool_def[:inputSchema],
|
|
76
|
+
mcp: server_name
|
|
77
|
+
) { |args| mcp_client.call_tool(tool_name, args) }
|
|
78
|
+
|
|
79
|
+
@mcp_tools << tool
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
RobotLab.config.logger.info(
|
|
83
|
+
"Robot '#{@name}' discovered #{tools.size} tools from MCP server '#{server_name}'"
|
|
84
|
+
)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RobotLab
|
|
4
|
+
class Robot < RubyLLM::Agent
|
|
5
|
+
# Template loading, rendering, and front-matter extraction.
|
|
6
|
+
#
|
|
7
|
+
# Expects the including class to provide:
|
|
8
|
+
# @chat, @template, @build_context, @name, @name_from_constructor,
|
|
9
|
+
# @description, @local_tools, @mcp_config
|
|
10
|
+
module TemplateRendering
|
|
11
|
+
# Front matter keys that map to chat configuration methods
|
|
12
|
+
FRONT_MATTER_CONFIG_KEYS = %i[
|
|
13
|
+
model temperature top_p top_k max_tokens
|
|
14
|
+
presence_penalty frequency_penalty stop
|
|
15
|
+
].freeze
|
|
16
|
+
|
|
17
|
+
# Front matter keys for robot identity and capabilities.
|
|
18
|
+
# Note: uses `robot_name` because PM::Metadata reserves `name` for the filename.
|
|
19
|
+
FRONT_MATTER_EXTRA_KEYS = %i[tools mcp robot_name description].freeze
|
|
20
|
+
|
|
21
|
+
# Apply a prompt_manager template to the robot's chat
|
|
22
|
+
#
|
|
23
|
+
# @param template_id [Symbol, String] the template identifier
|
|
24
|
+
# @param context [Hash] variables to pass to the template
|
|
25
|
+
# @return [self]
|
|
26
|
+
def with_template(template_id, **context)
|
|
27
|
+
@template = template_id.to_sym
|
|
28
|
+
@build_context = context
|
|
29
|
+
apply_template_to_chat(context)
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Apply a prompt_manager template to the persistent chat.
|
|
36
|
+
# If required parameters are missing, applies front matter config but
|
|
37
|
+
# defers rendering until run time when all values are available.
|
|
38
|
+
def apply_template_to_chat(context)
|
|
39
|
+
parsed = PM.parse(@template)
|
|
40
|
+
|
|
41
|
+
# Extract extra config from front matter (name, description, tools, mcp)
|
|
42
|
+
apply_front_matter_extras(parsed.metadata)
|
|
43
|
+
|
|
44
|
+
# Extract LLM config from front matter and apply to chat.
|
|
45
|
+
# Front matter is the base; @config (from constructor kwargs) overrides.
|
|
46
|
+
fm_config = RunConfig.from_front_matter(parsed.metadata)
|
|
47
|
+
effective = fm_config.merge(@config)
|
|
48
|
+
effective.apply_to(@chat)
|
|
49
|
+
|
|
50
|
+
# Resolve context (could be a Proc)
|
|
51
|
+
resolved_ctx = resolve_context(context, network: nil)
|
|
52
|
+
|
|
53
|
+
# Render the template body with context
|
|
54
|
+
begin
|
|
55
|
+
rendered = parsed.to_s(**resolved_ctx)
|
|
56
|
+
@chat.with_instructions(rendered)
|
|
57
|
+
rescue ArgumentError => e
|
|
58
|
+
raise unless e.message.start_with?("Missing required parameters:")
|
|
59
|
+
|
|
60
|
+
# Required parameters not yet available; template will be
|
|
61
|
+
# fully rendered at run time via rerender_template.
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Re-render the template with run-time context merged into build-time context.
|
|
67
|
+
# prompt_manager parameters may be required (null) and only available at run time.
|
|
68
|
+
def rerender_template(run_context)
|
|
69
|
+
merged = (@build_context || {}).merge(run_context)
|
|
70
|
+
parsed = PM.parse(@template)
|
|
71
|
+
resolved_ctx = resolve_context(merged, network: nil)
|
|
72
|
+
rendered = parsed.to_s(**resolved_ctx)
|
|
73
|
+
@chat.with_instructions(rendered)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Extract identity and capability keys from front matter metadata.
|
|
78
|
+
# Constructor-provided values take precedence over frontmatter.
|
|
79
|
+
def apply_front_matter_extras(metadata)
|
|
80
|
+
if metadata.respond_to?(:robot_name) && metadata.robot_name && !@name_from_constructor
|
|
81
|
+
@name = metadata.robot_name.to_s
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
if metadata.respond_to?(:description) && metadata.description && @description.nil?
|
|
85
|
+
@description = metadata.description.to_s
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
if metadata.respond_to?(:tools) && metadata.tools.is_a?(Array) && @local_tools.empty?
|
|
89
|
+
@local_tools = resolve_frontmatter_tools(metadata.tools)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if metadata.respond_to?(:mcp) && metadata.mcp.is_a?(Array) && ToolConfig.none_value?(@mcp_config)
|
|
93
|
+
@mcp_config = metadata.mcp.map { |m| m.is_a?(Hash) ? m.transform_keys(&:to_sym) : m }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# Resolve string tool names from frontmatter to Ruby constants.
|
|
99
|
+
# Tool subclasses are instantiated; instances are used as-is.
|
|
100
|
+
# Unresolvable names are skipped with a warning.
|
|
101
|
+
def resolve_frontmatter_tools(tool_names)
|
|
102
|
+
tool_names.filter_map do |name|
|
|
103
|
+
case name
|
|
104
|
+
when String
|
|
105
|
+
begin
|
|
106
|
+
const = Object.const_get(name)
|
|
107
|
+
const.is_a?(Class) && const < RubyLLM::Tool ? const.new : const
|
|
108
|
+
rescue NameError
|
|
109
|
+
RobotLab.config.logger.warn("Robot '#{@name}': tool '#{name}' not found, skipping")
|
|
110
|
+
nil
|
|
111
|
+
end
|
|
112
|
+
when Class
|
|
113
|
+
name.new
|
|
114
|
+
else
|
|
115
|
+
name
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def resolve_context(context, network:)
|
|
122
|
+
case context
|
|
123
|
+
when Proc then context.call(network: network)
|
|
124
|
+
when Hash then context
|
|
125
|
+
else {}
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|