language-operator 0.1.46 → 0.1.47
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/.claude/commands/task.md +10 -0
- data/Gemfile.lock +1 -1
- data/components/agent/.rubocop.yml +1 -0
- data/components/agent/Dockerfile +43 -0
- data/components/agent/Dockerfile.dev +38 -0
- data/components/agent/Gemfile +15 -0
- data/components/agent/Makefile +67 -0
- data/components/agent/bin/langop-agent +140 -0
- data/components/agent/config/config.yaml +47 -0
- data/components/base/Dockerfile +34 -0
- data/components/base/Makefile +42 -0
- data/components/base/entrypoint.sh +12 -0
- data/components/base/gem-credentials +2 -0
- data/components/tool/.gitignore +10 -0
- data/components/tool/.rubocop.yml +19 -0
- data/components/tool/.yardopts +7 -0
- data/components/tool/Dockerfile +44 -0
- data/components/tool/Dockerfile.dev +39 -0
- data/components/tool/Gemfile +18 -0
- data/components/tool/Makefile +77 -0
- data/components/tool/README.md +145 -0
- data/components/tool/config.ru +4 -0
- data/components/tool/examples/calculator.rb +63 -0
- data/components/tool/examples/example_tool.rb +190 -0
- data/components/tool/lib/langop/dsl.rb +20 -0
- data/components/tool/server.rb +7 -0
- data/lib/language_operator/agent/task_executor.rb +39 -7
- data/lib/language_operator/cli/commands/agent.rb +0 -3
- data/lib/language_operator/cli/commands/system.rb +1 -0
- data/lib/language_operator/cli/formatters/log_formatter.rb +19 -67
- data/lib/language_operator/cli/formatters/log_style.rb +151 -0
- data/lib/language_operator/cli/formatters/progress_formatter.rb +10 -6
- data/lib/language_operator/logger.rb +3 -8
- data/lib/language_operator/templates/agent_synthesis.tmpl +35 -28
- data/lib/language_operator/templates/schema/agent_dsl_openapi.yaml +1 -1
- data/lib/language_operator/templates/schema/agent_dsl_schema.json +1 -1
- data/lib/language_operator/version.rb +1 -1
- data/synth/001/README.md +72 -0
- data/synth/001/output.log +13 -13
- data/synth/002/Makefile +12 -0
- data/synth/002/README.md +287 -0
- data/synth/002/agent.rb +23 -0
- data/synth/002/agent.txt +1 -0
- data/synth/002/output.log +22 -0
- metadata +33 -3
- data/synth/Makefile +0 -39
- data/synth/README.md +0 -342
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Image configuration
|
|
2
|
+
IMAGE_NAME := ghcr.io/language-operator/tool
|
|
3
|
+
IMAGE_TAG := latest
|
|
4
|
+
IMAGE_FULL := $(IMAGE_NAME):$(IMAGE_TAG)
|
|
5
|
+
|
|
6
|
+
# Include shared targets
|
|
7
|
+
include ../../Makefile.common
|
|
8
|
+
|
|
9
|
+
# Default target
|
|
10
|
+
.PHONY: help
|
|
11
|
+
help:
|
|
12
|
+
@echo "Available targets:"
|
|
13
|
+
@echo " build - Build the Docker image"
|
|
14
|
+
@echo " build-dev - Build the Docker image with local gem"
|
|
15
|
+
@echo " push-dev - Build and push dev image to all k8s nodes"
|
|
16
|
+
@echo " scan - Scan the Docker image with Trivy"
|
|
17
|
+
@echo " shell - Run the image and exec into it with an interactive shell"
|
|
18
|
+
@echo " env - Display sorted list of environment variables in the image"
|
|
19
|
+
@echo " run - Run the MCP server on port 8080"
|
|
20
|
+
@echo " run-dev - Run with custom tools directory"
|
|
21
|
+
@echo " test - Run the server and test the endpoints"
|
|
22
|
+
@echo " lint - Run RuboCop linter"
|
|
23
|
+
@echo " lint-fix - Run RuboCop with auto-fix"
|
|
24
|
+
@echo " doc - Generate API documentation with YARD"
|
|
25
|
+
@echo " doc-serve - Serve documentation on http://localhost:8808"
|
|
26
|
+
@echo " doc-clean - Remove generated documentation"
|
|
27
|
+
|
|
28
|
+
# Build development image with local gem
|
|
29
|
+
.PHONY: build-dev
|
|
30
|
+
build-dev:
|
|
31
|
+
@echo "Building local gem..."
|
|
32
|
+
cd ../../../language-operator-gem && gem build language-operator.gemspec
|
|
33
|
+
@echo "Copying gem to build context..."
|
|
34
|
+
cp ../../../language-operator-gem/*.gem .
|
|
35
|
+
@echo "Building Docker image..."
|
|
36
|
+
docker build -f Dockerfile.dev -t $(IMAGE_NAME):dev -t $(IMAGE_NAME):$(IMAGE_TAG)-dev .
|
|
37
|
+
@echo "Cleaning up gem file..."
|
|
38
|
+
rm language-operator-*.gem
|
|
39
|
+
|
|
40
|
+
# Push development image to all k8s cluster nodes (containerd/k3s)
|
|
41
|
+
.PHONY: push-dev
|
|
42
|
+
push-dev: build-dev
|
|
43
|
+
@echo "Pushing development image to all k8s nodes..."
|
|
44
|
+
@for node in $$(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do \
|
|
45
|
+
echo "==> Pushing to node: $$node"; \
|
|
46
|
+
docker save $(IMAGE_NAME):dev | ssh $$node 'sudo k3s ctr images import -' || echo "Failed to push to $$node"; \
|
|
47
|
+
done
|
|
48
|
+
@echo "✓ Development image pushed to all nodes!"
|
|
49
|
+
|
|
50
|
+
# Display sorted list of environment variables in the image
|
|
51
|
+
.PHONY: env
|
|
52
|
+
env:
|
|
53
|
+
docker run --rm $(IMAGE_FULL) /bin/sh -c 'env | sort'
|
|
54
|
+
|
|
55
|
+
# Run the MCP server
|
|
56
|
+
.PHONY: run
|
|
57
|
+
run:
|
|
58
|
+
docker run --rm -p 8080:80 --name mcp-server $(IMAGE_FULL)
|
|
59
|
+
|
|
60
|
+
# Run with custom tools directory
|
|
61
|
+
.PHONY: run-dev
|
|
62
|
+
run-dev:
|
|
63
|
+
docker run --rm -p 8080:80 -v $(PWD)/examples:/mcp --name mcp-server $(IMAGE_FULL)
|
|
64
|
+
|
|
65
|
+
# Override test to test server endpoints
|
|
66
|
+
.PHONY: test
|
|
67
|
+
test:
|
|
68
|
+
@echo "Testing health endpoint..."
|
|
69
|
+
@curl -s http://localhost:8080/health | jq .
|
|
70
|
+
@echo ""
|
|
71
|
+
@echo "Listing tools..."
|
|
72
|
+
@curl -s http://localhost:8080/tools | jq .
|
|
73
|
+
@echo ""
|
|
74
|
+
@echo "Calling calculator tool..."
|
|
75
|
+
@curl -s -X POST http://localhost:8080/tools/call \
|
|
76
|
+
-H "Content-Type: application/json" \
|
|
77
|
+
-d '{"name":"calculator","arguments":{"operation":"add","a":5,"b":3}}' | jq .
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# based/server
|
|
2
|
+
|
|
3
|
+
An extendable tool server based on [the official MCP Ruby SDK](https://github.com/modelcontextprotocol/ruby-sdk).
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
Run the server with example tools:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
docker run -p 8080:80 based/server:latest
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Test the server:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl http://localhost:8080/health
|
|
17
|
+
curl http://localhost:8080/tools
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Creating Your Own MCP Server
|
|
21
|
+
|
|
22
|
+
### 1. Create Tool Definitions
|
|
23
|
+
|
|
24
|
+
Create Ruby files using the MCP DSL to define your tools:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
# tools/hello.rb
|
|
28
|
+
tool "greet" do
|
|
29
|
+
description "Greets a person by name"
|
|
30
|
+
|
|
31
|
+
parameter "name" do
|
|
32
|
+
type :string
|
|
33
|
+
required true
|
|
34
|
+
description "The name of the person to greet"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
parameter "greeting" do
|
|
38
|
+
type :string
|
|
39
|
+
required false
|
|
40
|
+
description "Custom greeting (default: Hello)"
|
|
41
|
+
default "Hello"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
execute do |params|
|
|
45
|
+
greeting = params["greeting"] || "Hello"
|
|
46
|
+
"#{greeting}, #{params['name']}!"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Mount Your Tools Directory
|
|
52
|
+
|
|
53
|
+
Run the container with your tools directory mounted at `/mcp/tools`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
docker run -p 8080:80 -v $(pwd)/tools:/mcp/tools based/server:latest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. Use Your Tools
|
|
60
|
+
|
|
61
|
+
Call your tools via the MCP protocol:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# List available tools
|
|
65
|
+
curl -X POST http://localhost:8080/tools/list
|
|
66
|
+
|
|
67
|
+
# Call a tool
|
|
68
|
+
curl -X POST http://localhost:8080/tools/call \
|
|
69
|
+
-H "Content-Type: application/json" \
|
|
70
|
+
-d '{"name":"greet","arguments":{"name":"World"}}'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## DSL Reference
|
|
74
|
+
|
|
75
|
+
### Defining a Tool
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
tool "tool_name" do
|
|
79
|
+
description "What this tool does"
|
|
80
|
+
|
|
81
|
+
parameter "param_name" do
|
|
82
|
+
type :string # :string, :number, :boolean, :array, :object
|
|
83
|
+
required true # or false
|
|
84
|
+
description "Parameter description"
|
|
85
|
+
enum ["option1", "option2"] # optional: restrict to specific values
|
|
86
|
+
default "default_value" # optional: default value
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
execute do |params|
|
|
90
|
+
# Your tool logic here
|
|
91
|
+
# Access parameters via params["param_name"]
|
|
92
|
+
# Return a string result
|
|
93
|
+
"Result: #{params['param_name']}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Parameter Types
|
|
99
|
+
|
|
100
|
+
- `:string` - Text values
|
|
101
|
+
- `:number` - Numeric values (integers or floats)
|
|
102
|
+
- `:boolean` - true/false
|
|
103
|
+
- `:array` - Lists of values
|
|
104
|
+
- `:object` - Complex objects
|
|
105
|
+
|
|
106
|
+
### Multiple Tools Per File
|
|
107
|
+
|
|
108
|
+
You can define multiple tools in a single file:
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
tool "add" do
|
|
112
|
+
# ... tool definition
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
tool "subtract" do
|
|
116
|
+
# ... tool definition
|
|
117
|
+
end
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Example Tools
|
|
121
|
+
|
|
122
|
+
See [examples/calculator.rb](examples/calculator.rb) for complete examples including:
|
|
123
|
+
- Calculator with arithmetic operations
|
|
124
|
+
- Echo tool for simple string operations
|
|
125
|
+
|
|
126
|
+
## Configuration
|
|
127
|
+
|
|
128
|
+
| Environment Variable | Default | Description |
|
|
129
|
+
| -- | -- | -- |
|
|
130
|
+
| PORT | 80 | Port to run HTTP server on |
|
|
131
|
+
| RACK_ENV | production | Rack environment |
|
|
132
|
+
|
|
133
|
+
## API Endpoints
|
|
134
|
+
|
|
135
|
+
### MCP Protocol Endpoints
|
|
136
|
+
|
|
137
|
+
- `POST /initialize` - Initialize MCP session
|
|
138
|
+
- `POST /tools/list` - List available tools
|
|
139
|
+
- `POST /tools/call` - Execute a tool
|
|
140
|
+
|
|
141
|
+
### Debug Endpoints
|
|
142
|
+
|
|
143
|
+
- `GET /health` - Health check
|
|
144
|
+
- `GET /tools` - List loaded tools (simple format)
|
|
145
|
+
- `POST /reload` - Reload tools from `/mcp` directory
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Example tool: Calculator
|
|
2
|
+
# This file demonstrates the MCP DSL for defining tools
|
|
3
|
+
|
|
4
|
+
tool 'calculator' do
|
|
5
|
+
description 'Performs basic arithmetic operations on two numbers'
|
|
6
|
+
|
|
7
|
+
parameter 'operation' do
|
|
8
|
+
type :string
|
|
9
|
+
required true
|
|
10
|
+
description 'The arithmetic operation to perform'
|
|
11
|
+
enum %w[add subtract multiply divide]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
parameter 'a' do
|
|
15
|
+
type :number
|
|
16
|
+
required true
|
|
17
|
+
description 'The first number'
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
parameter 'b' do
|
|
21
|
+
type :number
|
|
22
|
+
required true
|
|
23
|
+
description 'The second number'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
execute do |params|
|
|
27
|
+
a = params['a']
|
|
28
|
+
b = params['b']
|
|
29
|
+
|
|
30
|
+
result = case params['operation']
|
|
31
|
+
when 'add'
|
|
32
|
+
a + b
|
|
33
|
+
when 'subtract'
|
|
34
|
+
a - b
|
|
35
|
+
when 'multiply'
|
|
36
|
+
a * b
|
|
37
|
+
when 'divide'
|
|
38
|
+
if b.zero?
|
|
39
|
+
'Error: Division by zero'
|
|
40
|
+
else
|
|
41
|
+
a / b.to_f
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
'Unknown operation'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
"Result: #{result}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
tool 'echo' do
|
|
52
|
+
description 'Returns the input message'
|
|
53
|
+
|
|
54
|
+
parameter 'message' do
|
|
55
|
+
type :string
|
|
56
|
+
required true
|
|
57
|
+
description 'The message to echo back'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
execute do |params|
|
|
61
|
+
params['message']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Example tool demonstrating all the new helper features
|
|
2
|
+
|
|
3
|
+
# Example 1: Using built-in parameter validators
|
|
4
|
+
tool 'send_message' do
|
|
5
|
+
description 'Send a message to an email or phone number'
|
|
6
|
+
|
|
7
|
+
parameter 'recipient' do
|
|
8
|
+
type :string
|
|
9
|
+
required true
|
|
10
|
+
description 'Email address or phone number'
|
|
11
|
+
# You can use built-in validators
|
|
12
|
+
# email_format or phone_format
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
parameter 'message' do
|
|
16
|
+
type :string
|
|
17
|
+
required true
|
|
18
|
+
description 'The message to send'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
execute do |params|
|
|
22
|
+
recipient = params['recipient']
|
|
23
|
+
message = params['message']
|
|
24
|
+
|
|
25
|
+
# Use validation helpers
|
|
26
|
+
if recipient.include?('@')
|
|
27
|
+
error = validate_email(recipient)
|
|
28
|
+
return error if error
|
|
29
|
+
|
|
30
|
+
"Email sent to #{recipient}: #{message}"
|
|
31
|
+
else
|
|
32
|
+
error = validate_phone(recipient)
|
|
33
|
+
return error if error
|
|
34
|
+
|
|
35
|
+
"SMS sent to #{recipient}: #{message}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Example 2: Using Config helper for environment variables
|
|
41
|
+
tool 'check_smtp_config' do
|
|
42
|
+
description 'Check SMTP configuration with fallback keys'
|
|
43
|
+
|
|
44
|
+
execute do |_params|
|
|
45
|
+
# Get config with multiple fallback keys
|
|
46
|
+
host = Config.get('SMTP_HOST', 'MAIL_HOST', default: 'localhost')
|
|
47
|
+
port = Config.get_int('SMTP_PORT', 'MAIL_PORT', default: 587)
|
|
48
|
+
use_tls = Config.get_bool('SMTP_TLS', 'MAIL_TLS', default: true)
|
|
49
|
+
|
|
50
|
+
# Check required configs
|
|
51
|
+
missing = Config.check_required('SMTP_USER', 'SMTP_PASSWORD')
|
|
52
|
+
return error("Missing configuration: #{missing.join(', ')}") unless missing.empty?
|
|
53
|
+
|
|
54
|
+
success <<~CONFIG
|
|
55
|
+
SMTP Configuration:
|
|
56
|
+
Host: #{host}
|
|
57
|
+
Port: #{port}
|
|
58
|
+
TLS: #{use_tls}
|
|
59
|
+
User: #{Config.get('SMTP_USER')}
|
|
60
|
+
CONFIG
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Example 3: Using HTTP helper
|
|
65
|
+
tool 'fetch_json' do
|
|
66
|
+
description 'Fetch JSON data from a URL'
|
|
67
|
+
|
|
68
|
+
parameter 'url' do
|
|
69
|
+
type :string
|
|
70
|
+
required true
|
|
71
|
+
description 'URL to fetch'
|
|
72
|
+
url_format # Built-in validator
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
execute do |params|
|
|
76
|
+
url = params['url']
|
|
77
|
+
|
|
78
|
+
# Use HTTP helper instead of curl
|
|
79
|
+
response = HTTP.get(url, headers: { 'Accept' => 'application/json' })
|
|
80
|
+
|
|
81
|
+
if response[:success]
|
|
82
|
+
if response[:json]
|
|
83
|
+
"Fetched JSON with #{response[:json].keys.length} keys"
|
|
84
|
+
else
|
|
85
|
+
"Response received but not JSON: #{truncate(response[:body])}"
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
error("Failed to fetch URL: #{response[:error]}")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Example 4: Using Shell helper for safe command execution
|
|
94
|
+
tool 'safe_grep' do
|
|
95
|
+
description 'Safely search for a pattern in a file'
|
|
96
|
+
|
|
97
|
+
parameter 'pattern' do
|
|
98
|
+
type :string
|
|
99
|
+
required true
|
|
100
|
+
description 'Search pattern'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
parameter 'file' do
|
|
104
|
+
type :string
|
|
105
|
+
required true
|
|
106
|
+
description 'File to search in'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
execute do |params|
|
|
110
|
+
# This is safe from injection attacks!
|
|
111
|
+
result = Shell.run('grep', params['pattern'], params['file'])
|
|
112
|
+
|
|
113
|
+
if result[:success]
|
|
114
|
+
"Found matches:\n#{result[:output]}"
|
|
115
|
+
else
|
|
116
|
+
'No matches found'
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Example 5: Using custom validation
|
|
122
|
+
tool 'restricted_command' do
|
|
123
|
+
description 'Run a command from an allowed list'
|
|
124
|
+
|
|
125
|
+
parameter 'command' do
|
|
126
|
+
type :string
|
|
127
|
+
required true
|
|
128
|
+
description 'Command to run (must be in allowed list)'
|
|
129
|
+
validate lambda { |cmd|
|
|
130
|
+
allowed = %w[ls pwd whoami date]
|
|
131
|
+
allowed.include?(cmd) || "Command '#{cmd}' not allowed. Allowed: #{allowed.join(', ')}"
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
execute do |params|
|
|
136
|
+
result = Shell.run(params['command'])
|
|
137
|
+
result[:output]
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Example 6: Using multiple helpers together
|
|
142
|
+
tool 'web_health_check' do
|
|
143
|
+
description 'Check if a web service is healthy'
|
|
144
|
+
|
|
145
|
+
parameter 'url' do
|
|
146
|
+
type :string
|
|
147
|
+
required true
|
|
148
|
+
description 'Service URL to check'
|
|
149
|
+
url_format
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
parameter 'expected_status' do
|
|
153
|
+
type :number
|
|
154
|
+
required false
|
|
155
|
+
description 'Expected HTTP status code'
|
|
156
|
+
default 200
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
execute do |params|
|
|
160
|
+
url = params['url']
|
|
161
|
+
expected = params['expected_status'] || 200
|
|
162
|
+
|
|
163
|
+
# Use HTTP helper
|
|
164
|
+
response = HTTP.head(url)
|
|
165
|
+
|
|
166
|
+
if response[:error]
|
|
167
|
+
error("Health check failed: #{response[:error]}")
|
|
168
|
+
elsif response[:status] == expected
|
|
169
|
+
success("✓ Service healthy (HTTP #{response[:status]})")
|
|
170
|
+
else
|
|
171
|
+
error("Service returned HTTP #{response[:status]}, expected #{expected}")
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Example 7: Using env_required helper
|
|
177
|
+
tool 'database_info' do
|
|
178
|
+
description 'Show database connection info'
|
|
179
|
+
|
|
180
|
+
execute do |_params|
|
|
181
|
+
# Check required env vars
|
|
182
|
+
error = env_required('DATABASE_URL', 'DB_HOST')
|
|
183
|
+
return error if error
|
|
184
|
+
|
|
185
|
+
# Safe to use now
|
|
186
|
+
db_url = env_get('DATABASE_URL', 'DB_URL')
|
|
187
|
+
|
|
188
|
+
success("Database configured: #{db_url}")
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'language_operator'
|
|
4
|
+
|
|
5
|
+
# Convenience - export LanguageOperator classes at top level for tool definitions
|
|
6
|
+
#
|
|
7
|
+
# This allows tool files to use simplified syntax:
|
|
8
|
+
# tool "example" do
|
|
9
|
+
# ...
|
|
10
|
+
# end
|
|
11
|
+
#
|
|
12
|
+
# Instead of:
|
|
13
|
+
# LanguageOperator::Dsl.define do
|
|
14
|
+
# tool "example" do
|
|
15
|
+
# ...
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
|
|
19
|
+
# Alias ToolLoader at top level for convenience
|
|
20
|
+
ToolLoader = LanguageOperator::ToolLoader
|
|
@@ -76,7 +76,9 @@ module LanguageOperator
|
|
|
76
76
|
@config = default_config.merge(config)
|
|
77
77
|
logger.debug('TaskExecutor initialized',
|
|
78
78
|
task_count: @tasks.size,
|
|
79
|
-
|
|
79
|
+
timeout_symbolic: @config[:timeout_symbolic],
|
|
80
|
+
timeout_neural: @config[:timeout_neural],
|
|
81
|
+
timeout_hybrid: @config[:timeout_hybrid],
|
|
80
82
|
max_retries: @config[:max_retries])
|
|
81
83
|
end
|
|
82
84
|
|
|
@@ -95,13 +97,11 @@ module LanguageOperator
|
|
|
95
97
|
# @raise [TaskExecutionError] If task execution fails after retries
|
|
96
98
|
def execute_task(task_name, inputs: {}, timeout: nil, max_retries: nil)
|
|
97
99
|
execution_start = Time.now
|
|
98
|
-
timeout ||= @config[:timeout]
|
|
99
100
|
max_retries ||= @config[:max_retries]
|
|
100
101
|
|
|
101
102
|
with_span('task_executor.execute_task', attributes: {
|
|
102
103
|
'task.name' => task_name.to_s,
|
|
103
104
|
'task.inputs' => inputs.keys.map(&:to_s).join(','),
|
|
104
|
-
'task.timeout' => timeout,
|
|
105
105
|
'task.max_retries' => max_retries
|
|
106
106
|
}) do
|
|
107
107
|
# Find task definition
|
|
@@ -109,12 +109,19 @@ module LanguageOperator
|
|
|
109
109
|
raise ArgumentError, "Task not found: #{task_name}. Available tasks: #{@tasks.keys.join(', ')}" unless task
|
|
110
110
|
|
|
111
111
|
task_type = determine_task_type(task)
|
|
112
|
+
|
|
113
|
+
# Determine timeout based on task type if not explicitly provided
|
|
114
|
+
timeout ||= task_timeout_for_type(task)
|
|
115
|
+
|
|
112
116
|
logger.info('Executing task',
|
|
113
117
|
task: task_name,
|
|
114
118
|
type: task_type,
|
|
115
119
|
timeout: timeout,
|
|
116
120
|
max_retries: max_retries)
|
|
117
121
|
|
|
122
|
+
# Add timeout to span attributes after it's determined
|
|
123
|
+
OpenTelemetry::Trace.current_span&.set_attribute('task.timeout', timeout)
|
|
124
|
+
|
|
118
125
|
# Execute with retry logic
|
|
119
126
|
execute_with_retry(task, task_name, inputs, timeout, max_retries, execution_start)
|
|
120
127
|
end
|
|
@@ -335,10 +342,12 @@ module LanguageOperator
|
|
|
335
342
|
# @return [Hash] Default configuration
|
|
336
343
|
def default_config
|
|
337
344
|
{
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
timeout_symbolic: 30.0, # Default timeout for symbolic tasks (seconds)
|
|
346
|
+
timeout_neural: 120.0, # Default timeout for neural tasks (seconds)
|
|
347
|
+
timeout_hybrid: 120.0, # Default timeout for hybrid tasks (seconds)
|
|
348
|
+
max_retries: 3, # Default max retry attempts
|
|
349
|
+
retry_delay_base: 1.0, # Base delay for exponential backoff
|
|
350
|
+
retry_delay_max: 10.0 # Maximum delay between retries
|
|
342
351
|
}
|
|
343
352
|
end
|
|
344
353
|
|
|
@@ -358,6 +367,29 @@ module LanguageOperator
|
|
|
358
367
|
end
|
|
359
368
|
end
|
|
360
369
|
|
|
370
|
+
# Determine appropriate timeout for a task based on its type
|
|
371
|
+
#
|
|
372
|
+
# Neural tasks typically require longer timeouts due to LLM API calls,
|
|
373
|
+
# while symbolic tasks (pure Ruby code) can use shorter timeouts.
|
|
374
|
+
#
|
|
375
|
+
# @param task [TaskDefinition] The task definition
|
|
376
|
+
# @return [Float] Timeout in seconds
|
|
377
|
+
def task_timeout_for_type(task)
|
|
378
|
+
if task.neural? && task.symbolic?
|
|
379
|
+
# Hybrid tasks use neural timeout (they may call LLM)
|
|
380
|
+
@config[:timeout_hybrid]
|
|
381
|
+
elsif task.neural?
|
|
382
|
+
# Neural tasks need longer timeout for LLM calls
|
|
383
|
+
@config[:timeout_neural]
|
|
384
|
+
elsif task.symbolic?
|
|
385
|
+
# Symbolic tasks use shorter timeout
|
|
386
|
+
@config[:timeout_symbolic]
|
|
387
|
+
else
|
|
388
|
+
# Default to symbolic timeout for undefined tasks
|
|
389
|
+
@config[:timeout_symbolic]
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
361
393
|
# Execute task with retry logic and timeout
|
|
362
394
|
#
|
|
363
395
|
# @param task [TaskDefinition] The task definition
|
|
@@ -76,9 +76,6 @@ module LanguageOperator
|
|
|
76
76
|
|
|
77
77
|
ctx = Helpers::ClusterContext.from_options(options.merge(cluster: cluster))
|
|
78
78
|
|
|
79
|
-
Formatters::ProgressFormatter.info("Creating agent in cluster '#{ctx.name}'")
|
|
80
|
-
puts
|
|
81
|
-
|
|
82
79
|
# Generate agent name from description if not provided
|
|
83
80
|
agent_name = options[:name] || generate_agent_name(description)
|
|
84
81
|
|