Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
2e7c7ea
AISQLiteService
sjvans Aug 25, 2026
b8aa8d4
prettier
sjvans Aug 25, 2026
bc0d084
feat: sync wrapper for ONNX embeddings function (#46)
vkozyura Aug 25, 2026
8f8399b
chore: address ai-sqlite review feedback
sjvans Aug 26, 2026
14e8fd6
test: use CDS 9-compatible sqlite version
sjvans Aug 26, 2026
1dc7a88
fix: keep sqlite as an optional peer
sjvans Aug 26, 2026
7b407bc
ci: align sqlite peer for CDS 9 matrix
sjvans Aug 26, 2026
d92e446
fix: align sqlite peer with CDS 9 override
sjvans Aug 26, 2026
a69ca7a
fix: install supported sqlite version for development
sjvans Aug 26, 2026
b037f45
fix: test ai-sqlite across CDS versions
sjvans Aug 26, 2026
4f44634
fix: truncate embeddings to one model window (#58)
sjvans Aug 27, 2026
326bed6
feat: triple store support for `@cap-js/sqlite` (#49)
BobdenOs Aug 27, 2026
16cf65c
feat: configure and provision local embedding models (#51)
BobdenOs Aug 27, 2026
1e0c358
refactor: simplify synchronous inference session (#57)
sjvans Aug 27, 2026
0e81419
Apply suggestion from @sjvans
sjvans Aug 27, 2026
d3c1113
ai-sqlite:memory
sjvans Aug 27, 2026
8a757fa
Merge branch 'AISQLiteService' of https://github.com/cap-js/ai into A…
sjvans Aug 27, 2026
5a646ef
fix: allow additional embedding configuration
sjvans Aug 27, 2026
997c701
Merge branch 'main' into AISQLiteService
sjvans Aug 27, 2026
d619bcc
address ai-sqlite review feedback
sjvans Aug 28, 2026
549df5b
feat: discover compatible Hugging Face embedding models (#61)
sjvans Aug 28, 2026
6b401b9
fix: address ai-sqlite review feedback
sjvans Aug 28, 2026
758e105
test: provision embedding model explicitly
sjvans Aug 28, 2026
69c20db
fix: tolerate transient Hub rate limits
sjvans Aug 28, 2026
39b72c5
test: pin embedding model fixture
sjvans Aug 28, 2026
ed39a07
docs: streamline AI plugin onboarding
sjvans Aug 30, 2026
67c5c8a
style: format Bookshop service
sjvans Aug 30, 2026
bef17b5
test: skip local embedding sample on HANA
sjvans Aug 30, 2026
a1983e5
docs: keep embedding sample local
sjvans Aug 30, 2026
38644da
docs: clarify native HANA vector support
sjvans Aug 31, 2026
2f240b2
chore: integrate review feedback (#63)
PDT42 Aug 31, 2026
b9c0adf
Update tests/vector.test.js
sjvans Aug 31, 2026
ab78c37
feat: extend standard sqlite kinds
sjvans Aug 31, 2026
83be28b
style: format embedding configuration
sjvans Aug 31, 2026
a5b9b97
fix: inherit sqlite memory pool settings
sjvans Aug 31, 2026
b392d5d
fix: harden asymmetric embedding model support (#64)
sjvans Sep 1, 2026
b7df0ff
refactor: inherit AI sqlite memory configuration
sjvans Sep 1, 2026
9490509
style: format sqlite inheritance test
sjvans Sep 1, 2026
be35eb3
test: gate local embedding sample on AI sqlite
sjvans Sep 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions .docs/ai-core.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# SAP AI Core integration

The plugin exposes SAP AI Core resource groups, deployments, and configurations through the `AICore` CAP service. It also manages the resource groups and SAP-RPT-1 deployments used by recommendations.

> [!IMPORTANT]
> In multitenant applications with an MTX sidecar, include `@cap-js/ai` in the sidecar so tenant lifecycle events can manage SAP AI Core resources.

## Service binding

Production use requires an [SAP AI Core](https://help.sap.com/docs/sap-ai-core) service binding. A Cloud Foundry deployment can declare it like this:

```yaml
modules:
- name: incidents-srv
type: nodejs
path: gen/srv
requires:
- name: incidents-ai-core

resources:
- name: incidents-ai-core
type: org.cloudfoundry.managed-service
```

A resource group is SAP AI Core's isolation boundary: it scopes deployments, configurations, and executions so that tenants cannot access each other's resources.
The plugin provisions one resource group per tenant in multitenant applications, and uses a single resource group otherwise.

Single-tenant applications use the `default` resource group unless configured otherwise:
Comment thread
PDT42 marked this conversation as resolved.

```json
{
"cds": {
"requires": {
"AICore": {
"resourceGroup": "CUSTOM_RESOURCE_GROUP"
}
}
}
}
```

## Query API

```js
const aiCore = await cds.connect.to('AICore');
const { resourceGroups, deployments, configurations } = aiCore.entities;

await aiCore.run(SELECT.from(resourceGroups));
await aiCore.run(SELECT.from(resourceGroups).where({ tenantId: cds.context.tenant }));
await aiCore.run(
SELECT.from(deployments).where({
'resourceGroup.resourceGroupId': resourceGroups[0].resourceGroupId
})
);
```

Supported `cds.ql` operations:

| Operation | `resourceGroups` | `deployments` | `configurations` |
| ---------------------- | ---------------- | ------------- | ---------------- |
| `READ` list and single | yes | yes | yes |
| `CREATE` | yes | yes | yes |
| `UPDATE` | yes | yes | no |
| `UPSERT` | yes | yes | no |
| `DELETE` | yes | yes | no |
| `limit` | yes | yes | yes |
| `search` | no | no | yes |

Filters are limited to simple equality checks:

- `resourceGroups`: `tenantId`, `resourceGroupId`
- `deployments`: `id`, `resourceGroup.resourceGroupId`
- `configurations`: `resourceGroup.resourceGroupId`

## Helper methods

```js
const aiCore = await cds.connect.to('AICore');
const { resourceGroups, deployments } = aiCore.entities;

const resourceGroupId = await aiCore.resourceGroupForTenant(cds.context.tenant);
const predictions = await aiCore.predictRowColumns(/* SAP-RPT-1 payload */);
const deploymentId = await aiCore.rpt1DeploymentId(resourceGroups, { resourceGroupId });
await aiCore.stop(deployments, { id: deploymentId });
```

`rpt1DeploymentId` creates an SAP-RPT-1 deployment when the resource group does not already have one.
57 changes: 57 additions & 0 deletions .docs/knowledge-graph.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Local knowledge graph

> [!WARNING]
> The local knowledge graph is experimental and intended only for local development. Its API and storage model may change incompatibly.

Install the optional peer dependency:

```sh
npm add -D oxigraph
```

With `@cap-js/ai` installed, both `sqlite` and `sqlite:memory` expose a process-local Oxigraph store through `SPARQL_EXECUTE` and `sparql_table`.

## Load RDF

Use the HANA-compatible procedure shape:

```sql
CALL SPARQL_EXECUTE(
'LOAD <db/data/catalog.ttl> INTO GRAPH <https://example.test/catalog>',
'',
?,
?
)
```

The final two `?` tokens are required output placeholders, not input bindings. The local implementation accepts literal SPARQL and header strings only. `LOAD` returns no result.

Files must be inside the CAP project. Turtle and compressed Turtle inputs are supported; unsafe paths, symlinks escaping the project, malformed RDF, and unsupported formats are rejected.

## Query RDF

Procedure-style queries return a serialized result in `RESPONSE`:

```sql
CALL SPARQL_EXECUTE(
'SELECT ?subject WHERE { ?subject ?predicate ?object }',
'accept:application/sparql-results+json',
?,
?
)
```

Use `sparql_table` from CQN when rows should be projected into a query result:

```js
await db.run({
SELECT: {
from: cds.ql.func(
'sparql_table',
'SELECT ?subject ?predicate WHERE { ?subject ?predicate ?object }'
)
}
});
```

The RDF store lives in memory and is tied to the database service connection. Its contents are lost on disconnect or process restart even with file-based `sqlite`, and RDF updates are not transactionally coupled to SQLite changes.
50 changes: 50 additions & 0 deletions .docs/model-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Choosing a local embedding model

> [!WARNING]
> Local model execution and provisioning are experimental development features. A model that installs successfully is not automatically appropriate for an application's data, languages, or quality requirements.

## Start with a focused list

Use the [trending Apache-2.0 sentence-similarity models with ONNX artifacts](https://huggingface.co/models?pipeline_tag=sentence-similarity&library=onnx&license=license:apache-2.0&sort=trending) as a discovery starting point:

- `sentence-similarity` favors sentence-level semantic embeddings rather than text generation or token classification.
- `onnx` indicates that the repository advertises an ONNX export that can potentially run locally.
- `apache-2.0` narrows the list to a permissive license commonly suitable for experimentation. Always review the model card and license for your own use.
- `trending` makes active, commonly used candidates easier to find; it is not a quality ranking.

The Hub filters are not a compatibility guarantee. Repositories can contain several ambiguous exports, unsupported processing stages, or incomplete metadata.

## As big as necessary, as small as possible

For local development, start with the smallest model that meets the application's language, domain, and retrieval-quality needs. Smaller models download and start faster, use less memory, and block SQLite for less time. Move to a larger model only when measurements on representative data show that the smaller one is insufficient.

The current default and Bookshop sample use [`sentence-transformers/all-MiniLM-L6-v2`](https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2). It was selected only because, at the time of selection, it was the most-downloaded reasonably small candidate matching the sentence-similarity, ONNX, and Apache-2.0 filters. This is not a recommendation for any application or for production, and the default may change at any time while local embeddings remain experimental. Configure the model explicitly when that choice must stay stable.

Ideally, identical embedding-models should be employed during development and in production. However, this is not technically required and hard to realize, due to model availability: I.e. local ONNX models and SAP HANA native models will differ. For reference, SAP HANA Cloud's [`SAP_GXY.20250407` is based on RoBERTa base](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-vector-engine-guide/vector-embedding-function-vector#available-models-without-remote-source). A local MiniLM vector has different dimensions and semantics and is not interchangeable with a HANA-generated vector. Never mix vectors from different model setups; regenerate embeddings after a change.

## Check before installing

```sh
npx @cap-js/ai check-model owner/model
npx @cap-js/ai install-model owner/model
```

`check-model` examines repository metadata and reports likely compatibility without downloading the model weights. `install-model` performs the definitive check by downloading, loading, and probing the selected ONNX graph.

## Supported model contract

Discovery currently requires:

- a public Hugging Face repository whose declared task is absent, `sentence-similarity`, or `feature-extraction`
- an immutable repository revision
- an unambiguously selectable ONNX graph, preferring `onnx/model.onnx` and then `model.onnx`
- `tokenizer.json`, `tokenizer_config.json`, and `config.json` beside the graph or at repository root
- an embedding dimension in a common Transformers field such as `hidden_size`, `n_embd`, `d_model`, or `dim`
- a determinable input limit
- an unambiguous Sentence Transformers pipeline of Transformer, Pooling, and optional Normalize stages
- mean or CLS pooling
- when a model declares [prompts it was trained with](https://sbert.net/examples/sentence_transformer/training/prompts/README.html) (typically `query` and `document`): Metadata that maps these prompts to [SAP HANA `VECTOR_EMBEDDING`s `text-type`](https://help.sap.com/docs/hana-cloud-database/sap-hana-cloud-sap-hana-database-sql-reference-guide/vector-embedding-function-vector)

The ONNX graph must accept rank-2 `int64` `input_ids`; it may also accept `attention_mask` and `token_type_ids`. A token-level output used for pooling must be a floating-point rank-3 tensor whose final dimension matches the discovered model dimension.

Nested exports are supported when the model and its companion files are unambiguous. Conventional adjacent external-data names are supported. Other module chains, ambiguous pooling, decoder outputs, incompatible task tags, missing metadata, or arbitrary external-data paths are rejected instead of guessed.
72 changes: 72 additions & 0 deletions .docs/recommendations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Recommendations

`@cap-js/ai` uses [SAP-RPT-1](https://help.sap.com/docs/sap-ai-core/generative-ai/sap-rpt-1) through SAP AI Core to add recommendations to CAP draft entities.

## Selecting fields

Fields with `@Common.ValueList` or associations whose targets have `@cds.odata.valuelist` are included automatically. Disable recommendations for an individual field with `@UI.RecommendationState: 0`; dynamic expressions are supported as well.

```cds
annotate Books with {
genre @UI.RecommendationState: (price > 200 ? 0 : 1);
}
```

Scalar fields without a value help can opt in with `@UI.RecommendationState`:

```cds
entity CalibrationData : cuid {
measuringRangeMin : Decimal(16, 6) @UI.RecommendationState;
measuringRangeMax : Decimal(16, 6) @UI.RecommendationState;
description : String @UI.RecommendationState;
}
```

Numeric scalar fields without value helps use the `regression` task type. Other fields use `classification`. A numeric field with a value help remains a classification target.

> [!NOTE]
> SAP Fiori Elements does not yet render recommendations for scalar fields without a value help. The backend provides them, but the client currently requests and displays recommendation fields only when they have `@Common.ValueList` or `@Common.ValueListWithFixedValues`.

## Generated service shape

For each draft-enabled entity with recommendable fields, the plugin adds:

- `@UI.Recommendations: { '=': 'SAP_Recommendations' }`
- a virtual `<Entity>_Recommendations` companion entity
- one recommendation array per included field

Each recommendation contains `RecommendedFieldValue`, `RecommendedFieldDescription`, `RecommendedFieldScoreValue`, and `RecommendedFieldIsSuggestion`. Fiori Elements uses the first suggestion as the soft-fill default.

Recommendations are calculated when a draft-entity `READ` expands `SAP_Recommendations`. Active-entity reads return no recommendations, and reads during `draftActivate` are skipped.

## Prediction context and data handling

The plugin sends up to 2,000 active rows of the same entity to SAP-RPT-1. Only rows for which every recommendation target is non-null are included. The active version of the current draft is replaced by the draft row containing `[PREDICT]` placeholders.

The following elements are removed from the context:

- `createdAt`, `createdBy`, `modifiedAt`, and `modifiedBy`
- `cds.LargeBinary` and `cds.Vector` elements
- fields excluded by `@UI.RecommendationState: 0` or a matching dynamic expression

> [!IMPORTANT]
> All other selected columns are forwarded to SAP AI Core. Review the entity model and exclude sensitive fields explicitly.

There is no sampling or `ORDER BY`; for entities with more than 2,000 qualifying rows, the database determines which rows are used. If `@Common.Text` is configured, the plugin performs an additional lookup to populate the recommendation description.

## SAP-RPT-1 lifecycle

The first prediction for a resource group creates an `sap-rpt-1-small` deployment in scenario `foundation-models` when none exists. The plugin waits for the deployment to reach `RUNNING` and reuses it afterward.

Single-tenant applications use the configured `AICore.resourceGroup`, which defaults to `default`. Multitenant applications create a resource group per tenant during subscription and delete it during unsubscription.

## Local development

Without an SAP AI Core binding, the plugin uses `MockAICoreService`. It returns the first non-null value for each target column. This is useful for UI smoke tests but is not a quality signal.

To use a real deployment locally, bind the application and start it with the `hybrid` profile:

```sh
cds bind <your-aicore-instance>
cds watch --profile hybrid
```
Loading
Loading