-
Notifications
You must be signed in to change notification settings - Fork 5
feat: add AI-enabled SQLite service #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
2e7c7ea
AISQLiteService
sjvans b8aa8d4
prettier
sjvans bc0d084
feat: sync wrapper for ONNX embeddings function (#46)
vkozyura 8f8399b
chore: address ai-sqlite review feedback
sjvans 14e8fd6
test: use CDS 9-compatible sqlite version
sjvans 1dc7a88
fix: keep sqlite as an optional peer
sjvans 7b407bc
ci: align sqlite peer for CDS 9 matrix
sjvans d92e446
fix: align sqlite peer with CDS 9 override
sjvans a69ca7a
fix: install supported sqlite version for development
sjvans b037f45
fix: test ai-sqlite across CDS versions
sjvans 4f44634
fix: truncate embeddings to one model window (#58)
sjvans 326bed6
feat: triple store support for `@cap-js/sqlite` (#49)
BobdenOs 16cf65c
feat: configure and provision local embedding models (#51)
BobdenOs 1e0c358
refactor: simplify synchronous inference session (#57)
sjvans 0e81419
Apply suggestion from @sjvans
sjvans d3c1113
ai-sqlite:memory
sjvans 8a757fa
Merge branch 'AISQLiteService' of https://github.com/cap-js/ai into A…
sjvans 5a646ef
fix: allow additional embedding configuration
sjvans 997c701
Merge branch 'main' into AISQLiteService
sjvans d619bcc
address ai-sqlite review feedback
sjvans 549df5b
feat: discover compatible Hugging Face embedding models (#61)
sjvans 6b401b9
fix: address ai-sqlite review feedback
sjvans 758e105
test: provision embedding model explicitly
sjvans 69c20db
fix: tolerate transient Hub rate limits
sjvans 39b72c5
test: pin embedding model fixture
sjvans ed39a07
docs: streamline AI plugin onboarding
sjvans 67c5c8a
style: format Bookshop service
sjvans bef17b5
test: skip local embedding sample on HANA
sjvans a1983e5
docs: keep embedding sample local
sjvans 38644da
docs: clarify native HANA vector support
sjvans 2f240b2
chore: integrate review feedback (#63)
PDT42 b9c0adf
Update tests/vector.test.js
sjvans ab78c37
feat: extend standard sqlite kinds
sjvans 83be28b
style: format embedding configuration
sjvans a5b9b97
fix: inherit sqlite memory pool settings
sjvans b392d5d
fix: harden asymmetric embedding model support (#64)
sjvans b7df0ff
refactor: inherit AI sqlite memory configuration
sjvans 9490509
style: format sqlite inheritance test
sjvans be35eb3
test: gate local embedding sample on AI sqlite
sjvans File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: | ||
|
|
||
| ```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. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| ``` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.