Skip to content

Replace local SLM embeddings with scope.models.embed()#3

Open
kriszyp wants to merge 8 commits into
mainfrom
kris/scope-models-embed
Open

Replace local SLM embeddings with scope.models.embed()#3
kriszyp wants to merge 8 commits into
mainfrom
kris/scope-models-embed

Conversation

@kriszyp

@kriszyp kriszyp commented May 23, 2026

Copy link
Copy Markdown
Member

Summary

Removes the bundled `bge-small-en-v1.5` model and the `harper-fabric-embeddings` + `node-llama-cpp` dependency chain. Embeddings now run through Harper's new `scope.models` API (harper#510) — on Fabric GPU hosts this lands at the shared Ollama process the host-manager spins up (host-manager#86).

Changes

  • `lib/modelCapture.js` (new) — tiny custom plugin exporting `handleApplication(scope)` that stashes the Scope on `globalThis.harperScope`. Necessary because `scope` isn't a global in Harper's runtime and Resource classes have no direct accessor.
  • `lib/embeddings.js` — one-liner that reads `globalThis.harperScope.models.embed(text)` and returns a plain Array for the HNSW index.
  • `config.yaml` — adds `extensionModule: 'lib/modelCapture.js'` so the plugin loads at app boot.
  • `package.json` — `engines.harperdb` bumped to `^5.1`; drops `harper-fabric-embeddings` and the `@node-llama-cpp/*` optional deps.
  • `scripts/download-model.js` and `models/` directory removed.
  • `resources/Chat.js` — architecture diagram label updated from "bge-small-en-v1.5" to "nomic-embed-text via scope.models.embed()".

Status

⚠️ Not yet deployable on Fabric. Provisioning a new tenant on the `harperfast/harper-pro-gpu:5.1.100` image (built from `harper-pro` main) currently fails because `install_usage_license` isn't registered. Fix proposed in HarperFast/harper-pro#223; once that lands and an image is rebuilt, this PR can be tested end-to-end.

Test plan

  • Wait for harper-pro#223 to merge
  • Rebuild `harperfast/harper-pro-gpu:5.1.100` (or a successor tag)
  • Provision a tenant via CM `PUT /Cluster/...` against an HM that has `hm.gpu: true` set
  • Deploy this branch via `npm run deploy` against the new instance
  • Verify `POST /Agent` round-trip with a real message, confirming `scope.models.embed()` calls flow through to Ollama

Generated by Claude Sonnet 4.6.

kriszyp and others added 8 commits July 22, 2026 19:23
Removes the bundled bge-small-en-v1.5 model and the harper-fabric-embeddings
+ node-llama-cpp dependency chain. Embeddings now run through Harper's
new scope.models API (harper#510), which dispatches to whatever embedding
backend the host has configured (Ollama on GPU hosts, Anthropic via OpenAI
gateway, etc.) — zero model lifecycle to manage in the app.

- lib/modelCapture.js: tiny Plugin API hook that stashes Scope on
  globalThis so Resource classes can reach scope.models. Wired up via
  `extensionModule:` in config.yaml.
- lib/embeddings.js: now a one-line wrapper around scope.models.embed().
- package.json: bumps engines.harperdb to ^5.1, drops the SLM-only deps.
- scripts/download-model.js and models/ removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the @anthropic-ai/sdk + @anthropic-ai/vertex-sdk direct calls
with a single scope.models.generate() call. Routes to whatever
backend the host has configured for models.generative.default — vLLM
on Fabric GPU hosts, Anthropic/OpenAI/Ollama elsewhere.

Removes:
- @anthropic-ai/sdk + @anthropic-ai/vertex-sdk deps
- lib/config.js (LLM_PROVIDER / ANTHROPIC_API_KEY / VERTEX_*)
- Anthropic web search tool + pause_turn handling
- Per-token cost calculation (local model = no $)
- Multi-block response stitching (scope.models returns plain content)

Embeddings already used scope.models.embed() (prior commit on this branch).
The Stats table now tracks cacheHits only; totalSaved is held at zero.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UI was breaking on `meta.cost.saved` undefined. Bring back the
cost block but interpret it as a hypothetical — what each generation
WOULD have cost on Claude Sonnet 4.5 ($3/1M input, $15/1M output).
Local GPU compute is effectively $0; the dashboard tracks what we're
saving by self-hosting + the semantic cache.

- Estimated cost computed from token counts vLLM returns
- Stored on each assistant Message (same Float field as before)
- Cache hits credit the original message's cost to Stats.totalSaved
- meta.cost { input, output, total, saved } shape matches what
  Chat.js's buildMeta() reads, so no UI changes needed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
scope.models.generate() only returns { content, finishReason } today —
the backend's usage info isn't surfaced to the caller. Approximate
input/output tokens from character counts so the estimated-Claude-cost
comparator on the Chat dashboard shows non-zero values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs were stacking up:

1. The HNSW search returned matches that satisfy the threshold but didn't
   guarantee distance-ascending iteration order, so the loop's first
   match wasn't necessarily the closest. Collect candidates, compute
   cosine distance explicitly, sort, then pick the closest valid one.

2. The threshold (0.12 cosine distance ≈ 0.88 similarity) was loose
   enough that distinct queries about the same topic would collide
   ("describe the moon landing" vs "tell me about apollo 11"). Tighten
   to 0.05 (≈ 0.95 similarity), which still catches near-paraphrases
   and re-wordings while filtering out merely-related questions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous .find(role==='assistant') walked past any subsequent user
messages and grabbed the next assistant reply even if it was many turns
later in the conversation. With a long conversation like:

  user: Is soccer fun?
  assistant: ...soccer is fun...
  user: Is soccer fun        (cache hit, no new assistant stored)
  user: Is soccer fun??       (cache hit, no new assistant stored)
  user: what is 2 plus 3
  assistant: 2 plus 3 is 5

matching the second "Is soccer fun" returned "2 plus 3 is 5" — the next
assistant message in chronological order, but completely unrelated.

Only accept the IMMEDIATELY-following message as the reply. If the next
message is another user message, skip this candidate and try the next.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Now that the immediate-next-message fix is in, the threshold can be
relaxed to catch more rewordings without serving wrong answers. The
0.05 setting was paranoia about false positives that turned out to
come from the slice/find bug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kriszyp
kriszyp force-pushed the kris/scope-models-embed branch from 95dab14 to f5eed7b Compare July 23, 2026 01:26
@kriszyp
kriszyp marked this pull request as ready for review July 23, 2026 13:55
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.

1 participant