Skip to content

Commit b6ef1de

Browse files
authored
Merge pull request #560 from headlamp-k8s/holmes-integration
Holmes integration into ai-assistant plugin
2 parents e00b315 + 0e7f956 commit b6ef1de

19 files changed

+3102
-75
lines changed

ai-assistant/README.md

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,73 @@ The plugin supports multiple AI providers, allowing you to choose the one that b
2424
- **Anthropic** (Claude models)
2525
- **Mistral AI**
2626
- **Google** (Gemini models)
27+
- **DeepSeek** (DeepSeek-Chat, DeepSeek-Reasoner)
2728
- **Local Models** (via Ollama)
2829

2930
You will need to provide your own API keys and endpoint information for the provider you choose to use. Please note that using AI providers may incur costs, so check the pricing details of your chosen provider.
31+
32+
## Adding Holmes Agent to Your Cluster
33+
34+
The AI Assistant can connect to a [HolmesGPT](https://holmesgpt.dev) agent running in your cluster for enhanced Kubernetes diagnostics and troubleshooting. Follow the steps below to deploy Holmes.
35+
36+
### 1. Add the Robusta Helm Repository
37+
38+
```bash
39+
helm repo add robusta https://robusta-charts.storage.googleapis.com
40+
helm repo update
41+
```
42+
43+
### 2. Create a `values.yaml`
44+
45+
Below is an example using Azure OpenAI. For other providers (OpenAI, AWS Bedrock, etc.), see the [HolmesGPT installation docs](https://holmesgpt.dev/latest/installation/kubernetes-installation/#installation).
46+
47+
```yaml
48+
# values.yaml
49+
image: robustadev/holmes:0.19.1
50+
51+
additionalEnvVars:
52+
- name: AZURE_API_KEY
53+
value: ""
54+
- name: AZURE_API_BASE
55+
value: "https://<your-azure-endpoint>.openai.azure.com"
56+
- name: AZURE_API_VERSION
57+
value: "2024-02-15-preview"
58+
# Or load from secret:
59+
# - name: AZURE_API_KEY
60+
# valueFrom:
61+
# secretKeyRef:
62+
# name: holmes-secrets
63+
# key: azure-api-key
64+
# - name: AZURE_API_BASE
65+
# valueFrom:
66+
# secretKeyRef:
67+
# name: holmes-secrets
68+
# key: azure-api-base
69+
70+
modelList:
71+
azure-gpt4:
72+
api_key: "{{ env.AZURE_API_KEY }}"
73+
model: azure/gpt-5
74+
api_base: "{{ env.AZURE_API_BASE }}"
75+
api_version: "{{ env.AZURE_API_VERSION }}"
76+
```
77+
78+
### 3. Render and Patch the Helm Template
79+
80+
Holmes requires enabling the AG-UI server for the AI Assistant to communicate with it. Render the template and update the container command:
81+
82+
```bash
83+
helm template holmesgpt robusta/holmes -f values.yaml > rendered.yaml
84+
```
85+
86+
In `rendered.yaml`, find the container command and change it to:
87+
88+
```yaml
89+
command: ["python3", "-u", "/app/experimental/ag-ui/server-agui.py"]
90+
```
91+
92+
### 4. Deploy to Your Cluster
93+
94+
```bash
95+
kubectl apply -f rendered.yaml
96+
```

ai-assistant/package-lock.json

Lines changed: 119 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ai-assistant/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@kinvolk/headlamp-plugin": "^0.13.0-alpha.14"
3737
},
3838
"dependencies": {
39+
"@ag-ui/client": "^0.0.45",
3940
"@langchain/anthropic": "^1.3.16",
4041
"@langchain/core": "^1.1.20",
4142
"@langchain/deepseek": "1.0.8",

ai-assistant/src/ContentRenderer.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -280,38 +280,45 @@ const ContentRenderer: React.FC<ContentRendererProps> = React.memo(
280280
const history = useHistory();
281281
// Create code component that has access to onYamlDetected
282282
const CodeComponent = React.useMemo(() => {
283+
/** Extract a plain string from React children (may be string, array, or nested elements). */
284+
const extractText = (node: any): string => {
285+
if (typeof node === 'string') return node;
286+
if (Array.isArray(node)) return node.map(extractText).join('');
287+
if (node?.props?.children) return extractText(node.props.children);
288+
return String(node ?? '');
289+
};
290+
283291
const component = React.memo(({ className, children, ...props }: any) => {
292+
// Robustly get the text content from children (string or array)
293+
const textContent = extractText(children);
294+
284295
// Check if this is a YAML code block
285296
const isYamlBlock =
286297
!props.inline &&
287298
(className === 'language-yaml' ||
288299
className === 'language-yml' ||
289-
(typeof children === 'string' &&
290-
children.includes('apiVersion:') &&
291-
children.includes('kind:')));
300+
(textContent.includes('apiVersion:') && textContent.includes('kind:')));
292301

293302
// Check if this is a JSON code block with Kubernetes resource
294303
const isJsonKubernetesBlock =
295-
!props.inline &&
296-
typeof children === 'string' &&
297-
(className === 'language-json' || isJsonKubernetesResource(children));
304+
!props.inline && (className === 'language-json' || isJsonKubernetesResource(textContent));
298305

299-
if (isYamlBlock && onYamlDetected && typeof children === 'string') {
300-
const parsed = parseKubernetesYAML(children);
306+
if (isYamlBlock && onYamlDetected && textContent) {
307+
const parsed = parseKubernetesYAML(textContent);
301308
if (parsed.isValid) {
302309
return (
303310
<YamlDisplay
304-
yaml={children}
311+
yaml={textContent}
305312
title={parsed.resourceType}
306313
onOpenInEditor={onYamlDetected}
307314
/>
308315
);
309316
}
310317
}
311318

312-
if (isJsonKubernetesBlock && onYamlDetected && typeof children === 'string') {
319+
if (isJsonKubernetesBlock && onYamlDetected && textContent) {
313320
// Convert JSON to YAML and display
314-
const yamlContent = convertJsonToYaml(children);
321+
const yamlContent = convertJsonToYaml(textContent);
315322
const parsed = parseKubernetesYAML(yamlContent);
316323
if (parsed.isValid) {
317324
return (
@@ -325,7 +332,7 @@ const ContentRenderer: React.FC<ContentRendererProps> = React.memo(
325332
}
326333

327334
// Check if it's just one line of code
328-
if (typeof children === 'string' && !children.trim().includes('\n')) {
335+
if (textContent && !textContent.trim().includes('\n')) {
329336
// Display inline
330337
return <em>{children}</em>;
331338
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// ag-ui types for Holmes agent communication.
2+
// Event types and protocol types are provided by @ag-ui/client and @ag-ui/core.
3+
// This file only defines types specific to the Headlamp–Holmes integration.
4+
5+
export interface HolmesServiceInfo {
6+
namespace: string;
7+
service: string;
8+
port: number;
9+
}

0 commit comments

Comments
 (0)