robot_lab 0.0.1 → 0.0.6
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 +140 -0
- data/README.md +263 -48
- data/Rakefile +71 -1
- data/docs/api/core/index.md +53 -46
- data/docs/api/core/memory.md +200 -154
- data/docs/api/core/network.md +13 -3
- data/docs/api/core/robot.md +490 -130
- data/docs/api/core/state.md +55 -73
- data/docs/api/core/tool.md +205 -209
- data/docs/api/index.md +7 -28
- 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/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/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 +360 -116
- 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 +311 -49
- 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 +347 -154
- 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 +418 -212
- data/docs/guides/creating-networks.md +143 -24
- data/docs/guides/index.md +0 -5
- data/docs/guides/mcp-integration.md +152 -113
- data/docs/guides/memory.md +220 -164
- data/docs/guides/rails-integration.md +244 -162
- data/docs/guides/streaming.md +137 -187
- data/docs/guides/using-tools.md +259 -212
- data/docs/index.md +46 -41
- data/examples/01_simple_robot.rb +6 -9
- data/examples/02_tools.rb +6 -9
- data/examples/03_network.rb +19 -17
- 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 +169 -0
- data/examples/09_chaining.rb +262 -0
- data/examples/10_memory.rb +331 -0
- data/examples/11_network_introspection.rb +253 -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 +63 -0
- data/examples/14_rusty_circuit/open_mic.rb +123 -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 +156 -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/16_writers_room/display.rb +158 -0
- data/examples/16_writers_room/output/.gitignore +2 -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/prompts/writer.md +37 -0
- data/examples/16_writers_room/room.rb +150 -0
- data/examples/16_writers_room/tools.rb +162 -0
- data/examples/16_writers_room/writer.rb +121 -0
- data/examples/16_writers_room/writers_room.rb +162 -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 +0 -13
- 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 +10 -34
- 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 +240 -330
- data/lib/robot_lab/robot_message.rb +44 -0
- data/lib/robot_lab/robot_result.rb +1 -0
- data/lib/robot_lab/run_config.rb +184 -0
- data/lib/robot_lab/state_proxy.rb +2 -12
- data/lib/robot_lab/streaming/context.rb +1 -1
- data/lib/robot_lab/task.rb +8 -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/utils.rb +39 -0
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +89 -57
- data/mkdocs.yml +0 -11
- metadata +121 -135
- 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 -195
- data/docs/api/history/config.md +0 -191
- data/docs/api/history/index.md +0 -132
- data/docs/api/history/thread-manager.md +0 -144
- data/docs/guides/history.md +0 -359
- 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/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 -159
- data/lib/robot_lab/adapters/registry.rb +0 -81
- data/lib/robot_lab/configuration.rb +0 -143
- 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
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module Adapters
|
|
5
|
-
# Registry for looking up provider adapters
|
|
6
|
-
#
|
|
7
|
-
# Maps provider symbols to their adapter classes.
|
|
8
|
-
#
|
|
9
|
-
# @example
|
|
10
|
-
# adapter = Registry.for(:anthropic)
|
|
11
|
-
# adapter.format_messages(messages)
|
|
12
|
-
#
|
|
13
|
-
module Registry
|
|
14
|
-
# @return [Hash<Symbol, Class>] mapping of provider symbols to adapter classes
|
|
15
|
-
ADAPTERS = {
|
|
16
|
-
anthropic: Anthropic,
|
|
17
|
-
openai: OpenAI,
|
|
18
|
-
gemini: Gemini,
|
|
19
|
-
# Azure uses OpenAI adapter
|
|
20
|
-
azure_openai: OpenAI,
|
|
21
|
-
# Grok uses OpenAI adapter
|
|
22
|
-
grok: OpenAI,
|
|
23
|
-
# Ollama uses OpenAI adapter
|
|
24
|
-
ollama: OpenAI,
|
|
25
|
-
# OpenRouter uses OpenAI adapter
|
|
26
|
-
openrouter: OpenAI,
|
|
27
|
-
# Bedrock uses Anthropic adapter
|
|
28
|
-
bedrock: Anthropic,
|
|
29
|
-
# VertexAI uses Gemini adapter
|
|
30
|
-
vertexai: Gemini
|
|
31
|
-
}.freeze
|
|
32
|
-
|
|
33
|
-
class << self
|
|
34
|
-
# Get adapter for a provider
|
|
35
|
-
#
|
|
36
|
-
# @param provider [Symbol, String] Provider name
|
|
37
|
-
# @return [Base] Adapter instance
|
|
38
|
-
# @raise [ArgumentError] If provider not found
|
|
39
|
-
#
|
|
40
|
-
def for(provider)
|
|
41
|
-
provider_sym = provider.to_s.downcase.gsub("-", "_").to_sym
|
|
42
|
-
adapter_class = ADAPTERS[provider_sym]
|
|
43
|
-
|
|
44
|
-
unless adapter_class
|
|
45
|
-
raise ArgumentError, "Unknown provider: #{provider}. " \
|
|
46
|
-
"Available providers: #{available.join(', ')}"
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
adapter_class.new
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# List available providers
|
|
53
|
-
#
|
|
54
|
-
# @return [Array<Symbol>]
|
|
55
|
-
#
|
|
56
|
-
def available
|
|
57
|
-
ADAPTERS.keys
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# Check if provider is supported
|
|
61
|
-
#
|
|
62
|
-
# @param provider [Symbol, String]
|
|
63
|
-
# @return [Boolean]
|
|
64
|
-
#
|
|
65
|
-
def supports?(provider)
|
|
66
|
-
provider_sym = provider.to_s.downcase.gsub("-", "_").to_sym
|
|
67
|
-
ADAPTERS.key?(provider_sym)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Register a custom adapter
|
|
71
|
-
#
|
|
72
|
-
# @param provider [Symbol] Provider name
|
|
73
|
-
# @param adapter_class [Class] Adapter class
|
|
74
|
-
#
|
|
75
|
-
def register(provider, adapter_class)
|
|
76
|
-
ADAPTERS[provider.to_sym] = adapter_class
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
# Global configuration for RobotLab
|
|
5
|
-
#
|
|
6
|
-
# @example
|
|
7
|
-
# RobotLab.configure do |config|
|
|
8
|
-
# config.default_provider = :anthropic
|
|
9
|
-
# config.default_model = "claude-sonnet-4"
|
|
10
|
-
# config.template_path = "app/prompts"
|
|
11
|
-
# config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
|
12
|
-
#
|
|
13
|
-
# # Global MCP servers available to all networks and robots
|
|
14
|
-
# config.mcp = [
|
|
15
|
-
# { name: "github", transport: { type: "stdio", command: "github-mcp" } }
|
|
16
|
-
# ]
|
|
17
|
-
#
|
|
18
|
-
# # Global tools whitelist (only these tools are available)
|
|
19
|
-
# config.tools = %w[search_code create_issue]
|
|
20
|
-
# end
|
|
21
|
-
#
|
|
22
|
-
class Configuration
|
|
23
|
-
# @!attribute [rw] default_provider
|
|
24
|
-
# @return [Symbol] the default LLM provider (defaults to :anthropic)
|
|
25
|
-
# @!attribute [rw] default_model
|
|
26
|
-
# @return [String] the default model to use (defaults to "claude-sonnet-4")
|
|
27
|
-
# @!attribute [rw] max_iterations
|
|
28
|
-
# @return [Integer] maximum robot iterations per network run (defaults to 10)
|
|
29
|
-
# @!attribute [rw] max_tool_iterations
|
|
30
|
-
# @return [Integer] maximum tool iterations per robot run (defaults to 10)
|
|
31
|
-
# @!attribute [rw] streaming_enabled
|
|
32
|
-
# @return [Boolean] whether streaming is enabled by default (defaults to true)
|
|
33
|
-
# @!attribute [rw] logger
|
|
34
|
-
# @return [Logger] the logger instance
|
|
35
|
-
# @!attribute [rw] mcp
|
|
36
|
-
# @return [Symbol, Array] global MCP server configuration (:none, :inherit, or array)
|
|
37
|
-
# @!attribute [rw] tools
|
|
38
|
-
# @return [Symbol, Array] global tools whitelist (:none, :inherit, or array)
|
|
39
|
-
attr_accessor :default_provider,
|
|
40
|
-
:default_model,
|
|
41
|
-
:max_iterations,
|
|
42
|
-
:max_tool_iterations,
|
|
43
|
-
:streaming_enabled,
|
|
44
|
-
:logger,
|
|
45
|
-
:mcp,
|
|
46
|
-
:tools
|
|
47
|
-
|
|
48
|
-
# Creates a new Configuration with default values.
|
|
49
|
-
def initialize
|
|
50
|
-
@default_provider = :anthropic
|
|
51
|
-
@default_model = "claude-sonnet-4"
|
|
52
|
-
@max_iterations = 10
|
|
53
|
-
@max_tool_iterations = 10
|
|
54
|
-
@streaming_enabled = true
|
|
55
|
-
@logger = default_logger
|
|
56
|
-
@template_path = nil
|
|
57
|
-
@mcp = :none
|
|
58
|
-
@tools = :none
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Sets the Anthropic API key.
|
|
62
|
-
#
|
|
63
|
-
# @param key [String] the API key
|
|
64
|
-
# @return [void]
|
|
65
|
-
def anthropic_api_key=(key)
|
|
66
|
-
RubyLLM.configure { |c| c.anthropic_api_key = key }
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
# Sets the OpenAI API key.
|
|
70
|
-
#
|
|
71
|
-
# @param key [String] the API key
|
|
72
|
-
# @return [void]
|
|
73
|
-
def openai_api_key=(key)
|
|
74
|
-
RubyLLM.configure { |c| c.openai_api_key = key }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Sets the Google Gemini API key.
|
|
78
|
-
#
|
|
79
|
-
# @param key [String] the API key
|
|
80
|
-
# @return [void]
|
|
81
|
-
def gemini_api_key=(key)
|
|
82
|
-
RubyLLM.configure { |c| c.gemini_api_key = key }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Sets the AWS Bedrock API key.
|
|
86
|
-
#
|
|
87
|
-
# @param key [String] the API key
|
|
88
|
-
# @return [void]
|
|
89
|
-
def bedrock_api_key=(key)
|
|
90
|
-
RubyLLM.configure { |c| c.bedrock_api_key = key }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Sets the OpenRouter API key.
|
|
94
|
-
#
|
|
95
|
-
# @param key [String] the API key
|
|
96
|
-
# @return [void]
|
|
97
|
-
def openrouter_api_key=(key)
|
|
98
|
-
RubyLLM.configure { |c| c.openrouter_api_key = key }
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Set the template path and configure ruby_llm-template
|
|
102
|
-
#
|
|
103
|
-
# @param path [String] Path to the templates directory
|
|
104
|
-
#
|
|
105
|
-
def template_path=(path)
|
|
106
|
-
@template_path = path
|
|
107
|
-
configure_template_library if path
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Returns the template path.
|
|
111
|
-
#
|
|
112
|
-
# @return [String] the configured template path or default
|
|
113
|
-
def template_path
|
|
114
|
-
@template_path || default_template_path
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
private
|
|
118
|
-
|
|
119
|
-
def configure_template_library
|
|
120
|
-
require "ruby_llm/template"
|
|
121
|
-
RubyLLM::Template.configure do |config|
|
|
122
|
-
config.template_directory = @template_path
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def default_template_path
|
|
127
|
-
if defined?(Rails) && Rails.root
|
|
128
|
-
Rails.root.join("app", "prompts").to_s
|
|
129
|
-
else
|
|
130
|
-
"prompts"
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def default_logger
|
|
135
|
-
if defined?(Rails) && Rails.respond_to?(:logger)
|
|
136
|
-
Rails.logger
|
|
137
|
-
else
|
|
138
|
-
require "logger"
|
|
139
|
-
Logger.new($stdout, level: Logger::INFO)
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
data/lib/robot_lab/errors.rb
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
# Error serialization utilities
|
|
5
|
-
#
|
|
6
|
-
# Provides methods to serialize Ruby exceptions into a format
|
|
7
|
-
# suitable for tool results and logging.
|
|
8
|
-
#
|
|
9
|
-
module Errors
|
|
10
|
-
class << self
|
|
11
|
-
# Serialize an exception to a hash
|
|
12
|
-
#
|
|
13
|
-
# @param error [Exception] The error to serialize
|
|
14
|
-
# @param include_backtrace [Boolean] Whether to include backtrace
|
|
15
|
-
# @return [Hash] Serialized error
|
|
16
|
-
#
|
|
17
|
-
def serialize(error, include_backtrace: false)
|
|
18
|
-
result = {
|
|
19
|
-
type: error.class.name,
|
|
20
|
-
message: error.message
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if include_backtrace && error.backtrace
|
|
24
|
-
result[:backtrace] = error.backtrace.first(10)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
if error.cause
|
|
28
|
-
result[:cause] = serialize(error.cause, include_backtrace: include_backtrace)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
result
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
# Deserialize an error hash back to an exception
|
|
35
|
-
#
|
|
36
|
-
# @param hash [Hash] Serialized error
|
|
37
|
-
# @return [StandardError]
|
|
38
|
-
#
|
|
39
|
-
def deserialize(hash)
|
|
40
|
-
hash = hash.transform_keys(&:to_sym)
|
|
41
|
-
klass = begin
|
|
42
|
-
Object.const_get(hash[:type])
|
|
43
|
-
rescue NameError
|
|
44
|
-
StandardError
|
|
45
|
-
end
|
|
46
|
-
klass.new(hash[:message])
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Format error for display
|
|
50
|
-
#
|
|
51
|
-
# @param error [Exception] The error
|
|
52
|
-
# @return [String]
|
|
53
|
-
#
|
|
54
|
-
def format(error)
|
|
55
|
-
"[#{error.class.name}] #{error.message}"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Wrap a block and return error hash on failure
|
|
59
|
-
#
|
|
60
|
-
# @yield Block to execute
|
|
61
|
-
# @return [Hash] { data: result } or { error: serialized_error }
|
|
62
|
-
#
|
|
63
|
-
def capture
|
|
64
|
-
{ data: yield }
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
{ error: serialize(e) }
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
end
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module History
|
|
5
|
-
# ActiveRecord-based history persistence adapter
|
|
6
|
-
#
|
|
7
|
-
# Provides thread and result storage using ActiveRecord models.
|
|
8
|
-
# Requires Rails or standalone ActiveRecord setup.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# adapter = ActiveRecordAdapter.new(
|
|
12
|
-
# thread_model: RobotLabThread,
|
|
13
|
-
# result_model: RobotLabResult
|
|
14
|
-
# )
|
|
15
|
-
#
|
|
16
|
-
# config = adapter.to_config
|
|
17
|
-
# network = RobotLab.create_network(history: config)
|
|
18
|
-
#
|
|
19
|
-
class ActiveRecordAdapter
|
|
20
|
-
# @!attribute [r] thread_model
|
|
21
|
-
# @return [Class] ActiveRecord model class for threads
|
|
22
|
-
# @!attribute [r] result_model
|
|
23
|
-
# @return [Class] ActiveRecord model class for results
|
|
24
|
-
attr_reader :thread_model, :result_model
|
|
25
|
-
|
|
26
|
-
# Initialize adapter with ActiveRecord models
|
|
27
|
-
#
|
|
28
|
-
# @param thread_model [Class] ActiveRecord model for threads
|
|
29
|
-
# @param result_model [Class] ActiveRecord model for results
|
|
30
|
-
#
|
|
31
|
-
def initialize(thread_model:, result_model:)
|
|
32
|
-
@thread_model = thread_model
|
|
33
|
-
@result_model = result_model
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Create a new thread
|
|
37
|
-
#
|
|
38
|
-
# @param state [State] Current state
|
|
39
|
-
# @param input [String, UserMessage] Initial input
|
|
40
|
-
# @return [Hash] Thread ID and metadata
|
|
41
|
-
#
|
|
42
|
-
def create_thread(state:, input:, **)
|
|
43
|
-
input_content = input.is_a?(UserMessage) ? input.content : input.to_s
|
|
44
|
-
input_metadata = input.is_a?(UserMessage) ? input.metadata : {}
|
|
45
|
-
|
|
46
|
-
thread = @thread_model.create!(
|
|
47
|
-
session_id: SecureRandom.uuid,
|
|
48
|
-
initial_input: input_content,
|
|
49
|
-
input_metadata: input_metadata,
|
|
50
|
-
state_data: state.data.to_h
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
{ session_id: thread.session_id, created_at: thread.created_at }
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Retrieve results for a thread
|
|
57
|
-
#
|
|
58
|
-
# @param session_id [String] Thread identifier
|
|
59
|
-
# @return [Array<RobotResult>] History of results
|
|
60
|
-
#
|
|
61
|
-
def get(session_id:, **)
|
|
62
|
-
@result_model
|
|
63
|
-
.where(session_id: session_id)
|
|
64
|
-
.order(:sequence_number, :created_at)
|
|
65
|
-
.map { |record| deserialize_result(record) }
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Append user message to thread
|
|
69
|
-
#
|
|
70
|
-
# @param session_id [String] Thread identifier
|
|
71
|
-
# @param message [UserMessage] Message to append
|
|
72
|
-
#
|
|
73
|
-
def append_user_message(session_id:, message:, **)
|
|
74
|
-
@thread_model.where(session_id: session_id).update_all(
|
|
75
|
-
last_user_message: message.content,
|
|
76
|
-
last_user_message_at: Time.current
|
|
77
|
-
)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
# Append results to thread
|
|
81
|
-
#
|
|
82
|
-
# @param session_id [String] Thread identifier
|
|
83
|
-
# @param new_results [Array<RobotResult>] Results to append
|
|
84
|
-
#
|
|
85
|
-
def append_results(session_id:, new_results:, **)
|
|
86
|
-
base_sequence = @result_model.where(session_id: session_id).maximum(:sequence_number) || 0
|
|
87
|
-
|
|
88
|
-
new_results.each_with_index do |result, index|
|
|
89
|
-
@result_model.create!(
|
|
90
|
-
session_id: session_id,
|
|
91
|
-
robot_name: result.robot_name,
|
|
92
|
-
sequence_number: base_sequence + index + 1,
|
|
93
|
-
output_data: serialize_messages(result.output),
|
|
94
|
-
tool_calls_data: serialize_messages(result.tool_calls),
|
|
95
|
-
stop_reason: result.stop_reason,
|
|
96
|
-
checksum: result.checksum
|
|
97
|
-
)
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Update thread timestamp
|
|
101
|
-
@thread_model.where(session_id: session_id).update_all(
|
|
102
|
-
updated_at: Time.current
|
|
103
|
-
)
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
# Convert adapter to Config object
|
|
107
|
-
#
|
|
108
|
-
# @return [Config] History configuration
|
|
109
|
-
#
|
|
110
|
-
def to_config
|
|
111
|
-
Config.new(
|
|
112
|
-
create_thread: method(:create_thread),
|
|
113
|
-
get: method(:get),
|
|
114
|
-
append_user_message: method(:append_user_message),
|
|
115
|
-
append_results: method(:append_results)
|
|
116
|
-
)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
private
|
|
120
|
-
|
|
121
|
-
def serialize_messages(messages)
|
|
122
|
-
messages.map(&:to_h)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def deserialize_result(record)
|
|
126
|
-
output = deserialize_messages(record.output_data)
|
|
127
|
-
tool_calls = deserialize_messages(record.tool_calls_data)
|
|
128
|
-
|
|
129
|
-
RobotResult.new(
|
|
130
|
-
robot_name: record.robot_name,
|
|
131
|
-
output: output,
|
|
132
|
-
tool_calls: tool_calls,
|
|
133
|
-
stop_reason: record.stop_reason
|
|
134
|
-
)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def deserialize_messages(data)
|
|
138
|
-
return [] unless data
|
|
139
|
-
|
|
140
|
-
data.map do |msg_hash|
|
|
141
|
-
Message.from_hash(msg_hash.symbolize_keys)
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
-
end
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module History
|
|
5
|
-
# Configuration for conversation history persistence
|
|
6
|
-
#
|
|
7
|
-
# Defines callbacks for creating threads, retrieving history,
|
|
8
|
-
# and appending messages/results.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# config = History::Config.new(
|
|
12
|
-
# create_thread: ->(state:, input:, **) {
|
|
13
|
-
# { session_id: SecureRandom.uuid }
|
|
14
|
-
# },
|
|
15
|
-
# get: ->(session_id:, **) {
|
|
16
|
-
# database.find_results(session_id)
|
|
17
|
-
# },
|
|
18
|
-
# append_results: ->(session_id:, new_results:, **) {
|
|
19
|
-
# database.insert_results(session_id, new_results)
|
|
20
|
-
# }
|
|
21
|
-
# )
|
|
22
|
-
#
|
|
23
|
-
class Config
|
|
24
|
-
# @!attribute [rw] create_thread
|
|
25
|
-
# @return [Proc, nil] callback to create a new conversation thread
|
|
26
|
-
# @!attribute [rw] get
|
|
27
|
-
# @return [Proc, nil] callback to retrieve history for a thread
|
|
28
|
-
# @!attribute [rw] append_user_message
|
|
29
|
-
# @return [Proc, nil] callback to append user messages
|
|
30
|
-
# @!attribute [rw] append_results
|
|
31
|
-
# @return [Proc, nil] callback to append robot results
|
|
32
|
-
attr_accessor :create_thread, :get, :append_user_message, :append_results
|
|
33
|
-
|
|
34
|
-
# Initialize history configuration
|
|
35
|
-
#
|
|
36
|
-
# @param create_thread [Proc] Callback to create a new thread
|
|
37
|
-
# @param get [Proc] Callback to retrieve history for a thread
|
|
38
|
-
# @param append_user_message [Proc] Callback to append user messages
|
|
39
|
-
# @param append_results [Proc] Callback to append robot results
|
|
40
|
-
#
|
|
41
|
-
def initialize(create_thread: nil, get: nil, append_user_message: nil, append_results: nil)
|
|
42
|
-
@create_thread = create_thread
|
|
43
|
-
@get = get
|
|
44
|
-
@append_user_message = append_user_message
|
|
45
|
-
@append_results = append_results
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Check if history persistence is configured
|
|
49
|
-
#
|
|
50
|
-
# @return [Boolean]
|
|
51
|
-
#
|
|
52
|
-
def configured?
|
|
53
|
-
@create_thread && @get
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Create a new conversation thread
|
|
57
|
-
#
|
|
58
|
-
# @param state [State] Current state
|
|
59
|
-
# @param input [String, UserMessage] Initial input
|
|
60
|
-
# @param kwargs [Hash] Additional arguments
|
|
61
|
-
# @return [Hash] Must include :session_id
|
|
62
|
-
#
|
|
63
|
-
def create_thread!(state:, input:, **kwargs)
|
|
64
|
-
raise HistoryError, "create_thread callback not configured" unless @create_thread
|
|
65
|
-
|
|
66
|
-
result = @create_thread.call(state: state, input: input, **kwargs)
|
|
67
|
-
|
|
68
|
-
unless result.is_a?(Hash) && result[:session_id]
|
|
69
|
-
raise HistoryError, "create_thread must return a hash with :session_id"
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
result
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
# Retrieve history for a thread
|
|
76
|
-
#
|
|
77
|
-
# @param session_id [String] Thread identifier
|
|
78
|
-
# @param kwargs [Hash] Additional arguments
|
|
79
|
-
# @return [Array<RobotResult>] History of results
|
|
80
|
-
#
|
|
81
|
-
def get!(session_id:, **kwargs)
|
|
82
|
-
raise HistoryError, "get callback not configured" unless @get
|
|
83
|
-
|
|
84
|
-
@get.call(session_id: session_id, **kwargs)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
# Append a user message to the thread
|
|
88
|
-
#
|
|
89
|
-
# @param session_id [String] Thread identifier
|
|
90
|
-
# @param message [UserMessage] Message to append
|
|
91
|
-
# @param kwargs [Hash] Additional arguments
|
|
92
|
-
#
|
|
93
|
-
def append_user_message!(session_id:, message:, **kwargs)
|
|
94
|
-
return unless @append_user_message
|
|
95
|
-
|
|
96
|
-
@append_user_message.call(session_id: session_id, message: message, **kwargs)
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
# Append robot results to the thread
|
|
100
|
-
#
|
|
101
|
-
# @param session_id [String] Thread identifier
|
|
102
|
-
# @param new_results [Array<RobotResult>] Results to append
|
|
103
|
-
# @param kwargs [Hash] Additional arguments
|
|
104
|
-
#
|
|
105
|
-
def append_results!(session_id:, new_results:, **kwargs)
|
|
106
|
-
return unless @append_results
|
|
107
|
-
|
|
108
|
-
@append_results.call(session_id: session_id, new_results: new_results, **kwargs)
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Error raised when history operations fail
|
|
113
|
-
class HistoryError < RobotLab::Error; end
|
|
114
|
-
end
|
|
115
|
-
end
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module RobotLab
|
|
4
|
-
module History
|
|
5
|
-
# Manages conversation thread lifecycle
|
|
6
|
-
#
|
|
7
|
-
# Handles thread creation, history retrieval, and result persistence
|
|
8
|
-
# using the configured history adapter.
|
|
9
|
-
#
|
|
10
|
-
# @example
|
|
11
|
-
# manager = ThreadManager.new(config)
|
|
12
|
-
# session_id = manager.create_thread(state: state, input: "Hello")
|
|
13
|
-
# history = manager.get_history(session_id)
|
|
14
|
-
#
|
|
15
|
-
class ThreadManager
|
|
16
|
-
# @!attribute [r] config
|
|
17
|
-
# @return [Config] the history configuration
|
|
18
|
-
attr_reader :config
|
|
19
|
-
|
|
20
|
-
# Initialize thread manager
|
|
21
|
-
#
|
|
22
|
-
# @param config [Config] History configuration
|
|
23
|
-
#
|
|
24
|
-
def initialize(config)
|
|
25
|
-
@config = config
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Create a new conversation thread
|
|
29
|
-
#
|
|
30
|
-
# @param state [State] Current state
|
|
31
|
-
# @param input [String, UserMessage] Initial input
|
|
32
|
-
# @return [String] Thread ID
|
|
33
|
-
#
|
|
34
|
-
def create_thread(state:, input:)
|
|
35
|
-
result = @config.create_thread!(state: state, input: input)
|
|
36
|
-
result[:session_id]
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Get history for a thread
|
|
40
|
-
#
|
|
41
|
-
# @param session_id [String] Thread identifier
|
|
42
|
-
# @return [Array<RobotResult>] History of results
|
|
43
|
-
#
|
|
44
|
-
def get_history(session_id)
|
|
45
|
-
@config.get!(session_id: session_id)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Append user message to thread
|
|
49
|
-
#
|
|
50
|
-
# @param session_id [String] Thread identifier
|
|
51
|
-
# @param message [UserMessage] Message to append
|
|
52
|
-
#
|
|
53
|
-
def append_user_message(session_id:, message:)
|
|
54
|
-
@config.append_user_message!(session_id: session_id, message: message)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Append results to thread
|
|
58
|
-
#
|
|
59
|
-
# @param session_id [String] Thread identifier
|
|
60
|
-
# @param results [Array<RobotResult>] Results to append
|
|
61
|
-
#
|
|
62
|
-
def append_results(session_id:, results:)
|
|
63
|
-
@config.append_results!(session_id: session_id, new_results: results)
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# Load state from thread history
|
|
67
|
-
#
|
|
68
|
-
# @param session_id [String] Thread identifier
|
|
69
|
-
# @param state [State, Memory] State/Memory to populate
|
|
70
|
-
# @return [State, Memory] State/Memory with loaded history
|
|
71
|
-
#
|
|
72
|
-
def load_state(session_id:, state:)
|
|
73
|
-
results = get_history(session_id)
|
|
74
|
-
|
|
75
|
-
state.session_id = session_id
|
|
76
|
-
results.each { |r| state.append_result(r) }
|
|
77
|
-
|
|
78
|
-
state
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Save state results to thread
|
|
82
|
-
#
|
|
83
|
-
# @param session_id [String] Thread identifier
|
|
84
|
-
# @param state [State] State with results to save
|
|
85
|
-
# @param since_index [Integer] Save results from this index
|
|
86
|
-
#
|
|
87
|
-
def save_state(session_id:, state:, since_index: 0)
|
|
88
|
-
new_results = state.results[since_index..]
|
|
89
|
-
append_results(session_id: session_id, results: new_results) if new_results.any?
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
end
|