From a1bc123010a496a3e04528372326c369dc785f8a Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 19 Jun 2026 08:58:47 +0200 Subject: [PATCH 1/4] root-readme --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0fb4375..55e2ee5 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,13 @@ See [`cds-starter-ai`](cds-starter-ai/README.md) for the quickest setup. ## Prerequisites - Java 17+ +- Maven 3.6.3+ - CAP Java 5.0+ -- Node.js 20+ with `@sap/cds-dk` 9+ (for CDS build tooling) -- An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for `cds-feature-ai-core` and `cds-feature-recommendations`) +- An [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding (for production use) -Without the respective service binding, each plugin falls back to a mock or degraded mode for local development. +The build is hermetic: `cds-maven-plugin` downloads its own Node runtime and `cds-feature-ai-core/package.json` pins `@sap/cds-dk`. A globally installed `@sap/cds-dk` is **not** required. + +Without an AI Core binding the plugins fall back to mock implementations for local development. ## Samples @@ -37,7 +39,14 @@ mvn clean install # build all modules mvn test # run unit tests ``` -For per-plugin details (configuration, programmatic API, multi-tenancy behaviour) see the individual module READMEs. For integration tests against a real AI Core instance see [`integration-tests/`](integration-tests/README.md). +For integration tests against a real AI Core instance: + +```bash +cds bind ai-core -2 +cds bind --exec mvn verify +``` + +See [`integration-tests/README.md`](integration-tests/README.md) for the full integration-test layout, including the multi-tenancy profile. ## Support, Feedback, Contributing From 9284d550fb9038d80e5eb056f3df0681ede35ca0 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 19 Jun 2026 08:58:50 +0200 Subject: [PATCH 2/4] ai-core --- cds-feature-ai-core/README.md | 122 +++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/cds-feature-ai-core/README.md b/cds-feature-ai-core/README.md index 9ad83dd..b19acc1 100644 --- a/cds-feature-ai-core/README.md +++ b/cds-feature-ai-core/README.md @@ -4,7 +4,7 @@ Bridges CAP Java applications to [SAP AI Core](https://help.sap.com/docs/sap-ai- ## Features -- **`AICore` CDS Service** - Exposes resource groups, deployments, and configurations as CDS entities with full CRUD support +- **`AICore` CDS Service** - Internal CDS service (annotated `@protocol: 'none'`) modelling resource groups, deployments, and configurations as CDS entities. The plugin does **not** expose this service via OData; it is consumed in-process via `RemoteService`. To expose it externally, project it from your own service or use the `@cap-js/ai` model. - **Multi-Tenancy** - Automatic per-tenant resource group creation/deletion on subscribe/unsubscribe - **Deployment Management** - Auto-creates configurations and deployments for AI Core models with retry and backoff - **Inference Client Factory** - Provides ready-to-use `ApiClient` instances scoped to a deployment for downstream foundation-model SDKs @@ -27,12 +27,14 @@ The plugin auto-registers via Java's `ServiceLoader` mechanism - no code changes ### AI Core Binding -In production, bind an SAP AI Core service instance to your application. Supported methods: +In production, bind an SAP AI Core service instance to your application via a standard service binding (Cloud Foundry / Kubernetes). For local hybrid testing against a real AI Core instance, use the CAP CLI: -- **Service binding** (Cloud Foundry / Kubernetes) -- **Environment variable** `AICORE_SERVICE_KEY` - for local hybrid testing (via `cds bind --exec`) +```bash +cds bind ai-core -2 +cds bind --exec mvn spring-boot:run +``` -Without a binding the plugin registers a mock implementation. +Without a binding the plugin registers a mock implementation suitable for local development. ## Configuration @@ -53,75 +55,91 @@ and the presence of a `DeploymentService`. No additional configuration flag is r ## CDS Service: `AICore` -The plugin registers a CAP service named `AICore` that proxies AI Core REST APIs as CDS entities: +The plugin registers a CAP service named `AICore` that proxies AI Core REST APIs as CDS entities. +The service is internal (`@protocol: 'none'`); use a `RemoteService` lookup to interact with it. ### Entities -| Entity | Operations | Description | -| ----------------------- | -------------------- | ---------------------------------------------------------------- | -| `AICore.resourceGroups` | READ, CREATE, DELETE | Resource group lifecycle, supports label filtering by `tenantId` | -| `AICore.deployments` | READ, CREATE, DELETE | Deployment management with status tracking | -| `AICore.configurations` | READ, CREATE | Configuration management for scenarios and executables | +| Entity | Operations | Description | +| ----------------------- | ---------------------------- | ---------------------------------------------------------------- | +| `AICore.resourceGroups` | READ, CREATE, UPDATE, DELETE | Resource group lifecycle, supports label filtering by `tenantId` | +| `AICore.deployments` | READ, CREATE, UPDATE, DELETE | Deployment management with status tracking; bound action `stop` | +| `AICore.configurations` | READ, CREATE | Configuration management for scenarios and executables | + +## Programmatic API -### Programmatic API +The plugin exposes its functionality through three event contexts emitted on the `AICore` `RemoteService`. This pattern decouples callers from the implementation and makes it easy to override individual steps in tests. ```java -// Get the resource group for the current tenant +import com.sap.cds.feature.aicore.api.DeploymentIdContext; +import com.sap.cds.feature.aicore.api.InferenceClientContext; +import com.sap.cds.feature.aicore.api.ResourceGroupContext; +import com.sap.cds.feature.aicore.generated.cds4j.aicore.AICore_; +import com.sap.cds.services.cds.RemoteService; +import com.sap.cds.feature.aicore.api.ModelDeploymentSpec; // or RPT-1 instead: import com.sap.cds.feature.recommendation.api.RptModelSpec; + +// 1. Obtain the AICore service as a RemoteService +RemoteService aiCore = runtime.getServiceCatalog() + .getService(RemoteService.class, AICore_.CDS_NAME); + +// 2. Resolve the resource group for the current tenant +// (auto-creates the group on first use in multi-tenant mode) ResourceGroupContext rgCtx = ResourceGroupContext.create(); -aiCoreService.emit(rgCtx); -String resourceGroup = rgCtx.getResult(); +aiCore.emit(rgCtx); +String resourceGroupId = rgCtx.getResult(); -// Get (or auto-create) a deployment ID for a model spec in the given resource group +// 3. Resolve (or create) a deployment for a given model spec DeploymentIdContext depCtx = DeploymentIdContext.create(); -depCtx.setResourceGroupId(resourceGroup); -depCtx.setSpec(RptModelSpec.rpt1()); -aiCoreService.emit(depCtx); +depCtx.setResourceGroupId(resourceGroupId); +depCtx.setSpec(new ModelDeploymentSpec( + "foundation-models", "azure-openai", "my-gpt4o-config", List.of(), d -> true)); +// or RPT-1 model instead: +// depCtx.setSpec(RptModelSpec.rpt1()); +aiCore.emit(depCtx); String deploymentId = depCtx.getResult(); -``` - -## Multi-Tenancy -When multi-tenancy is active (detected via `cds.multiTenancy.sidecar.url`): +// 4. Obtain a configured ApiClient for the deployment +InferenceClientContext infCtx = InferenceClientContext.create(); +infCtx.setResourceGroupId(resourceGroupId); +infCtx.setDeploymentId(deploymentId); +aiCore.emit(infCtx); +ApiClient client = infCtx.getResult(); +``` -1. **Subscribe** - Creates resource group `{prefix}{tenantId}` with label `ext.ai.sap.com/CDS_TENANT_ID` -2. **Unsubscribe** - Deletes the tenant's resource group -3. **Isolation** - Each tenant's predictions use their own resource group and deployment +The `ApiClient` returned by `InferenceClientContext` is preconfigured with the AI Core +destination and the deployment URL; use it to construct foundation-model SDK +clients (for example `RptInferenceClient` from `cds-feature-recommendations`). -The lifecycle hooks are registered automatically when multi-tenancy is enabled. +See [`RecommendationConfiguration.java`](../cds-feature-recommendations/src/main/java/com/sap/cds/feature/recommendation/RecommendationConfiguration.java) for a real-world example of this pattern. -## Programmatic Usage +Because `RemoteService` extends `CqnService`, you can also run CDS queries against +the entities directly: ```java -// Obtain the AICore service (registered as a RemoteService by the plugin) -RemoteService aiCoreService = runtime.getServiceCatalog() - .getService(RemoteService.class, "AICore"); +Result rgs = aiCore.run(Select.from(AICore_.CDS_NAME + ".resourceGroups")); +``` -// Use for entity operations (RemoteService extends CqnService) -Result rgs = aiCoreService.run(Select.from("AICore.resourceGroups")); +### Public API -// Resolve resource group, deployment, and inference client via CDS events -ResourceGroupContext rgCtx = ResourceGroupContext.create(); -aiCoreService.emit(rgCtx); -String resourceGroup = rgCtx.getResult(); +The stable public API of this plugin lives in the `com.sap.cds.feature.aicore.api` package. +Implementation classes in sibling packages may change without notice. -DeploymentIdContext depCtx = DeploymentIdContext.create(); -depCtx.setResourceGroupId(resourceGroup); -depCtx.setSpec(RptModelSpec.rpt1()); -aiCoreService.emit(depCtx); -String deploymentId = depCtx.getResult(); +| Type | Purpose | +| ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| [`ResourceGroupContext`](src/main/java/com/sap/cds/feature/aicore/api/ResourceGroupContext.java) | Event context for `resourceGroup` - resolves (and auto-creates in MTX mode) the AI Core resource group for the current/explicit tenant | +| [`DeploymentIdContext`](src/main/java/com/sap/cds/feature/aicore/api/DeploymentIdContext.java) | Event context for `deploymentId` - resolves or creates a deployment matching a `ModelDeploymentSpec` inside a resource group | +| [`InferenceClientContext`](src/main/java/com/sap/cds/feature/aicore/api/InferenceClientContext.java) | Event context for `inferenceClient` - returns an `ApiClient` preconfigured with the inference destination for a given deployment | +| [`ModelDeploymentSpec`](src/main/java/com/sap/cds/feature/aicore/api/ModelDeploymentSpec.java) | Record describing a target deployment (scenario, executable, configuration name, parameter bindings, match predicate) | -InferenceClientContext infCtx = InferenceClientContext.create(); -infCtx.setResourceGroupId(resourceGroup); -infCtx.setDeploymentId(deploymentId); -aiCoreService.emit(infCtx); -ApiClient client = infCtx.getResult(); -``` +## Multi-Tenancy -The `ApiClient` returned via `InferenceClientContext` is preconfigured with the AI Core -destination and the deployment URL; use it to construct foundation-model SDK -clients (for example `RptInferenceClient` from `cds-feature-recommendations`). +When multi-tenancy is active (detected via `cds.multiTenancy.sidecar.url`): -See [`RecommendationConfiguration.java`](../cds-feature-recommendations/src/main/java/com/sap/cds/feature/recommendation/RecommendationConfiguration.java) for a real-world example of this pattern. +1. **Subscribe** - Creates resource group `{prefix}{tenantId}` with label `ext.ai.sap.com/CDS_TENANT_ID` +2. **Unsubscribe** - Deletes the tenant's resource group +3. **Isolation** - Each tenant's predictions use their own resource group and deployment + +The lifecycle hooks are registered automatically when multi-tenancy is enabled. ## Related From 1e14818ffc543662b30efd1355446afc42344a83 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 19 Jun 2026 08:58:55 +0200 Subject: [PATCH 3/4] recommendations --- cds-feature-recommendations/README.md | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/cds-feature-recommendations/README.md b/cds-feature-recommendations/README.md index cb74aa5..e9d65bd 100644 --- a/cds-feature-recommendations/README.md +++ b/cds-feature-recommendations/README.md @@ -50,9 +50,11 @@ Add `@cap-js/ai` to your project's `package.json`: } ``` -Then run `npm install`. The plugin hooks into the CDS compiler and automatically adds the `SAP_Recommendations` navigation property to draft-enabled entities that have value-list fields. +Then run `npm install`. The plugin hooks into the CDS compiler and automatically adds the `SAP_Recommendations` navigation property to draft-enabled entities that have value-list fields. -Since the Java module `cds-feature-ai-core` already provides the `AICore` service CDS model, disable the duplicate model from `@cap-js/ai` in your `.cdsrc.json`: +Both the Java module `cds-feature-ai-core` and the `@cap-js/ai` package ship a CDS model for the `AICore` service. Pick **one** of the following patterns in your `.cdsrc.json`, depending on whether you need to expose AICore via OData: + +**Option A — Java-internal use only (recommended for most apps):** the Java plugin's CDS model is sufficient because the recommendation handler consumes it in-process. Disable the duplicate model from `@cap-js/ai`: ```json { @@ -64,6 +66,18 @@ Since the Java module `cds-feature-ai-core` already provides the `AICore` servic } ``` +**Option B — Expose AICore via OData:** keep the `@cap-js/ai` model and project from it in your own service (see `samples/bookshop/srv/ai-core-service.cds` for an example): + +```json +{ + "requires": { + "AICore": { + "model": "@cap-js/ai/srv/AICoreService" + } + } +} +``` + ## Enabling Recommendations For recommendations to fire on an entity: @@ -72,7 +86,7 @@ For recommendations to fire on an entity: - At least one field must be annotated with a **value list** - The `SAP_Recommendations` navigation property must be present — either via the CDS plugin (see above) or added manually (see below). Without it, predictions are computed but not serialized in OData responses. -Recommendations are triggered for fields annotated with `@Common.ValueList`, `@Common.ValueListWithFixedValues`, or whose association target has `@cds.odata.valuelist`: +Recommendations are triggered for fields annotated with `@Common.ValueList` or `@Common.ValueListWithFixedValues`. The CDS compiler also derives `@Common.ValueList` from `@cds.odata.valuelist` on association targets, so annotating the target entity has the same effect: ```cds @odata.draft.enabled @@ -179,7 +193,7 @@ The following configuration applies to the RPT-1 model implementation. ```yaml cds: - requires: + ai: recommendations: contextRowLimit: 2000 # Max historical rows used as training context (RPT-1) ``` @@ -218,11 +232,13 @@ The following field types are supported by the RPT-1 model implementation: | Temporal | `Date`, `Time`, `DateTime`, `Timestamp` | | Other | `Boolean` | +Equivalent CDS HANA types (e.g. `hana.SMALLINT`, `hana.TINYINT`, `hana.SMALLDECIMAL`, `hana.REAL`, `hana.CHAR`, `hana.NCHAR`, `hana.VARCHAR`, `hana.CLOB`) are also supported. + Binary, vector, and draft system fields are excluded automatically. ## Local Development -Without an AI Core binding, the plugin uses a `MockAIClient` that returns random predictions from existing context rows - useful for UI development without AI Core access. The `@cap-js/ai` CDS plugin is still required for the model enhancement. +Without an AI Core binding, the plugin uses a `MockRecommendationClient` that returns random predictions from existing context rows - useful for UI development without AI Core access. The `@cap-js/ai` CDS plugin is still required for the model enhancement. ## Related From 84da04346bcea46586dbc8a9e38a4165be5b2491 Mon Sep 17 00:00:00 2001 From: Marvin Lindner Date: Fri, 19 Jun 2026 08:58:59 +0200 Subject: [PATCH 4/4] itests --- integration-tests/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/integration-tests/README.md b/integration-tests/README.md index a60e85a..ef662d4 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -25,17 +25,18 @@ mvn verify -Pmtx-integration-tests **Skipping all integration tests (source modules only):** +The `with-integration-tests` profile is active by default at the root. Deactivate it to skip both `integration-tests/` and `coverage-report/`: + ```bash -mvn verify -Pskip-integration-tests +mvn install -P-with-integration-tests ``` ## Profiles | Profile | Scope | Effect | |---------|-------|--------| -| _(default)_ | Root | Builds all modules; runs spring integration tests | +| `with-integration-tests` | Root | **Active by default**; includes `integration-tests/` and `coverage-report/`. Deactivate with `-P-with-integration-tests`. | | `mtx-integration-tests` | `integration-tests/` | Also includes the `mtx-local/srv` module | -| `skip-integration-tests` | Root | Excludes `integration-tests/` and `coverage-report/` entirely | ## Coverage @@ -61,7 +62,7 @@ coverage-report/target/site/jacoco-aggregate/index.html ### Thresholds -Coverage thresholds are enforced by SonarQube in the CI pipeline (80% on new code). +Coverage thresholds are enforced by SonarQube's Quality Gate in the CI pipeline (80%, configured on SonarQube). ### Coverage data sources