Skip to content

feat: add AI-enabled SQLite service - #53

Open
sjvans wants to merge 25 commits into
mainfrom
AISQLiteService
Open

feat: add AI-enabled SQLite service#53
sjvans wants to merge 25 commits into
mainfrom
AISQLiteService

Conversation

@sjvans

@sjvans sjvans commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two beta database kinds extending @cap-js/sqlite with local AI capabilities:

  • ai-sqlite for a file-based database
  • ai-sqlite:memory for an in-memory database

Both provide:

  • VECTOR_EMBEDDING backed by compatible ONNX encoder models
  • required embedding.model configuration with no default model
  • automatic Hugging Face model discovery through the official @huggingface/hub client
  • warned, on-demand provisioning into <cds.root>/.cds/models/<model>
  • explicit provisioning with npx @cap-js/ai install-model <model> and an optional shared directory
  • metadata-only compatibility checks with npx @cap-js/ai check-model <model>
  • Hugging Face tokenization with first-window truncation
  • local SPARQL_EXECUTE and sparql_table support backed by Oxigraph

Configuration

{
  "cds": {
    "requires": {
      "db": {
        "kind": "ai-sqlite",
        "embedding": {
          "model": "foo/bar",
          "directory": "~/.cds/models"
        }
      }
    }
  }
}

directory is optional. Without it, a missing model is downloaded after a startup warning and cached below .cds/models. With it, the directory is treated as a pre-provisioned local/shared cache. Relative paths resolve from the enclosing CAP project root; absolute and home-relative paths allow reuse across projects. Additional embedding properties remain allowed for extensions.

Model discovery supports public repositories, conventional/root/nested ONNX layouts, common Transformers dimension and input-length aliases, and Sentence Transformers pooling/normalization metadata. Nested exports prefer adjacent metadata with repository-root fallback. Declared incompatible tasks are rejected, while a missing task tag is allowed and assessed using the remaining metadata and installation-time runtime probe.

@cap-js/sqlite, @huggingface/hub, @huggingface/tokenizers, onnxruntime-node, and oxigraph are optional peer dependencies and can be installed as development dependencies for local SQLite use.

Provisioning and validation

check-model reads Hub metadata without downloading model weights. install-model additionally downloads the selected artifacts, loads the model with ONNX Runtime, validates its input/output contract through a probe, and writes embedding.lock.json.

Hub operations use bounded timeouts and retry transient network failures plus HTTP 408, 429, and 5xx responses. The integration with the installed @huggingface/hub client is covered using a fake HTTP transport. Authentication support was deliberately removed; discovery currently targets public repositories only.

Conventional adjacent external-data files are supported (<model>.onnx_data, <model>.onnx.data, and numbered <model>.onnx.data.*). Arbitrary ONNX protobuf external_data paths are not yet parsed and are rejected.

Security and runtime boundaries

Provisioning is trust-on-first-use. The first installation trusts the selected Hugging Face repository and its metadata; the generated lock pins that resolved revision, artifact sizes, and checksums for later integrity checks. It does not authenticate the publisher or make an untrusted model safe. Provisioning loads native tokenizer/ONNX code and performs a probe, so users should select trusted repositories and preferably provision models in a controlled build environment.

Embedding generation is synchronous because SQLite user-defined functions cannot await. Tokenization and inference block the Node.js event loop for each invocation. Input is limited to the first model window; long documents must be split before persistence.

The Oxigraph store is process-local, in-memory, and not transactionally coupled to SQLite. RDF data is lost on disconnect or restart even for file-based ai-sqlite databases.

Included work

Consolidates #46, #49, #51/#55, #57, #58, and #61. It also:

  • removes the unused process-wide embedding singleton in favor of per-service runtimes
  • gives missing optional SQLite and ONNX Runtime peers actionable installation errors
  • resolves CLI model-cache paths from the enclosing CAP project root
  • documents and tests the four-argument SPARQL_EXECUTE compatibility contract
  • documents the trust, blocking-inference, truncation, and ephemeral-store limitations

Validation

  • npm test — 102 tests passed locally
  • npm run lint
  • Prettier check passed
  • npm pack --dry-run --json
  • git diff --check

Larger architectural follow-ups are tracked together in #62. ONNX Runtime version compatibility remains tracked separately in #54.

@sjvans
sjvans requested a review from a team as a code owner August 25, 2026 13:42
@sjvans
sjvans requested a review from BobdenOs August 25, 2026 13:42
@hyperspace-pr-bot

Copy link
Copy Markdown
Contributor

Summary

The following content is AI-generated and provides a summary of the pull request:


feat: Introduce AISQLiteService with ai-sqlite Kind

This PR adds the groundwork for an AI-enabled SQLite service by introducing a new AISQLiteService class and registering it as a new ai-sqlite CDS kind.

What's changed:

  • lib/sqlite/AISQLiteService.js (new file): Extends SQLiteService from @cap-js/sqlite with an AISQLiteService class. It overrides:

    • init() – entry point for future AI-specific service initialization
    • factory getter – wraps the connection factory's create method to allow augmenting database connections
    • CQN2AISQLite – extends the SQL generation class with a placeholder for future AI-specific SQL functions
  • package.json: Registers the new ai-sqlite kind in cds.env, pointing to the AISQLiteService implementation with an in-memory SQLite database as default credentials. This allows consumers to use kind: ai-sqlite in their CDS configuration.

ℹ️ In the future, kind: sqlite could be overridden directly, but that would require prioritized cds.env resolution.

Have you...

  • Added relevant entry to the change log?

  • 🔄 Regenerate and Update Summary
  • ✏️ Insert as PR Description (deletes this comment)
  • 🗑️ Delete comment
PR Bot Information

Version: 1.29.44

  • Correlation ID: dbae24b0-a08a-11f1-8798-3c02d2a4f852
  • Event Trigger: pull_request.opened
  • Output Template: Repository PR Template
  • File Content Strategy: Full file content
  • LLM: anthropic--claude-4.6-sonnet
  • Summary Prompt: Default Prompt

@hyperspace-pr-bot hyperspace-pr-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR introduces a thin AISQLiteService extension and a new ai-sqlite kind registration, but has two substantive issues: the factory getter mutates the shared parent factory object on every call without a guard, risking infinite recursion on repeated access; and @cap-js/sqlite is imported unconditionally without being declared as a peer or optional dependency, which will cause a module-not-found error for consumers who don't have it installed.

PR Bot Information

Version: 1.29.44

  • Correlation ID: dbae24b0-a08a-11f1-8798-3c02d2a4f852
  • Event Trigger: pull_request.opened
  • File Content Strategy: Full file content
  • LLM: anthropic--claude-4.6-sonnet

Comment thread lib/sqlite/AISQLiteService.js
BobdenOs
BobdenOs previously approved these changes Aug 25, 2026
* Sync wrapper for Sqlite for using ONNX embeddings function

* fix imple and add tests

* add semantic tests

* use LOG

* test 4 params

* small fixes

* fix: address PR bot comments - add division by zero guard and fix lint errors

* chore: run prettier formatting

* fix tests

* dix duplicated function registration

* more frixes

* export vector_embedding directly

* rem unused

* refactor

* refactor

* linter

* remove comment

* Update CHANGELOG.md

* Update README.md

* export embeddings

* fix: add missing exports paths for CDS plugin loading

The exports field was blocking CDS from loading:
- cds-plugin.js (plugin registration)
- srv/* (AICoreService, MockAICoreService)
- lib/* (internal modules)

Without these exports, Node.js blocks access to these paths,
causing "Navigation property SAP_Recommendations is not defined"
errors because the CSN enhancement never registers.

* fix: include cds-plugin.js in npm package files

Without this, npm pack excludes cds-plugin.js from the tarball,
breaking plugin auto-registration when installed as a dependency.
This caused MTX integration tests to fail with 'ResourceGroup undefined'
because the plugin never loaded.

* feat: integrate embeddings with ai-sqlite

* fix: harden local embedding runtime

---------

Co-authored-by: Sebastian Van Syckel <sebastian.van.syckel@sap.com>
Comment thread lib/vector_embedding/embedding.js Outdated
Comment thread tests/vector.test.js Outdated
Comment thread lib/vector_embedding/InferenceSession.js Outdated
Comment thread lib/vector_embedding/InferenceSession.js Outdated
Comment thread lib/vector_embedding/InferenceSession.js Outdated
Comment thread lib/vector_embedding/model-utils.js Outdated
Comment thread lib/vector_embedding/model-utils.js Outdated
Comment thread CHANGELOG.md Outdated
Comment thread package.json
Comment thread lib/sqlite/AISQLiteService.js
sjvans and others added 3 commits August 27, 2026 14:31
* fix: truncate embeddings to one model window

* Update lib/vector_embedding/embedding.js

Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>

---------

Co-authored-by: hyperspace-pr-bot[bot] <209611008+hyperspace-pr-bot[bot]@users.noreply.github.com>
* Add triple store support for SQLiteService to match HANA capabilities

* fix: harden SQLite knowledge graph loading

* Apply suggestion from @sjvans

---------

Co-authored-by: Sebastian Van Syckel <sebastian.van.syckel@sap.com>
Co-authored-by: sjvans <30337871+sjvans@users.noreply.github.com>
* feat: support configurable local embedding models

* feat: provision embedding models by name (#55)

* feat: add explicit embedding model provisioning

* feat: support lazy embedding model provisioning

* docs: explain embedding model provisioning

* fix: require explicit embedding model

* refactor: require provisioned embedding models

* feat: provision embedding models by name

* fix: make tokenizer an optional peer

---------

Co-authored-by: Sebastian Van Syckel <sebastian.van.syckel@sap.com>
Co-authored-by: sjvans <30337871+sjvans@users.noreply.github.com>
@sjvans sjvans changed the title feat: AISQLiteService feat: add AI-enabled SQLite service Aug 27, 2026
Comment thread CHANGELOG.md Outdated
@sjvans
sjvans requested a review from a team as a code owner August 27, 2026 17:52
* feat: discover Hugging Face embedding models

* feat: add embedding model compatibility check

* docs: clarify local SQLite model setup

* refactor: validate embedding models with ONNX Runtime

* refactor: remove Hugging Face token support

* address model discovery review feedback
Comment thread lib/vector_embedding/model-discovery.js
@sjvans

sjvans commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Re-review (post #61 merge, @ 549df5b)

Follow-up to the earlier review. Net: the feedback was addressed substantively and — importantly — honestly. Nearly every prior finding is either fixed in code or deferred to #62 with a matching limitation actually written into the README/CHANGELOG, not silently dropped. One real gap remains (below).

Fixed in code

  • HF_TOKEN leak to arbitrary hub host (the hard blocker) — resolved. No HF_TOKEN/accessToken/Authorization/Bearer remains anywhere in lib or bin; the auto-attach was removed outright and the fix is now locked in by tests ('accessToken' in options === false and no authorization header). Worth calling out in the PR body: gated/private-model support advertised earlier is dropped rather than allowlisted — a defensible beta scope cut, but a behavior change consumers should know about.
  • Integrity docs no longer oversell — README now states the lock is trust-on-first-use and "does not authenticate the publisher or make an untrusted model safe." 👍
  • Discovery timeouts added — hub ops get 30s + retry; download path keeps its AbortController timeout. No unbounded discovery I/O left.
  • Dead global-singleton embedding path deleted (index.js gone); runtime is strictly per-service-instance now.
  • Friendly missing-peer errors via load-onnx-runtime.js / load-sqlite.js (+ the hub loader) instead of raw MODULE_NOT_FOUND.
  • CLI walks up to the CAP project root instead of process.cwd(), so subdirectory installs land where the runtime reads them.
  • origin/hubUrl unified across discovery, download, CLI, and embedding.
  • HF-hub contract test runs the real installed @huggingface/hub against a fake HTTP transport — a good middle ground between mock-only and flaky live-network.

Deferred to #62 — and genuinely documented

Event-loop-blocking inference, provenance-beyond-TOFU, RDF persistence/scoping, generic ONNX external_data parsing, and the onnxruntime-node exact-pin (#54, also runtime-enforced) are each stated as known limitations in README/CHANGELOG. The SPARQL_EXECUTE two-? placeholders turned out to be intentional (documented HANA-compat output placeholders). Silent first-window truncation is documented as design (a one-time debug log on truncation would still be a cheap courtesy).

The one thing I'd fix before merge

Unbounded in-memory buffering in discovery / check-model is neither fixed nor documented. Provisioning downloads are byte-capped, but the discovery path still does Buffer.from(await file.arrayBuffer()) with no size limit (huggingface-hub.js getFile, used by the JSON/file fetchers and the integrity-hash fallback in model-discovery.js). A hostile or accidentally-huge repo can OOM the discovery / check-model / startup-discovery process. This maps to #62 item (e) "resource controls" — but unlike the other five deferrals, (e) is documented nowhere.

Minimum: add a line to the README limitations block (and #62) naming the missing discovery-side size cap. Better: apply the same content-length precheck + streaming cap the download path already uses to metadata/JSON fetches.

Minor residual (not a blocker)

normalizeHubUrl accepts any non-empty string, so http:// or an arbitrary host passes. With no credential attached and SHA-256 verification of downloaded bytes, the residual risk is just plaintext transport / redirection to a host that would still have to match pinned checksums. A new URL() parse + https:-by-default check would be cheap hardening, but I wouldn't gate on it.

Bottom line: ship-ready as a beta once the discovery-buffering OOM gap is either capped or at least documented under #62(e). The #61 merge left no scars (no dangling singleton refs, duplicated logic, or conflict markers). Good, non-cosmetic response to the review — the TOFU doc rewrite and the token-removal tests are the tells.

Comment thread lib/vector_embedding/model-discovery.js
Comment thread lib/vector_embedding/model-discovery.js
Comment thread CHANGELOG.md
- Adds `npx @cap-js/ai install-model <model>` with an optional shared model-cache root
- Adds metadata-only `npx @cap-js/ai check-model <model>` to report likely model compatibility before downloading model artifacts; installation remains the definitive runtime validation
- Uses the optional `@huggingface/tokenizers` peer dependency and truncates long input to the first model input window
- Requires `model`, supports an optional relative, absolute, or home-relative `directory`, and allows additional embedding properties for extensions; discovered metadata remains in the provisioned lock

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too complicated.

Comment thread CHANGELOG.md
Comment on lines +19 to +23
- Bounds Hugging Face discovery requests with timeouts and retries transient network and server failures
- Prefers metadata adjacent to nested ONNX exports and supports conventional adjacent external-data sidecars
- Rejects incompatible decoder and masked-language-model tasks instead of guessing embedding semantics
- Supports both 3-parameter `(text, text_type, model_and_version)` and 4-parameter variants with `remote_source`
- Compatible with `SAP_GXY.20250407` and `SAP_GXY.20240715` model versions

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO irrelevant or inaccurate, no?

Comment thread CHANGELOG.md
- Compatible with `SAP_GXY.20250407` and `SAP_GXY.20240715` model versions
- Runs synchronously as required by SQLite user-defined functions and therefore blocks the Node.js event loop during tokenization and inference
- Embeds one model input window; applications split long documents and store one vector per chunk
- Uses trust-on-first-use provisioning: the lock pins the first resolved Hugging Face revision and checksums for later integrity checks but does not authenticate the model publisher

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO also irrelevant.

Comment thread CHANGELOG.md
- Supports both 3-parameter `(text, text_type, model_and_version)` and 4-parameter variants with `remote_source`
- Compatible with `SAP_GXY.20250407` and `SAP_GXY.20240715` model versions
- Runs synchronously as required by SQLite user-defined functions and therefore blocks the Node.js event loop during tokenization and inference
- Embeds one model input window; applications split long documents and store one vector per chunk

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat redundant with L16.

Comment thread package.json Outdated
Comment thread README.md

These packages are optional peer dependencies of `@cap-js/ai` and are required only for the corresponding local SQLite capabilities. `@huggingface/hub` is required for explicit or ad-hoc model provisioning. Both database kinds currently require exactly `onnxruntime-node` 1.20.1 because synchronous SQLite functions need a version-specific native runtime API.

Tokenization, ONNX inference, pooling, and normalization run synchronously for each `VECTOR_EMBEDDING` call. SQLite user-defined functions cannot await, so inference blocks the Node.js event loop until it completes. The feature is intended for local development and low-volume use; server workloads should precompute or batch embeddings outside SQL.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... server workloads should precompute or batch embeddings outside SQL.

We want people to use SQL, but backed by HANA, no?

Comment thread README.md
Comment on lines +233 to +246
```json
{
"cds": {
"requires": {
"db": {
"kind": "ai-sqlite",
"embedding": {
"model": "foo/bar"
}
}
}
}
}
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example does not include the model-cache root, the headline promises.

@sjvans

sjvans commented Aug 28, 2026

Copy link
Copy Markdown
Contributor Author

Code-level review follow-up is in 6b401b9, 758e105, and 39b72c5:\n\n- removed pretest/pretest:hybrid lifecycle hooks; the explicitly invoked test and test:hybrid scripts now provision the test model themselves, which also works with ignore-scripts=true\n- made test provisioning deterministic with a checked-in descriptor for the pinned MiniLM revision, avoiding anonymous Hugging Face discovery API rate limits while still downloading and verifying the real artifacts\n- bounded Hugging Face responses to 64 MiB using Content-Length/Content-Range prechecks plus an enforced streaming limit\n- replaced the unbounded arrayBuffer conversion with bounded streaming\n- require HTTPS Hub URLs and reject embedded credentials, queries, and fragments\n- stopped treating tokenizer_config.json max_length as an authoritative model input window\n- documented in code and tested why unsupported Sentence Transformers pipeline stages must be rejected\n\nREADME and CHANGELOG feedback is intentionally left open for the planned documentation rewrite.\n\nVerification: npm --ignore-scripts test passes all 107 tests; lint and targeted Prettier checks pass.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants