> For the complete documentation index, see [llms.txt](https://docs.opendatadiscovery.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.opendatadiscovery.org/features/active-platform-features/genai.md).

# GenAI assistant

The **GenAI assistant** is an opt-in feature that lets the ODD Platform proxy natural-language questions to an **external AI service** that the operator deploys and operates separately. The platform itself does not embed an LLM; it forwards the question, waits for a response, and returns it through its API.

The feature is **disabled by default** (`genai.enabled: false`) and is **API-only today** — the generated TypeScript client (`GenaiApi.ts`, `GenAIRequest.ts`, `GenAIResponse.ts`) ships with the platform UI, but no in-app affordance currently calls it. Operators who turn this on either drive it from their own UI / scripts against the platform's `/api/genai/ask` endpoint, or wait for a future UI surface.

## What gets proxied

Each call from the operator → platform → external AI service has this shape:

```
[client] → POST /api/genai/ask  (JSON: {"body": "<question text>"})
              → platform forwards to: POST {genai.url}/query_data
                                       (JSON: {"question": "<question text>"})
              ← external service returns raw JSON-encoded string
              ← platform un-quotes + Java-unescapes, returns JSON: {"body": "<answer>"}
[client] ← 200 OK (JSON: {"body": "<answer>"})
```

The platform does not modify the question text it forwards (just rewraps it from `body` → `question`). Any catalog context, retrieval-augmentation, or prompt construction must happen inside the external AI service — the platform is a thin proxy.

## Configuration

The three `genai.*` keys (`enabled`, `url`, `request_timeout`), their defaults, the silent-misconfiguration warning when `enabled=true` is set without `url` and `request_timeout`, the working YAML / environment-variable example, and the platform-restart requirement when any of the three change all live on the operator-facing configuration reference at [Configure ODD Platform → GenAI Configuration](/configuration-and-deployment/odd-platform.md#genai-configuration). That page is the canonical home for the platform's configuration keys; this page links rather than embedding so the two surfaces never drift.

## External AI service contract

When `genai.enabled=true`, the platform's `genAiWebClient` is configured at startup with `baseUrl = genai.url` and `responseTimeout = Duration.ofMinutes(genai.request_timeout)`. Each call from `/api/genai/ask` forwards to the external service as:

* **Method:** `POST`
* **Path:** `{genai.url}/query_data` (the `/query_data` suffix is fixed in `GenAIServiceImpl.java:22`)
* **Headers:** none added by the platform (default WebClient headers; no auth, no API key)
* **Request body** (`application/json`):

  ```json
  { "question": "<the question text from the GenAIRequest.body field>" }
  ```
* **Response body** (the platform expects `application/json`): a **raw JSON-encoded string** — that is, the response payload is a single JSON string value (with leading and trailing `"` quotes). The platform strips the outer quotes via `CharMatcher.is('"').trimFrom(item)` and then runs `StringEscapeUtils.unescapeJava(...)` to interpret embedded escape sequences (`\"`, `\n`, etc.) before placing the result in the `GenAIResponse.body` field.

A minimal-but-conforming AI service implementation in Python (Flask), for reference:

```python
from flask import Flask, request, jsonify
import json

app = Flask(__name__)

@app.post("/query_data")
def query_data():
    payload = request.get_json(force=True)
    question = payload["question"]
    # ... your AI / RAG / LLM call here ...
    answer = my_ai_pipeline(question)
    # Return a raw JSON-encoded string. The platform expects to read this with
    # bodyToMono(String.class) and trim the surrounding quotes; jsonify wraps
    # it correctly.
    return app.response_class(json.dumps(answer), mimetype="application/json")
```

{% hint style="info" %}
The platform sends **no authentication** to the external service. If the service must reject anonymous traffic, deploy it behind a network policy / mesh / ingress that authenticates / restricts callers — there is no `genai.token` or similar in `GenAIProperties` today. The external service is also fully responsible for prompt construction, catalog-aware retrieval, and rate limiting; the platform forwards questions verbatim and returns whatever the service returns.
{% endhint %}

## Platform endpoint

The platform's GenAI surface is a single endpoint:

| Method | Path             | Operation ID    | Purpose                                                                         |
| ------ | ---------------- | --------------- | ------------------------------------------------------------------------------- |
| `POST` | `/api/genai/ask` | `genAiQuestion` | Forward a question to the configured external AI service and return its answer. |

Request schema (`GenAIRequest`):

```json
{ "body": "<question text>" }
```

Response schema (`GenAIResponse`):

```json
{ "body": "<answer text>" }
```

When `genai.enabled=false`, the endpoint responds with a `BadUserRequestException` carrying the message "Gen AI is disabled" (HTTP 400) — operators get a clear error rather than a silent no-op.

When the external service times out, the endpoint responds with a `GenAIException` carrying "Gen AI request take longer that {minutes} min" (HTTP 500) — the timeout duration in the message is the configured `genai.request_timeout` value, so operators see exactly what they configured.

For non-timeout errors from the external service (HTTP 5xx, connection refused, malformed response), the platform wraps the underlying cause in a `GenAIException` (HTTP 500) and surfaces it on the `/api/genai/ask` response. There is no retry loop; a single attempt per request.

## UI status

**API-only today.** The platform UI ships generated clients (`odd-platform-ui/src/generated-sources/apis/GenaiApi.ts` plus the request/response models) but no view, button, or panel currently calls them — verified via grep across `odd-platform-ui/src/` excluding `generated-sources/`. To use the feature, drive `POST /api/genai/ask` from your own client (curl, an internal app, a custom in-platform fork). When a UI affordance ships, this section will be updated.

## Platform-to-user security posture

The "Known limitations" section below covers the platform's posture toward the **external** AI service (no auth headers, no retry, no prompt modification, the misconfiguration-fail-at-first-request behaviour). This section covers the platform's posture toward the **user calling the platform** — the surface operators must harden when enabling GenAI.

{% hint style="danger" %}
**The `POST /api/genai/ask` endpoint has no RBAC entry — every authenticated caller can drive arbitrary cost on the operator's external LLM account.** The platform's authorization rules do not enumerate `/api/genai/**`; the route falls through to the catch-all "any authenticated user" rule under `LOGIN_FORM` / `OAUTH2` / `LDAP`, and is reachable **anonymously** under `auth.type=DISABLED`. The endpoint accepts any well-formed `GenAIRequest`, forwards it to the external service, and pays the request cost from the operator's LLM account. Five sub-gaps compound the exposure:

* **No per-user quota or rate limit.** A single client can issue as many requests per second as the network and the external service tolerate.
* **No in-platform audit trail.** The Activity Feed is structurally data-entity-scoped (see [Activity Feed → Scope](/features/active-platform-features/activity-feed.md)) — GenAI events have no data entity to anchor against and cannot be added to the feed without a schema migration. There is no platform-side answer to "who asked what at what time."
* **No PII redaction.** The user's question text, including any free-form content typed in, flows verbatim to the external LLM. Cross-team entity names, owner identities, and downstream-lineage names reach the external service if a question references them.
* **No GenAI-specific request-body size cap.** Large free-form prompts pass through up to the platform's global WebFlux in-memory codec limit (`spring.codec.max-in-memory-size`, default `20MB`); there is no tighter per-user or per-endpoint cap on the GenAI route.
* **Under `auth.type=DISABLED`, no per-user attribution at all.** Every request appears as an anonymous call; both the cost and any external-service-side audit trail attribute the request to the platform process, not to a specific user.

**Mitigation today.** Place a rate-limiting reverse proxy or an API gateway in front of `/api/genai/**` before enabling GenAI in production. Do **not** enable GenAI under `auth.type=DISABLED`; tighten who has `.authenticated()` access if the platform is fronted by a permissive auth mode. Treat the external LLM bill as user-driven cost and monitor it for spikes. The upstream platform hardening (RBAC entry, rate limit, body-size cap, PII scrubber) is on the roadmap.
{% endhint %}

{% hint style="warning" %}
**`genai.url` accepts any URL with any scheme — a config-driven SSRF surface.** The `GenAIProperties` class declares `url` as a plain string with no `@URL` constraint and no scheme / host allow-list. An operator (or any party with config-write access — a Helm overlay, an env-var injection, a leaked configmap) can point GenAI outbound at any reachable URL, **including the platform's own internal network**: cloud-metadata endpoints (`http://169.254.169.254/...`), internal services accessible from the platform's egress identity, file-protocol URIs that the underlying HTTP client may follow.

**Mitigation today.** Treat `genai.url` as a privileged configuration value — restrict who can modify it the same way you restrict database credentials or signing keys. Audit the resolved value at deployment time (don't accept a `genai.url` from a CI variable without a deploy-time review). The upstream fix is a configurable scheme / host allow-list on the property; until it ships, the only guard is operator discipline at the config layer.
{% endhint %}

## Known limitations

* **`@ConfigurationProperties` defaults bite when only `genai.enabled` is set.** `url` defaults to `null` (no field initializer in `GenAIProperties.java`) and `request_timeout` defaults to `0` (= immediate timeout). The platform accepts the misconfiguration at startup and fails at first request. Set `genai.url` and `genai.request_timeout` explicitly when enabling — the operator-side guidance lives on [Configure ODD Platform → GenAI Configuration](/configuration-and-deployment/odd-platform.md#genai-configuration). The upstream-side improvement (defaults declared in `GenAIProperties` or a startup validation) is queued as a separate platform issue.
* **No authentication to the external service.** If the external AI must reject anonymous callers, do it at the network layer (mesh, ingress, NetworkPolicy) — there is no per-request auth header or token added by the platform.
* **No retries.** A single `POST /query_data` per `/api/genai/ask` invocation. The external service must be reliable enough that a single attempt is acceptable, or operators must add retry / circuit-breaker logic upstream of the platform's caller.
* **`request_timeout` is the reply-wait timeout, in minutes** (not seconds, not milliseconds). It sets how long the platform waits for the model's response — the outbound response timeout on the WebClient call to the external service — not a budget for sending the request. The minimum effective value is 1 minute; setting `0` means immediate timeout (see above). It is read once at startup (see "Bean is built once at startup" below), so a change requires a platform restart.
* **Bean is built once at startup.** `WebClientConfiguration` reads `genai.url` and `genai.request_timeout` to construct the `genAiWebClient` bean — changing those values requires a Platform restart for the new WebClient to pick them up.
* **No UI today.** Generated TypeScript clients exist but are not wired into any view; the feature is callable only via the API.

## Where to next

* [Configure ODD Platform → GenAI](/configuration-and-deployment/odd-platform.md#genai-configuration) — the same three keys in the platform's full configuration reference, with environment-variable equivalents.
* [Main Concepts → AI aspects](/introduction/main-concepts.md#ai-aspects) — where GenAI sits among the other AI capabilities (data profiling, ML lineage).
* [API Reference → GenAI](/developer-guides/api-reference/genai.md) — the single `POST /api/genai/ask` endpoint (operationId `genAiQuestion`), the request / response shapes, status codes, and the authorisation posture (cross-link to the security caveat above).


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.opendatadiscovery.org/features/active-platform-features/genai.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
