language-operator 0.0.1 → 0.1.30
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/.rubocop.yml +125 -0
- data/CHANGELOG.md +53 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +284 -0
- data/LICENSE +229 -21
- data/Makefile +77 -0
- data/README.md +3 -11
- data/Rakefile +34 -0
- data/bin/aictl +7 -0
- data/completions/_aictl +232 -0
- data/completions/aictl.bash +121 -0
- data/completions/aictl.fish +114 -0
- data/docs/architecture/agent-runtime.md +585 -0
- data/docs/dsl/agent-reference.md +591 -0
- data/docs/dsl/best-practices.md +1078 -0
- data/docs/dsl/chat-endpoints.md +895 -0
- data/docs/dsl/constraints.md +671 -0
- data/docs/dsl/mcp-integration.md +1177 -0
- data/docs/dsl/webhooks.md +932 -0
- data/docs/dsl/workflows.md +744 -0
- data/examples/README.md +569 -0
- data/examples/agent_example.rb +86 -0
- data/examples/chat_endpoint_agent.rb +118 -0
- data/examples/github_webhook_agent.rb +171 -0
- data/examples/mcp_agent.rb +158 -0
- data/examples/oauth_callback_agent.rb +296 -0
- data/examples/stripe_webhook_agent.rb +219 -0
- data/examples/webhook_agent.rb +80 -0
- data/lib/language_operator/agent/base.rb +110 -0
- data/lib/language_operator/agent/executor.rb +440 -0
- data/lib/language_operator/agent/instrumentation.rb +54 -0
- data/lib/language_operator/agent/metrics_tracker.rb +183 -0
- data/lib/language_operator/agent/safety/ast_validator.rb +272 -0
- data/lib/language_operator/agent/safety/audit_logger.rb +104 -0
- data/lib/language_operator/agent/safety/budget_tracker.rb +175 -0
- data/lib/language_operator/agent/safety/content_filter.rb +93 -0
- data/lib/language_operator/agent/safety/manager.rb +207 -0
- data/lib/language_operator/agent/safety/rate_limiter.rb +150 -0
- data/lib/language_operator/agent/safety/safe_executor.rb +115 -0
- data/lib/language_operator/agent/scheduler.rb +183 -0
- data/lib/language_operator/agent/telemetry.rb +116 -0
- data/lib/language_operator/agent/web_server.rb +610 -0
- data/lib/language_operator/agent/webhook_authenticator.rb +226 -0
- data/lib/language_operator/agent.rb +149 -0
- data/lib/language_operator/cli/commands/agent.rb +1252 -0
- data/lib/language_operator/cli/commands/cluster.rb +335 -0
- data/lib/language_operator/cli/commands/install.rb +404 -0
- data/lib/language_operator/cli/commands/model.rb +266 -0
- data/lib/language_operator/cli/commands/persona.rb +396 -0
- data/lib/language_operator/cli/commands/quickstart.rb +22 -0
- data/lib/language_operator/cli/commands/status.rb +156 -0
- data/lib/language_operator/cli/commands/tool.rb +537 -0
- data/lib/language_operator/cli/commands/use.rb +47 -0
- data/lib/language_operator/cli/errors/handler.rb +180 -0
- data/lib/language_operator/cli/errors/suggestions.rb +176 -0
- data/lib/language_operator/cli/formatters/code_formatter.rb +81 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +290 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +53 -0
- data/lib/language_operator/cli/formatters/table_formatter.rb +179 -0
- data/lib/language_operator/cli/formatters/value_formatter.rb +113 -0
- data/lib/language_operator/cli/helpers/cluster_context.rb +62 -0
- data/lib/language_operator/cli/helpers/cluster_validator.rb +101 -0
- data/lib/language_operator/cli/helpers/editor_helper.rb +58 -0
- data/lib/language_operator/cli/helpers/kubeconfig_validator.rb +167 -0
- data/lib/language_operator/cli/helpers/resource_dependency_checker.rb +74 -0
- data/lib/language_operator/cli/helpers/schedule_builder.rb +108 -0
- data/lib/language_operator/cli/helpers/user_prompts.rb +69 -0
- data/lib/language_operator/cli/main.rb +232 -0
- data/lib/language_operator/cli/templates/tools/generic.yaml +66 -0
- data/lib/language_operator/cli/wizards/agent_wizard.rb +246 -0
- data/lib/language_operator/cli/wizards/quickstart_wizard.rb +588 -0
- data/lib/language_operator/client/base.rb +214 -0
- data/lib/language_operator/client/config.rb +136 -0
- data/lib/language_operator/client/cost_calculator.rb +37 -0
- data/lib/language_operator/client/mcp_connector.rb +123 -0
- data/lib/language_operator/client.rb +19 -0
- data/lib/language_operator/config/cluster_config.rb +101 -0
- data/lib/language_operator/config/tool_patterns.yaml +57 -0
- data/lib/language_operator/config/tool_registry.rb +96 -0
- data/lib/language_operator/config.rb +138 -0
- data/lib/language_operator/dsl/adapter.rb +124 -0
- data/lib/language_operator/dsl/agent_context.rb +90 -0
- data/lib/language_operator/dsl/agent_definition.rb +427 -0
- data/lib/language_operator/dsl/chat_endpoint_definition.rb +115 -0
- data/lib/language_operator/dsl/config.rb +119 -0
- data/lib/language_operator/dsl/context.rb +50 -0
- data/lib/language_operator/dsl/execution_context.rb +47 -0
- data/lib/language_operator/dsl/helpers.rb +109 -0
- data/lib/language_operator/dsl/http.rb +184 -0
- data/lib/language_operator/dsl/mcp_server_definition.rb +73 -0
- data/lib/language_operator/dsl/parameter_definition.rb +124 -0
- data/lib/language_operator/dsl/registry.rb +36 -0
- data/lib/language_operator/dsl/shell.rb +125 -0
- data/lib/language_operator/dsl/tool_definition.rb +112 -0
- data/lib/language_operator/dsl/webhook_authentication.rb +114 -0
- data/lib/language_operator/dsl/webhook_definition.rb +106 -0
- data/lib/language_operator/dsl/workflow_definition.rb +259 -0
- data/lib/language_operator/dsl.rb +160 -0
- data/lib/language_operator/errors.rb +60 -0
- data/lib/language_operator/kubernetes/client.rb +279 -0
- data/lib/language_operator/kubernetes/resource_builder.rb +194 -0
- data/lib/language_operator/loggable.rb +47 -0
- data/lib/language_operator/logger.rb +141 -0
- data/lib/language_operator/retry.rb +123 -0
- data/lib/language_operator/retryable.rb +132 -0
- data/lib/language_operator/tool_loader.rb +242 -0
- data/lib/language_operator/validators.rb +170 -0
- data/lib/language_operator/version.rb +1 -1
- data/lib/language_operator.rb +65 -3
- data/requirements/tasks/challenge.md +9 -0
- data/requirements/tasks/iterate.md +36 -0
- data/requirements/tasks/optimize.md +21 -0
- data/requirements/tasks/tag.md +5 -0
- data/test_agent_dsl.rb +108 -0
- metadata +503 -20
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parameter_definition'
|
|
4
|
+
require_relative 'http'
|
|
5
|
+
require_relative 'shell'
|
|
6
|
+
require_relative 'helpers'
|
|
7
|
+
|
|
8
|
+
module LanguageOperator
|
|
9
|
+
module Dsl
|
|
10
|
+
# Tool definition for MCP tools
|
|
11
|
+
#
|
|
12
|
+
# Defines an MCP tool with parameters, description, and execution logic.
|
|
13
|
+
# Used within the DSL to create tools that can be registered and served.
|
|
14
|
+
#
|
|
15
|
+
# @example Define a simple tool
|
|
16
|
+
# tool "greet" do
|
|
17
|
+
# description "Greet a user by name"
|
|
18
|
+
#
|
|
19
|
+
# parameter :name do
|
|
20
|
+
# type :string
|
|
21
|
+
# required true
|
|
22
|
+
# description "Name to greet"
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# execute do |params|
|
|
26
|
+
# "Hello, #{params['name']}!"
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
class ToolDefinition
|
|
30
|
+
include LanguageOperator::Dsl::Helpers
|
|
31
|
+
|
|
32
|
+
# Provide access to HTTP and Shell helper classes as constants
|
|
33
|
+
HTTP = LanguageOperator::Dsl::HTTP
|
|
34
|
+
Shell = LanguageOperator::Dsl::Shell
|
|
35
|
+
|
|
36
|
+
attr_reader :name, :parameters, :execute_block
|
|
37
|
+
|
|
38
|
+
def initialize(name)
|
|
39
|
+
@name = name
|
|
40
|
+
@parameters = {}
|
|
41
|
+
@execute_block = nil
|
|
42
|
+
@description = nil
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def description(val = nil)
|
|
46
|
+
return @description if val.nil?
|
|
47
|
+
|
|
48
|
+
@description = val
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parameter(name, &)
|
|
52
|
+
param = ParameterDefinition.new(name)
|
|
53
|
+
param.instance_eval(&) if block_given?
|
|
54
|
+
@parameters[name.to_s] = param
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def execute(&block)
|
|
58
|
+
@execute_block = block
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def call(params)
|
|
62
|
+
log_debug "Calling tool '#{@name}' with params: #{params.inspect}"
|
|
63
|
+
|
|
64
|
+
# Apply default values for missing optional parameters
|
|
65
|
+
@parameters.each do |name, param_def|
|
|
66
|
+
default_value = param_def.instance_variable_get(:@default)
|
|
67
|
+
params[name] = default_value if !params.key?(name) && !default_value.nil?
|
|
68
|
+
|
|
69
|
+
# Validate required parameters
|
|
70
|
+
raise ArgumentError, "Missing required parameter: #{name}" if param_def.required? && !params.key?(name)
|
|
71
|
+
|
|
72
|
+
# Validate parameter format if validator is set and value is present
|
|
73
|
+
if params.key?(name)
|
|
74
|
+
error = param_def.validate_value(params[name])
|
|
75
|
+
raise ArgumentError, error if error
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Call the execute block with parameters
|
|
80
|
+
result = @execute_block.call(params) if @execute_block
|
|
81
|
+
|
|
82
|
+
log_debug "Tool '#{@name}' completed: #{truncate_for_log(result)}"
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def to_schema
|
|
87
|
+
{
|
|
88
|
+
'name' => @name,
|
|
89
|
+
'description' => @description,
|
|
90
|
+
'inputSchema' => {
|
|
91
|
+
'type' => 'object',
|
|
92
|
+
'properties' => @parameters.transform_values(&:to_schema),
|
|
93
|
+
'required' => @parameters.select { |_, p| p.required? }.keys
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def log_debug(message)
|
|
101
|
+
puts "[DEBUG] #{message}" if ENV['DEBUG'] || ENV['MCP_DEBUG']
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def truncate_for_log(text)
|
|
105
|
+
return text.inspect if text.nil?
|
|
106
|
+
|
|
107
|
+
str = text.to_s
|
|
108
|
+
str.length > 100 ? "#{str[0..100]}..." : str
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Defines authentication configuration for webhooks
|
|
6
|
+
class WebhookAuthentication
|
|
7
|
+
attr_reader :type, :config
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@type = nil
|
|
11
|
+
@config = {}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Verify HMAC signature (GitHub/Stripe style)
|
|
15
|
+
# @param header [String] Header name containing the signature
|
|
16
|
+
# @param secret [String] Secret key for HMAC
|
|
17
|
+
# @param algorithm [Symbol] Hash algorithm (:sha1, :sha256, :sha512)
|
|
18
|
+
# @param prefix [String, nil] Optional prefix to strip from signature (e.g., "sha256=")
|
|
19
|
+
def verify_signature(header:, secret:, algorithm: :sha256, prefix: nil)
|
|
20
|
+
if @current_methods
|
|
21
|
+
# Inside any_of/all_of - create child auth object
|
|
22
|
+
auth = WebhookAuthentication.new
|
|
23
|
+
auth.verify_signature(header: header, secret: secret, algorithm: algorithm, prefix: prefix)
|
|
24
|
+
@current_methods << auth
|
|
25
|
+
else
|
|
26
|
+
@type = :signature
|
|
27
|
+
@config[:header] = header
|
|
28
|
+
@config[:secret] = secret
|
|
29
|
+
@config[:algorithm] = algorithm
|
|
30
|
+
@config[:prefix] = prefix
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Verify API key from header
|
|
35
|
+
# @param header [String] Header name containing the API key
|
|
36
|
+
# @param key [String] Expected API key value
|
|
37
|
+
def verify_api_key(header:, key:)
|
|
38
|
+
if @current_methods
|
|
39
|
+
auth = WebhookAuthentication.new
|
|
40
|
+
auth.verify_api_key(header: header, key: key)
|
|
41
|
+
@current_methods << auth
|
|
42
|
+
else
|
|
43
|
+
@type = :api_key
|
|
44
|
+
@config[:header] = header
|
|
45
|
+
@config[:key] = key
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Verify bearer token
|
|
50
|
+
# @param token [String] Expected bearer token value
|
|
51
|
+
def verify_bearer_token(token:)
|
|
52
|
+
if @current_methods
|
|
53
|
+
auth = WebhookAuthentication.new
|
|
54
|
+
auth.verify_bearer_token(token: token)
|
|
55
|
+
@current_methods << auth
|
|
56
|
+
else
|
|
57
|
+
@type = :bearer_token
|
|
58
|
+
@config[:token] = token
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Verify basic auth credentials
|
|
63
|
+
# @param username [String] Expected username
|
|
64
|
+
# @param password [String] Expected password
|
|
65
|
+
def verify_basic_auth(username:, password:)
|
|
66
|
+
if @current_methods
|
|
67
|
+
auth = WebhookAuthentication.new
|
|
68
|
+
auth.verify_basic_auth(username: username, password: password)
|
|
69
|
+
@current_methods << auth
|
|
70
|
+
else
|
|
71
|
+
@type = :basic_auth
|
|
72
|
+
@config[:username] = username
|
|
73
|
+
@config[:password] = password
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Custom authentication callback
|
|
78
|
+
# @yield [context] Block receives request context hash
|
|
79
|
+
# @yieldreturn [Boolean] true if authenticated, false otherwise
|
|
80
|
+
def verify_custom(&block)
|
|
81
|
+
if @current_methods
|
|
82
|
+
auth = WebhookAuthentication.new
|
|
83
|
+
auth.verify_custom(&block)
|
|
84
|
+
@current_methods << auth
|
|
85
|
+
else
|
|
86
|
+
@type = :custom
|
|
87
|
+
@config[:callback] = block
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Allow multiple authentication methods (any can succeed)
|
|
92
|
+
def any_of(&block)
|
|
93
|
+
@type = :any_of
|
|
94
|
+
@config[:methods] = []
|
|
95
|
+
@current_methods = @config[:methods]
|
|
96
|
+
instance_eval(&block) if block
|
|
97
|
+
@current_methods = nil
|
|
98
|
+
# Restore type in case it was overwritten by method calls
|
|
99
|
+
@type = :any_of
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Allow multiple authentication methods (all must succeed)
|
|
103
|
+
def all_of(&block)
|
|
104
|
+
@type = :all_of
|
|
105
|
+
@config[:methods] = []
|
|
106
|
+
@current_methods = @config[:methods]
|
|
107
|
+
instance_eval(&block) if block
|
|
108
|
+
@current_methods = nil
|
|
109
|
+
# Restore type in case it was overwritten by method calls
|
|
110
|
+
@type = :all_of
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'webhook_authentication'
|
|
4
|
+
|
|
5
|
+
module LanguageOperator
|
|
6
|
+
module Dsl
|
|
7
|
+
# Webhook Definition
|
|
8
|
+
#
|
|
9
|
+
# Defines webhook endpoints for reactive agents.
|
|
10
|
+
#
|
|
11
|
+
# @example Define a webhook with authentication
|
|
12
|
+
# webhook "/github/pr-opened" do
|
|
13
|
+
# method :post
|
|
14
|
+
# authenticate do
|
|
15
|
+
# verify_signature header: "X-Hub-Signature-256",
|
|
16
|
+
# secret: ENV['GITHUB_WEBHOOK_SECRET'],
|
|
17
|
+
# algorithm: :sha256,
|
|
18
|
+
# prefix: "sha256="
|
|
19
|
+
# end
|
|
20
|
+
# on_request do |context|
|
|
21
|
+
# # Handle the webhook
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
class WebhookDefinition
|
|
25
|
+
attr_reader :path, :http_method, :handler, :authentication, :validations
|
|
26
|
+
|
|
27
|
+
# Initialize webhook definition
|
|
28
|
+
#
|
|
29
|
+
# @param path [String] URL path for the webhook
|
|
30
|
+
def initialize(path)
|
|
31
|
+
@path = path
|
|
32
|
+
@http_method = :post
|
|
33
|
+
@handler = nil
|
|
34
|
+
@authentication = nil
|
|
35
|
+
@validations = []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set HTTP method
|
|
39
|
+
#
|
|
40
|
+
# @param method_name [Symbol] HTTP method (:get, :post, :put, :delete, :patch)
|
|
41
|
+
# @return [void]
|
|
42
|
+
def method(method_name)
|
|
43
|
+
@http_method = method_name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Define authentication for this webhook
|
|
47
|
+
#
|
|
48
|
+
# @yield Authentication configuration block
|
|
49
|
+
# @return [void]
|
|
50
|
+
def authenticate(&block)
|
|
51
|
+
@authentication = WebhookAuthentication.new
|
|
52
|
+
@authentication.instance_eval(&block) if block
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Require specific headers
|
|
56
|
+
#
|
|
57
|
+
# @param headers [Hash] Required headers and their expected values (nil = just check presence)
|
|
58
|
+
# @return [void]
|
|
59
|
+
def require_headers(headers)
|
|
60
|
+
@validations << { type: :headers, config: headers }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Require specific content type
|
|
64
|
+
#
|
|
65
|
+
# @param content_type [String, Array<String>] Allowed content type(s)
|
|
66
|
+
# @return [void]
|
|
67
|
+
def require_content_type(*content_types)
|
|
68
|
+
@validations << { type: :content_type, config: content_types.flatten }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Add custom validation
|
|
72
|
+
#
|
|
73
|
+
# @yield [context] Validation block
|
|
74
|
+
# @yieldreturn [Boolean, String] true if valid, or error message string if invalid
|
|
75
|
+
# @return [void]
|
|
76
|
+
def validate(&block)
|
|
77
|
+
@validations << { type: :custom, config: block }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Define the request handler
|
|
81
|
+
#
|
|
82
|
+
# @yield [context] Request handler block
|
|
83
|
+
# @yieldparam context [Hash] Request context with :path, :method, :headers, :params, :body
|
|
84
|
+
# @return [void]
|
|
85
|
+
def on_request(&block)
|
|
86
|
+
@handler = block
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Register this webhook with a web server
|
|
90
|
+
#
|
|
91
|
+
# @param web_server [LanguageOperator::Agent::WebServer] Web server instance
|
|
92
|
+
# @return [void]
|
|
93
|
+
def register(web_server)
|
|
94
|
+
return unless @handler
|
|
95
|
+
|
|
96
|
+
web_server.register_route(
|
|
97
|
+
@path,
|
|
98
|
+
method: @http_method,
|
|
99
|
+
authentication: @authentication,
|
|
100
|
+
validations: @validations,
|
|
101
|
+
&@handler
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../logger'
|
|
4
|
+
require_relative '../loggable'
|
|
5
|
+
|
|
6
|
+
module LanguageOperator
|
|
7
|
+
module Dsl
|
|
8
|
+
# Workflow definition for agent execution
|
|
9
|
+
#
|
|
10
|
+
# Defines a series of steps that an agent executes to achieve objectives.
|
|
11
|
+
# Steps can depend on other steps, call tools, or perform LLM processing.
|
|
12
|
+
#
|
|
13
|
+
# @example Define a workflow
|
|
14
|
+
# workflow do
|
|
15
|
+
# step :search do
|
|
16
|
+
# tool "web_search"
|
|
17
|
+
# params query: "latest news"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# step :summarize do
|
|
21
|
+
# depends_on :search
|
|
22
|
+
# prompt "Summarize: {search.output}"
|
|
23
|
+
# end
|
|
24
|
+
# end
|
|
25
|
+
class WorkflowDefinition
|
|
26
|
+
include LanguageOperator::Loggable
|
|
27
|
+
|
|
28
|
+
attr_reader :steps, :step_order
|
|
29
|
+
|
|
30
|
+
def initialize
|
|
31
|
+
@steps = {}
|
|
32
|
+
@step_order = []
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Define a workflow step
|
|
36
|
+
#
|
|
37
|
+
# @param name [Symbol] Step name
|
|
38
|
+
# @param tool [String, nil] Tool to use (optional)
|
|
39
|
+
# @param params [Hash] Tool parameters (optional)
|
|
40
|
+
# @param depends_on [Symbol, Array<Symbol>] Dependencies (optional)
|
|
41
|
+
# @yield Step definition block
|
|
42
|
+
# @return [void]
|
|
43
|
+
def step(name, tool: nil, params: {}, depends_on: nil, &block)
|
|
44
|
+
step_def = StepDefinition.new(name, logger: @logger)
|
|
45
|
+
|
|
46
|
+
if tool
|
|
47
|
+
step_def.tool(tool)
|
|
48
|
+
step_def.params(params) unless params.empty?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
step_def.depends_on(depends_on) if depends_on
|
|
52
|
+
|
|
53
|
+
step_def.instance_eval(&block) if block
|
|
54
|
+
@steps[name] = step_def
|
|
55
|
+
@step_order << name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Execute the workflow
|
|
59
|
+
#
|
|
60
|
+
# @param context [Object] Execution context
|
|
61
|
+
# @return [Hash] Results from each step
|
|
62
|
+
def execute(context = nil)
|
|
63
|
+
results = {}
|
|
64
|
+
|
|
65
|
+
logger.info('Executing workflow', step_count: @steps.size)
|
|
66
|
+
|
|
67
|
+
@step_order.each do |step_name|
|
|
68
|
+
step_def = @steps[step_name]
|
|
69
|
+
|
|
70
|
+
# Check dependencies
|
|
71
|
+
if step_def.dependencies.any?
|
|
72
|
+
logger.debug('Checking dependencies',
|
|
73
|
+
step: step_name,
|
|
74
|
+
dependencies: step_def.dependencies)
|
|
75
|
+
step_def.dependencies.each do |dep|
|
|
76
|
+
next if results.key?(dep)
|
|
77
|
+
|
|
78
|
+
logger.error('Dependency not satisfied',
|
|
79
|
+
step: step_name,
|
|
80
|
+
missing_dependency: dep)
|
|
81
|
+
raise "Step #{step_name} depends on #{dep}, but #{dep} has not been executed"
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Execute step
|
|
86
|
+
logger.info('Executing step',
|
|
87
|
+
step: step_name,
|
|
88
|
+
tool: step_def.tool_name,
|
|
89
|
+
has_prompt: !step_def.prompt_template.nil?)
|
|
90
|
+
|
|
91
|
+
result = logger.timed('Step execution') do
|
|
92
|
+
step_def.execute(results, context)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
results[step_name] = result
|
|
96
|
+
logger.info('Step completed', step: step_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
logger.info('Workflow execution completed', total_steps: @steps.size)
|
|
100
|
+
results
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
|
|
105
|
+
def logger_component
|
|
106
|
+
'Workflow'
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Individual step definition
|
|
111
|
+
class StepDefinition
|
|
112
|
+
include LanguageOperator::Loggable
|
|
113
|
+
|
|
114
|
+
attr_reader :name, :dependencies, :tool_name, :tool_params, :prompt_template
|
|
115
|
+
|
|
116
|
+
def initialize(name, logger: nil)
|
|
117
|
+
@name = name
|
|
118
|
+
@tool_name = nil
|
|
119
|
+
@tool_params = {}
|
|
120
|
+
@prompt_template = nil
|
|
121
|
+
@dependencies = []
|
|
122
|
+
@execute_block = nil
|
|
123
|
+
@parent_logger = logger
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Set the tool to use
|
|
127
|
+
#
|
|
128
|
+
# @param name [String] Tool name
|
|
129
|
+
# @return [void]
|
|
130
|
+
def tool(name = nil)
|
|
131
|
+
return @tool_name if name.nil?
|
|
132
|
+
|
|
133
|
+
@tool_name = name
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Set tool parameters
|
|
137
|
+
#
|
|
138
|
+
# @param hash [Hash] Parameters
|
|
139
|
+
# @return [Hash] Current parameters
|
|
140
|
+
def params(hash = nil)
|
|
141
|
+
return @tool_params if hash.nil?
|
|
142
|
+
|
|
143
|
+
@tool_params = hash
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Set prompt template (for LLM processing)
|
|
147
|
+
#
|
|
148
|
+
# @param template [String] Prompt template
|
|
149
|
+
# @return [String] Current prompt
|
|
150
|
+
def prompt(template = nil)
|
|
151
|
+
return @prompt_template if template.nil?
|
|
152
|
+
|
|
153
|
+
@prompt_template = template
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Declare dependencies on other steps
|
|
157
|
+
#
|
|
158
|
+
# @param steps [Symbol, Array<Symbol>] Step names this depends on
|
|
159
|
+
# @return [Array<Symbol>] Current dependencies
|
|
160
|
+
def depends_on(*steps)
|
|
161
|
+
return @dependencies if steps.empty?
|
|
162
|
+
|
|
163
|
+
@dependencies = steps.flatten
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Define custom execution logic
|
|
167
|
+
#
|
|
168
|
+
# @yield Execution block
|
|
169
|
+
# @return [void]
|
|
170
|
+
def execute(&block)
|
|
171
|
+
@execute_block = block if block
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Execute this step
|
|
175
|
+
#
|
|
176
|
+
# @param results [Hash] Results from previous steps
|
|
177
|
+
# @param context [Object] Execution context
|
|
178
|
+
# @return [Object] Step result
|
|
179
|
+
def execute_step(results, context)
|
|
180
|
+
if @execute_block
|
|
181
|
+
# Custom execution logic
|
|
182
|
+
logger.debug('Executing custom logic', step: @name)
|
|
183
|
+
@execute_block.call(results, context)
|
|
184
|
+
elsif @tool_name
|
|
185
|
+
# Tool execution
|
|
186
|
+
params = interpolate_params(@tool_params, results)
|
|
187
|
+
logger.info('Calling tool',
|
|
188
|
+
step: @name,
|
|
189
|
+
tool: @tool_name,
|
|
190
|
+
params: params)
|
|
191
|
+
# In real implementation, this would call the actual tool
|
|
192
|
+
"Tool #{@tool_name} executed with #{params.inspect}"
|
|
193
|
+
elsif @prompt_template
|
|
194
|
+
# LLM processing
|
|
195
|
+
prompt = interpolate_template(@prompt_template, results)
|
|
196
|
+
logger.debug('LLM prompt',
|
|
197
|
+
step: @name,
|
|
198
|
+
prompt: prompt[0..200])
|
|
199
|
+
# In real implementation, this would call the LLM
|
|
200
|
+
"LLM processed: #{prompt}"
|
|
201
|
+
else
|
|
202
|
+
# No-op step
|
|
203
|
+
logger.debug('No execution logic defined', step: @name)
|
|
204
|
+
nil
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
alias execute execute_step
|
|
209
|
+
|
|
210
|
+
private
|
|
211
|
+
|
|
212
|
+
def logger
|
|
213
|
+
@parent_logger || super
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def logger_component
|
|
217
|
+
"Step:#{@name}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Interpolate parameters with results from previous steps
|
|
221
|
+
#
|
|
222
|
+
# @param params [Hash] Parameter template
|
|
223
|
+
# @param results [Hash] Previous results
|
|
224
|
+
# @return [Hash] Interpolated parameters
|
|
225
|
+
def interpolate_params(params, results)
|
|
226
|
+
params.transform_values do |value|
|
|
227
|
+
if value.is_a?(String) && value.match?(/\{(\w+)\.(\w+)\}/)
|
|
228
|
+
# Replace {step.field} with actual value
|
|
229
|
+
value.gsub(/\{(\w+)\.(\w+)\}/) do
|
|
230
|
+
step_name = Regexp.last_match(1).to_sym
|
|
231
|
+
field = Regexp.last_match(2)
|
|
232
|
+
results.dig(step_name, field) || value
|
|
233
|
+
end
|
|
234
|
+
else
|
|
235
|
+
value
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Interpolate template string with results
|
|
241
|
+
#
|
|
242
|
+
# @param template [String] Template string
|
|
243
|
+
# @param results [Hash] Previous results
|
|
244
|
+
# @return [String] Interpolated string
|
|
245
|
+
def interpolate_template(template, results)
|
|
246
|
+
template.gsub(/\{(\w+)(?:\.(\w+))?\}/) do
|
|
247
|
+
step_name = Regexp.last_match(1).to_sym
|
|
248
|
+
field = Regexp.last_match(2)
|
|
249
|
+
|
|
250
|
+
if field
|
|
251
|
+
results.dig(step_name, field)&.to_s || "{#{step_name}.#{field}}"
|
|
252
|
+
else
|
|
253
|
+
results[step_name]&.to_s || "{#{step_name}}"
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|