Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ jobs:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bunx tsc --noEmit
- run: bun test ./src
- run: bunx prettier --check src
- run: bun run test
- run: bunx prettier --check src tests
- run: bun run build
- name: Import packed dist with Node
run: |
Expand Down
16 changes: 11 additions & 5 deletions IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,14 @@ package depends only on the published surface).

## Tests

`bun run test` is `bun test ./src`. Coverage is the client contract: empty
input, URL and body knobs, auth header presence, batch split and order,
`batchSize` rejection, malformed / short / duplicate-index replies,
index-not-position placement, 429 retry, 401 abort, `probeEmbedDims`,
overridable `Retry-After`.
`bun run test` runs `bun test ./src ./tests`.

- `src/embed.test.ts` — units on `@intx/inference-testing` fakes: index
placement across batches, short reply, base64 decode.
- `tests/retry.test.ts` — 429 retried once after `Retry-After` on the
harness clock; 401 rejects without retry.
- `tests/base64.test.ts` — `base64` and `float` agree over a real HTTP stub.
- `live/embeddings.test.ts` (`bun run test:live`, not in `test` or CI) —
round trip against a real `/v1/embeddings` (`EMBEDDING_E2E_BASE_URL`,
required; `EMBEDDING_E2E_MODEL`, default `nomic-embed-text`).
Skips when unset or unreachable.
49 changes: 49 additions & 0 deletions live/embeddings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, test } from "bun:test";

import { embedTexts, probeEmbedDims } from "../src/index";

// Any OpenAI-compatible `/v1/embeddings` server. Skips unless
// EMBEDDING_E2E_BASE_URL is set, so it never loads a model on this machine.
const baseURL = process.env.EMBEDDING_E2E_BASE_URL;
const model = process.env.EMBEDDING_E2E_MODEL ?? "nomic-embed-text";

// Skips unless the server answers and lists the model.
const reachable =
baseURL !== undefined &&
(await fetch(`${baseURL}/models`, {
signal: AbortSignal.timeout(1_000),
}).then(
async (res) => res.ok && (await res.text()).includes(`"${model}`),
() => false,
));
const config = { baseURL: baseURL ?? "", model };

function cosine(a: number[], b: number[]): number {
let dot = 0;
let na = 0;
let nb = 0;
a.forEach((x, i) => {
const y = b[i] ?? 0;
dot += x * y;
na += x * x;
nb += y * y;
});
return dot / Math.sqrt(na * nb);
}

describe.skipIf(!reachable)(`live /v1/embeddings at ${config.baseURL}`, () => {
test("embeds three texts in input order at the probed width", async () => {
const texts = ["the cat sat", "quarterly revenue grew", "rust borrowck"];
const dims = await probeEmbedDims(config);
const vectors = await embedTexts(texts, config);

expect(vectors).toHaveLength(texts.length);
for (const vector of vectors) expect(vector).toHaveLength(dims);

// Order: each batched vector matches that text embedded alone.
for (const [i, text] of texts.entries()) {
const [alone] = await embedTexts([text], config);
expect(cosine(vectors[i] ?? [], alone ?? [])).toBeGreaterThan(0.99);
}
}, 120_000); // a cold local model can take a while to page in
});
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"build": "tsc -p tsconfig.build.json",
"prepack": "bun run build",
"typecheck": "tsc --noEmit",
"test": "bun test ./src",
"test": "bun test ./src ./tests",
"test:live": "bun test ./live",
"format": "prettier -w ."
},
"dependencies": {
Expand Down
Loading
Loading