diff --git a/CLAUDE.md b/CLAUDE.md index 672b79242..168cd58d1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -74,6 +74,7 @@ Deep dives (do not duplicate here — read the doc when relevant): - [docs/developers/operations/testing-endpoints.md](docs/developers/operations/testing-endpoints.md) — **canonical UI + API endpoint reference** (services, custom endpoints, auth scopes) - [docs/developers/operations/mta-deployment.md](docs/developers/operations/mta-deployment.md) — deploy runbook - [docs/developers/operations/rebuild-content-workflow.md](docs/developers/operations/rebuild-content-workflow.md) — three rebuild modes, auto-classify +- [docs/developers/operations/hana-aicore-embeddings.md](docs/developers/operations/hana-aicore-embeddings.md) — connect HANA to AI Core for DB-native `VECTOR_EMBEDDING` (PSE + `sapgenaihub` remote source); built-in NLP vs AI Core tiers; auto-embedding mechanisms (#2810) - [docs/developers/operations/qa-channel-bootstrap.md](docs/developers/operations/qa-channel-bootstrap.md) — QA author-preview channel - [docs/developers/operations/scheduler-troubleshooting.md](docs/developers/operations/scheduler-troubleshooting.md) — outbox-wedge recovery runbook - [docs/developers/reference/tutorials-ims-gotchas.md](docs/developers/reference/tutorials-ims-gotchas.md) — everything else (build pipeline quirks, publish flags, AI features, env vars, migration) diff --git a/db/src/_grants.hdbgrants b/db/src/_grants.hdbgrants index 81458d70e..3d5cf995e 100644 --- a/db/src/_grants.hdbgrants +++ b/db/src/_grants.hdbgrants @@ -5,7 +5,8 @@ { "privileges": [ "SPARQL QUERY", - "SPARQL UPDATE" + "SPARQL UPDATE", + "CREATE PSE" ] } ] @@ -15,7 +16,8 @@ { "privileges": [ "SPARQL QUERY", - "SPARQL UPDATE" + "SPARQL UPDATE", + "CREATE PSE" ] } ] diff --git a/docs/developers/operations/hana-aicore-embeddings.md b/docs/developers/operations/hana-aicore-embeddings.md new file mode 100644 index 000000000..8faf63182 --- /dev/null +++ b/docs/developers/operations/hana-aicore-embeddings.md @@ -0,0 +1,205 @@ +# HANA-Native AI Core Embeddings — Setup Runbook + +> **What this is.** How to connect the existing SAP AI Core instance to a +> `tutorials-ims` HANA container so embeddings can be generated **by SQL +> inside the database** (`VECTOR_EMBEDDING`), instead of only at the +> Node/CAP layer. Tracks issue +> [#2810](https://github.com/sap-tutorials/tutorials-ims/issues/2810). +> Design rationale: [`docs/superpowers/specs/2026-10-19-hana-aicore-embedding-design.md`](../../superpowers/specs/2026-10-19-hana-aicore-embedding-design.md). +> +> **Status:** DEV setup. This is the *connection + proof* unit only — no +> application feature has been moved to DB-side embeddings yet. + +## Two tiers + +| Tier | What | AI Core needed? | Privilege needed? | +| --- | --- | --- | --- | +| **A — built-in NLP** | HANA's own embedding models (`SAP_GXY.20250407`, `SAP_NEB.20240715`) | No | None | +| **B — AI Core remote source** | AI Core gen-AI hub models (`text-embedding-3-small`, …) via `sapgenaihub` adapter | Yes | `CREATE PSE` (see below) | + +Tier A works out of the box and is the fastest sanity check. Tier B is +issue #2810's actual target. + +--- + +## Tier A — built-in NLP embedding (no setup) + +Run as the container runtime user (any `tutorials-hana` service key / +`cds bind`). Confirms `VECTOR_EMBEDDING` works from the DB layer: + +```sql +SELECT VECTOR_EMBEDDING('Hello world!', 'DOCUMENT', 'SAP_GXY.20250407') FROM DUMMY; +``` + +Expected: a 768-dimension `REAL_VECTOR`. List available built-in models +with `SELECT * FROM NLP_VECTOR_EMBEDDING_MODELS;`. + +> **Verified on DEV 2026-10-19:** returned a 768-dim vector. + +--- + +## Tier B — AI Core remote source + +### Prerequisites + +1. **Dedicated x509 service key** on the AI Core instance. On DEV this is + `hana-ai-core` on `tutorials-aicore` (a separate key from the CAP app + binding — different security requirements). Create with: + ```bash + cf create-service-key tutorials-aicore hana-ai-core \ + -c '{"credential-type":"x509","validity":365,"validity-type":"DAYS"}' + cf service-key tutorials-aicore hana-ai-core # read values + ``` + You need from the key JSON: `serviceurls.AI_API_URL`, `certurl`, + `clientid`, `certificate`, `key`. + +2. **AI Core deployment ID** of an orchestration deployment. List RUNNING + deployments (x509 mTLS against the token endpoint, then `/v2/lm/deployments`): + ``` + POST {certurl}/oauth/token grant_type=client_credentials&client_id={clientid} (client cert + key as mTLS) + GET {AI_API_URL}/v2/lm/deployments?status=RUNNING Authorization: Bearer AI-Resource-Group: default + ``` + Pick an `orchestration`-scenario deployment ID. + + > **DEV worked example:** deployment `da6d9c5e3fb50c3d` + > (`joule-chat-orchestration`). Other RUNNING on DEV: + > `d2f9fa53543e5c4e` (defaultOrchestrationConfig), + > `dfad0fa7cd47ab38` (text-embedding-3-small). + +### Step B1 — grant `CREATE PSE` (declarative) + +The container runtime user already holds `CREATE REMOTE SOURCE`; it is +missing only `CREATE PSE`. Grant it through the existing +`db/src/_grants.hdbgrants` / `tutorials-kg-grantor` mechanism — add +`CREATE PSE` to the `system_privileges` of both `application_user` and +`object_owner`, then deploy `tutorials-db-deployer`. + +```jsonc +// db/src/_grants.hdbgrants → tutorials-kg-grantor → application_user / object_owner +"privileges": [ "SPARQL QUERY", "SPARQL UPDATE", "CREATE PSE" ] +``` + +> **Try-first risk.** The grant only lands if the grantor identity behind +> `tutorials-kg-grantor` holds `CREATE PSE` **WITH ADMIN OPTION**. +> +> - **If deploy succeeds:** done, proceed to B2. +> - **Failure signature:** `tutorials-db-deployer` fails with an +> insufficient-privilege / cannot-grant error naming `CREATE PSE`. +> - **Fallback:** as a DEV HANA Cloud admin (DBADMIN or equivalent), run +> **one** of: +> ```sql +> -- Option 1: let the grantor delegate it (keeps the declarative path working) +> GRANT CREATE PSE TO WITH ADMIN OPTION; +> -- Option 2: grant the container user directly (bypasses the grantor) +> GRANT CREATE PSE TO AC9753D6C4764F5ABE3B3CA4E88233C0_..._RT; +> ``` +> then re-deploy (Option 1) or skip straight to B2 (Option 2). + +### Step B2 — create the PSE and remote source + +Run as the container runtime user once `CREATE PSE` is held. For +`SET OWN CERTIFICATE`, paste the **private key first, then the client +certificate**, both taken verbatim from the `hana-ai-core` service key +and unescaped (`\n` → real line breaks). I.e. the service key's `key` +value (the full `BEGIN/END RSA PRIVATE KEY` block) immediately followed +by its `certificate` value (the full `BEGIN/END CERTIFICATE` block), +inside the single-quoted string: + +```sql +CREATE PSE TUTORIALS_AICORE_PSE; +ALTER PSE TUTORIALS_AICORE_PSE SET OWN CERTIFICATE ' +<<< hana-ai-core service-key "key" value here (RSA private key PEM block) >>> +<<< hana-ai-core service-key "certificate" value here (certificate PEM block) >>> +'; + +CREATE REMOTE SOURCE TUTORIALS_AICORE ADAPTER "sapgenaihub" CONFIGURATION + 'aiApiUrl=; + authUrl=; + resourceGroup=default; + deploymentId=; + clientId=' +WITH CREDENTIAL TYPE 'X509' PSE TUTORIALS_AICORE_PSE; +``` + +DEV worked values: `aiApiUrl=https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com`, +`authUrl=https://tutorial-system.authentication.cert.eu10.hana.ondemand.com`, +`deploymentId=da6d9c5e3fb50c3d`, +`clientId=sb-78c0840a-3613-4a19-be4f-5cd52760090e!b676072|aicore!b540`. + +> **Secrets:** the cert + private key come only from the live service key +> — never commit them. Pull at setup via `cf service-key`. Rotate +> `hana-ai-core` if the key JSON was exposed anywhere shared. + +> **Lifecycle caveat:** a PSE / remote source created imperatively by the +> runtime user is **not** an HDI-managed artifact and will be orphaned on +> the next `tutorials-db-deployer` container rebuild. Acceptable for this +> DEV proof. **Before any feature rewrite, convert these to design-time +> artifacts** (`.hdbpse` + remote-source artifact) so they survive +> redeploy — tracked as a follow-up. + +### Step B3 — prove it + +```sql +CALL GET_REMOTE_SOURCE_AI_MODELS('TUTORIALS_AICORE', ?); -- enumerate reachable embedding models +-- use a model+version from that list, e.g. text-embedding-3-small.1 +SELECT VECTOR_EMBEDDING('Hello world!', 'DOCUMENT', + 'text-embedding-3-small.1', TUTORIALS_AICORE) FROM DUMMY; +``` + +Expected: a non-null `REAL_VECTOR` (dimension depends on the model). +Recommended: a thin hybrid test asserting a non-null vector returns. + +--- + +## PROD repeat checklist + +Same steps, PROD inputs. Confirm `cf target` is PROD before any key read. + +- [ ] Create / confirm a dedicated x509 key on the PROD AI Core instance. +- [ ] Retrieve a PROD orchestration deployment ID. +- [ ] Add `CREATE PSE` to `_grants.hdbgrants` (already in repo from DEV) — + deploy PROD `tutorials-db-deployer`; apply admin fallback if it fails. +- [ ] Run B2 with PROD `AI_API_URL` / `certurl` / `deploymentId` / `clientId`. +- [ ] Run B3 proof. +- [ ] (Before rewrite) convert PSE + remote source to design-time artifacts. + +--- + +## Appendix 1 — Automatic embedding generation (for the later rewrite) + +Once the remote source works, HANA can keep embeddings in sync with +source text with no application pipeline. Four mechanisms: + +| Method | Column type | Timing | +| --- | --- | --- | +| `CREATE_VECTOR_EMBEDDINGS` system procedure | NCLOB, NVARCHAR | On-demand / one-time backfill | +| Scheduler job (`CREATE SCHEDULER JOB` → the procedure) | NCLOB, NVARCHAR | Periodic background sync | +| `GENERATED ALWAYS AS VECTOR_EMBEDDING(...)` computed column | **NVARCHAR only** | Synchronous on insert/update | +| `BEFORE INSERT OR UPDATE` trigger | NCLOB, NVARCHAR | Synchronous on insert/update | + +> This platform's content is stored as **NCLOB** (gzip BLOB / long text), +> so `GENERATED ALWAYS AS` (NVARCHAR-only) does **not** apply — use the +> procedure (backfill), scheduler job (periodic), or trigger (synchronous) +> routes. Example backfill: +> ```sql +> CALL SYS.CREATE_VECTOR_EMBEDDINGS( +> SCHEMA_NAME => CURRENT_SCHEMA, TABLE_NAME => 'KNOWLEDGE_BASE', +> TEXT_COLUMN_NAME => 'TEXT', EMBEDDING_COLUMN_NAME => 'TEXT_EMBEDDING', +> TEXT_TYPE => 'DOCUMENT', MODEL_AND_VERSION => 'SAP_GXY.20250407'); +> ``` + +## Appendix 2 — Wider HANA AI function surface + +From the SAP HANA AI Development Guide — candidates for later moving +Node/CAP AI work into the DB layer (each worth its own evaluation unit): + +- `VECTOR_EMBEDDING`, `CREATE_VECTOR_EMBEDDINGS` — embeddings (this doc). +- `AI_TEXT_COMPLETION` — LLM text generation in SQL. +- `AI_CORE_REQUEST` — generic AI Core call from SQL. +- `CROSS_ENCODE` — reranking / cross-encoder scoring. +- `AI_TABULAR_PREDICTION` — tabular ML prediction (cf. RPT ValueList use). + +These overlap with features currently served via `@cap-js/ai` / +`@sap-ai-sdk/orchestration` (semantic search, text-gen, Joule, RPT +recommendations); consolidation is a future architecture decision, not +part of this unit. diff --git a/docs/superpowers/specs/2026-10-19-hana-aicore-embedding-design.md b/docs/superpowers/specs/2026-10-19-hana-aicore-embedding-design.md new file mode 100644 index 000000000..9e171cde4 --- /dev/null +++ b/docs/superpowers/specs/2026-10-19-hana-aicore-embedding-design.md @@ -0,0 +1,183 @@ +# HANA-Native AI Core Embeddings — DEV Setup & Proof (design) + +**Issue:** [#2810](https://github.com/sap-tutorials/tutorials-ims/issues/2810) +**Date:** 2026-10-19 +**Status:** design — no SQL run against HANA yet +**Scope of this unit:** basic setup only. Connect the existing AI Core +instance to the DEV HANA container and prove `VECTOR_EMBEDDING` works from +the DB layer. **No application rewrite.** Moving existing Node/CAP +embeddings into the DB is explicitly a later unit. + +## Intent & why + +Today every embedding this platform produces is computed at the Node/CAP +layer via `@cap-js/ai` / the AI SDK (`@sap-ai-sdk/orchestration`) calling +AI Core, then written to HANA. HANA Cloud now exposes `VECTOR_EMBEDDING` +(and a wider AI function family) that can call AI Core **from inside the +database**, removing a network round-trip and the application-side +pipeline for keeping embeddings in sync with their source text. + +Before committing to any rewrite, we want a de-risking proof: reuse the +**existing** AI Core service instance + a dedicated x509 service key, wire +it into the DEV HANA container as a remote source, and confirm an +embedding can be generated by SQL. Prove it on DEV; repeat on PROD only +after. + +## Goals + +- Reuse the existing `tutorials-aicore` instance via the dedicated + `hana-ai-core` (x509) service key — no new AI Core instance. +- Create the PSE + remote source **inside the `tutorials-hana` HDI + container** (shippable shape CAP can later consume), not as a throwaway + admin-level object. +- Extend the container user's privileges **declaratively** through the + existing `db/src/_grants.hdbgrants` mechanism — no manual one-off + DBADMIN grant. +- Produce a committed operations runbook so the PROD repeat is mechanical. +- Confirm two tiers of embedding work: HANA built-in NLP, and AI Core via + the remote source. + +## Non-goals / anti-goals + +- **Not** rewriting any existing embedding feature (freshness corpus, + semantic search, KG, RPT ValueList) to use DB-side embeddings. Later. +- **Not** touching PROD in this unit. +- **Anti-goal:** creating the remote source as a bare instance-level + admin object that CAP can't reach — that would "work" in a test and + still require a full redo to ship. +- **Anti-goal:** hand-running `GRANT` as DBADMIN outside the HDI deploy — + it drifts from the repo and is lost on next container rebuild. + +## Confirmed facts (verified this session, DEV / `tutorial-system/dev`) + +### AI Core — instance `tutorials-aicore`, key `hana-ai-core` +- `credential-type`: **x509** (client cert + RSA key; no clientsecret). +- `AI_API_URL`: `https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com` +- `authUrl` (certurl): + `https://tutorial-system.authentication.cert.eu10.hana.ondemand.com` +- `clientId`: `sb-78c0840a-3613-4a19-be4f-5cd52760090e!b676072|aicore!b540` +- RUNNING deployments (via `/v2/lm/deployments`): `text-embedding-3-small` + (`dfad0fa7cd47ab38`), `defaultOrchestrationConfig` (`d2f9fa53543e5c4e`), + **`joule-chat-orchestration` (`da6d9c5e3fb50c3d`)**, `claude-4.6-sonnet`, + `sap-rpt-1-small`. +- **Chosen `deploymentId` for the remote source: `da6d9c5e3fb50c3d`** + (joule-chat-orchestration) — per Tom's decision. The `sapgenaihub` + adapter routes by model name over an orchestration deployment; + `GET_REMOTE_SOURCE_AI_MODELS` then lists reachable embedding models. + +### HANA — container `tutorials-hana` +- Instance `f1f99bc6-89c8-4f81-a089-1c3732c30b13.hna2.prod-eu10.hanacloud.ondemand.com:443` +- Schema / runtime user `AC9753D6C4764F5ABE3B3CA4E88233C0` / `..._RT`. +- **Privilege gap (verified by capability probe):** the only missing + privilege is **`CREATE PSE`** (probe: `CREATE PSE` → *insufficient + privilege*). **`CREATE REMOTE SOURCE` is ALREADY held** — the probe got + past authorization and failed only on a missing-PSE error, proving the + right exists. A standalone `CREATE CREDENTIAL` object is not needed for + the X509-PSE remote-source form. So the AI Core tier needs exactly one + grant: `CREATE PSE`. +- **Built-in NLP models visible now:** `SAP_GXY.20250407`, + `SAP_NEB.20240715` (from `NLP_VECTOR_EMBEDDING_MODELS`). The built-in + tier needs no AI Core setup. +- **Tier A PROVEN (2026-10-19):** + `VECTOR_EMBEDDING('...','DOCUMENT','SAP_GXY.20250407')` run as the `_RT` + user returned a **768-dim** `REAL_VECTOR`. The DB-layer embedding path + works today. + +## How it gets built (the decided parts) + +### Tier A — built-in NLP embedding (no AI Core, immediate) +Proves `VECTOR_EMBEDDING` end-to-end with zero new privileges: +```sql +SELECT VECTOR_EMBEDDING('Hello world!', 'DOCUMENT', 'SAP_GXY.20250407') FROM DUMMY; +``` +Run as the container `_RT` user via a `tutorials-hana` service key +(hana-cli / `@sap/hana-client`). Expected: a 768-dim `REAL_VECTOR`. + +### Tier B — AI Core remote source (the issue's actual target) + +**B1. Declarative privilege grant.** Extend `db/src/_grants.hdbgrants` +(the file that already grants `SPARQL QUERY/UPDATE` via the +`tutorials-kg-grantor` UPS) with the one missing system privilege, under +both `application_user` and `object_owner`: **`CREATE PSE`**. Applied by +`tutorials-db-deployer` on deploy. (`CREATE REMOTE SOURCE` is already +held — see confirmed facts.) + +> **Deploy-time risk (try-first, per decision):** the grant only succeeds +> if the `tutorials-kg-grantor` grantor identity holds `CREATE PSE` **WITH +> ADMIN OPTION**. SPARQL privileges were delegable; `CREATE PSE` may not +> be. **Failure signature at deploy:** the `tutorials-db-deployer` task +> fails with a grant/insufficient-privilege error naming `CREATE PSE`. +> **Fallback:** a one-time admin grant of `CREATE PSE` WITH ADMIN OPTION +> to the grantor user (or `CREATE PSE` directly to the `_RT` container +> user), then re-deploy. Runbook documents both. + +**B2. PSE + remote source SQL** (verbatim shape from the HANA Vector +Engine Guide "Connecting to SAP AI Core", mapped to our values): +```sql +CREATE PSE TUTORIALS_AICORE_PSE; +ALTER PSE TUTORIALS_AICORE_PSE SET OWN CERTIFICATE ' + + +'; + +CREATE REMOTE SOURCE TUTORIALS_AICORE ADAPTER "sapgenaihub" CONFIGURATION + 'aiApiUrl=https://api.ai.prod.eu-central-1.aws.ml.hana.ondemand.com; + authUrl=https://tutorial-system.authentication.cert.eu10.hana.ondemand.com; + resourceGroup=default; + deploymentId=da6d9c5e3fb50c3d; + clientId=sb-78c0840a-3613-4a19-be4f-5cd52760090e!b676072|aicore!b540' +WITH CREDENTIAL TYPE 'X509' PSE TUTORIALS_AICORE_PSE; +``` +Open question for the runbook: whether the PSE/remote source are best +created as container design-time artifacts (`.hdbpse` / grants) or as a +bootstrap SQL step run as the `_RT` user once privileges land. Default +call: bootstrap SQL first to prove it, then convert to design-time +artifacts as a follow-up so it survives container rebuilds. Flagged as my +call, revisit in the runbook. + +**B3. Proof queries:** +```sql +CALL GET_REMOTE_SOURCE_AI_MODELS('TUTORIALS_AICORE', ?); -- lists embedding models +SELECT VECTOR_EMBEDDING('Hello world!', 'DOCUMENT', + 'text-embedding-3-small.1', TUTORIALS_AICORE) FROM DUMMY; -- model from B3 list +``` + +### Secrets handling +The x509 cert+key live only in the `hana-ai-core` service key. For PSE +creation they are pasted into `ALTER PSE ... SET OWN CERTIFICATE`. They +must **not** be committed to the repo. The runbook instructs pulling them +at setup time via `cf service-key tutorials-aicore hana-ai-core`. +Consider rotating `hana-ai-core` after setup if the key JSON was exposed +in any shared transcript. + +## Deliverable: committed repo runbook + +A new operations runbook, to be linked from `CLAUDE.md`'s deep-dive list +and from the issue: +`docs/developers/operations/hana-aicore-embeddings.md`, covering: +1. Prerequisites (x509 key, deployment ID retrieval via `/v2/lm/deployments`). +2. Tier A built-in NLP proof. +3. Tier B privilege grant (declarative `_grants.hdbgrants` + grantor + delegation caveat + admin fallback). +4. Tier B PSE + remote source SQL with our DEV values as the worked + example and `` for PROD. +5. Proof queries + expected output. +6. PROD repeat checklist (same steps, PROD key/deployment/container). +7. **Reference appendix — Automatic embedding generation** (the four HANA + mechanisms: `CREATE_VECTOR_EMBEDDINGS` procedure, scheduler job, + `GENERATED ALWAYS AS` (NVARCHAR only), BEFORE INSERT/UPDATE trigger + (NCLOB)). Captured for the later rewrite; our content BLOBs are + NCLOB/gzip so the trigger/procedure routes apply, not computed column. +8. **Reference appendix — wider HANA AI surface** from the AI Development + Guide (`AI_CORE_REQUEST`, `AI_TEXT_COMPLETION`, `CROSS_ENCODE`, + `AI_TABULAR_PREDICTION`) as candidates for later consolidation of + text-gen / Joule / RPT work into the DB layer. + +## What's left to the builder / implementer + +- Decide design-time artifact vs bootstrap SQL for the PSE/remote source + (default: bootstrap first — see B2). +- Confirm grantor delegation (B1 caveat); pick declarative vs admin-fallback. +- Pick the exact embedding model string from `GET_REMOTE_SOURCE_AI_MODELS` + output (likely `text-embedding-3-small.1`). +- Whether to add a thin hybrid test asserting a non-null vector comes back.