Skip to content

Commit 9e2b604

Browse files
committed
Add hype-rag CLI, wasm bindings and examples
Convert repository to a Cargo workspace and bump hypembed to 0.3.0. Add new crates: "hype-rag" (CLI for local RAG with indexing, chunking, sqlite-backed store, and search utilities) and "hypembed-wasm" (wasm-bindgen wrapper exposing a WasmEmbedder). Add unicode-normalization and platform-gated deps, example helper to write a tiny model, Bunny Edge WebAssembly glue (JS/TS + wasm artifact) and a Cloudflare-style embed handler, tests for RAG and wasm paths, and utility scripts to build/verify wasm glue. Also update .gitignore to ignore /mcps/.
1 parent c2b8142 commit 9e2b604

37 files changed

Lines changed: 1856 additions & 67 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/target
2+
/mcps/
23
Cargo.lock
34
*.swp
45
*.swo

Cargo.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
[workspace]
2+
members = [".", "hype-rag", "hypembed-wasm"]
3+
resolver = "2"
4+
15
[package]
26
name = "hypembed"
3-
version = "0.2.1"
7+
version = "0.3.0"
48
edition = "2021"
59
rust-version = "1.70"
610
description = "Pure-Rust BERT-compatible text embedding inference for local-first applications."
@@ -16,13 +20,19 @@ categories = ["science", "text-processing"]
1620
serde = { version = "1", features = ["derive"] }
1721
serde_json = "1"
1822
thiserror = "2"
23+
unicode-normalization = "0.1"
24+
25+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
1926
memmap2 = "0.9"
2027
rayon = "1.10"
21-
unicode-normalization = "0.1"
2228

2329
[dev-dependencies]
2430
criterion = { version = "0.5", features = ["html_reports"] }
2531

2632
[[bench]]
2733
name = "inference_bench"
2834
harness = false
35+
36+
[[example]]
37+
name = "write_tiny_model"
38+
path = "examples/write_tiny_model.rs"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Bunny Edge Script example: POST JSON { "text": "..." } -> embedding vector.
3+
*
4+
* Prerequisites:
5+
* - Regenerate glue: scripts\generate_bunny_glue.cmd (or verify_wasm.cmd)
6+
* - Deploy hypembed_wasm.js + hypembed_wasm_bg.wasm alongside this handler
7+
* - Provide model bytes (config.json, vocab.txt, model.safetensors) via edge storage or KV
8+
*/
9+
10+
import init, { WasmEmbedder } from "./hypembed_wasm.js";
11+
12+
let embedder = null;
13+
let wasmReady = false;
14+
15+
async function ensureWasm() {
16+
if (!wasmReady) {
17+
await init();
18+
wasmReady = true;
19+
}
20+
}
21+
22+
async function loadEmbedder(env) {
23+
if (embedder) {
24+
return embedder;
25+
}
26+
27+
await ensureWasm();
28+
29+
const configJson = await env.MODEL_CONFIG.get("config.json", { type: "text" });
30+
const vocabTxt = await env.MODEL_VOCAB.get("vocab.txt", { type: "text" });
31+
const weights = await env.MODEL_WEIGHTS.get("model.safetensors", { type: "arrayBuffer" });
32+
33+
embedder = new WasmEmbedder(configJson, vocabTxt, new Uint8Array(weights));
34+
return embedder;
35+
}
36+
37+
export default {
38+
async fetch(request, env) {
39+
if (request.method !== "POST") {
40+
return new Response(JSON.stringify({ error: "POST required" }), {
41+
status: 405,
42+
headers: { "content-type": "application/json" },
43+
});
44+
}
45+
46+
let body;
47+
try {
48+
body = await request.json();
49+
} catch {
50+
return new Response(JSON.stringify({ error: "invalid JSON body" }), {
51+
status: 400,
52+
headers: { "content-type": "application/json" },
53+
});
54+
}
55+
56+
const text = body?.text;
57+
if (typeof text !== "string" || text.trim().length === 0) {
58+
return new Response(JSON.stringify({ error: "text field required" }), {
59+
status: 400,
60+
headers: { "content-type": "application/json" },
61+
});
62+
}
63+
64+
try {
65+
const model = await loadEmbedder(env);
66+
const vector = model.embed(text);
67+
return new Response(
68+
JSON.stringify({
69+
dim: vector.length,
70+
embedding: Array.from(vector),
71+
}),
72+
{ headers: { "content-type": "application/json" } },
73+
);
74+
} catch (err) {
75+
return new Response(
76+
JSON.stringify({ error: String(err?.message ?? err) }),
77+
{ status: 500, headers: { "content-type": "application/json" } },
78+
);
79+
}
80+
},
81+
};
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/* tslint:disable */
2+
/* eslint-disable */
3+
4+
/**
5+
* WASM embedder wrapping the hypembed core pipeline.
6+
*/
7+
export class WasmEmbedder {
8+
free(): void;
9+
[Symbol.dispose](): void;
10+
/**
11+
* Embed a single text and return the vector as a JS Float32Array-compatible Vec.
12+
*/
13+
embed(text: string): Float32Array;
14+
/**
15+
* Return the model hidden size (embedding dimension).
16+
*/
17+
hidden_size(): number;
18+
/**
19+
* Create an embedder from in-memory model bytes (no filesystem access).
20+
*/
21+
constructor(config_json: string, vocab_txt: string, weights: Uint8Array);
22+
}
23+
24+
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
25+
26+
export interface InitOutput {
27+
readonly memory: WebAssembly.Memory;
28+
readonly __wbg_wasmembedder_free: (a: number, b: number) => void;
29+
readonly wasmembedder_embed: (a: number, b: number, c: number) => [number, number, number, number];
30+
readonly wasmembedder_hidden_size: (a: number) => number;
31+
readonly wasmembedder_new: (a: number, b: number, c: number, d: number, e: number, f: number) => [number, number, number];
32+
readonly __wbindgen_externrefs: WebAssembly.Table;
33+
readonly __wbindgen_malloc: (a: number, b: number) => number;
34+
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
35+
readonly __externref_table_dealloc: (a: number) => void;
36+
readonly __wbindgen_free: (a: number, b: number, c: number) => void;
37+
readonly __wbindgen_start: () => void;
38+
}
39+
40+
export type SyncInitInput = BufferSource | WebAssembly.Module;
41+
42+
/**
43+
* Instantiates the given `module`, which can either be bytes or
44+
* a precompiled `WebAssembly.Module`.
45+
*
46+
* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
47+
*
48+
* @returns {InitOutput}
49+
*/
50+
export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
51+
52+
/**
53+
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
54+
* for everything else, calls `WebAssembly.instantiate` directly.
55+
*
56+
* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
57+
*
58+
* @returns {Promise<InitOutput>}
59+
*/
60+
export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;

0 commit comments

Comments
 (0)