|
1 | 1 | # @corbits/embedding |
2 | 2 |
|
3 | | -Embedding client for OpenAI-compatible `/v1/embeddings` endpoints. Given a |
4 | | -`baseURL`, a model, and some texts, `embedTexts` batches them, posts each batch, |
5 | | -validates the response, and returns vectors in input order. |
| 3 | +One API for every embedding provider. Point at any OpenAI-compatible `/v1/embeddings` endpoint, swap models with a config change, and get ordered vectors back. |
6 | 4 |
|
7 | | -One wire format, not a provider switch. OpenAI, Ollama, TEI, vLLM and Jina all |
8 | | -serve `/v1/embeddings` — what differs between them is parameters: |
| 5 | +OpenAI, Ollama, TEI, vLLM, and Jina all serve the same wire shape. This package is one code path over that shape, with parameters for what varies between providers: |
9 | 6 |
|
10 | | -- `dimensions` — Matryoshka truncation, supported by OpenAI text-embedding-3 |
11 | | - (down to 256), Jina v3 (32) and v4 (128). Models without MRL reject it, so it |
12 | | - is only sent when set. |
13 | | -- `encodingFormat` — `float` or `base64`. |
14 | | -- `batchSize` — providers cap inputs per request; OpenAI rejects arrays over |
15 | | - 2048, and a smaller batch is also how you stay under a token ceiling. |
| 7 | +- `dimensions` — Matryoshka truncation for models that support it, including OpenAI `text-embedding-3` (down to 256), Jina v3 (32), and Jina v4 (128). Sent only when set, so models without MRL stay happy. |
| 8 | +- `encodingFormat` — `float` (default) or compact `base64`. |
| 9 | +- `batchSize` — inputs per request (default 32). Tune it to provider caps and per-request token ceilings. |
16 | 10 |
|
17 | | -Transport, error classification and retry come from `@intx/inference` rather |
18 | | -than being reimplemented here: `deps.fetch` is the single request path, |
19 | | -failures become an `InferenceError` through the same classifiers a chat call |
20 | | -uses, and `createDefaultRetryPolicy` decides whether to back off or abort. A 429 |
21 | | -from an embedding endpoint therefore behaves exactly as one from a chat |
22 | | -endpoint, and a `credential_failure` aborts immediately rather than retrying a |
23 | | -bad key. |
| 11 | +Transport, error classification, and retry build on `@intx/inference`: requests go through the shared `deps.fetch` path, failures classify into `InferenceError` just like chat calls, and `createDefaultRetryPolicy` guides backoff. A 429 from an embeddings endpoint behaves like one from a chat endpoint, and credential failures short-circuit. |
24 | 12 |
|
25 | | -## Installation |
26 | | - |
27 | | -Not published to npm yet. Until a registry publish, `npm install @corbits/embedding` |
28 | | -404s. Git is the install path. |
| 13 | +## Install |
29 | 14 |
|
30 | 15 | ```bash |
31 | | -bun add github:corbitsdev/corbits-embedding |
| 16 | +bun add @corbits/embedding |
32 | 17 | ``` |
33 | 18 |
|
34 | | -Requires Node >= 24 or Bun >= 1.2. The default export is built `dist/`; native Node |
35 | | -does not load this package's extensionless TypeScript source. |
| 19 | +Runs on Bun >= 1.2 or Node >= 24. The built `dist/` entry is the default import. |
| 20 | + |
| 21 | +## Quickstart |
36 | 22 |
|
37 | 23 | ```ts |
38 | 24 | import { createDefaultScheduler } from "@intx/inference"; |
39 | 25 | import { embedTexts, probeEmbedDims } from "@corbits/embedding"; |
40 | 26 |
|
41 | | -// Only `fetch` and `scheduler` are needed — a full harness `Dependencies` |
42 | | -// also satisfies this if you already have one. |
43 | 27 | const deps = { fetch, scheduler: createDefaultScheduler() }; |
44 | 28 |
|
45 | 29 | const config = { |
46 | | - baseURL: "http://localhost:11434/v1", // include the provider's version prefix |
| 30 | + baseURL: "http://localhost:11434/v1", // provider root, version prefix included |
47 | 31 | model: "nomic-embed-text", |
48 | 32 | }; |
49 | 33 |
|
50 | 34 | const dims = await probeEmbedDims(config, { deps }); |
51 | 35 | const [vector] = await embedTexts(["hello world"], config, { deps }); |
52 | 36 | ``` |
53 | 37 |
|
54 | | -## Dimensionality is discovered, never assumed |
| 38 | +`embedTexts(texts, config, options)` batches sequentially, posts each batch to `{baseURL}/embeddings`, and returns vectors in input order. Options carry `deps`, with optional `retryPolicy`, `extractRetryAfterMs`, and `signal`. |
55 | 39 |
|
56 | | -`probeEmbedDims` embeds one probe string and reports the length it got back. |
57 | | -Dimensionality is not knowable from a model name — it varies by provider and is |
58 | | -changed by `dimensions` — so a caller that persists vectors must discover it. |
59 | | -Defaults differ enough to matter: 1536 for OpenAI `text-embedding-3-small`, 768 |
60 | | -for `nomic-embed-text`, 384 for `bge-small-en-v1.5`. Swapping models is a |
61 | | -migration, not a config change. |
| 40 | +## Dimensionality |
62 | 41 |
|
63 | | -## Errors |
| 42 | +`probeEmbedDims(config, { deps })` embeds one probe string and reports the length received. Dimensionality follows the provider and the `dimensions` setting, so callers that persist vectors discover it at startup and treat a model swap as a migration. Representative widths: 1536 for OpenAI `text-embedding-3-small`, 768 for `nomic-embed-text`, 384 for `bge-small-en-v1.5`. |
64 | 43 |
|
65 | | -Every failure — transport, HTTP status, or a 200 whose body is not JSON — |
66 | | -raises `ModelRequestError`, carrying the classified `InferenceError` as `reason` |
67 | | -and the URL. |
| 44 | +## Errors |
68 | 45 |
|
69 | | -Note that `@corbits/embedding` and `@corbits/reranking` each carry their own |
70 | | -copy of this class until the shared transport is upstreamed, so `instanceof` |
71 | | -does not hold across the two. Catching both? Discriminate on |
72 | | -`error.name === "ModelRequestError"`. |
| 46 | +Every failure mode — transport, HTTP status, or a 200 with an unexpected body — raises `ModelRequestError`, carrying the classified `InferenceError` as `reason` plus the request URL. The embedding and reranking packages each carry their own copy of this class while the shared transport is upstreamed, so code catching both discriminates on `error.name === "ModelRequestError"`. |
73 | 47 |
|
74 | 48 | ## Transport exports |
75 | 49 |
|
76 | | -The barrel re-exports the transport `embedTexts` is built on, for sibling |
77 | | -one-shot JSON clients that want the same classified, retried request path |
78 | | -without touching `@intx/inference` internals: |
| 50 | +The barrel also re-exports the one-shot JSON transport `embedTexts` is built on, for sibling clients that want the same classified, retried request path: |
79 | 51 |
|
80 | 52 | - `runJSONRequest` — one JSON POST with error classification and retry. |
81 | | -- `extractRetryAfterMs` — the default `Retry-After` reader, overridable per |
82 | | - call via `extractRetryAfterMs`. |
83 | | -- `ModelRequestError` — the error every failure mode raises, carrying the |
84 | | - classified `InferenceError` as `reason` and the URL. |
| 53 | +- `extractRetryAfterMs` — default `Retry-After` reader, overridable per call. |
| 54 | +- `ModelRequestError` — error for every failure mode, with `reason` and URL. |
85 | 55 | - Types `RunRequestOptions` and `RetryAfterExtractor`. |
86 | 56 |
|
| 57 | +## Interchange |
| 58 | + |
| 59 | +Interchange hubs and ingestion workers use this package for embeddings behind the shared `@intx/inference` transport: same fetch path, same error taxonomy, and same retry behavior as chat. Memory and retrieval pipelines pair `probeEmbedDims` at startup with `embedTexts` batches at ingest and query time. |
| 60 | + |
87 | 61 | ## Versioning |
88 | 62 |
|
89 | | -Semver. Released manually with `bun run build && npm publish` once CI is green. |
| 63 | +Semver. Releases run `bun run build && npm publish` with green CI. |
90 | 64 |
|
91 | 65 | ## Development |
92 | 66 |
|
|
0 commit comments