Commit 60fc145c authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

feat: add overview and architecture documentation for AI² platform

parent 657462c2
Loading
Loading
Loading
Loading
+2 −1
Changes for README.md: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -21,7 +21,7 @@ This repository is an [Open Knowledge Format (OKF) v0.1](https://github.com/Goog
**Implementing a component**

1. Read [platform/architecture.md](platform/architecture.md) for the end-to-end mental model.
2. Open the component folder ([oeg/](oeg/), [srm/](srm/), [fm/](fm/), [tf-sdk/](tf-sdk/), [data-lab/](data-lab/), [portal/](portal/)) via its `index.md`.
2. Open the component folder ([oeg/](oeg/), [srm/](srm/), [fm/](fm/), [tf-sdk/](tf-sdk/), [data-lab/](data-lab/), [portal/](portal/), [ai2/](ai2/)) via its `index.md`.
3. Follow architecture → interface contract → persistence model → flow walk-throughs in that order.
4. When a spec cites an ADR, read the decision record for the rationale behind the rule.

@@ -47,6 +47,7 @@ This repository is an [Open Knowledge Format (OKF) v0.1](https://github.com/Goog
| [tf-sdk/](tf-sdk/) | Transformation Function SDK — vendor adapters inside SRM |
| [portal/](portal/) | Portal — developer portal and admin console |
| [data-lab/](data-lab/) | Data Lab — dataspace metadata catalogue |
| [ai2/](ai2/) | AI² — agent and MCP server over OEG's APIs |
| [decisions/](decisions/) | Architecture Decision Records |
| [external-specs/](external-specs/) | Vendored third-party API definitions |
| [archive/](archive/) | Superseded working documents |
+367 −0
Changes for ai2/implementation-and-delivery.md: 367 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Implementation & Delivery
title: "AI² — Implementation & Delivery"
description: "How the AI² code is laid out, its settings and their defaults in each deployment, how it is built and deployed, what it logs, how it is tested, its known gaps, what is not yet scheduled, and the changes developers make most often."
tags: [ai2, implementation, delivery]
timestamp: 2026-10-21T00:00:00Z
component: AI²
version: "2.0"
status: "ETSI SDG OOP"
---

# AI² — Implementation & Delivery

**Companion documents:**
[ai2/overview-and-architecture.md](/ai2/overview-and-architecture.md),
[ai2/requirements-and-design.md](/ai2/requirements-and-design.md),
[ai2/interface-contract.md](/ai2/interface-contract.md)

Code: the `ai2` repository. The agent is in `ai_agent/`, the MCP server in `mcp_module/`. The
platform deploys AI² with the chart in the helm repository (`oop-platform-chart/charts/ai2`).

---

## O. Code Layout

### O.1 Agent (`ai_agent/`)

| Folder | Holds |
|---|---|
| `app.py` | The FastAPI app, cross-origin settings, the routers, and the local `uvicorn` entry point |
| `routes/` | `groq.py`: the chat routes. `debug.py`: `/health` and `/debug/objgraph` |
| `sub_agents/` | One file per LLM provider. `groq_agent.py` builds the MCP client, the Groq model and the agent for one request |
| `prompts/` | System prompts |
| `memory.py` | Session memory in Redis |
| `security.py` | The bearer-token check and `AUTH_ENABLED` |
| `utils.py` | Server-sent event helpers |

### O.2 MCP Server (`mcp_module/`)

| Folder | Holds |
|---|---|
| `app.py` | The FastMCP server, the `SAFE_TOOLS` list, `/health`, and the FastAPI app that mounts MCP |
| `tools/` | One file per CAMARA API: `edge_application.py`, `qod.py`, `traffic_influence.py`, `location.py`. `oeg_request.py` builds OEG addresses and headers; `oeg_errors.py` turns OEG errors into tool errors |
| `models/` | Pydantic input models, one file per API. They follow the CAMARA API-to-MCP tool mapping, or define custom models where none exists |
| `security/` | `authorization.py`: the token passed on to OEG |

### O.3 Elsewhere

| Folder | Holds |
|---|---|
| `tests/ai_agent/`, `tests/mcp_module/` | pytest tests (§S) |
| `deployment/docker/` | Docker Compose file for local runs |
| `deployment/helm/ai2/` | The AI2 repository's own chart (§Q) |
| `.gitlab-ci.yml` | The pipeline (§Q) |

Each service has its own `Dockerfile` and `requirements.txt`. The agent uses `langchain_groq` for
the model and `mcp_use` as MCP client and agent loop; the MCP server uses `fastmcp` and `httpx`.
Versions are pinned in each `requirements.txt`. There is no import-linter configuration, so the
pipeline's architecture stage is skipped.

---

## P. Configuration

Environment variables. Locally, each service reads a `.env` file in its folder.

### P.1 Agent

| Group | Settings | Notes |
|---|---|---|
| Server | `APP_HOST`, `APP_PORT` | `127.0.0.1` and `9013` for a local run; the image binds `0.0.0.0:9013` |
| MCP | `MCP_SERVER_URL` | Required. The chart sets `http://<mcp service>:<port>/mcp`. The image default `http://127.0.0.1:8004/mcp` only works with both on one host |
| Groq | `GROQ_API_KEY`, `GROQ_MODEL_NAME` | The key is required; the chart reads it from `secrets.groqApiKey`. Model default in code: `openai/gpt-oss-20b`. The model must support tool calling (overview E5) |
| Memory | `REDIS_URL`, `MEMORY_WINDOW_TURNS`, `MEMORY_TTL_SECONDS`, `MEMORY_KEY_PREFIX` | `REDIS_URL` is a Redis address such as `redis://host:6379/0`; empty turns memory off. Defaults 6 turns, 3600 seconds, `ai_agent:session`. Whole numbers of at least 1 (§L.2 in the design) |
| Login | `AUTH_ENABLED` | `true` by default; only the value `false` (any case) turns it off |
| Telemetry | `MCP_USE_ANONYMIZED_TELEMETRY` | The library sends usage data unless this is `false` |

`MCP_SERVER_URL` and `GROQ_API_KEY` are read on each chat request, not at start. A missing value
shows as a `500` on the first chat, not as a failed start.

### P.2 MCP Server

| Group | Settings | Notes |
|---|---|---|
| Server | `MCP_HOST`, `MCP_PORT` | `127.0.0.1` and `8004` for a local run; the image binds `0.0.0.0:8004` |
| OEG | `OEG_SERVICE_URL` | Required. OEG's base address, without a path; the chart default is `http://oeg` (OEG's Service listens on port 80). Read on each tool call. The image default `http://127.0.0.1:32263` is a development leftover |
| Login | `AUTH_ENABLED` | As for the agent |

### P.3 Defaults by Deployment

| Setting | Code | Platform chart | AI2 repository chart | Docker Compose |
|---|---|---|---|---|
| `GROQ_MODEL_NAME` | `openai/gpt-oss-20b` | `qwen/qwen3-32b` | `qwen/qwen3.8-27b` | `qwen/qwen3.8-27b` |
| `MEMORY_WINDOW_TURNS` | `6` | `3` | `3` | `10` |
| `MEMORY_TTL_SECONDS` | `3600` | `3600` | `3600` | `3600` |
| `AUTH_ENABLED` | `true` | From `global.auth.enabled` | Not set (`true`) | Not set (`true`) |
| Telemetry | On | Off | On | On |
| Redis | — | Own Deployment, `redis:7.4-alpine` | Bitnami chart 25.5.3 | `redis:7-alpine`, host port `6385` |

The READMEs give other values (window `6` or `10`). Which models are allowed is open (overview F3).

### P.4 Platform Chart Values

| Group | Values | Notes |
|---|---|---|
| Images | `images.mcp.*`, `images.aiAgent.*` (`repository`, `tag`, `pullPolicy`), `images.waitForMcp` | Tags `latest` in the chart; kind sets `"1.0.0"` (§T). The init container image defaults to `curlimages/curl:8.11.1` |
| Replicas | `replicaCount.mcp`, `replicaCount.aiAgent` | `1` each |
| Settings | `config.mcp.port`, `config.mcp.oegServiceUrl`, `config.aiAgent.port`, `config.aiAgent.groqModelName`, `config.aiAgent.memoryWindowTurns`, `config.aiAgent.memoryTtlSeconds`, `config.aiAgent.memoryKeyPrefix` | Become the ConfigMap |
| Redis | `redis.enabled`, `redis.image`, `redis.port`, `redis.resources` | `false` turns memory off. Requests 50m CPU and 64Mi; limit 128Mi |
| Secret | `secrets.groqApiKey` | Set in `environments/<env>/secrets.values.yaml`, kept out of Git. Empty: the deploy script warns and every chat fails |
| Services | `service.mcp.*`, `service.aiAgent.*` (`type`, `port`, `nodePort`) | `ClusterIP` in the umbrella chart; NodePort `32004` and `32013` in kind |

Umbrella values: `ai2.enabled: true`, `ai2.fullnameOverride: ai2`, which names the services
`ai2-mcp` and `ai2-ai-agent`. In the umbrella chart every value above sits under `ai2.`, for example
`ai2.secrets.groqApiKey` and `ai2.service.mcp.nodePort`. For kind the key goes in
`helm/environments/kind/secrets.values.yaml`.

### P.5 Portal Settings Pointing at AI²

| Setting | Notes |
|---|---|
| `AI2__CHAT_URL` (chart `ai2.chatUrl`) | Default `http://ai2-ai-agent:9013/groq-mcp`. Empty turns the chat off ([portal §P](/portal/implementation-and-delivery.md)) |
| `AI2__TIMEOUT` | Default `120` seconds. Not in the portal's chart |

---

## Q. Build and Deployment

**Images.** Each service builds from `python:3.12-slim` with pip and runs `uvicorn`. Neither image
sets a user, so both run as root. Registry: `labs.etsi.org:5050/oop/code/ai2/ai_agent` and
`…/mcp_module`.

**Pipeline.** `.gitlab-ci.yml` runs mypy, import-linter (skipped, no configuration), ruff check,
ruff format check, pytest and GitLab SAST. On `main`, `develop` and tags it builds both images with
`docker buildx --provenance=false` and pushes them. Tags: the Git tag; `latest` on `main`;
`develop` on `develop`; the branch name with `/` as `-` otherwise.

**Platform chart.** `oop-platform-chart/charts/ai2` in the helm repository (chart 2.0.0):

| The chart | How |
|---|---|
| Agent | Deployment `ai2-ai-agent`. An init container polls the MCP server's `/health` every 3 seconds before the agent starts |
| MCP server | Deployment `ai2-mcp` |
| Redis | Deployment and Service on `emptyDir`, no snapshots, non-root, read-only root filesystem. A NetworkPolicy admits only the agent |
| Settings | ConfigMap (§P) and Secret `GROQ_API_KEY` |
| Probes | Readiness and liveness on `/health` for both services |
| Login off | `global.auth.enabled: false` sets `AUTH_ENABLED=false` on both |

**AI2 repository chart.** `deployment/helm/ai2` (chart 1.0.0) differs from the platform chart
(§P.3): Bitnami Redis, NodePort services, no `AUTH_ENABLED`, no telemetry switch. Which chart is
authoritative is open (overview F10). Run `helm dependency update` before installing it. Its Redis
settings are `redis.master.service.ports.redis` (`6379`) and `redis.memory.database` (`0`), with
Redis auth and persistence off. Compose's Redis host port `6385` has no NodePort equivalent, since
NodePorts must be in `30000-32767`.

**kind.** `helm/scripts/helm-deploy.sh` deploys the umbrella chart with
`environments/kind/values.yaml` and, if present, `secrets.values.yaml`. The agent is at
`http://localhost:32013`, the MCP server at `http://localhost:32004`. To run locally built
images, load them with `kind load docker-image` and set `pullPolicy: IfNotPresent`.

**Local.** `docker compose --env-file .env up -d` in `deployment/docker/` starts Redis, the MCP
server and the agent, with every port on the host. This is AI²'s own Compose file; the platform's
single root Compose layout with an `ai` profile ([platform §Docker Compose Profiles](/platform/architecture.md))
is the target, and this spec does not cover it. Without Docker: `python -m ai_agent.app` and
`python -m mcp_module.app` from the repository root, each with its `.env`. Use Python 3.12, as the
images do, and install with `pip install -r ai_agent/requirements.txt -r mcp_module/requirements.txt`.
Build an image with `docker build -t <name> ai_agent` (or `mcp_module`). Compose's Redis runs with
the image defaults and no volume, so its data goes when the container is removed.

---

## R. Observability

- **Logs.** The agent logs at `INFO` and runs the agent library in verbose mode; the library's own
  logger is limited to `ERROR`. Whether verbose mode logs prompts, answers or tool results is not
  verified. The MCP server logs at `DEBUG`, including every OEG address. Plain
  text, not the platform's JSON format (overview F11).
- **Request id.** None. A correlator reaches OEG only when the model passes `xCorrelator`.
- **Health.** `/health` on both services shows only that the process runs. It does not check
  Groq, Redis or OEG.
- **Metrics.** None.
- **Portal side.** The portal logs AI² refusing a token as a security event
  ([portal §R](/portal/implementation-and-delivery.md)).

Troubleshooting in the cluster:

```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 had no bearer token. A tool error naming OEG's status
means OEG refused the call, usually because the user lacks that API product.

---

## S. Testing

| Kind | Covers | Runs With |
|---|---|---|
| Unit, agent | `400` without `query` on both routes, `401` without a token, success with login off, `500` when the agent run fails | `pytest` from the repository root |
| Unit, MCP server | Token passing and refusal; unknown routes and `/health/` answer `404`; input rules; resource rules; correlator; address joining; idempotency key length; OEG error to tool error | `pytest` |
| Contract, MCP to OEG | For every registered tool: method, path, headers and body against a mocked OEG; CAMARA errors become tool errors; no HTTP call without a token; transport failures never look like success. A check fails when a registered tool has no case | `pytest` (`tests/mcp_module/test_tool_requests.py`) |
| Static | mypy, ruff lint and format, GitLab SAST | GitLab pipeline |

Run one file with `pytest tests/mcp_module/test_oeg_errors.py`. The MCP server README reports use
with Open-WebUI 0.6.34 on Python 3.11; no test covers it.

Missing coverage: session memory with Redis, a successful stream, the agent passing the token to
the MCP server, the portal's chat contract, any test against a real OEG or Groq, and chart
rendering.

---

## T. Known Gaps

Unspecified means no reason has been agreed.

| Gap | Why It Is Acceptable for Now | Workaround |
|---|---|---|
| Tokens are not validated (overview F1) | Unspecified. The umbrella chart keeps both services `ClusterIP` | Do not expose the agent outside the cluster |
| Memory is not tied to the caller (F4) | Unspecified | `redis.enabled: false` |
| User data goes to Groq (F3) | Unspecified | None |
| No time budget; a run is not stopped when the caller leaves (F5) | Unspecified | Check the state in OEG before retrying |
| No confirmation before writes; retries can repeat creates (F6, F7) | Unspecified | Ask the agent to use an idempotency key; check state before retrying |
| No federation tools, though the public documentation says AI² manages federation | Unspecified | The portal's Federation tab |
| OEG operations without a tool (§K.1 in the design) | Unspecified | The portal, or OEG directly |
| The two charts differ (F10) | Unspecified | Deploy with the platform chart |
| kind pins AI² images to `1.0.0`. The `v1.0.0-release` tag predates token passing, and the pipeline names that image `v1.0.0-release`; whether a `1.0.0` image exists is not verified | Image tags are set by hand at release | Set `ai2.images.*.tag` to a current image |
| Missing settings show at the first chat, as a plain `500` | Unspecified | Check the agent log after the first chat |
| Health checks miss Groq, Redis and OEG | Unspecified | Watch the logs |
| Containers run as root; no pod security settings for the agent or MCP server | Unspecified | None |
| No CPU or memory requests or limits for the agent and MCP server | Unspecified | None |
| OEG does not yet scope reads and deletes by tenant, and that applies through AI² ([oeg/architecture.md F5](/oeg/architecture.md)) | Deferred in OEG's spec | None in AI² |
| Plain logs, `DEBUG` on the MCP server, no request id or metrics (F11) | Unspecified | None |
| `/debug/objgraph` needs no token (F9) | Unspecified | Keep the agent `ClusterIP` |
| The system prompt ends mid-sentence; `TOOL_USING_ASSISTANT_PROMPT` and `UserPhoneInput` are unused | Unspecified | None |
| What happens at the 20-model-call limit is not verified | Unspecified | None |
| READMEs are out of date: 11 of 19 tools listed, "Quart backend", other defaults | Unspecified | Use this spec |
| The portal keeps the conversation list only in the browser tab ([portal §U.9](/portal/implementation-and-delivery.md)) | Accepted for now in the portal's spec | — |

---

## U. Next Release

No AI² work is scheduled for the next release. These are intentions with some evidence, not plans.

### U.1 Conversation History That Survives Reload

- **Missing.** The portal loses the conversation list on reload ([portal §U.9](/portal/implementation-and-delivery.md)).
- **Plan.** Unspecified.
- **Already in place.** The agent keeps recent turns per `session_id` (§L in the design).

### U.2 Other LLM Providers

- **Missing.** Only Groq is implemented.
- **Plan.** Unspecified. Intent: AI² should not depend on one LLM provider. No provider is agreed
  (overview F3).
- **Already in place.** The sub-agent layout (`sub_agents/`, `routes/`) and the portal's provider-free
  `ai2.chatUrl`.

### U.3 More Tools and Prompts

- **Missing.** Tools for the OEG operations listed in §K.1 of the design; MCP prompts.
- **Plan.** Unspecified. The MCP server README's roadmap names prompts, more tool calls and fuller
  parameter checks.
- **Already in place.** The tool pattern and its wire-contract tests (§V.1).

### U.4 Agent-to-Agent and Operator Agents

- **Missing.** A2A support and operator agents.
- **Plan.** Future phases in [platform/architecture.md §Agentic AI Integration](/platform/architecture.md).
- **Already in place.** The MCP server that wraps OEG.

---

## V. Common Changes

### V.1 Add a Tool for an OEG Operation

1. Read the operation in [oeg/interface-contract.md](/oeg/interface-contract.md) and note which API
   product's scope it needs.
2. Add the input model to `mcp_module/models/<api>_models.py`.
3. Add the function to `mcp_module/tools/<api>.py`. Use `get_service_url`, `get_headers` and
   `raise_for_oeg_status`, and set `timeout=15`.
4. Add it to `SAFE_TOOLS` in `mcp_module/app.py`.
5. Add a `Case` to `tests/mcp_module/test_tool_requests.py`. Without one, the coverage check fails.
6. Update [interface-contract.md §2–3](/ai2/interface-contract.md); the tool count and lists in
   design §I.2 and §K.1 (including the "no tool" list); the overview's diagram and §C features;
   §T and §U.3 here; and the MCP server README. The spec is the source; the README follows it.

- **Check:** design §K, [ADR-0030](/decisions/0030-oeg-error-model-is-camara-errorinfo-not-rfc7807.md),
  [ADR-0048](/decisions/0048-platform-roles.md).
- **Run:** `pytest`. The pipeline also runs mypy and ruff.
- **Elsewhere:** none, unless OEG must add the operation first.

**Note:** A tool not listed in `SAFE_TOOLS` is never offered to the model.

### V.2 Follow an OEG API Change

1. Change the base path in `mcp_module/tools/<api>.py` and the affected models.
2. Update the expected paths and bodies in `test_tool_requests.py`.
3. Update design §K.1 and the contract.

- **Check:** the OEG interface contract and the vendored CAMARA specification.
- **Run:** `pytest`.
- **Elsewhere:** release AI² with the OEG version that serves the new path.

### V.3 Change the Model, or Add a Provider

1. Model only: set `config.aiAgent.groqModelName` in both charts (and `GROQ_MODEL_NAME` in Compose).
   Pick a model that supports tool calling.
2. New provider: add `ai_agent/sub_agents/<provider>_agent.py`, routes in `ai_agent/routes/`, and
   register them in `app.py`. Reuse `get_authorization` (`ai_agent/security.py`) and
   `RedisShortTermMemory` (`ai_agent/memory.py`).
3. Add its settings and secret to both charts.
4. If the chat path changes, set the portal's `ai2.chatUrl`.

- **Check:** overview F3; design §M.3.
- **Run:** `pytest`; add error-code tests like `tests/ai_agent/test_error_codes.py`.
- **Elsewhere:** helm repository; portal chart for a new path.

### V.4 Change Session Memory

1. Set `config.aiAgent.memoryWindowTurns`, `memoryTtlSeconds` or `memoryKeyPrefix`, or
   `redis.enabled: false` to turn memory off.
2. To change how memory works, edit `ai_agent/memory.py`.

- **Check:** design §L, overview F4 and F8.
- **Run:** `pytest`. There is no memory test yet; add one with a fake Redis.
- **Elsewhere:** helm repository; the AI2 repository's chart too.

### V.5 Change Deployment Settings

1. Change the platform chart in the helm repository.
2. Make the same change in `deployment/helm/ai2` in the AI2 repository, until overview F10 is
   decided.
3. Environment overrides go in `environments/<env>/values.yaml`; secrets in
   `secrets.values.yaml`.

- **Check:** §P and §Q.
- **Run:** `helm template` on the umbrella chart, then deploy to kind.
- **Elsewhere:** the portal chart if the agent's address changes.

**Note:** Both services must get the same `AUTH_ENABLED`, or login-off mode fails at the MCP
server.

### V.6 Change the Chat Contract With the Portal

1. Change `ai_agent/routes/groq.py`.
2. Change the portal's `portal-gui/src/types/ai2.ts` and `services/ai2/chat.ts`. The portal backend
   forwards the body unchanged.
3. Update [interface-contract.md §1](/ai2/interface-contract.md) and
   [portal §K](/portal/requirements-and-design.md).

- **Check:** REQ-AI2-04, REQ-PORTAL-09.
- **Run:** `pytest` in AI2; the portal's `make check` and `make test`.
- **Elsewhere:** release AI² and the portal together.

---

## Revision History

| Version | Date | Summary |
|---------|------|---------|
| 2.0 | 2026-10-21 | First version, describing the Release 2 implementation. |

ai2/index.md

0 → 100644
+8 −0
Changes for ai2/index.md: 8 added lines, 0 removed lines.
Original line number Diff line number Diff line
# AI² (AI Squared)

Four-part spec; read in order.

* [Overview & Architecture](overview-and-architecture.md) — what AI² is, who can use it, terms, features, boundaries, assumptions, open questions
* [Requirements & Design](requirements-and-design.md) — requirements, interfaces, the chat flow, tool calls to OEG, session memory, security, errors
* [Implementation & Delivery](implementation-and-delivery.md) — code layout, settings, deployment, observability, testing, known gaps, common changes
* [Interface Contract](interface-contract.md) — chat API payloads, every MCP tool, input rules, worked examples
+313 −0

File added.

Preview size limit exceeded, changes collapsed.

+232 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading