Repository navigation
models: a metered embeddings slot, one vector space - #772
Conversation
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this stack layer at a1f4efd9bd3a095f0ff787136f79023e4baafd83, against #771 at c842c4cc174ad85aafcc116df1ba6145b5e38194.
The bounded embeddings route preserves the existing agent authentication, spending gates and credential boundary, meters successful responses before validating vectors, and keeps embeddings out of chat routing. The implementation appropriately reuses the existing transport and deployment key binding; I found no blocking regression in this layer.
Validation: cargo test -p fragment-core passed all 525 tests (including the seven embeddings tests). CI check and images check are green; e2e shards and real-Hermes Docker checks were still pending when inspected. I did not run local e2e or paid vendor calls. The external engineering-style document was unavailable at the documented path.
Non-blocking: crates/core/src/embed.rs describes ceil(bytes / 3) as never undercounting tokens, but an average English characters-per-token assumption does not establish that bound. The test verifies the formula rather than comparing it with BGE-M3 tokenization. Describe this as a billing estimate, or substantiate the guarantee with the actual tokenizer and representative short, punctuation-heavy and multilingual inputs before relying on it as an upper bound.
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now at 71951b4568ecd8e7a076c61d113190337550f5ee, against #771 at c842c4cc174ad85aafcc116df1ba6145b5e38194.
The previous head had no blocking findings. The new private-slot guard is appropriately before dispatch and charging, and reuses the existing configuration checks. However, this head now uses the earlier review's unproven billing estimate to enforce a model window, making that concern a correctness issue in the newly promised behavior.
Blocking:
crates/core/src/embed.rs:103–104(max_input_bytes, used byboundat line 206) admits inputs beyondmax_input_tokens. Reproduction with BAAI/bge-m3's publishedtokenizer.json, loaded with Pythontokenizers.Tokenizer.from_fileand truncation disabled:("1,2,3,4,5,6,7,8,9,0\n" * 70)is 1,400 UTF-8 bytes, so the default bound accepts it and estimates 467 tokens. The tokenizer produces 771 tokens excluding special tokens (773 including them), exceeding the configured 512-token window. This is ordinary numeric/tabular content.requestforwards the entire input without further window validation: a provider enforcing that window must truncate or reject it, rather than this route refusing it before dispatch as promised. Shape/dimension validation cannot detect a vector made from a truncated prefix.
Please enforce the configured window with the relevant tokenizer or a substantiated conservative bound, accounting for special tokens. Apply the same safe boundary to callers' chunk sizing; do not use the average three-bytes-per-token billing estimate as a maximum. Cover prose, numeric/punctuation-heavy and multilingual inputs, including exact-window boundaries. This is a demonstrated admission failure; I did not call a live provider to observe its truncation policy.
Validation: cargo test -p fragment-core passed all 526 tests. CI check, images check, e2e shards and real-Hermes Docker checks were still pending when inspected. Local e2e and paid vendor calls were not run. The external engineering-style document was unavailable at its documented path.
alexlwn123
left a comment
There was a problem hiding this comment.
Pass for this stack layer at 6a0ff549ba7745a001492459d717b7bbbc72cd28, against #771 at 6d1c3b679db53118bf694576d76c061d7a5bcd78.
The previous token-window blocker is fixed: admission now uses a conservative decomposition-aware bound with special-token allowance, independently of the billing estimate. The numeric reproduction is refused before dispatch, and the tests cover punctuation, multilingual text, normalization expansion and exact-window boundaries. The earlier misleading billing guarantee is also removed.
The metered embeddings route retains the existing authentication, spending and private-slot gates. Reusing ICU normalization and the existing transport keeps the correction focused; I found no blocking regression in this layer.
Validation: cargo test -p fragment-core --quiet passed all 528 tests. CI images check passed; check, four e2e shards and real-Hermes Docker were pending when inspected. Local e2e and paid vendor calls were not run. The external engineering-style document was unavailable at its documented path.
Non-blocking: the “any tokenizer” guarantee in crates/core/src/embed.rs and docs/api.md is broader than the demonstrated BGE-M3 evidence: it assumes the listed normalization forms and at most three overhead tokens. Document those assumptions for custom slots, and retain a reproducible tokenizer-comparison script before extending the guarantee to other model families. No additional concrete admission failure was established in this review.
`POST /api/models/v1/embeddings` (an agent's computer's `/v1/embeddings`), OpenAI's shape, on the deployment's `models.embeddings`: BGE-M3 on Workers AI unnamed, or a model on an OpenAI-compatible API (a self-hosted deployment's own), with its dimensions and an optional version. Every answer names its space; a vector of another length, or another count than asked, is never passed on (502, charged: it was paid for); vectors are unit length. Charged after the answer at its input tokens (BGE-M3's $0.012 a million; a self-hosted slot names its price), counted from the input when the API reports none, as Workers AI does for embeddings. `models::embedded` is the call the search capability will make. The fake answers BGE-M3 on its Workers AI face and `/v1/embeddings` on its OpenAI-compatible one; the ledger lane checks the route, its refusals, its charge and its pass-through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nly privately A slot names `max_input_tokens` (512 unnamed, BGE-M3's on Workers AI): an input past it, counted at a token every 3 bytes, is refused, never cut, so its caller splits a long text (the search capability will embed a section's chunks). A slot may be marked private (a confidential API, never Workers AI or OpenRouter); a deployment whose tiers are private embeds only on one, refusing 403 otherwise, as Finite Private never falls back to a model that is not. Its deploy is not refused: search answers by words alone there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three bytes a token admitted 1,400 bytes of numbers, which BGE-M3 reads as 773 tokens, into a 512-token window. tokens_max counts each character at the UTF-8 bytes of its longest form (itself, NFD, NFKD) plus 3 for the model's own tokens: a tokenizer reads no token shorter than a character or a byte, after a normalization that never makes a character longer than that. Checked against BGE-M3's tokenizer for every code point; tests carry its real counts for prose, numbers, punctuation, eight scripts and the exact window. The 3-byte estimate stays only as a price. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6a0ff54 to
98c1081
Compare
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now at 98c1081af2b65f444f14ce2aa79d676e056c6272.
The earlier token-window blocker remains fixed. The three embeddings commits are patch-equivalent to the previously approved head after rebasing onto master. However, that review missed a deterministic failure in the ledger e2e test.
Blocking:
crates/e2e/src/lanes/ledger.rs:1132–1143still requirespast the embeddings slot's 512 tokens. For its exact input,["ok", "word ".repeat(400)], the currentembed::bound(...).unwrap_err().message()returnsinput 1 may be 2003 tokens, past the embeddings slot's 512: split it, as nothing is cut. I ran that input against this head's core library: the e2e'smessage.contains(why)is false. Consequently the required ledger lane fails even when the route correctly refuses 400 and sends/charges nothing. This is a test regression from the changed refusal wording, not an admission regression.
Update the expected message or retain the intended wording in Refusal::TooLong, then run cargo xtask e2e --only ledger to verify the route and the unchanged no-dispatch/no-charge assertions together.
Validation: all 530 core tests passed; the exact ledger message predicate reproduced false in a temporary executable (removed afterward). CI check and images check passed; e2e shards and real-Hermes Docker were pending when inspected. Full local e2e and paid vendor calls were not run. The external engineering-style document is unavailable at its documented path. The implementation remains focused and reuses the existing transport, gates and ICU normalization appropriately.
A caller chunking to the window needs room for a header and any character, which tokens_max counts at most 33 (U+FDFA, checked over every code point). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
left a comment
There was a problem hiding this comment.
Fail for now at 63058fdad1976412425f2c05f22990b6fc5eb580.
The previous ledger-e2e blocker remains. This head only changes the minimum embedding window, its documentation and tests; it leaves the failing assertion and refusal message unchanged. The earlier token-admission blocker remains fixed.
Blocking:
crates/e2e/src/lanes/ledger.rs:1132expectspast the embeddings slot's 512 tokens, butcrates/core/src/embed.rs:218producesinput 1 may be 2003 tokens, past the embeddings slot's 512: split it, as nothing is cutfor its input["ok", "word ".repeat(400)]. The route forwards that message atcell/src/models.rs:716, so thecontains(why)assertion at ledger.rs:1140 remains false and the required ledger lane fails despite correct refusal behavior.
Align the expected substring with the intended refusal wording, preserving the status and no-dispatch/no-charge assertions, then run cargo xtask e2e --only ledger.
Validation: all 531 core tests passed (cargo test -p fragment-core --quiet); the unchanged failure is established by the source trace above. CI check, images check, e2e shards and real-Hermes Docker were pending when inspected. Full local e2e and paid vendor calls were not run. The external engineering-style document remains unavailable at its documented path. The new minimum-window change is small and direct; I found no additional blocker in it.
|
Thanks, Alex. Fixed at the root in 98c1081, with a follow-up in 63058fd. 1. The window holds on real tokens (blocking).
Why it holds:
Bytes alone are not a bound either: 30 × U+FDFA is 90 bytes but 122 BGE-M3 tokens, because NFKC turns it into 18 characters. The bound says 993. Substantiated with BAAI/bge-m3's own
Chunk sizing. The capability's chunker (#783, A slot's Cost. ASCII counts a token a byte, so BGE-M3's 512 holds 509 bytes, about 115 English tokens. Prose chunks are about 3× smaller than at 1,536 bytes. Billing is unchanged. The normalization data comes from Non-blocking. |
…1,403 tokens Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alexlwn123
left a comment
There was a problem hiding this comment.
Pass at 5b643e9d714b768f30ed6ef1fd5b89f46e2b53a6.
The previous ledger-e2e blocker is fixed. This head changes only that refusal fixture: the numeric reproduction is bounded at 1,403 tokens, and the expected substring now matches Refusal::TooLong::message. The route still forwards that message as a 400, and the test preserves its no-dispatch/no-charge assertions. The earlier token-admission blocker remains fixed.
The correction is small and direct, with no new implementation machinery. I found no additional blocking regression in the delta from the previously reviewed head.
Validation: cargo test -p fragment-core --quiet passed all 531 tests, including the numeric bound regression. CI check and images check passed; four e2e shards and real-Hermes Docker were pending when inspected. I attempted cargo xtask e2e --only ledger: the Worker and native builds passed, but I stopped it while the prerequisite Docker image was still building, before the lane ran. Paid vendor calls were not run. The external engineering-style document was unavailable at its documented path.
The second PR of the search stack (on #771): a metered
/v1/embeddingsslot on the model route. It is what the search capability (next PR) embeds sections and questions with, and what an agent can call itself.What it adds
models.embeddingsin a deployment's config is where its embeddings run, plus the space their vectors are in:{provider, model, dimensions, version?}, withkeyandpriceas any OpenAI-compatible slot names them.@cf/baai/bge-m3, 1,024 dimensions), the benchmark's model (bench/search: a search benchmark (brain today, fbrain, DO baseline, LanceDB) #771).POST <base_url>/embeddings, such as Hugging Face's TEI running BGE-M3 on its machines.POST /api/models/v1/embeddings[?fragment=<name>], signed by an agent: OpenAI's request and answer, plusspace: {model, dimensions, version}.POST /v1/embeddings.base64output is supported, since OpenAI's SDKs ask for it.modelmust beembeddingsor the slot's own;dimensions, if named, the slot's (vectors are never cut).encoding_formatmust befloatorbase64.models::embeddedis the call the search capability will make: gated, metered and checked exactly like the route.Tests
Models::parsefor the slot (default, a self-hosted price laid over the book, a chat model refused, an unpriced model refused).{shape, data}on its Workers AI face and/v1/embeddingson its OpenAI-compatible face, both with hashed-word vectors.cargo xtask e2e --only ledgergives 111 passed, 0 failed;cargo xtask checkpasses.Decided since (the coordinator, for Paul)
"private": true, which must be a confidential API, never Workers AI or OpenRouter. Without one, every embeddings call is refused 403, saying why, and the deployment's search will answer by words alone. Its deploy is not refused. This follows Paul's standing rule that Finite Private never falls back to a model that is not private. The e2e's private-tier node checks it.max_input_tokens(128 to 32,768; 512 unnamed). An input that might be past it is refused 400 and never cut. Since Alex's review, "might be" isembed::tokens_max, a bound for any tokenizer: each character at the UTF-8 bytes of the longest of itself, its NFD and its NFKD, plus 3 for the model's own tokens. It is checked against BGE-M3's tokenizer for every code point, and it is exact at the window on real input. ASCII is a token a byte, so 512 holds 509 bytes. The search capability chunks with the same bound. The app-facing API doesn't change, and total tokens (cost) stay about the same; only the vectors stored grow (pennies on R2).🤖 Generated with Claude Code