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,404 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require_relative '../formatters/progress_formatter'
|
|
6
|
+
require_relative '../helpers/cluster_validator'
|
|
7
|
+
require_relative '../helpers/user_prompts'
|
|
8
|
+
|
|
9
|
+
module LanguageOperator
|
|
10
|
+
module CLI
|
|
11
|
+
module Commands
|
|
12
|
+
# Install, upgrade, and uninstall commands for the language-operator
|
|
13
|
+
class Install < Thor
|
|
14
|
+
HELM_REPO_NAME = 'language-operator'
|
|
15
|
+
HELM_REPO_URL = 'https://language-operator.github.io/charts'
|
|
16
|
+
CHART_NAME = 'language-operator/language-operator'
|
|
17
|
+
RELEASE_NAME = 'language-operator'
|
|
18
|
+
DEFAULT_NAMESPACE = 'language-operator-system'
|
|
19
|
+
|
|
20
|
+
# Long descriptions for commands
|
|
21
|
+
LONG_DESCRIPTIONS = {
|
|
22
|
+
install: <<-DESC,
|
|
23
|
+
Install the language-operator into your Kubernetes cluster using Helm.
|
|
24
|
+
|
|
25
|
+
This command will:
|
|
26
|
+
1. Add the language-operator Helm repository
|
|
27
|
+
2. Update Helm repositories
|
|
28
|
+
3. Install the language-operator chart
|
|
29
|
+
4. Verify the installation
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
# Install with defaults
|
|
33
|
+
aictl install
|
|
34
|
+
|
|
35
|
+
# Install with custom values
|
|
36
|
+
aictl install --values my-values.yaml
|
|
37
|
+
|
|
38
|
+
# Install specific version
|
|
39
|
+
aictl install --version 0.1.0
|
|
40
|
+
|
|
41
|
+
# Dry run to see what would be installed
|
|
42
|
+
aictl install --dry-run
|
|
43
|
+
DESC
|
|
44
|
+
upgrade: <<-DESC,
|
|
45
|
+
Upgrade the language-operator to a newer version using Helm.
|
|
46
|
+
|
|
47
|
+
This command will:
|
|
48
|
+
1. Update Helm repositories
|
|
49
|
+
2. Upgrade the language-operator release
|
|
50
|
+
3. Wait for the rollout to complete
|
|
51
|
+
4. Verify the operator is running
|
|
52
|
+
|
|
53
|
+
Examples:
|
|
54
|
+
# Upgrade to latest version
|
|
55
|
+
aictl upgrade
|
|
56
|
+
|
|
57
|
+
# Upgrade with custom values
|
|
58
|
+
aictl upgrade --values my-values.yaml
|
|
59
|
+
|
|
60
|
+
# Upgrade to specific version
|
|
61
|
+
aictl upgrade --version 0.2.0
|
|
62
|
+
DESC
|
|
63
|
+
uninstall: <<-DESC
|
|
64
|
+
Uninstall the language-operator from your Kubernetes cluster.
|
|
65
|
+
|
|
66
|
+
WARNING: This will remove the operator but NOT the CRDs or custom resources.
|
|
67
|
+
Agents, tools, models, and personas will remain in the cluster.
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
# Uninstall with confirmation
|
|
71
|
+
aictl uninstall
|
|
72
|
+
|
|
73
|
+
# Force uninstall without confirmation
|
|
74
|
+
aictl uninstall --force
|
|
75
|
+
|
|
76
|
+
# Uninstall from specific namespace
|
|
77
|
+
aictl uninstall --namespace my-namespace
|
|
78
|
+
DESC
|
|
79
|
+
}.freeze
|
|
80
|
+
|
|
81
|
+
# Helper method to get long descriptions for commands
|
|
82
|
+
def self.long_desc_for(command)
|
|
83
|
+
LONG_DESCRIPTIONS[command]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
desc 'install', 'Install the language-operator using Helm'
|
|
87
|
+
long_desc <<-DESC
|
|
88
|
+
Install the language-operator into your Kubernetes cluster using Helm.
|
|
89
|
+
|
|
90
|
+
This command will:
|
|
91
|
+
1. Add the language-operator Helm repository
|
|
92
|
+
2. Update Helm repositories
|
|
93
|
+
3. Install the language-operator chart
|
|
94
|
+
4. Verify the installation
|
|
95
|
+
|
|
96
|
+
Examples:
|
|
97
|
+
# Install with defaults
|
|
98
|
+
aictl install
|
|
99
|
+
|
|
100
|
+
# Install with custom values
|
|
101
|
+
aictl install --values my-values.yaml
|
|
102
|
+
|
|
103
|
+
# Install specific version
|
|
104
|
+
aictl install --version 0.1.0
|
|
105
|
+
|
|
106
|
+
# Dry run to see what would be installed
|
|
107
|
+
aictl install --dry-run
|
|
108
|
+
DESC
|
|
109
|
+
option :values, type: :string, desc: 'Path to custom Helm values file'
|
|
110
|
+
option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
|
|
111
|
+
option :version, type: :string, desc: 'Specific chart version to install'
|
|
112
|
+
option :dry_run, type: :boolean, default: false, desc: 'Preview installation without applying'
|
|
113
|
+
option :wait, type: :boolean, default: true, desc: 'Wait for deployment to complete'
|
|
114
|
+
option :create_namespace, type: :boolean, default: true, desc: 'Create namespace if it does not exist'
|
|
115
|
+
def install
|
|
116
|
+
# Check if helm is available
|
|
117
|
+
check_helm_installed!
|
|
118
|
+
|
|
119
|
+
# Check if operator is already installed
|
|
120
|
+
if operator_installed? && !options[:dry_run]
|
|
121
|
+
Formatters::ProgressFormatter.warn('Language operator is already installed')
|
|
122
|
+
puts
|
|
123
|
+
puts 'To upgrade, use:'
|
|
124
|
+
puts ' aictl upgrade'
|
|
125
|
+
return
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
namespace = options[:namespace]
|
|
129
|
+
|
|
130
|
+
puts 'Installing language-operator...'
|
|
131
|
+
puts " Namespace: #{namespace}"
|
|
132
|
+
puts " Chart: #{CHART_NAME}"
|
|
133
|
+
puts
|
|
134
|
+
|
|
135
|
+
# Add Helm repository
|
|
136
|
+
add_helm_repo unless options[:dry_run]
|
|
137
|
+
|
|
138
|
+
# Build helm install command
|
|
139
|
+
cmd = build_helm_command('install', namespace)
|
|
140
|
+
|
|
141
|
+
# Execute helm install
|
|
142
|
+
if options[:dry_run]
|
|
143
|
+
puts 'Dry run - would execute:'
|
|
144
|
+
puts " #{cmd}"
|
|
145
|
+
puts
|
|
146
|
+
success, output = run_helm_command(cmd)
|
|
147
|
+
puts output
|
|
148
|
+
else
|
|
149
|
+
Formatters::ProgressFormatter.with_spinner('Installing language-operator') do
|
|
150
|
+
success, output = run_helm_command(cmd)
|
|
151
|
+
raise "Helm install failed: #{output}" unless success
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
Formatters::ProgressFormatter.success('Language operator installed successfully!')
|
|
155
|
+
puts
|
|
156
|
+
puts 'Next steps:'
|
|
157
|
+
puts ' 1. Create a cluster: aictl cluster create my-cluster'
|
|
158
|
+
puts ' 2. Create a model: aictl model create gpt4 --provider openai --model gpt-4-turbo'
|
|
159
|
+
puts ' 3. Create an agent: aictl agent create "your agent description"'
|
|
160
|
+
end
|
|
161
|
+
rescue StandardError => e
|
|
162
|
+
Formatters::ProgressFormatter.error("Installation failed: #{e.message}")
|
|
163
|
+
raise if ENV['DEBUG']
|
|
164
|
+
|
|
165
|
+
exit 1
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
desc 'upgrade', 'Upgrade the language-operator using Helm'
|
|
169
|
+
long_desc <<-DESC
|
|
170
|
+
Upgrade the language-operator to a newer version using Helm.
|
|
171
|
+
|
|
172
|
+
This command will:
|
|
173
|
+
1. Update Helm repositories
|
|
174
|
+
2. Upgrade the language-operator release
|
|
175
|
+
3. Wait for the rollout to complete
|
|
176
|
+
4. Verify the operator is running
|
|
177
|
+
|
|
178
|
+
Examples:
|
|
179
|
+
# Upgrade to latest version
|
|
180
|
+
aictl upgrade
|
|
181
|
+
|
|
182
|
+
# Upgrade with custom values
|
|
183
|
+
aictl upgrade --values my-values.yaml
|
|
184
|
+
|
|
185
|
+
# Upgrade to specific version
|
|
186
|
+
aictl upgrade --version 0.2.0
|
|
187
|
+
DESC
|
|
188
|
+
option :values, type: :string, desc: 'Path to custom Helm values file'
|
|
189
|
+
option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
|
|
190
|
+
option :version, type: :string, desc: 'Specific chart version to upgrade to'
|
|
191
|
+
option :dry_run, type: :boolean, default: false, desc: 'Preview upgrade without applying'
|
|
192
|
+
option :wait, type: :boolean, default: true, desc: 'Wait for deployment to complete'
|
|
193
|
+
def upgrade
|
|
194
|
+
# Check if helm is available
|
|
195
|
+
check_helm_installed!
|
|
196
|
+
|
|
197
|
+
# Check if operator is installed
|
|
198
|
+
unless operator_installed?
|
|
199
|
+
Formatters::ProgressFormatter.error('Language operator is not installed')
|
|
200
|
+
puts
|
|
201
|
+
puts 'To install, use:'
|
|
202
|
+
puts ' aictl install'
|
|
203
|
+
exit 1
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
namespace = options[:namespace]
|
|
207
|
+
|
|
208
|
+
puts 'Upgrading language-operator...'
|
|
209
|
+
puts " Namespace: #{namespace}"
|
|
210
|
+
puts " Chart: #{CHART_NAME}"
|
|
211
|
+
puts
|
|
212
|
+
|
|
213
|
+
# Update Helm repository
|
|
214
|
+
update_helm_repo unless options[:dry_run]
|
|
215
|
+
|
|
216
|
+
# Build helm upgrade command
|
|
217
|
+
cmd = build_helm_command('upgrade', namespace)
|
|
218
|
+
|
|
219
|
+
# Execute helm upgrade
|
|
220
|
+
if options[:dry_run]
|
|
221
|
+
puts 'Dry run - would execute:'
|
|
222
|
+
puts " #{cmd}"
|
|
223
|
+
puts
|
|
224
|
+
success, output = run_helm_command(cmd)
|
|
225
|
+
puts output
|
|
226
|
+
else
|
|
227
|
+
Formatters::ProgressFormatter.with_spinner('Upgrading language-operator') do
|
|
228
|
+
success, output = run_helm_command(cmd)
|
|
229
|
+
raise "Helm upgrade failed: #{output}" unless success
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
Formatters::ProgressFormatter.success('Language operator upgraded successfully!')
|
|
233
|
+
end
|
|
234
|
+
rescue StandardError => e
|
|
235
|
+
Formatters::ProgressFormatter.error("Upgrade failed: #{e.message}")
|
|
236
|
+
raise if ENV['DEBUG']
|
|
237
|
+
|
|
238
|
+
exit 1
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
desc 'uninstall', 'Uninstall the language-operator using Helm'
|
|
242
|
+
long_desc <<-DESC
|
|
243
|
+
Uninstall the language-operator from your Kubernetes cluster.
|
|
244
|
+
|
|
245
|
+
WARNING: This will remove the operator but NOT the CRDs or custom resources.
|
|
246
|
+
Agents, tools, models, and personas will remain in the cluster.
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
# Uninstall with confirmation
|
|
250
|
+
aictl uninstall
|
|
251
|
+
|
|
252
|
+
# Force uninstall without confirmation
|
|
253
|
+
aictl uninstall --force
|
|
254
|
+
|
|
255
|
+
# Uninstall from specific namespace
|
|
256
|
+
aictl uninstall --namespace my-namespace
|
|
257
|
+
DESC
|
|
258
|
+
option :namespace, type: :string, default: DEFAULT_NAMESPACE, desc: 'Kubernetes namespace'
|
|
259
|
+
option :force, type: :boolean, default: false, desc: 'Skip confirmation prompt'
|
|
260
|
+
def uninstall
|
|
261
|
+
# Check if helm is available
|
|
262
|
+
check_helm_installed!
|
|
263
|
+
|
|
264
|
+
# Check if operator is installed
|
|
265
|
+
unless operator_installed?
|
|
266
|
+
Formatters::ProgressFormatter.warn('Language operator is not installed')
|
|
267
|
+
return
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
namespace = options[:namespace]
|
|
271
|
+
|
|
272
|
+
# Confirm deletion unless --force
|
|
273
|
+
unless options[:force]
|
|
274
|
+
puts "This will uninstall the language-operator from namespace '#{namespace}'"
|
|
275
|
+
puts
|
|
276
|
+
puts 'WARNING: This will NOT delete:'
|
|
277
|
+
puts ' - CRDs (CustomResourceDefinitions)'
|
|
278
|
+
puts ' - LanguageAgent resources'
|
|
279
|
+
puts ' - LanguageTool resources'
|
|
280
|
+
puts ' - LanguageModel resources'
|
|
281
|
+
puts ' - LanguagePersona resources'
|
|
282
|
+
puts
|
|
283
|
+
return unless Helpers::UserPrompts.confirm('Continue with uninstall?')
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
# Build helm uninstall command
|
|
287
|
+
cmd = "helm uninstall #{RELEASE_NAME} --namespace #{namespace}"
|
|
288
|
+
|
|
289
|
+
# Execute helm uninstall
|
|
290
|
+
Formatters::ProgressFormatter.with_spinner('Uninstalling language-operator') do
|
|
291
|
+
success, output = run_helm_command(cmd)
|
|
292
|
+
raise "Helm uninstall failed: #{output}" unless success
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
Formatters::ProgressFormatter.success('Language operator uninstalled successfully!')
|
|
296
|
+
puts
|
|
297
|
+
puts 'Note: CRDs and custom resources remain in the cluster.'
|
|
298
|
+
puts 'To completely remove all resources, you must manually delete them.'
|
|
299
|
+
rescue StandardError => e
|
|
300
|
+
Formatters::ProgressFormatter.error("Uninstall failed: #{e.message}")
|
|
301
|
+
raise if ENV['DEBUG']
|
|
302
|
+
|
|
303
|
+
exit 1
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
private
|
|
307
|
+
|
|
308
|
+
def check_helm_installed!
|
|
309
|
+
_, _, status = Open3.capture3('which helm')
|
|
310
|
+
return if status.success?
|
|
311
|
+
|
|
312
|
+
Formatters::ProgressFormatter.error('Helm is not installed')
|
|
313
|
+
puts
|
|
314
|
+
puts 'Install Helm from: https://helm.sh/docs/intro/install/'
|
|
315
|
+
exit 1
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def operator_installed?
|
|
319
|
+
namespace = options[:namespace] || DEFAULT_NAMESPACE
|
|
320
|
+
cmd = "helm list --namespace #{namespace} --filter #{RELEASE_NAME} --output json"
|
|
321
|
+
success, output = run_helm_command(cmd)
|
|
322
|
+
return false unless success
|
|
323
|
+
|
|
324
|
+
require 'json'
|
|
325
|
+
releases = JSON.parse(output)
|
|
326
|
+
releases.any? { |r| r['name'] == RELEASE_NAME }
|
|
327
|
+
rescue StandardError
|
|
328
|
+
false
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def add_helm_repo
|
|
332
|
+
Formatters::ProgressFormatter.with_spinner('Adding Helm repository') do
|
|
333
|
+
# Check if repo already exists
|
|
334
|
+
cmd = 'helm repo list --output json'
|
|
335
|
+
success, output = run_helm_command(cmd)
|
|
336
|
+
|
|
337
|
+
if success
|
|
338
|
+
require 'json'
|
|
339
|
+
repos = JSON.parse(output)
|
|
340
|
+
repo_exists = repos.any? { |r| r['name'] == HELM_REPO_NAME }
|
|
341
|
+
|
|
342
|
+
unless repo_exists
|
|
343
|
+
cmd = "helm repo add #{HELM_REPO_NAME} #{HELM_REPO_URL}"
|
|
344
|
+
success, output = run_helm_command(cmd)
|
|
345
|
+
raise "Failed to add Helm repo: #{output}" unless success
|
|
346
|
+
end
|
|
347
|
+
else
|
|
348
|
+
# helm repo list failed, try adding anyway
|
|
349
|
+
cmd = "helm repo add #{HELM_REPO_NAME} #{HELM_REPO_URL}"
|
|
350
|
+
success, output = run_helm_command(cmd)
|
|
351
|
+
raise "Failed to add Helm repo: #{output}" unless success
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Update repo
|
|
355
|
+
cmd = "helm repo update #{HELM_REPO_NAME}"
|
|
356
|
+
success, output = run_helm_command(cmd)
|
|
357
|
+
raise "Failed to update Helm repo: #{output}" unless success
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
def update_helm_repo
|
|
362
|
+
Formatters::ProgressFormatter.with_spinner('Updating Helm repository') do
|
|
363
|
+
cmd = "helm repo update #{HELM_REPO_NAME}"
|
|
364
|
+
success, output = run_helm_command(cmd)
|
|
365
|
+
raise "Failed to update Helm repo: #{output}" unless success
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def build_helm_command(action, namespace)
|
|
370
|
+
cmd = ['helm', action, RELEASE_NAME]
|
|
371
|
+
|
|
372
|
+
# Add chart name for install
|
|
373
|
+
cmd << CHART_NAME if action == 'install'
|
|
374
|
+
|
|
375
|
+
# Add namespace
|
|
376
|
+
cmd << '--namespace' << namespace
|
|
377
|
+
|
|
378
|
+
# Add create-namespace for install
|
|
379
|
+
cmd << '--create-namespace' if action == 'install' && options[:create_namespace]
|
|
380
|
+
|
|
381
|
+
# Add values file
|
|
382
|
+
cmd << '--values' << options[:values] if options[:values]
|
|
383
|
+
|
|
384
|
+
# Add version
|
|
385
|
+
cmd << '--version' << options[:version] if options[:version]
|
|
386
|
+
|
|
387
|
+
# Add wait
|
|
388
|
+
cmd << '--wait' if options[:wait]
|
|
389
|
+
|
|
390
|
+
# Add dry-run
|
|
391
|
+
cmd << '--dry-run' if options[:dry_run]
|
|
392
|
+
|
|
393
|
+
cmd.join(' ')
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def run_helm_command(cmd)
|
|
397
|
+
stdout, stderr, status = Open3.capture3(cmd)
|
|
398
|
+
output = stdout + stderr
|
|
399
|
+
[status.success?, output.strip]
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
end
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative '../formatters/progress_formatter'
|
|
6
|
+
require_relative '../formatters/table_formatter'
|
|
7
|
+
require_relative '../helpers/cluster_validator'
|
|
8
|
+
require_relative '../helpers/cluster_context'
|
|
9
|
+
require_relative '../helpers/user_prompts'
|
|
10
|
+
require_relative '../helpers/resource_dependency_checker'
|
|
11
|
+
require_relative '../helpers/editor_helper'
|
|
12
|
+
require_relative '../../config/cluster_config'
|
|
13
|
+
require_relative '../../kubernetes/client'
|
|
14
|
+
require_relative '../../kubernetes/resource_builder'
|
|
15
|
+
|
|
16
|
+
module LanguageOperator
|
|
17
|
+
module CLI
|
|
18
|
+
module Commands
|
|
19
|
+
# Model management commands
|
|
20
|
+
class Model < Thor
|
|
21
|
+
include Helpers::ClusterValidator
|
|
22
|
+
|
|
23
|
+
desc 'list', 'List all models in current cluster'
|
|
24
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
25
|
+
def list
|
|
26
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
27
|
+
|
|
28
|
+
models = ctx.client.list_resources('LanguageModel', namespace: ctx.namespace)
|
|
29
|
+
|
|
30
|
+
if models.empty?
|
|
31
|
+
Formatters::ProgressFormatter.info("No models found in cluster '#{ctx.name}'")
|
|
32
|
+
puts
|
|
33
|
+
puts 'Models define LLM configurations for agents.'
|
|
34
|
+
puts
|
|
35
|
+
puts 'Create a model with:'
|
|
36
|
+
puts ' aictl model create <name> --provider <provider> --model <model>'
|
|
37
|
+
return
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
table_data = models.map do |model|
|
|
41
|
+
name = model.dig('metadata', 'name')
|
|
42
|
+
provider = model.dig('spec', 'provider') || 'unknown'
|
|
43
|
+
model_name = model.dig('spec', 'modelName') || 'unknown'
|
|
44
|
+
status = model.dig('status', 'phase') || 'Unknown'
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
name: name,
|
|
48
|
+
provider: provider,
|
|
49
|
+
model: model_name,
|
|
50
|
+
status: status
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Formatters::TableFormatter.models(table_data)
|
|
55
|
+
rescue StandardError => e
|
|
56
|
+
Formatters::ProgressFormatter.error("Failed to list models: #{e.message}")
|
|
57
|
+
raise if ENV['DEBUG']
|
|
58
|
+
|
|
59
|
+
exit 1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc 'create NAME', 'Create a new model'
|
|
63
|
+
long_desc <<-DESC
|
|
64
|
+
Create a new LanguageModel resource in the cluster.
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
aictl model create gpt4 --provider openai --model gpt-4-turbo
|
|
68
|
+
aictl model create claude --provider anthropic --model claude-3-opus-20240229
|
|
69
|
+
aictl model create local --provider openai_compatible --model llama-3 --endpoint http://localhost:8080
|
|
70
|
+
DESC
|
|
71
|
+
option :provider, type: :string, required: true, desc: 'LLM provider (e.g., openai, anthropic, openai_compatible)'
|
|
72
|
+
option :model, type: :string, required: true, desc: 'Model identifier (e.g., gpt-4, claude-3-opus)'
|
|
73
|
+
option :endpoint, type: :string, desc: 'Custom endpoint URL (for openai_compatible or self-hosted)'
|
|
74
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
75
|
+
option :dry_run, type: :boolean, default: false, desc: 'Output the manifest without creating'
|
|
76
|
+
def create(name)
|
|
77
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
78
|
+
|
|
79
|
+
# Build LanguageModel resource
|
|
80
|
+
resource = Kubernetes::ResourceBuilder.language_model(
|
|
81
|
+
name,
|
|
82
|
+
provider: options[:provider],
|
|
83
|
+
model: options[:model],
|
|
84
|
+
endpoint: options[:endpoint],
|
|
85
|
+
cluster: ctx.namespace
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# Handle dry-run: output manifest and exit
|
|
89
|
+
if options[:dry_run]
|
|
90
|
+
puts resource.to_yaml
|
|
91
|
+
return
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Check if model already exists
|
|
95
|
+
begin
|
|
96
|
+
ctx.client.get_resource('LanguageModel', name, ctx.namespace)
|
|
97
|
+
Formatters::ProgressFormatter.error("Model '#{name}' already exists in cluster '#{ctx.name}'")
|
|
98
|
+
exit 1
|
|
99
|
+
rescue K8s::Error::NotFound
|
|
100
|
+
# Model doesn't exist, proceed with creation
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Create model
|
|
104
|
+
Formatters::ProgressFormatter.with_spinner("Creating model '#{name}'") do
|
|
105
|
+
ctx.client.apply_resource(resource)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
Formatters::ProgressFormatter.success("Model '#{name}' created successfully")
|
|
109
|
+
puts
|
|
110
|
+
puts 'Model Details:'
|
|
111
|
+
puts " Name: #{name}"
|
|
112
|
+
puts " Provider: #{options[:provider]}"
|
|
113
|
+
puts " Model: #{options[:model]}"
|
|
114
|
+
puts " Endpoint: #{options[:endpoint]}" if options[:endpoint]
|
|
115
|
+
puts " Cluster: #{ctx.name}"
|
|
116
|
+
rescue StandardError => e
|
|
117
|
+
Formatters::ProgressFormatter.error("Failed to create model: #{e.message}")
|
|
118
|
+
raise if ENV['DEBUG']
|
|
119
|
+
|
|
120
|
+
exit 1
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
desc 'inspect NAME', 'Show detailed model information'
|
|
124
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
125
|
+
def inspect(name)
|
|
126
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
127
|
+
|
|
128
|
+
# Get model
|
|
129
|
+
begin
|
|
130
|
+
model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
|
|
131
|
+
rescue K8s::Error::NotFound
|
|
132
|
+
Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
|
|
133
|
+
exit 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
puts "Model: #{name}"
|
|
137
|
+
puts " Cluster: #{ctx.name}"
|
|
138
|
+
puts " Namespace: #{ctx.namespace}"
|
|
139
|
+
puts " Provider: #{model.dig('spec', 'provider')}"
|
|
140
|
+
puts " Model: #{model.dig('spec', 'modelName')}"
|
|
141
|
+
puts " Endpoint: #{model.dig('spec', 'endpoint')}" if model.dig('spec', 'endpoint')
|
|
142
|
+
puts " Status: #{model.dig('status', 'phase') || 'Unknown'}"
|
|
143
|
+
puts
|
|
144
|
+
|
|
145
|
+
# Get agents using this model
|
|
146
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
147
|
+
agents_using = Helpers::ResourceDependencyChecker.agents_using_model(agents, name)
|
|
148
|
+
|
|
149
|
+
if agents_using.any?
|
|
150
|
+
puts "Agents using this model (#{agents_using.count}):"
|
|
151
|
+
agents_using.each do |agent|
|
|
152
|
+
puts " - #{agent.dig('metadata', 'name')}"
|
|
153
|
+
end
|
|
154
|
+
else
|
|
155
|
+
puts 'No agents using this model'
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
puts
|
|
159
|
+
puts 'Labels:'
|
|
160
|
+
labels = model.dig('metadata', 'labels') || {}
|
|
161
|
+
if labels.empty?
|
|
162
|
+
puts ' (none)'
|
|
163
|
+
else
|
|
164
|
+
labels.each do |key, value|
|
|
165
|
+
puts " #{key}: #{value}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
rescue StandardError => e
|
|
169
|
+
Formatters::ProgressFormatter.error("Failed to inspect model: #{e.message}")
|
|
170
|
+
raise if ENV['DEBUG']
|
|
171
|
+
|
|
172
|
+
exit 1
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
desc 'delete NAME', 'Delete a model'
|
|
176
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
177
|
+
option :force, type: :boolean, default: false, desc: 'Skip confirmation'
|
|
178
|
+
def delete(name)
|
|
179
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
180
|
+
|
|
181
|
+
# Get model
|
|
182
|
+
begin
|
|
183
|
+
model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
|
|
184
|
+
rescue K8s::Error::NotFound
|
|
185
|
+
Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
|
|
186
|
+
exit 1
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Check for agents using this model
|
|
190
|
+
agents = ctx.client.list_resources('LanguageAgent', namespace: ctx.namespace)
|
|
191
|
+
agents_using = Helpers::ResourceDependencyChecker.agents_using_model(agents, name)
|
|
192
|
+
|
|
193
|
+
if agents_using.any? && !options[:force]
|
|
194
|
+
Formatters::ProgressFormatter.warn("Model '#{name}' is in use by #{agents_using.count} agent(s)")
|
|
195
|
+
puts
|
|
196
|
+
puts 'Agents using this model:'
|
|
197
|
+
agents_using.each do |agent|
|
|
198
|
+
puts " - #{agent.dig('metadata', 'name')}"
|
|
199
|
+
end
|
|
200
|
+
puts
|
|
201
|
+
puts 'Delete these agents first, or use --force to delete anyway.'
|
|
202
|
+
puts
|
|
203
|
+
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Confirm deletion unless --force
|
|
207
|
+
unless options[:force] || agents_using.any?
|
|
208
|
+
puts "This will delete model '#{name}' from cluster '#{ctx.name}':"
|
|
209
|
+
puts " Provider: #{model.dig('spec', 'provider')}"
|
|
210
|
+
puts " Model: #{model.dig('spec', 'modelName')}"
|
|
211
|
+
puts " Status: #{model.dig('status', 'phase')}"
|
|
212
|
+
puts
|
|
213
|
+
return unless Helpers::UserPrompts.confirm('Are you sure?')
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Delete model
|
|
217
|
+
Formatters::ProgressFormatter.with_spinner("Deleting model '#{name}'") do
|
|
218
|
+
ctx.client.delete_resource('LanguageModel', name, ctx.namespace)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
Formatters::ProgressFormatter.success("Model '#{name}' deleted successfully")
|
|
222
|
+
rescue StandardError => e
|
|
223
|
+
Formatters::ProgressFormatter.error("Failed to delete model: #{e.message}")
|
|
224
|
+
raise if ENV['DEBUG']
|
|
225
|
+
|
|
226
|
+
exit 1
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
desc 'edit NAME', 'Edit model configuration'
|
|
230
|
+
option :cluster, type: :string, desc: 'Override current cluster context'
|
|
231
|
+
def edit(name)
|
|
232
|
+
ctx = Helpers::ClusterContext.from_options(options)
|
|
233
|
+
|
|
234
|
+
# Get current model
|
|
235
|
+
begin
|
|
236
|
+
model = ctx.client.get_resource('LanguageModel', name, ctx.namespace)
|
|
237
|
+
rescue K8s::Error::NotFound
|
|
238
|
+
Formatters::ProgressFormatter.error("Model '#{name}' not found in cluster '#{ctx.name}'")
|
|
239
|
+
exit 1
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
# Edit model YAML in user's editor
|
|
243
|
+
edited_yaml = Helpers::EditorHelper.edit_content(
|
|
244
|
+
model.to_yaml,
|
|
245
|
+
'model-',
|
|
246
|
+
'.yaml',
|
|
247
|
+
default_editor: 'vim'
|
|
248
|
+
)
|
|
249
|
+
edited_model = YAML.safe_load(edited_yaml)
|
|
250
|
+
|
|
251
|
+
# Apply changes
|
|
252
|
+
Formatters::ProgressFormatter.with_spinner("Updating model '#{name}'") do
|
|
253
|
+
ctx.client.apply_resource(edited_model)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
Formatters::ProgressFormatter.success("Model '#{name}' updated successfully")
|
|
257
|
+
rescue StandardError => e
|
|
258
|
+
Formatters::ProgressFormatter.error("Failed to edit model: #{e.message}")
|
|
259
|
+
raise if ENV['DEBUG']
|
|
260
|
+
|
|
261
|
+
exit 1
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|