diff --git a/ai_agent/README.md b/ai_agent/README.md index c1619597cdf4e9ef62b79419c617b6de3fea39d3..5bd76af63c27673ac6041a642d8614a751240c83 100644 --- a/ai_agent/README.md +++ b/ai_agent/README.md @@ -112,7 +112,6 @@ Run the container: The Dockerfile sets these defaults: ``` -ENV APP_HOST=0.0.0.0 ENV APP_PORT=9013 ENV MCP_SERVER_URL=http://127.0.0.1:8004/mcp ... diff --git a/deployment/docker/README.md b/deployment/docker/README.md index 8a4672cd8c258bed9b3cde02a122c79a9719a733..c40ef52b888dabeb75d381de2714f026564062e5 100644 --- a/deployment/docker/README.md +++ b/deployment/docker/README.md @@ -8,13 +8,13 @@ This deployment relies on a `.env` file that is loaded at runtime by Docker Compose. ``` -GROQ_API_KEY=your-secret-key -OpenOP_BACKEND_OAI=http://example-backend +GROQ_API_KEY=your-groq-api-key +OEG_SERVICE_URL=http://your-oeg-api-url/oeg/1.0.0 +GROQ_MODEL_NAME=qwen/qwen3-32b ``` ## Run Start services - ``` docker compose --env-file .env up -d ``` @@ -25,6 +25,11 @@ docker compose down ## Additional Info All service ports are explicitly forwarded to the host to simplify local development, testing and debugging. -This allows you to interact with APIs and MCP Servers individually, without attaching to containers or using internal Docker networking tools. +This allows you to interact with APIs and MCP servers individually, without attaching to containers or using internal Docker networking tools. + +| Service | Host Port | +|-----------|-----------| +| mcp | 8004 | +| ai-agent | 9013 | --- \ No newline at end of file diff --git a/deployment/docker/docker-compose.yaml b/deployment/docker/docker-compose.yaml index 53557d285e488d9b688da88d6f578d61fe4e5948..0abbb0e0bf7d9e1c09189e246b2bbde886efb468 100644 --- a/deployment/docker/docker-compose.yaml +++ b/deployment/docker/docker-compose.yaml @@ -1,40 +1,32 @@ version: "3.9" services: - api: - image: labs.etsi.org:5050/oop/code/ai2/dummy_backend - container_name: api - environment: - API_HOST: 0.0.0.0 - API_PORT: 8081 - ports: - - "8081:8081" - networks: - - ai_net - mcp: image: labs.etsi.org:5050/oop/code/ai2/mcp_module container_name: mcp environment: - MCP_HOST: 0.0.0.0 - MCP_PORT: 8004 - OpenOP_BACKEND_OAI: ${OpenOP_BACKEND_OAI} + OEG_SERVICE_URL: ${OEG_SERVICE_URL} ports: - "8004:8004" networks: - ai_net + healthcheck: + test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8004/health')"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s ai-agent: image: labs.etsi.org:5050/oop/code/ai2/ai_agent container_name: ai-agent depends_on: - - mcp + mcp: + condition: service_healthy environment: - APP_HOST: 0.0.0.0 - APP_PORT: 9013 MCP_SERVER_URL: http://mcp:8004/mcp GROQ_API_KEY: ${GROQ_API_KEY} - GROQ_MODEL_NAME: openai/gpt-oss-20b + GROQ_MODEL_NAME: ${GROQ_MODEL_NAME:-qwen/qwen3-32b} ports: - "9013:9013" networks: diff --git a/deployment/helm/README.md b/deployment/helm/README.md new file mode 100644 index 0000000000000000000000000000000000000000..9d1d2f490d3df4cb4cd3765bc18a1e4c32a36f22 --- /dev/null +++ b/deployment/helm/README.md @@ -0,0 +1,94 @@ +# AI2 Helm Chart + +Deploys the AI2 stack — MCP server and AI Agent — into a Kubernetes cluster. + +## 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). + +## Prerequisites + +- Kubernetes cluster +- Helm 3+ +- Groq API key +- OEG service reachable from within the cluster + +## Install + +```bash +helm install ai2 ./helm/ai2 \ + --namespace oop \ + --create-namespace \ + --set secrets.groqApiKey= \ + --set config.mcp.oegServiceUrl=http:///oeg/1.0.0 +``` + +## Upgrade + +```bash +helm upgrade ai2 ./helm/ai2 \ + --namespace oop \ + --set secrets.groqApiKey= \ + --set config.mcp.oegServiceUrl=http:///oeg/1.0.0 +``` + +## Uninstall + +```bash +helm uninstall ai2 --namespace oop +``` + +## Key Values + +| Value | Description | Default | +|---------------------------------|----------------------------------|--------------------------| +| `secrets.groqApiKey` | Groq API key (required) | `""` | +| `config.mcp.oegServiceUrl` | Base URL of the OEG service | `http://oeg/oeg/1.0.0` | +| `config.aiAgent.groqModelName` | Groq model identifier | `qwen/qwen3-32b` | +| `service.mcp.type` | Kubernetes service type for MCP | `NodePort` | +| `service.mcp.nodePort` | NodePort for MCP | `32004` | +| `service.aiAgent.nodePort` | NodePort for ai-agent | `32013` | +| `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 :latest ../../mcp_module +docker build -t :latest ../../ai_agent + +kind load docker-image :latest --name +kind load docker-image :latest --name +``` + +Then install with `pullPolicy: IfNotPresent` to use the local images: + +```bash +helm install ai2 ./helm/ai2 \ + --namespace oop \ + --create-namespace \ + --set secrets.groqApiKey= \ + --set config.mcp.oegServiceUrl=http:///oeg/1.0.0 \ + --set images.mcp.pullPolicy=IfNotPresent \ + --set images.aiAgent.pullPolicy=IfNotPresent +``` + +## Test the Deployment + +```bash +# Health checks +curl http://:32004/health # MCP +curl http://:32013/health # AI Agent + +# Send a natural language prompt +curl -X POST http://:32013/groq-mcp \ + -H "Content-Type: application/json" \ + -d '{"query": "Create a QoD session for device with public IP 12.1.0.20, application server IP 198.51.100.10, QoS profile qos-e, duration 3600 seconds"}' +``` diff --git a/deployment/helm/ai2/Chart.yaml b/deployment/helm/ai2/Chart.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ec7b05ce32a0889f3a522a1025e30263a32c12c --- /dev/null +++ b/deployment/helm/ai2/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v2 +name: ai2 +type: application +version: 1.0.0 +appVersion: "1.0.0" +keywords: + - ai2 + - oop + - etsi + - mcp + - ai-agent +home: https://labs.etsi.org/rep/oop/code/ai2 +maintainers: + - name: OOP Team diff --git a/deployment/helm/ai2/templates/_helpers.tpl b/deployment/helm/ai2/templates/_helpers.tpl new file mode 100644 index 0000000000000000000000000000000000000000..964474e5afa2489f33a71c58fe3fec0534e7aa4e --- /dev/null +++ b/deployment/helm/ai2/templates/_helpers.tpl @@ -0,0 +1,50 @@ +{{- define "ai2.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "ai2.fullname" -}} +{{- if .Values.fullnameOverride }} + {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} + {{- $name := default .Chart.Name .Values.nameOverride }} + {{- if contains $name .Release.Name }} + {{- .Release.Name | trunc 63 | trimSuffix "-" }} + {{- else }} + {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} + {{- end }} +{{- end }} +{{- end }} + +{{- define "ai2.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "ai2.labels" -}} +helm.sh/chart: {{ include "ai2.chart" . }} +{{ include "ai2.selectorLabels" . }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +{{- end }} + +{{- define "ai2.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ai2.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{- define "ai2.configMapName" -}} +{{- include "ai2.fullname" . }}-config +{{- end }} + +{{- define "ai2.secretName" -}} +{{- include "ai2.fullname" . }}-secrets +{{- end }} + +{{- define "ai2.mcpServiceName" -}} +{{- include "ai2.fullname" . }}-mcp +{{- end }} + +{{- define "ai2.aiAgentServiceName" -}} +{{- include "ai2.fullname" . }}-ai-agent +{{- end }} diff --git a/deployment/helm/ai2/templates/ai-agent-deployment.yaml b/deployment/helm/ai2/templates/ai-agent-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e49338983b8d09fb1b325b7739b0c025ca12eb64 --- /dev/null +++ b/deployment/helm/ai2/templates/ai-agent-deployment.yaml @@ -0,0 +1,61 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ai2.fullname" . }}-ai-agent + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} + app.kubernetes.io/component: ai-agent +spec: + replicas: {{ .Values.replicaCount.aiAgent }} + selector: + matchLabels: + {{- include "ai2.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: ai-agent + template: + metadata: + labels: + {{- include "ai2.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: ai-agent + spec: + initContainers: + - name: wait-for-mcp + image: curlimages/curl:latest + command: ['sh', '-c'] + args: + - | + until curl -sf http://{{ include "ai2.mcpServiceName" . }}:{{ .Values.config.mcp.port }}/health; do + echo "Waiting for MCP to be ready..." + sleep 3 + done + echo "" + echo "MCP is ready." + containers: + - name: ai-agent + image: "{{ .Values.images.aiAgent.repository }}:{{ .Values.images.aiAgent.tag }}" + imagePullPolicy: {{ .Values.images.aiAgent.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.config.aiAgent.port | int }} + protocol: TCP + env: + - name: APP_PORT + valueFrom: + configMapKeyRef: + name: {{ include "ai2.configMapName" . }} + key: APP_PORT + - name: MCP_SERVER_URL + valueFrom: + configMapKeyRef: + name: {{ include "ai2.configMapName" . }} + key: MCP_SERVER_URL + - name: GROQ_MODEL_NAME + valueFrom: + configMapKeyRef: + name: {{ include "ai2.configMapName" . }} + key: GROQ_MODEL_NAME + - name: GROQ_API_KEY + valueFrom: + secretKeyRef: + name: {{ include "ai2.secretName" . }} + key: GROQ_API_KEY diff --git a/deployment/helm/ai2/templates/ai-agent-service.yaml b/deployment/helm/ai2/templates/ai-agent-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..045d7e9bb3d6556a7d0b935fceb5e15bdcd64b70 --- /dev/null +++ b/deployment/helm/ai2/templates/ai-agent-service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ai2.aiAgentServiceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} + app.kubernetes.io/component: ai-agent +spec: + type: {{ .Values.service.aiAgent.type }} + ports: + - name: http + port: {{ .Values.service.aiAgent.port }} + targetPort: http + {{- if eq .Values.service.aiAgent.type "NodePort" }} + nodePort: {{ .Values.service.aiAgent.nodePort }} + {{- end }} + protocol: TCP + selector: + {{- include "ai2.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: ai-agent diff --git a/deployment/helm/ai2/templates/configmap.yaml b/deployment/helm/ai2/templates/configmap.yaml new file mode 100644 index 0000000000000000000000000000000000000000..25e4983c7ec31814e18532030cb4930ad8200c52 --- /dev/null +++ b/deployment/helm/ai2/templates/configmap.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ai2.configMapName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} +data: + MCP_PORT: {{ .Values.config.mcp.port | quote }} + OEG_SERVICE_URL: {{ .Values.config.mcp.oegServiceUrl | quote }} + APP_PORT: {{ .Values.config.aiAgent.port | quote }} + GROQ_MODEL_NAME: {{ .Values.config.aiAgent.groqModelName | quote }} + MCP_SERVER_URL: {{ printf "http://%s:%s/mcp" (include "ai2.mcpServiceName" .) .Values.config.mcp.port | quote }} diff --git a/deployment/helm/ai2/templates/mcp-deployment.yaml b/deployment/helm/ai2/templates/mcp-deployment.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bb84b034099d0fad94aaba20e41f7e6623b5b496 --- /dev/null +++ b/deployment/helm/ai2/templates/mcp-deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ai2.fullname" . }}-mcp + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} + app.kubernetes.io/component: mcp +spec: + replicas: {{ .Values.replicaCount.mcp }} + selector: + matchLabels: + {{- include "ai2.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: mcp + template: + metadata: + labels: + {{- include "ai2.selectorLabels" . | nindent 8 }} + app.kubernetes.io/component: mcp + spec: + containers: + - name: mcp + image: "{{ .Values.images.mcp.repository }}:{{ .Values.images.mcp.tag }}" + imagePullPolicy: {{ .Values.images.mcp.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.config.mcp.port | int }} + protocol: TCP + env: + - name: MCP_PORT + valueFrom: + configMapKeyRef: + name: {{ include "ai2.configMapName" . }} + key: MCP_PORT + - name: OEG_SERVICE_URL + valueFrom: + configMapKeyRef: + name: {{ include "ai2.configMapName" . }} + key: OEG_SERVICE_URL + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 diff --git a/deployment/helm/ai2/templates/mcp-service.yaml b/deployment/helm/ai2/templates/mcp-service.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4e4414fcb3b8f21abc6d2408a6f0956e16b5e656 --- /dev/null +++ b/deployment/helm/ai2/templates/mcp-service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ai2.mcpServiceName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} + app.kubernetes.io/component: mcp +spec: + type: {{ .Values.service.mcp.type }} + ports: + - name: http + port: {{ .Values.service.mcp.port }} + targetPort: http + {{- if eq .Values.service.mcp.type "NodePort" }} + nodePort: {{ .Values.service.mcp.nodePort }} + {{- end }} + protocol: TCP + selector: + {{- include "ai2.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: mcp diff --git a/deployment/helm/ai2/templates/secret.yaml b/deployment/helm/ai2/templates/secret.yaml new file mode 100644 index 0000000000000000000000000000000000000000..494ba6dc2940ef31c14147ba5f76c191448417f0 --- /dev/null +++ b/deployment/helm/ai2/templates/secret.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ai2.secretName" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "ai2.labels" . | nindent 4 }} +type: Opaque +data: + GROQ_API_KEY: {{ .Values.secrets.groqApiKey | b64enc | quote }} diff --git a/deployment/helm/ai2/values.yaml b/deployment/helm/ai2/values.yaml new file mode 100644 index 0000000000000000000000000000000000000000..fec8602abfff7c36cc80fdf536e25ff635d18af5 --- /dev/null +++ b/deployment/helm/ai2/values.yaml @@ -0,0 +1,34 @@ +replicaCount: + mcp: 1 + aiAgent: 1 + +images: + mcp: + repository: labs.etsi.org:5050/oop/code/ai2/mcp_module + tag: latest + pullPolicy: IfNotPresent + aiAgent: + repository: labs.etsi.org:5050/oop/code/ai2/ai_agent + tag: latest + pullPolicy: IfNotPresent + +config: + mcp: + port: "8004" + oegServiceUrl: "http://oeg/oeg/1.0.0" + aiAgent: + port: "9013" + groqModelName: "qwen/qwen3-32b" + +secrets: + groqApiKey: "" + +service: + mcp: + type: NodePort + port: 8004 + nodePort: 32004 + aiAgent: + type: NodePort + port: 9013 + nodePort: 32013 \ No newline at end of file diff --git a/deployment/k8s/README.md b/deployment/k8s/README.md deleted file mode 100644 index 52cac77eb1fd89acef9d112d9566b99ebc2f1d44..0000000000000000000000000000000000000000 --- a/deployment/k8s/README.md +++ /dev/null @@ -1,129 +0,0 @@ -# Kubernetes Deployment (ai2) - -This directory contains Kubernetes manifests and a deployment script for running: -- `mcp_module` (service: `mcp-service`, port `8004`) -- `ai_agent` (service: `ai-agent-service`, port `9013`) - -The default namespace is `ai2`. - -## Files - -- `namespace.yaml`: Creates namespace `ai2`. -- `configmap.yaml`: Non-sensitive runtime configuration. -- `secret.yaml`: Sensitive runtime configuration (`GROQ_API_KEY`). -- `mcp-deployment.yaml` / `mcp-service.yaml`: MCP Deployment and NodePort Service (`32004`). -- `ai-agent-deployment.yaml` / `ai-agent-service.yaml`: AI Agent Deployment and NodePort Service (`32013`). -- `deploy.sh`: Applies resources in order and waits for deployments to become available. - -## Prerequisites - -- Kubernetes cluster with `kubectl` configured. -- Access to pull container images from: - - `labs.etsi.org:5050/oop/code/ai2/mcp_module:latest` - - `labs.etsi.org:5050/oop/code/ai2/ai_agent:latest` -- A valid `GROQ_API_KEY`. - -## Configure Secrets - -Set the API key in `secret.yaml`: - -```yaml -stringData: - GROQ_API_KEY: "" -``` - -Alternative (CLI): - -```bash -kubectl create secret generic ai2-secrets \ - --from-literal=GROQ_API_KEY= \ - -n ai2 \ - --dry-run=client -o yaml | kubectl apply -f - -``` - -## Deploy - -From this directory: - -```bash -chmod +x deploy.sh -./deploy.sh -``` - -Optional namespace override: - -```bash -NAMESPACE=ai2 ./deploy.sh -``` - -## Verify - -```bash -kubectl get ns ai2 -kubectl get all -n ai2 -kubectl get configmap ai2-config -n ai2 -kubectl get secret ai2-secrets -n ai2 -``` - -Check readiness and logs: - -```bash -kubectl rollout status deployment/mcp-deployment -n ai2 -kubectl rollout status deployment/ai-agent-deployment -n ai2 -kubectl logs deployment/mcp-deployment -n ai2 -kubectl logs deployment/ai-agent-deployment -n ai2 -``` - -Health endpoints: - -- MCP: `http://:32004/health` - -## Test API Locally - -Forward the AI Agent service to localhost: - -```bash -kubectl port-forward svc/ai-agent-service 9013:9013 -n ai2 -``` - -In a separate terminal, test endpoint `http://127.0.0.1:9013/groq-mcp` with `Content-Type: application/json`. - -Create QoD session: - -```bash -curl -X POST "http://127.0.0.1:9013/groq-mcp" \ - -H "Content-Type: application/json" \ - -d '{"query":"Create QoD session for device IP 12.1.0.20 application server IP 198.51.100.10 with QoS qos-e and duration 60 seconds"}' -``` - -Get QoD session (copy the previous session id) : - -```bash -curl -X POST "http://127.0.0.1:9013/groq-mcp" \ - -H "Content-Type: application/json" \ - -d '{"query":"Get QoD session information for 33f6392b-57d0-42a3-a9fd-b4dcce617fce"}' -``` - -Delete QoD session (copy the previous session id) : - -```bash -curl -X POST "http://127.0.0.1:9013/groq-mcp" \ - -H "Content-Type: application/json" \ - -d '{"query":"Delete QoD session 33f6392b-57d0-42a3-a9fd-b4dcce617fce"}' -``` - -## Deployment Order - -`deploy.sh` enforces this sequence: -1. Namespace -2. ConfigMap + Secret -3. MCP Deployment + Service -4. AI Agent Deployment + Service - -The AI Agent Pod also includes an init container that waits for `http://mcp-service:8004/health` before starting. - -## Cleanup - -```bash -kubectl delete namespace ai2 -``` diff --git a/deployment/k8s/ai-agent-deployment.yaml b/deployment/k8s/ai-agent-deployment.yaml deleted file mode 100644 index 5add661ee70e889e0a0f402bfdcae3c45c83c64f..0000000000000000000000000000000000000000 --- a/deployment/k8s/ai-agent-deployment.yaml +++ /dev/null @@ -1,84 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: ai-agent-deployment - namespace: ai2 - labels: - app: ai-agent - component: ai-agent -spec: - replicas: 1 - selector: - matchLabels: - app: ai-agent - template: - metadata: - labels: - app: ai-agent - component: ai-agent - spec: - initContainers: - - name: wait-for-mcp - image: curlimages/curl:latest - command: ["sh", "-c"] - args: - - | - until curl -f http://mcp-service:8004/health; do - echo "Waiting for MCP service to be ready..." - sleep 2 - done - echo "MCP service is ready!" - - containers: - - name: ai-agent - image: labs.etsi.org:5050/oop/code/ai2/ai_agent:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 9013 - name: http - protocol: TCP - - env: - - name: APP_HOST - valueFrom: - configMapKeyRef: - name: ai2-config - key: APP_HOST - - name: APP_PORT - valueFrom: - configMapKeyRef: - name: ai2-config - key: APP_PORT - - name: MCP_SERVER_URL - valueFrom: - configMapKeyRef: - name: ai2-config - key: MCP_SERVER_URL - - name: GROQ_API_KEY - valueFrom: - secretKeyRef: - name: ai2-secrets - key: GROQ_API_KEY - - name: GROQ_MODEL_NAME - valueFrom: - configMapKeyRef: - name: ai2-config - key: GROQ_MODEL_NAME - - livenessProbe: - httpGet: - path: /health - port: 9013 - initialDelaySeconds: 10 - periodSeconds: 10 - timeoutSeconds: 5 - failureThreshold: 3 - - readinessProbe: - httpGet: - path: /health - port: 9013 - initialDelaySeconds: 5 - periodSeconds: 5 - timeoutSeconds: 3 - failureThreshold: 3 \ No newline at end of file diff --git a/deployment/k8s/ai-agent-service.yaml b/deployment/k8s/ai-agent-service.yaml deleted file mode 100644 index bc5343ae533c9abc2dd8d79404b57cf096ea364a..0000000000000000000000000000000000000000 --- a/deployment/k8s/ai-agent-service.yaml +++ /dev/null @@ -1,19 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: ai-agent-service - namespace: ai2 - labels: - app: ai-agent - component: ai-agent -spec: - type: NodePort - ports: - - port: 9013 - targetPort: 9013 - nodePort: 32013 - protocol: TCP - name: http - selector: - app: ai-agent - diff --git a/deployment/k8s/configmap.yaml b/deployment/k8s/configmap.yaml deleted file mode 100644 index b72587c7cc35aed1d8c0a0dab91579b2816321fc..0000000000000000000000000000000000000000 --- a/deployment/k8s/configmap.yaml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: ai2-config - namespace: ai2 -data: - - # MCP Configuration - MCP_HOST: "0.0.0.0" - MCP_PORT: "8004" - - # AI Agent Configuration - APP_HOST: "0.0.0.0" - APP_PORT: "9013" - GROQ_MODEL_NAME: "qwen/qwen3-32b" - - # Backend URLs (will be set via service names) - - MCP_SERVER_URL: "http://mcp-service:8004/mcp" - OpenOP_BACKEND_OAI: "http://oeg.oop.svc.cluster.local/oeg/1.0.0" - diff --git a/deployment/k8s/deploy.sh b/deployment/k8s/deploy.sh deleted file mode 100755 index dceed546d55c1e8f775d0152bf8bfe26bc387164..0000000000000000000000000000000000000000 --- a/deployment/k8s/deploy.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Bash deployment script with strict ordering: MCP -> AI Agent - -set -euo pipefail - -NAMESPACE="${NAMESPACE:-ai2}" -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - - -# Function to apply deployment and wait for it to be available -apply_deployment() { - local deployment_file=$1 - local service_file=$2 - local deployment_name=$3 - - kubectl apply -f "${SCRIPT_DIR}/${deployment_file}" -n "$NAMESPACE" - kubectl apply -f "${SCRIPT_DIR}/${service_file}" -n "$NAMESPACE" - - echo "Waiting for deployment $deployment_name to be available..." - if ! kubectl wait --for=condition=available --timeout=300s "deployment/$deployment_name" -n "$NAMESPACE"; then - echo "Error: Deployment $deployment_name failed to become available" - exit 1 - fi - echo "Deployment $deployment_name is available" -} - -# Step 1: Create namespace -echo "Step 1: Creating namespace..." -kubectl apply -f "${SCRIPT_DIR}/namespace.yaml" - -# Step 2: Create ConfigMap and Secret -echo "Step 2: Creating ConfigMap and Secret..." -kubectl apply -f "${SCRIPT_DIR}/configmap.yaml" -n "$NAMESPACE" -kubectl apply -f "${SCRIPT_DIR}/secret.yaml" -n "$NAMESPACE" - -# Step 3: Deploy MCP -echo "Step 3: Deploying MCP Module..." -apply_deployment "mcp-deployment.yaml" "mcp-service.yaml" "mcp-deployment" - -# Step 4: Deploy AI Agent -echo "Step 4: Deploying AI Agent..." -apply_deployment "ai-agent-deployment.yaml" "ai-agent-service.yaml" "ai-agent-deployment" - - -echo "Deployment completed successfully!" diff --git a/deployment/k8s/mcp-deployment.yaml b/deployment/k8s/mcp-deployment.yaml deleted file mode 100644 index 9aaeecc5ba1269a6c5437debf2960117549c1794..0000000000000000000000000000000000000000 --- a/deployment/k8s/mcp-deployment.yaml +++ /dev/null @@ -1,43 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: mcp-deployment - namespace: ai2 - labels: - app: mcp - component: mcp-module -spec: - replicas: 1 - selector: - matchLabels: - app: mcp - template: - metadata: - labels: - app: mcp - component: mcp-module - spec: - containers: - - name: mcp - image: labs.etsi.org:5050/oop/code/ai2/mcp_module:latest - imagePullPolicy: IfNotPresent - ports: - - containerPort: 8004 - name: http - protocol: TCP - env: - - name: MCP_HOST - valueFrom: - configMapKeyRef: - name: ai2-config - key: MCP_HOST - - name: MCP_PORT - valueFrom: - configMapKeyRef: - name: ai2-config - key: MCP_PORT - - name: OpenOP_BACKEND_OAI - valueFrom: - configMapKeyRef: - name: ai2-config - key: OpenOP_BACKEND_OAI diff --git a/deployment/k8s/mcp-service.yaml b/deployment/k8s/mcp-service.yaml deleted file mode 100644 index 76773eca6a373be3c141790e1bc9b87ed71c5386..0000000000000000000000000000000000000000 --- a/deployment/k8s/mcp-service.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: mcp-service - namespace: ai2 - labels: - app: mcp - component: mcp-module -spec: - type: NodePort - ports: - - port: 8004 - targetPort: 8004 - nodePort: 32004 # external port on node - protocol: TCP - name: http - selector: - app: mcp diff --git a/deployment/k8s/namespace.yaml b/deployment/k8s/namespace.yaml deleted file mode 100644 index 2c297d30cf9d2b79362c3eabd0ac83f34291b8d9..0000000000000000000000000000000000000000 --- a/deployment/k8s/namespace.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: ai2 - labels: - name: ai2 - app: ai2 diff --git a/deployment/k8s/secret.yaml b/deployment/k8s/secret.yaml deleted file mode 100644 index 717092373458e790fa3abbef31e4a8370afa5289..0000000000000000000000000000000000000000 --- a/deployment/k8s/secret.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: ai2-secrets - namespace: ai2 -type: Opaque -stringData: - # Add your secrets here. These are base64 encoded in Kubernetes. - # To create: kubectl create secret generic ai2-secrets --from-literal=GROQ_API_KEY=your-key -n ai2 - # Or update this file with base64 encoded values: - # GROQ_API_KEY: - GROQ_API_KEY: "" diff --git a/mcp_module/Dockerfile b/mcp_module/Dockerfile index b03963cc951c44641f1070c5f7af3f95f7da8024..ae56bec21c20ebf5fea938e38ced241b98e90f7a 100644 --- a/mcp_module/Dockerfile +++ b/mcp_module/Dockerfile @@ -13,8 +13,7 @@ RUN pip install --trusted-host pypi.org --trusted-host pypi.python.org --trusted COPY . /app/mcp_module # Load environment variables (optional, can also use .env file) -ENV OpenOP_BACKEND_OAI=http://127.0.0.1:32263/oeg/1.0.0 - +ENV OEG_SERVICE_URL=http://127.0.0.1:32263 ENV MCP_HOST=0.0.0.0 ENV MCP_PORT=8004 diff --git a/mcp_module/README.md b/mcp_module/README.md index 70fd47bc23448888cd50d144853b74eefbdd8b05..413aa2699a88c3557832c1912a91b19241d39cd9 100644 --- a/mcp_module/README.md +++ b/mcp_module/README.md @@ -22,7 +22,6 @@ mcp_module/ │ ├── qod_legacy.py # Quality of Demand (QoD) related tools leveraging dummy backend │ └── edge_application.py # Discovery of edge applications ├── app.py -├── .env ├── requirements.txt └── Dockerfile ``` @@ -38,25 +37,12 @@ mcp_module/ * **`tools/`** Implementation of MCP tools — each file should correspond to the equivalent CRUD operations for the relevant REST API. * All MCP tools are explicitly registered in `app.py` for safety and traceability: -Using dummy backend: - -| Tool | File | Description | -| --------------------- | --------------------------- |------------------------------------------| -| `create_qod_session` | `tools/qod.py` | Creates a CAMARA QoD session | -| `get_qod_session` | `tools/qod.py` | Retrieves QoD session details | -| `delete_qod_session` | `tools/qod.py` | Deletes an existing QoD session -| `list_qod_sessions` | `tools/qod.py` | Get QoS session information for a device -| `get_app_definitions` | `tools/edge_application.py` | Retrieves available edge app information | - - -Using OOP OEG: - -| Tool | File | Description | -| --------------------- | --------------------------- |------------------------------------------| -| `create_qod_session` | `tools/qod.py` | Creates a CAMARA QoD session | -| `get_qod_session` | `tools/qod.py` | Retrieves QoD session details | -| `delete_qod_session` | `tools/qod.py` | Deletes an existing QoD session -| `get_app_definitions` | `tools/edge_application.py` | Retrieves available edge app information | +| Tool | File | Description | +| -------------------------- | --------------------------- |------------------------------------------| +| `create_qod_session_oai` | `tools/qod.py` | Creates a CAMARA QoD session | +| `get_qod_session_oai` | `tools/qod.py` | Retrieves QoD session details | +| `delete_qod_session_oai` | `tools/qod.py` | Deletes an existing QoD session | +| `get_app_definitions` | `tools/edge_application.py` | Retrieves available edge app definitions | --- @@ -72,9 +58,9 @@ source venv/bin/activate # (on Linux/macOS) venv\Scripts\activate # (on Win pip install -r requirements.txt Create a `.env` file in the project root (if not already present) and set your API configuration: -MCP_HOST= -MCP_PORT= -ENV OpenOP_BACKEND_OAI=http://127.0.0.1:32263/oeg/1.0.0 +``` +OEG_SERVICE_URL=http://your-oeg-host/oeg/1.0.0 +``` ``` @@ -100,26 +86,14 @@ Run the application with: python app.py ``` -By default, it on `127.0.0.1:8000`. +By default, it runs on `0.0.0.0:8004`. ### Option 2 - Containerized Run the container: -The Dockerfile sets these defaults: -``` - -MCP_HOST= -MCP_PORT= -OpenOP_BACKEND_OAI= -``` -To start the backend with these defaults: -``` -docker run -p 8000:8000 mcp_module -``` -You can override the defaults at runtime. -Example change port: +To start the container: ``` -docker run -p 5001:5001 -e MCP_PORT=5001 -e OpenOP_Backend=x mcp_module +docker run -p 8004:8004 -e OEG_SERVICE_URL=http://your-oeg-host/oeg/1.0.0 mcp_module ``` --- ## 🧩 Integration with 3rd party apps diff --git a/mcp_module/app.py b/mcp_module/app.py index c75593d0ab227915a6f7a4b0d3ee39650e80d413..066c1cd712bc62a61b5343b8d0e9611b39a84e07 100644 --- a/mcp_module/app.py +++ b/mcp_module/app.py @@ -50,7 +50,7 @@ if __name__ == "__main__": logger.info("Starting MCP Server with FastAPI...") host = os.getenv("MCP_HOST", "127.0.0.1") - port = int(os.getenv("MCP_PORT", "8000")) + port = int(os.getenv("MCP_PORT", "8004")) try: uvicorn.run(app, host=host, port=port) diff --git a/mcp_module/tools/edge_application.py b/mcp_module/tools/edge_application.py index c5f107d77f5dc1240a478ba529c81c9f2d3687b2..8020ec8663af263a0aa1072d993c2394c3a92272 100644 --- a/mcp_module/tools/edge_application.py +++ b/mcp_module/tools/edge_application.py @@ -13,9 +13,9 @@ env_path = root_dir / ".env" # Load the .env file load_dotenv(dotenv_path=env_path) -SESSION_SERVICE_URL = os.getenv("OpenOP_BACKEND_OAI") +SESSION_SERVICE_URL = os.getenv("OEG_SERVICE_URL") if not SESSION_SERVICE_URL: - raise EnvironmentError("OpenOP environment variable is not set") + raise EnvironmentError("OEG_SERVICE_URL environment variable is not set") async def get_app_definitions() -> dict: """ diff --git a/mcp_module/tools/qod.py b/mcp_module/tools/qod.py index c951398c2e2687780a70776003c65f639ecb813c..b1c754d0fbd2538fcb63bd8d7de859d39f2efbaa 100644 --- a/mcp_module/tools/qod.py +++ b/mcp_module/tools/qod.py @@ -15,10 +15,9 @@ root_dir = Path(__file__).resolve().parent.parent env_path = root_dir / ".env" load_dotenv(dotenv_path=env_path) -SESSION_SERVICE_URL = os.getenv( - "OpenOP_BACKEND_OAI") +SESSION_SERVICE_URL = os.getenv("OEG_SERVICE_URL") if not SESSION_SERVICE_URL: - raise EnvironmentError("OpenOP_BACKEND_OAI environment variable is not set") + raise EnvironmentError("OEG_SERVICE_URL environment variable is not set") async def create_qod_session_oai(inp: CreateQoDSessionOAIInput) -> dict: diff --git a/mcp_module/tools/qod_legacy.py b/mcp_module/tools/qod_legacy.py index b6d0ba995a111872accda3c2714e673d617be775..d5c031d43bd0e7fb5fc721674ee202ef6af55f64 100644 --- a/mcp_module/tools/qod_legacy.py +++ b/mcp_module/tools/qod_legacy.py @@ -18,7 +18,7 @@ env_path = root_dir / ".env" # Load the .env file load_dotenv(dotenv_path=env_path) # -# SESSION_SERVICE_URL = os.getenv("OpenOP_Backend") +# SESSION_SERVICE_URL = os.getenv("OEG_SERVICE_URL") # if not SESSION_SERVICE_URL: # raise EnvironmentError("SESSION_SERVICE_URL environment variable is not set") #