Commit ad9a888f authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: update AI2 Helm chart to version 2.0.0 with improved configuration and Redis integration

parent 2fed5cc9
Loading
Loading
Loading
Loading
Loading
+22 −56
Changes for deployment/helm/README.md: 22 added lines, 56 removed lines.
Original line number Diff line number Diff line
# AI2 Helm Chart

Deploys the AI2 stack — MCP server and AI Agent — into a Kubernetes cluster.
`ai2/` is the same chart the platform deploys as a subchart of `oop-platform-chart` in the helm
repository; change it there and copy it here. See [ai2/README.md](ai2/README.md) for its values.

## Components

| Component  | Description                                              | Default Port |
|------------|----------------------------------------------------------|-------------|
| mcp        | MCP server exposing CAMARA-compliant tools via FastMCP   | 8004        |
| ai-agent   | Quart backend accepting natural language prompts via Groq | 9013        |

The ai-agent waits for the MCP server to be healthy before starting (via initContainer).
It deploys the MCP server, the AI Agent and a Redis for the agent's session memory. The ai-agent
waits for the MCP server to be healthy before starting (via initContainer).

## Prerequisites

@@ -20,27 +15,24 @@ The ai-agent waits for the MCP server to be healthy before starting (via initCon

## Install

Fetch the Bitnami Redis dependency before installing:

```bash
helm dependency update ./deployment/helm/ai2
```

```bash
helm install ai2test ./helm/ai2 \
helm install ai2test ./deployment/helm/ai2 \
  --namespace ai2test \
  --create-namespace \
  --set secrets.groqApiKey=<your-groq-api-key> \
  --set config.mcp.oegServiceUrl=http://<oeg-host>
  --set mcp.oegUrl=http://<oeg-host>
```

Without the umbrella chart there is no Keycloak; add `--set global.auth.enabled=false` to call
AI2 without a token.

## Upgrade

```bash
helm upgrade ai2test ./helm/ai2 \
helm upgrade ai2test ./deployment/helm/ai2 \
  --namespace ai2test \
  --set secrets.groqApiKey=<your-groq-api-key> \
  --set config.mcp.oegServiceUrl=http://<new-oeg-host>
  --reuse-values \
  --set mcp.oegUrl=http://<new-oeg-host>
```

## Uninstall
@@ -49,59 +41,33 @@ helm upgrade ai2test ./helm/ai2 \
helm uninstall ai2test --namespace ai2test
```

## Key Values

| Value                           | Description                      | Default                  |
|---------------------------------|----------------------------------|--------------------------|
| `secrets.groqApiKey`            | Groq API key (required)          | `""`                     |
| `config.mcp.oegServiceUrl`      | Base URL of the OEG service      | `http://oeg`   |
| `config.aiAgent.groqModelName`  | Groq model identifier            | `qwen/qwen3.8-27b`         |
| `service.mcp.type`              | Kubernetes service type for MCP  | `NodePort`               |
| `service.mcp.nodePort`          | NodePort for MCP                 | `32004`                  |
| `service.aiAgent.nodePort`      | NodePort for ai-agent            | `32013`                  |
| `redis.enabled`                 | Deploy Redis for agent memory    | `true`                   |
| `redis.master.service.ports.redis` | Bitnami Redis service port     | `6379`                   |
| `redis.memory.database`         | Redis database index for AI memory | `0`                    |
| `config.aiAgent.memoryWindowTurns` | Recent turns retained in memory | `10`                    |
| `config.aiAgent.memoryTtlSeconds` | Session memory TTL in seconds   | `3600`                  |
| `config.aiAgent.memoryKeyPrefix` | Redis key prefix                 | `ai_agent:session`       |
| `images.mcp.tag`                | MCP image tag                    | `latest`                 |
| `images.aiAgent.tag`            | AI Agent image tag               | `latest`                 |

## Local Development (kind)

Build and load images locally instead of pulling from the registry:

```bash
docker build -t <your_mcp_dev_image>:latest ../../mcp_module
docker build -t <your_ai_agent_dev_image>:latest ../../ai_agent
docker build -t <your_mcp_dev_image>:latest mcp_module
docker build -t <your_ai_agent_dev_image>:latest ai_agent

kind load docker-image <your_mcp_dev_image>:latest --name <your-cluster>
kind load docker-image <your_ai_agent_dev_image>:latest --name <your-cluster>
```

Then install with `pullPolicy: IfNotPresent` to use the local images:
Then install with those images, exposed on NodePorts:

```bash
helm install ai2 ./helm/ai2 \
helm install ai2 ./deployment/helm/ai2 \
  --namespace ai2test \
  --create-namespace \
  --set secrets.groqApiKey=<your-groq-api-key> \
  --set config.mcp.oegServiceUrl=http://<oeg-host> \
  --set images.mcp.pullPolicy=IfNotPresent \
  --set images.aiAgent.pullPolicy=IfNotPresent
  --set mcp.oegUrl=http://<oeg-host> \
  --set global.auth.enabled=false \
  --set mcp.image.repository=<your_mcp_dev_image>,mcp.image.tag=latest \
  --set aiAgent.image.repository=<your_ai_agent_dev_image>,aiAgent.image.tag=latest \
  --set mcp.service.type=NodePort,mcp.service.nodePort=32004 \
  --set aiAgent.service.type=NodePort,aiAgent.service.nodePort=32013
```

## Redis Memory

Redis is deployed through the Bitnami Redis Helm chart dependency as an in-cluster service and is used by the AI agent for optional short-term session memory. The generated agent value is:

```bash
REDIS_URL=redis://<redis-service>:6379/0
```

This mirrors the Docker Compose Redis container port `6379`; the chart disables Redis auth and persistence by default to match the local cache/session-store behavior. The Compose host mapping `6385:6379` is not represented as a Kubernetes NodePort because Kubernetes NodePorts must normally be in the cluster NodePort range, commonly `30000-32767`.

## Test the Deployment

```bash

deployment/helm/ai2/Chart.lock

deleted100644 → 0
+0 −6
Changes for deployment/helm/ai2/Chart.lock: 0 added lines, 6 removed lines.
Original line number Diff line number Diff line
dependencies:
- name: redis
  repository: oci://registry-1.docker.io/bitnamicharts
  version: 25.5.3
digest: sha256:b680f268339ce6899da9e632b2839d21e8391886211d57f7bcc735c73ffa6e1c
generated: "2026-06-04T11:48:21.792172108+03:00"
+4 −9
Changes for deployment/helm/ai2/Chart.yaml: 4 added lines, 9 removed lines.
Original line number Diff line number Diff line
apiVersion: v2
name: ai2
description: AI2 (AI squared) - MCP server over OEG's APIs and the AI agent behind the portal chat
type: application
version: 1.0.0
appVersion: "1.0.0"
version: 2.0.0
appVersion: "2.0.0"
keywords:
  - ai2
  - oop
@@ -11,10 +12,4 @@ keywords:
  - ai-agent
home: https://labs.etsi.org/rep/oop/code/ai2
maintainers:
  - name: OOP Team

dependencies:
  - name: redis
    version: 25.5.3
    repository: oci://registry-1.docker.io/bitnamicharts
    condition: redis.enabled
  - name: Open Operator Platform Team
+45 −0
Changes for deployment/helm/ai2/README.md: 45 added lines, 0 removed lines.
Original line number Diff line number Diff line
# AI2 Helm Chart

Deploys AI2, the platform's natural-language assistant:

- **ai-agent**: takes a chat message, asks a Groq model what to do and calls the MCP server's
  tools. The portal forwards `/api/ai2/chat` to it.
- **mcp**: MCP server whose tools call OEG's CAMARA APIs.
- **redis**: in-memory store for the agent's session memory. Nothing is persisted.

Every request needs the user's Keycloak access token (`Authorization: Bearer …`). The agent
passes it to the MCP server, which sends it to OEG, so the user's own roles decide what AI2 may
do. The portal adds the token itself. With `global.auth.enabled=false` no token is needed, and
both components get `AUTH_ENABLED=false`.

## Configuration

| Parameter | Description | Default |
|-----------|-------------|---------|
| `mcp.image.repository` / `tag` | MCP server image | `labs.etsi.org:5050/oop/code/ai2/mcp_module`, `2.0.0` |
| `mcp.oegUrl` | OEG base URL, without a path | `http://<global.oeg.host>:<global.oeg.port>` |
| `mcp.service.type` / `nodePort` | MCP Service type | `ClusterIP` |
| `aiAgent.image.repository` / `tag` | Agent image | `labs.etsi.org:5050/oop/code/ai2/ai_agent`, `2.0.0` |
| `aiAgent.waitForMcpImage` | Init container image that waits for the MCP server | `curlimages/curl:8.11.1` |
| `aiAgent.groqModelName` | Groq model | `qwen/qwen3.8-27b` |
| `aiAgent.memory.windowTurns` | Turns of a chat kept as context | `3` |
| `aiAgent.memory.ttlSeconds` | How long an idle chat is remembered | `3600` |
| `aiAgent.service.name` / `port` | Agent Service, the address the portal calls | `global.ai2.host` / `port` (`ai2-ai-agent`, `9013`) |
| `aiAgent.service.type` / `nodePort` | Agent Service type | `ClusterIP` |
| `secrets.groqApiKey` | Groq API key; required for the agent to answer | `""` |
| `secrets.existingSecret` | Existing Secret with the key under `GROQ_API_KEY`; `groqApiKey` is then ignored | `""` |
| `redis.enabled` | Deploy Redis; `false` turns session memory off | `true` |
| `redis.image` | Redis image | `redis:7.4-alpine` |

Only the ai-agent may connect to Redis (NetworkPolicy), since Redis has no password.

## Troubleshooting

```bash
kubectl get pods -n oop -l app.kubernetes.io/name=ai2
kubectl logs -f deployment/ai2-ai-agent -n oop
kubectl logs -f deployment/ai2-mcp -n oop
```

A 401 from the agent means the request carried no bearer token. A tool error naming OEG's
status means OEG refused the call, usually because the user lacks the API product's role.
+12 −7
Changes for deployment/helm/ai2/templates/_helpers.tpl: 12 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "ai2.secretName" -}}
{{- include "ai2.fullname" . }}-secrets
{{- .Values.secrets.existingSecret | default (printf "%s-secrets" (include "ai2.fullname" .)) -}}
{{- end }}

{{- define "ai2.mcpServiceName" -}}
@@ -46,13 +46,18 @@ app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "ai2.aiAgentServiceName" -}}
{{- include "ai2.fullname" . }}-ai-agent
{{- .Values.aiAgent.service.name | default (dig "ai2" "host" (printf "%s-ai-agent" (include "ai2.fullname" .)) (.Values.global | default dict)) -}}
{{- end }}

{{- define "ai2.aiAgentServicePort" -}}
{{- .Values.aiAgent.service.port | default (dig "ai2" "port" 9013 (.Values.global | default dict)) -}}
{{- end }}

{{- define "ai2.oegUrl" -}}
{{- $g := .Values.global | default dict -}}
{{- .Values.mcp.oegUrl | default (printf "http://%s:%v" (dig "oeg" "host" "oeg" $g) (dig "oeg" "port" 80 $g)) -}}
{{- end }}

{{- define "ai2.redisServiceName" -}}
{{- if .Values.redis.fullnameOverride -}}
{{- printf "%s-master" .Values.redis.fullnameOverride -}}
{{- else -}}
{{- printf "%s-redis-master" .Release.Name -}}
{{- end -}}
{{- include "ai2.fullname" . }}-redis
{{- end }}
Loading