Skip to content

refactor: simplify synchronous inference session - #57

Merged
sjvans merged 1 commit into
AISQLiteServicefrom
fix/synchronous-inference-session
Aug 27, 2026
Merged

refactor: simplify synchronous inference session#57
sjvans merged 1 commit into
AISQLiteServicefrom
fix/synchronous-inference-session

Conversation

@sjvans

@sjvans sjvans commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • rebase the synchronous inference adapter onto the model-name provisioning and tokenizer changes from feat: configure and provision local embedding models #51
  • replace the two-layer InferenceSession / SynchronousSessionHandler wrapper with one SynchronousInferenceSession
  • keep onnxruntime-node and native Tensor creation behind the dynamically imported session boundary
  • preserve the exact onnxruntime-node@1.20.1 guard required by the private native API
  • preserve startup cleanup, make disposal idempotent, and reject inference after disposal
  • expose immutable input/output names and filter portable feed descriptors to the model's actual inputs

SQLite user-defined functions must return synchronously, while the public onnxruntime-node session API returns promises. The dedicated adapter therefore remains necessary, but the extra handler abstraction is not.

Validation

  • focused vector, model-provisioning, and knowledge-graph tests: 61 passed
  • real cached MiniLM inference, double disposal, and use-after-disposal pass
  • ESLint 10, Prettier, syntax checks, and git diff --check pass

Stack

Targets AISQLiteService so merging this PR updates #53 directly.

@sjvans
sjvans requested a review from a team as a code owner August 26, 2026 17:10
@hyperspace-pr-bot

Copy link
Copy Markdown
Contributor

Summary

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


refactor: Simplify Synchronous Inference Session

This PR removes the two-layer InferenceSession / SynchronousSessionHandler wrapper pattern and replaces it with a single, self-contained SynchronousInferenceSession class.

Changes

  • lib/vector_embedding/SynchronousInferenceSession.js (renamed from InferenceSession.js):

    • Merges InferenceSession and SynchronousSessionHandler into one SynchronousInferenceSession class
    • The class now directly owns the private ONNX Runtime native binding via a private #session field
    • Constructor accepts a model path or Uint8Array directly (replacing the previous static async create() factory method)
    • dispose() is now synchronous (was previously async)
    • Retains all existing functionality: feed validation, input/output name exposure, synchronous execution, result normalization to public Tensor instances
  • lib/vector_embedding/embedding.js: Updates import to reference the renamed module

  • lib/vector_embedding/model-utils.js: Updates import and replaces await InferenceSession.create(modelPath) with new SynchronousInferenceSession(modelPath)

Motivation

SQLite user-defined functions must return synchronously, while the public onnxruntime-node session API is promise-based. A dedicated synchronous adapter remains necessary, but the intermediate handler abstraction added unnecessary complexity without any benefit. The version guard for the private native API is preserved.

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.49

  • Correlation ID: 1b195f20-a171-11f1-8ad8-04ebc3770916
  • Output Template: Repository PR Template
  • GithubContextProvider: feat: add AI-enabled SQLite service #53
  • File Content Strategy: Full file content
  • Event Trigger: pull_request.opened
  • 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 refactor cleanly collapses the unnecessary two-layer abstraction into a single SynchronousInferenceSession class with a well-encapsulated private binding — a clear improvement in readability and ownership. Two small robustness gaps were flagged: the public name arrays are writable/mutable and could silently corrupt run() state, and the native binding is not guarded against double-dispose or use-after-dispose, which can crash the process. Please address those before merging.

PR Bot Information

Version: 1.29.49

  • Correlation ID: 1b195f20-a171-11f1-8ad8-04ebc3770916
  • LLM: anthropic--claude-4.6-sonnet
  • File Content Strategy: Full file content
  • Event Trigger: pull_request.opened

Comment on lines +40 to +41
this.inputNames = this.#session.inputNames;
this.outputNames = this.#session.outputNames;

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.

Bug: Public inputNames and outputNames properties are mutable and reflect the internal state

this.inputNames and this.outputNames are assigned directly from this.#session.inputNames (likely an array). Because they are plain public properties, any caller can accidentally or maliciously overwrite or mutate them (e.g. session.inputNames = [] or session.inputNames.push('x')), which would break subsequent run() calls that iterate over them without touching the private binding. Freezing the arrays when storing them prevents both mutation and reassignment confusion.

Consider making them read-only:

this.inputNames = Object.freeze([...this.#session.inputNames]);
this.outputNames = Object.freeze([...this.#session.outputNames]);

Or define them as non-writable, non-configurable own properties if the spread/freeze pattern is too heavy.

Suggested change
this.inputNames = this.#session.inputNames;
this.outputNames = this.#session.outputNames;
this.inputNames = Object.freeze([...this.#session.inputNames]);
this.outputNames = Object.freeze([...this.#session.outputNames]);

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

@PDT42 PDT42 Aug 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sounds reasonable to me; Could also just use the getters as was done in #51; We do need to expose these for createFeeds in embeddings.js, although I am not sure if createFeeds really makes a lot of sense ... Looking at that implementation, instead of creating feeds for the model, we could just create feeds hand them to the inference session and stop caring about input and output names on the outside ... I think that might also let us get rid of the Tensor re-export.

Comment on lines +75 to 77
dispose() {
this.#session.dispose();
}

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.

Bug: dispose() does not guard against double-dispose

After this.#session.dispose() is called the native binding object is freed. A second call to dispose() (or a run() after dispose) will dereference the already-freed native object and likely crash the process or produce undefined behaviour. The old SynchronousSessionHandler had the same gap, but since this PR is consolidating the class it is a good moment to fix it.

Consider nulling out the private field after disposal and guarding both run and dispose:

  dispose() {
    this.#session?.dispose();
    this.#session = null;
  }

And in run, add an early guard: if (!this.#session) throw new Error('Session has been disposed.');

Suggested change
dispose() {
this.#session.dispose();
}
dispose() {
this.#session?.dispose();
this.#session = null;
}

Double-check suggestion before committing. Edit this comment for amendments.


Please provide feedback on the review comment by checking the appropriate box:

  • 🌟 Awesome comment, a human might have missed that.
  • ✅ Helpful comment
  • 🤷 Neutral
  • ❌ This comment is not helpful

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sounds reasonable to me.

Comment thread lib/vector_embedding/model-utils.js Outdated
}

const session = await InferenceSession.create(modelPath);
const session = new SynchronousInferenceSession(modelPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think create may have been an intentional mirroring of the ONNX API, but I am not sure if that's something we want to maintain.

@PDT42 PDT42 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think we should update createFeeds and decouple even more.

@sjvans

sjvans commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

I think we should update createFeeds and decouple even more.

createFeeds introduced only by #51

@sjvans
sjvans force-pushed the fix/synchronous-inference-session branch from 20585f2 to 13af14d Compare August 27, 2026 13:18
@sjvans

sjvans commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto merged #51 and addressed the review feedback in 13af14d:

  • createFeeds() now returns portable tensor descriptors and no longer depends on session input names
  • SynchronousInferenceSession.run() filters to actual model inputs and creates native Tensors internally, so embedding.js no longer imports ONNX eagerly or uses the Tensor re-export
  • input/output names are private frozen arrays exposed through getters
  • dispose() is idempotent, run() rejects use after disposal, and model-load failures clean up the native session while preserving the original error
  • retained async create() to mirror the ONNX API

Focused vector/provisioning/knowledge-graph tests pass (61 tests), as do ESLint, Prettier, syntax checks, and git diff --check. Please re-review.

@sjvans
sjvans merged commit 1e0c358 into AISQLiteService Aug 27, 2026
1 check passed
@sjvans
sjvans deleted the fix/synchronous-inference-session branch August 27, 2026 13:37
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.

2 participants