13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
# File 'lib/language_operator/cli/commands/model/test.rb', line 13
def self.included(base)
base.class_eval do
desc 'test NAME', 'Test model connectivity and functionality'
long_desc <<-DESC
Test that a model is operational by:
1. Verifying the pod is running
2. Testing the chat completion endpoint with a simple message
This command helps diagnose model deployment issues.
DESC
option :cluster, type: :string, desc: 'Override current cluster context'
option :timeout, type: :numeric, default: 30, desc: 'Timeout in seconds for endpoint test'
def test(name)
handle_command_error('test model') do
model = get_resource_or_exit(RESOURCE_MODEL, name)
model_name = model.dig('spec', 'modelName')
deployment = check_deployment_status(name)
pod = check_pod_status(name, deployment)
test_chat_completion(name, model_name, pod, options[:timeout])
end
end
private
def check_deployment_status(name)
Formatters::ProgressFormatter.with_spinner('Verifying deployment') do
deployment = ctx.client.get_resource('Deployment', name, ctx.namespace)
replicas = deployment.dig('spec', 'replicas') || 1
ready_replicas = deployment.dig('status', 'readyReplicas') || 0
unless ready_replicas >= replicas
raise "Deployment not ready (#{ready_replicas}/#{replicas}). " \
"Run 'kubectl get deployment #{name} -n #{ctx.namespace}' for details."
end
deployment
end
rescue K8s::Error::NotFound
Formatters::ProgressFormatter.error("Deployment '#{name}' not found")
exit 1
end
def check_pod_status(name, deployment)
Formatters::ProgressFormatter.with_spinner('Verifying pod') do
labels = deployment.dig('spec', 'selector', 'matchLabels')
pods = CLI::Helpers::LabelUtils.find_pods_by_deployment_labels(ctx, name, labels)
raise "No pods found for model '#{name}'" if pods.empty?
running_pod = pods.find do |pod|
pod.dig('status', 'phase') == 'Running' &&
pod.dig('status', 'conditions')&.any? { |c| c['type'] == 'Ready' && c['status'] == 'True' }
end
if running_pod.nil?
pod_phases = pods.map { |p| p.dig('status', 'phase') }.join(', ')
labels_hash = labels.respond_to?(:to_h) ? labels.to_h : labels
label_selector = labels_hash.map { |k, v| "#{k}=#{v}" }.join(',')
raise "No running pods found. Pod phases: #{pod_phases}. " \
"Run 'kubectl get pods -l #{label_selector} -n #{ctx.namespace}' for details."
end
running_pod
end
end
def test_chat_completion(_name, model_name, pod, timeout)
Formatters::ProgressFormatter.with_spinner('Verifying chat completion requests') do
pod_name = pod.dig('metadata', 'name')
payload = JSON.generate({
model: model_name,
messages: [{ role: 'user', content: 'hello' }],
max_tokens: 10
})
temp_file = nil
begin
temp_file = Tempfile.new(['model_test_payload', '.json'])
temp_file.write(payload)
temp_file.close
curl_command = 'curl -s -X POST http://localhost:4000/v1/chat/completions ' \
"-H 'Content-Type: application/json' -d @#{temp_file.path} --max-time #{timeout.to_i}"
result = execute_in_pod(pod_name, curl_command)
ensure
temp_file&.unlink
end
response = JSON.parse(result)
if response['error']
error_msg = response['error']['message'] || response['error']
raise error_msg
elsif !response['choices']
raise "Unexpected response format: #{result.lines.first.strip}"
end
response
rescue JSON::ParserError => e
raise "Failed to parse response: #{e.message}"
end
rescue StandardError => e
puts
puts Formatters::ProgressFormatter.pastel.bold.red(e.message)
exit 1
end
def execute_in_pod(pod_name, command)
kubectl_command = if command.is_a?(String)
"kubectl exec -n #{ctx.namespace} #{pod_name} -- sh -c #{Shellwords.escape(command)}"
else
Shellwords.join(['kubectl', 'exec', '-n', ctx.namespace, pod_name, '--'] + command)
end
output = `#{kubectl_command} 2>&1`
exit_code = $CHILD_STATUS.exitstatus
if exit_code != 0
Formatters::ProgressFormatter.error("Failed to execute command in pod: #{output}")
exit 1
end
output
end
end
end
|