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,427 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'workflow_definition'
|
|
4
|
+
require_relative 'webhook_definition'
|
|
5
|
+
require_relative 'mcp_server_definition'
|
|
6
|
+
require_relative 'chat_endpoint_definition'
|
|
7
|
+
require_relative '../logger'
|
|
8
|
+
require_relative '../loggable'
|
|
9
|
+
|
|
10
|
+
module LanguageOperator
|
|
11
|
+
module Dsl
|
|
12
|
+
# Agent definition for autonomous agents
|
|
13
|
+
#
|
|
14
|
+
# Defines an agent with objectives, workflow, schedule, and constraints.
|
|
15
|
+
# Used within the DSL to create agents that can be executed standalone
|
|
16
|
+
# or deployed to Kubernetes.
|
|
17
|
+
#
|
|
18
|
+
# @example Define a simple scheduled agent
|
|
19
|
+
# agent "news-summarizer" do
|
|
20
|
+
# description "Daily news summarization agent"
|
|
21
|
+
#
|
|
22
|
+
# schedule "0 12 * * *"
|
|
23
|
+
#
|
|
24
|
+
# objectives [
|
|
25
|
+
# "Search for recent news",
|
|
26
|
+
# "Summarize findings"
|
|
27
|
+
# ]
|
|
28
|
+
#
|
|
29
|
+
# workflow do
|
|
30
|
+
# step :search, tool: "web_search", params: {query: "latest news"}
|
|
31
|
+
# step :summarize, depends_on: :search
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
|
+
#
|
|
35
|
+
# @example Define a webhook agent
|
|
36
|
+
# agent "github-webhook" do
|
|
37
|
+
# description "Handle GitHub webhooks"
|
|
38
|
+
# mode :reactive
|
|
39
|
+
#
|
|
40
|
+
# webhook "/github/pr-opened" do
|
|
41
|
+
# method :post
|
|
42
|
+
# on_request do |context|
|
|
43
|
+
# # Process webhook
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
# end
|
|
47
|
+
class AgentDefinition
|
|
48
|
+
include LanguageOperator::Loggable
|
|
49
|
+
|
|
50
|
+
attr_reader :name, :description, :persona, :schedule, :objectives, :workflow,
|
|
51
|
+
:constraints, :output_config, :execution_mode, :webhooks, :mcp_server, :chat_endpoint
|
|
52
|
+
|
|
53
|
+
def initialize(name)
|
|
54
|
+
@name = name
|
|
55
|
+
@description = nil
|
|
56
|
+
@persona = nil
|
|
57
|
+
@schedule = nil
|
|
58
|
+
@objectives = []
|
|
59
|
+
@workflow = nil
|
|
60
|
+
@constraints = {}
|
|
61
|
+
@output_config = {}
|
|
62
|
+
@execution_mode = :autonomous
|
|
63
|
+
@webhooks = []
|
|
64
|
+
@mcp_server = nil
|
|
65
|
+
@chat_endpoint = nil
|
|
66
|
+
|
|
67
|
+
logger.debug('Agent definition initialized',
|
|
68
|
+
name: name,
|
|
69
|
+
mode: @execution_mode)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Set or get description
|
|
73
|
+
#
|
|
74
|
+
# @param val [String, nil] Description text
|
|
75
|
+
# @return [String] Current description
|
|
76
|
+
def description(val = nil)
|
|
77
|
+
return @description if val.nil?
|
|
78
|
+
|
|
79
|
+
@description = val
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Set persona/system prompt
|
|
83
|
+
#
|
|
84
|
+
# @param text [String] Persona text or system prompt
|
|
85
|
+
# @return [String] Current persona
|
|
86
|
+
def persona(text = nil)
|
|
87
|
+
return @persona if text.nil?
|
|
88
|
+
|
|
89
|
+
@persona = text
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Set schedule (cron expression)
|
|
93
|
+
#
|
|
94
|
+
# @param cron [String] Cron expression
|
|
95
|
+
# @return [String] Current schedule
|
|
96
|
+
def schedule(cron = nil)
|
|
97
|
+
return @schedule if cron.nil?
|
|
98
|
+
|
|
99
|
+
@schedule = cron
|
|
100
|
+
@execution_mode = :scheduled
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Set objectives (list of goals)
|
|
104
|
+
#
|
|
105
|
+
# @param list [Array<String>] List of objectives
|
|
106
|
+
# @return [Array<String>] Current objectives
|
|
107
|
+
def objectives(list = nil)
|
|
108
|
+
return @objectives if list.nil?
|
|
109
|
+
|
|
110
|
+
@objectives = list
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Define a single objective
|
|
114
|
+
#
|
|
115
|
+
# @param text [String] Objective text
|
|
116
|
+
# @return [void]
|
|
117
|
+
def objective(text)
|
|
118
|
+
@objectives << text
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Define workflow with steps
|
|
122
|
+
#
|
|
123
|
+
# @yield Workflow definition block
|
|
124
|
+
# @return [WorkflowDefinition] Current workflow
|
|
125
|
+
def workflow(&block)
|
|
126
|
+
return @workflow if block.nil?
|
|
127
|
+
|
|
128
|
+
@workflow = WorkflowDefinition.new
|
|
129
|
+
@workflow.instance_eval(&block) if block
|
|
130
|
+
@workflow
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Define constraints (max_iterations, timeout, etc.)
|
|
134
|
+
#
|
|
135
|
+
# @yield Constraints block
|
|
136
|
+
# @return [Hash] Current constraints
|
|
137
|
+
def constraints(&block)
|
|
138
|
+
return @constraints if block.nil?
|
|
139
|
+
|
|
140
|
+
constraint_builder = ConstraintBuilder.new
|
|
141
|
+
constraint_builder.instance_eval(&block) if block
|
|
142
|
+
@constraints = constraint_builder.to_h
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Define output configuration
|
|
146
|
+
#
|
|
147
|
+
# @yield Output configuration block
|
|
148
|
+
# @return [Hash] Current output config
|
|
149
|
+
def output(&block)
|
|
150
|
+
return @output_config if block.nil?
|
|
151
|
+
|
|
152
|
+
output_builder = OutputBuilder.new
|
|
153
|
+
output_builder.instance_eval(&block) if block
|
|
154
|
+
@output_config = output_builder.to_h
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Set execution mode
|
|
158
|
+
#
|
|
159
|
+
# @param mode [Symbol] Execution mode (:autonomous, :scheduled, :reactive)
|
|
160
|
+
# @return [Symbol] Current execution mode
|
|
161
|
+
def mode(mode = nil)
|
|
162
|
+
return @execution_mode if mode.nil?
|
|
163
|
+
|
|
164
|
+
@execution_mode = mode
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Define a webhook endpoint
|
|
168
|
+
#
|
|
169
|
+
# @param path [String] URL path for the webhook
|
|
170
|
+
# @yield Webhook configuration block
|
|
171
|
+
# @return [WebhookDefinition] The webhook definition
|
|
172
|
+
def webhook(path, &block)
|
|
173
|
+
webhook_def = WebhookDefinition.new(path)
|
|
174
|
+
webhook_def.instance_eval(&block) if block
|
|
175
|
+
@webhooks << webhook_def
|
|
176
|
+
@execution_mode = :reactive if @execution_mode == :autonomous
|
|
177
|
+
webhook_def
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Define MCP server capabilities
|
|
181
|
+
#
|
|
182
|
+
# Allows this agent to expose tools via MCP protocol.
|
|
183
|
+
# Other agents or MCP clients can discover and call these tools.
|
|
184
|
+
#
|
|
185
|
+
# @yield MCP server configuration block
|
|
186
|
+
# @return [McpServerDefinition] The MCP server definition
|
|
187
|
+
def as_mcp_server(&block)
|
|
188
|
+
@mcp_server = McpServerDefinition.new(@name)
|
|
189
|
+
@mcp_server.instance_eval(&block) if block
|
|
190
|
+
@execution_mode = :reactive if @execution_mode == :autonomous
|
|
191
|
+
@mcp_server
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Define chat endpoint capabilities
|
|
195
|
+
#
|
|
196
|
+
# Allows this agent to respond to OpenAI-compatible chat completion requests.
|
|
197
|
+
# Other systems can treat this agent as a language model.
|
|
198
|
+
#
|
|
199
|
+
# @yield Chat endpoint configuration block
|
|
200
|
+
# @return [ChatEndpointDefinition] The chat endpoint definition
|
|
201
|
+
def as_chat_endpoint(&block)
|
|
202
|
+
@chat_endpoint ||= ChatEndpointDefinition.new(@name)
|
|
203
|
+
@chat_endpoint.instance_eval(&block) if block
|
|
204
|
+
@execution_mode = :reactive if @execution_mode == :autonomous
|
|
205
|
+
@chat_endpoint
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Execute the agent
|
|
209
|
+
#
|
|
210
|
+
# @return [void]
|
|
211
|
+
def run!
|
|
212
|
+
logger.info('Starting agent',
|
|
213
|
+
name: @name,
|
|
214
|
+
mode: @execution_mode,
|
|
215
|
+
objectives_count: @objectives.size,
|
|
216
|
+
has_workflow: !@workflow.nil?)
|
|
217
|
+
|
|
218
|
+
case @execution_mode
|
|
219
|
+
when :scheduled
|
|
220
|
+
run_scheduled
|
|
221
|
+
when :autonomous
|
|
222
|
+
run_autonomous
|
|
223
|
+
when :reactive
|
|
224
|
+
run_reactive
|
|
225
|
+
else
|
|
226
|
+
logger.error('Unknown execution mode', mode: @execution_mode)
|
|
227
|
+
raise "Unknown execution mode: #{@execution_mode}"
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
private
|
|
232
|
+
|
|
233
|
+
def logger_component
|
|
234
|
+
"Agent:#{@name}"
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def run_scheduled
|
|
238
|
+
require 'rufus-scheduler'
|
|
239
|
+
|
|
240
|
+
scheduler = Rufus::Scheduler.new
|
|
241
|
+
|
|
242
|
+
logger.info('Scheduling agent',
|
|
243
|
+
name: @name,
|
|
244
|
+
cron: @schedule)
|
|
245
|
+
|
|
246
|
+
scheduler.cron(@schedule) do
|
|
247
|
+
logger.timed('Scheduled execution') do
|
|
248
|
+
execute_objectives
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
scheduler.join
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def run_autonomous
|
|
256
|
+
logger.info('Running agent in autonomous mode', name: @name)
|
|
257
|
+
execute_objectives
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def run_reactive
|
|
261
|
+
logger.info('Running agent in reactive mode',
|
|
262
|
+
name: @name,
|
|
263
|
+
webhooks: @webhooks.size,
|
|
264
|
+
mcp_tools: @mcp_server&.tools&.size || 0,
|
|
265
|
+
chat_endpoint: !@chat_endpoint.nil?)
|
|
266
|
+
|
|
267
|
+
# Create an Agent::Base instance with this definition
|
|
268
|
+
require_relative '../agent/base'
|
|
269
|
+
require_relative '../agent/web_server'
|
|
270
|
+
|
|
271
|
+
# Build agent config
|
|
272
|
+
agent_config = build_agent_config
|
|
273
|
+
|
|
274
|
+
# Create agent instance
|
|
275
|
+
agent = LanguageOperator::Agent::Base.new(agent_config)
|
|
276
|
+
agent.instance_variable_set(:@mode, 'reactive')
|
|
277
|
+
|
|
278
|
+
# Create web server
|
|
279
|
+
web_server = LanguageOperator::Agent::WebServer.new(agent)
|
|
280
|
+
|
|
281
|
+
# Register webhooks
|
|
282
|
+
@webhooks.each do |webhook_def|
|
|
283
|
+
webhook_def.register(web_server)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Register MCP tools
|
|
287
|
+
web_server.register_mcp_tools(@mcp_server) if @mcp_server&.tools?
|
|
288
|
+
|
|
289
|
+
# Register chat endpoint
|
|
290
|
+
web_server.register_chat_endpoint(@chat_endpoint, agent) if @chat_endpoint
|
|
291
|
+
|
|
292
|
+
# Start the server
|
|
293
|
+
web_server.start
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# Build agent configuration hash
|
|
297
|
+
#
|
|
298
|
+
# @return [Hash] Agent configuration
|
|
299
|
+
def build_agent_config
|
|
300
|
+
{
|
|
301
|
+
'agent' => {
|
|
302
|
+
'name' => @name,
|
|
303
|
+
'instructions' => @description || "Process incoming requests for #{@name}",
|
|
304
|
+
'persona' => @persona
|
|
305
|
+
},
|
|
306
|
+
'llm' => {
|
|
307
|
+
'provider' => ENV['LLM_PROVIDER'] || 'anthropic',
|
|
308
|
+
'model' => ENV['LLM_MODEL'] || 'claude-3-5-sonnet-20241022',
|
|
309
|
+
'api_key' => ENV.fetch('ANTHROPIC_API_KEY', nil)
|
|
310
|
+
},
|
|
311
|
+
'mcp' => {
|
|
312
|
+
'servers' => {}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def execute_objectives
|
|
318
|
+
logger.info('Executing objectives',
|
|
319
|
+
total: @objectives.size,
|
|
320
|
+
has_workflow: !@workflow.nil?)
|
|
321
|
+
|
|
322
|
+
@objectives.each_with_index do |objective, index|
|
|
323
|
+
logger.info('Executing objective',
|
|
324
|
+
index: index + 1,
|
|
325
|
+
total: @objectives.size,
|
|
326
|
+
objective: objective[0..100])
|
|
327
|
+
|
|
328
|
+
# If workflow defined, execute it; otherwise just log
|
|
329
|
+
if @workflow
|
|
330
|
+
logger.timed('Objective workflow execution') do
|
|
331
|
+
@workflow.execute(objective)
|
|
332
|
+
end
|
|
333
|
+
else
|
|
334
|
+
logger.warn('No workflow defined, skipping execution')
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
logger.info('All objectives completed', total: @objectives.size)
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Helper class for building constraints
|
|
343
|
+
class ConstraintBuilder
|
|
344
|
+
def initialize
|
|
345
|
+
@constraints = {}
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def max_iterations(value)
|
|
349
|
+
@constraints[:max_iterations] = value
|
|
350
|
+
end
|
|
351
|
+
|
|
352
|
+
def timeout(value)
|
|
353
|
+
@constraints[:timeout] = value
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def memory(value)
|
|
357
|
+
@constraints[:memory] = value
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def rate_limit(value)
|
|
361
|
+
@constraints[:rate_limit] = value
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
# Budget constraints
|
|
365
|
+
def daily_budget(value)
|
|
366
|
+
@constraints[:daily_budget] = value
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def hourly_budget(value)
|
|
370
|
+
@constraints[:hourly_budget] = value
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def token_budget(value)
|
|
374
|
+
@constraints[:token_budget] = value
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Rate limiting
|
|
378
|
+
def requests_per_minute(value)
|
|
379
|
+
@constraints[:requests_per_minute] = value
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
def requests_per_hour(value)
|
|
383
|
+
@constraints[:requests_per_hour] = value
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def requests_per_day(value)
|
|
387
|
+
@constraints[:requests_per_day] = value
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# Content filtering
|
|
391
|
+
def blocked_patterns(patterns)
|
|
392
|
+
@constraints[:blocked_patterns] = patterns
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
def blocked_topics(topics)
|
|
396
|
+
@constraints[:blocked_topics] = topics
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def to_h
|
|
400
|
+
@constraints
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
# Helper class for building output configuration
|
|
405
|
+
class OutputBuilder
|
|
406
|
+
def initialize
|
|
407
|
+
@config = {}
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
def workspace(path)
|
|
411
|
+
@config[:workspace] = path
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def slack(channel:)
|
|
415
|
+
@config[:slack] = { channel: channel }
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def email(to:)
|
|
419
|
+
@config[:email] = { to: to }
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
def to_h
|
|
423
|
+
@config
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Chat endpoint definition for agents
|
|
6
|
+
#
|
|
7
|
+
# Allows agents to expose an OpenAI-compatible chat completion endpoint.
|
|
8
|
+
# Other systems can treat the agent as a language model.
|
|
9
|
+
#
|
|
10
|
+
# @example Define chat endpoint in an agent
|
|
11
|
+
# agent "github-expert" do
|
|
12
|
+
# as_chat_endpoint do
|
|
13
|
+
# system_prompt "You are a GitHub API expert"
|
|
14
|
+
# temperature 0.7
|
|
15
|
+
# max_tokens 2000
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
class ChatEndpointDefinition
|
|
19
|
+
attr_reader :system_prompt, :temperature, :max_tokens, :model_name,
|
|
20
|
+
:top_p, :frequency_penalty, :presence_penalty, :stop_sequences
|
|
21
|
+
|
|
22
|
+
def initialize(agent_name)
|
|
23
|
+
@agent_name = agent_name
|
|
24
|
+
@system_prompt = nil
|
|
25
|
+
@temperature = 0.7
|
|
26
|
+
@max_tokens = 2000
|
|
27
|
+
@model_name = agent_name
|
|
28
|
+
@top_p = 1.0
|
|
29
|
+
@frequency_penalty = 0.0
|
|
30
|
+
@presence_penalty = 0.0
|
|
31
|
+
@stop_sequences = nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Set system prompt for chat mode
|
|
35
|
+
#
|
|
36
|
+
# @param prompt [String] System prompt
|
|
37
|
+
# @return [String] Current system prompt
|
|
38
|
+
def system_prompt(prompt = nil)
|
|
39
|
+
return @system_prompt if prompt.nil?
|
|
40
|
+
|
|
41
|
+
@system_prompt = prompt
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Set temperature parameter
|
|
45
|
+
#
|
|
46
|
+
# @param value [Float] Temperature (0.0-2.0)
|
|
47
|
+
# @return [Float] Current temperature
|
|
48
|
+
def temperature(value = nil)
|
|
49
|
+
return @temperature if value.nil?
|
|
50
|
+
|
|
51
|
+
@temperature = value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Set maximum tokens
|
|
55
|
+
#
|
|
56
|
+
# @param value [Integer] Max tokens
|
|
57
|
+
# @return [Integer] Current max tokens
|
|
58
|
+
def max_tokens(value = nil)
|
|
59
|
+
return @max_tokens if value.nil?
|
|
60
|
+
|
|
61
|
+
@max_tokens = value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Set model name exposed in API
|
|
65
|
+
#
|
|
66
|
+
# @param name [String] Model name
|
|
67
|
+
# @return [String] Current model name
|
|
68
|
+
def model(name = nil)
|
|
69
|
+
return @model_name if name.nil?
|
|
70
|
+
|
|
71
|
+
@model_name = name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Set top_p parameter
|
|
75
|
+
#
|
|
76
|
+
# @param value [Float] Top-p (0.0-1.0)
|
|
77
|
+
# @return [Float] Current top_p
|
|
78
|
+
def top_p(value = nil)
|
|
79
|
+
return @top_p if value.nil?
|
|
80
|
+
|
|
81
|
+
@top_p = value
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Set frequency penalty
|
|
85
|
+
#
|
|
86
|
+
# @param value [Float] Frequency penalty (-2.0 to 2.0)
|
|
87
|
+
# @return [Float] Current frequency penalty
|
|
88
|
+
def frequency_penalty(value = nil)
|
|
89
|
+
return @frequency_penalty if value.nil?
|
|
90
|
+
|
|
91
|
+
@frequency_penalty = value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Set presence penalty
|
|
95
|
+
#
|
|
96
|
+
# @param value [Float] Presence penalty (-2.0 to 2.0)
|
|
97
|
+
# @return [Float] Current presence penalty
|
|
98
|
+
def presence_penalty(value = nil)
|
|
99
|
+
return @presence_penalty if value.nil?
|
|
100
|
+
|
|
101
|
+
@presence_penalty = value
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Set stop sequences
|
|
105
|
+
#
|
|
106
|
+
# @param sequences [Array<String>] Stop sequences
|
|
107
|
+
# @return [Array<String>] Current stop sequences
|
|
108
|
+
def stop(sequences = nil)
|
|
109
|
+
return @stop_sequences if sequences.nil?
|
|
110
|
+
|
|
111
|
+
@stop_sequences = sequences
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# Configuration helper for managing environment variables
|
|
6
|
+
#
|
|
7
|
+
# Provides utilities for reading and managing environment variables with fallback support,
|
|
8
|
+
# type conversion, and validation. All methods are class methods.
|
|
9
|
+
#
|
|
10
|
+
# @example Basic usage
|
|
11
|
+
# Config.get('SMTP_HOST', 'MAIL_HOST', default: 'localhost')
|
|
12
|
+
# Config.require('DATABASE_URL')
|
|
13
|
+
# Config.get_bool('USE_TLS', default: true)
|
|
14
|
+
class Config
|
|
15
|
+
# Get environment variable with multiple fallback keys
|
|
16
|
+
#
|
|
17
|
+
# @param keys [Array<String>] Environment variable names to try
|
|
18
|
+
# @param default [Object, nil] Default value if none found
|
|
19
|
+
# @return [String, nil] The first non-nil value or default
|
|
20
|
+
def self.get(*keys, default: nil)
|
|
21
|
+
keys.each do |key|
|
|
22
|
+
value = ENV.fetch(key.to_s, nil)
|
|
23
|
+
return value if value
|
|
24
|
+
end
|
|
25
|
+
default
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get required environment variable with fallback keys
|
|
29
|
+
#
|
|
30
|
+
# @param keys [Array<String>] Environment variable names to try
|
|
31
|
+
# @return [String] The first non-nil value
|
|
32
|
+
# @raise [ArgumentError] If none of the keys are set
|
|
33
|
+
def self.require(*keys)
|
|
34
|
+
value = get(*keys)
|
|
35
|
+
raise ArgumentError, "Missing required configuration: #{keys.join(' or ')}" unless value
|
|
36
|
+
|
|
37
|
+
value
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Get environment variable as integer
|
|
41
|
+
#
|
|
42
|
+
# @param keys [Array<String>] Environment variable names to try
|
|
43
|
+
# @param default [Integer, nil] Default value if none found
|
|
44
|
+
# @return [Integer, nil] The value converted to integer, or default
|
|
45
|
+
def self.get_int(*keys, default: nil)
|
|
46
|
+
value = get(*keys)
|
|
47
|
+
return default if value.nil?
|
|
48
|
+
|
|
49
|
+
value.to_i
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Get environment variable as boolean
|
|
53
|
+
#
|
|
54
|
+
# Treats 'true', '1', 'yes', 'on' as true (case insensitive).
|
|
55
|
+
#
|
|
56
|
+
# @param keys [Array<String>] Environment variable names to try
|
|
57
|
+
# @param default [Boolean] Default value if none found
|
|
58
|
+
# @return [Boolean] The value as boolean
|
|
59
|
+
def self.get_bool(*keys, default: false)
|
|
60
|
+
value = get(*keys)
|
|
61
|
+
return default if value.nil?
|
|
62
|
+
|
|
63
|
+
value.to_s.downcase.match?(/^(true|1|yes|on)$/)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get environment variable as array (split by separator)
|
|
67
|
+
#
|
|
68
|
+
# @param keys [Array<String>] Environment variable names to try
|
|
69
|
+
# @param default [Array] Default value if none found
|
|
70
|
+
# @param separator [String] Character to split on (default: ',')
|
|
71
|
+
# @return [Array<String>] The value split into array
|
|
72
|
+
def self.get_array(*keys, default: [], separator: ',')
|
|
73
|
+
value = get(*keys)
|
|
74
|
+
return default if value.nil? || value.empty?
|
|
75
|
+
|
|
76
|
+
value.split(separator).map(&:strip).reject(&:empty?)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Check if all required keys are present
|
|
80
|
+
#
|
|
81
|
+
# @param keys [Array<String>] Environment variable names to check
|
|
82
|
+
# @return [Array<String>] Array of missing keys (empty if all present)
|
|
83
|
+
def self.check_required(*keys)
|
|
84
|
+
keys.reject { |key| ENV.fetch(key.to_s, nil) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Check if environment variable is set (even if empty string)
|
|
88
|
+
#
|
|
89
|
+
# @param keys [Array<String>] Environment variable names to check
|
|
90
|
+
# @return [Boolean] True if any key is set
|
|
91
|
+
def self.set?(*keys)
|
|
92
|
+
keys.any? { |key| ENV.key?(key.to_s) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Get all environment variables matching a prefix
|
|
96
|
+
#
|
|
97
|
+
# @param prefix [String] Prefix to match
|
|
98
|
+
# @return [Hash<String, String>] Hash with prefix removed from keys
|
|
99
|
+
def self.with_prefix(prefix)
|
|
100
|
+
ENV.select { |key, _| key.start_with?(prefix) }
|
|
101
|
+
.transform_keys { |key| key.sub(prefix, '') }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Build a configuration hash from environment variables
|
|
105
|
+
#
|
|
106
|
+
# @param mappings [Hash{Symbol => Array<String>, String}] Config key to env var(s) mapping
|
|
107
|
+
# @return [Hash{Symbol => String}] Configuration hash with values found
|
|
108
|
+
def self.build(mappings)
|
|
109
|
+
config = {}
|
|
110
|
+
mappings.each do |config_key, env_keys|
|
|
111
|
+
env_keys = [env_keys] unless env_keys.is_a?(Array)
|
|
112
|
+
value = get(*env_keys)
|
|
113
|
+
config[config_key] = value if value
|
|
114
|
+
end
|
|
115
|
+
config
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LanguageOperator
|
|
4
|
+
module Dsl
|
|
5
|
+
# DSL context for defining tools
|
|
6
|
+
#
|
|
7
|
+
# Provides the evaluation context for tool definition files. Tools are
|
|
8
|
+
# defined using the `tool` method within this context.
|
|
9
|
+
#
|
|
10
|
+
# @example Tool definition file
|
|
11
|
+
# tool "search" do
|
|
12
|
+
# description "Search the web"
|
|
13
|
+
#
|
|
14
|
+
# parameter :query do
|
|
15
|
+
# description "Search query"
|
|
16
|
+
# type :string
|
|
17
|
+
# required true
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# execute do |params|
|
|
21
|
+
# http_get("https://api.search.com?q=#{params[:query]}")
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
class Context
|
|
25
|
+
include LanguageOperator::Dsl::Helpers
|
|
26
|
+
|
|
27
|
+
# Provide access to HTTP and Shell helper classes as constants
|
|
28
|
+
HTTP = LanguageOperator::Dsl::HTTP
|
|
29
|
+
Shell = LanguageOperator::Dsl::Shell
|
|
30
|
+
|
|
31
|
+
# Initialize context with registry
|
|
32
|
+
#
|
|
33
|
+
# @param registry [LanguageOperator::Dsl::Registry] Tool registry
|
|
34
|
+
def initialize(registry)
|
|
35
|
+
@registry = registry
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Define a tool
|
|
39
|
+
#
|
|
40
|
+
# @param name [String] Tool name
|
|
41
|
+
# @yield Tool definition block
|
|
42
|
+
# @return [void]
|
|
43
|
+
def tool(name, &)
|
|
44
|
+
tool_def = ToolDefinition.new(name)
|
|
45
|
+
tool_def.instance_eval(&) if block_given?
|
|
46
|
+
@registry.register(tool_def)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|