libs/fuzzy-computing-batch-test.lua
1-- {{{ fuzzy-computing-batch-test.lua
2-- Issue 10-050: live smoke test for M.get_embeddings_batch.
3--
4-- Needs a running inference server (the selected one from config.lua). If none
5-- is reachable it SKIPS (exit 0) rather than failing, so it is safe to wire
6-- into a build step that may run without the GPU box up. Run:
7-- luajit libs/fuzzy-computing-batch-test.lua
8--
9-- What it proves end-to-end (only possible against a real server):
10-- - a 3-input batch returns 3 vectors in input order
11-- - every vector has the same, positive dimension
12-- - the single-input shim get_embedding still works
13-- - chunk-then-batch round-trips an over-long text into >1 vector
14-- }}}
15
16package.path = "libs/?.lua;" .. package.path
17local fuzzy = require("fuzzy-computing")
18local chunk = require("text-chunking")
19local inference = require("inference-server-config")
20
21inference.set_project_root(".")
22local server = inference.get_selected_server()
23local url = inference.build_host_url(server)
24local model = server.model
25
26-- {{{ skip if no server
27local probe = io.popen(string.format(
28 "curl -s -o /dev/null -w '%%{http_code}' --max-time 3 '%s/health'", url))
29local code = probe:read("*a")
30probe:close()
31if code ~= "200" then
32 print(string.format("SKIP: no server at %s/health (got '%s')", url, code))
33 os.exit(0)
34end
35print("server reachable at " .. url .. " (model: " .. tostring(model) .. ")")
36-- }}}
37
38local failures = 0
39-- {{{ local function check(name, cond, detail)
40local function check(name, cond, detail)
41 if cond then
42 print(" ok " .. name)
43 else
44 failures = failures + 1
45 print(" FAIL " .. name .. (detail and (" -> " .. detail) or ""))
46 end
47end
48-- }}}
49
50-- {{{ /tokenize gives an exact, positive token count
51local n_short = fuzzy.tokenize_count("the sea at dawn")
52local n_long = fuzzy.tokenize_count(string.rep("word ", 200))
53check("tokenize_count returns a positive integer",
54 type(n_short) == "number" and n_short > 0, "got " .. tostring(n_short))
55check("tokenize_count scales with text length",
56 type(n_long) == "number" and n_long > n_short, "short=" .. tostring(n_short) .. " long=" .. tostring(n_long))
57-- }}}
58
59-- {{{ batch of three returns three aligned vectors
60local vecs = fuzzy.get_embeddings_batch(
61 { "the sea at dawn", "a quiet kind of grief", "burning revolution" }, model)
62check("batch returns 3 vectors", vecs and #vecs == 3, "got " .. tostring(vecs and #vecs))
63if vecs and vecs[1] and vecs[2] and vecs[3] then
64 local d = #vecs[1]
65 check("all vectors share one positive dimension",
66 d > 0 and #vecs[2] == d and #vecs[3] == d, "dim=" .. d)
67end
68-- }}}
69
70-- {{{ single-input shim still works
71local one = fuzzy.get_embedding("a single line", model)
72check("get_embedding shim returns a vector", type(one) == "table" and #one > 0)
73-- }}}
74
75-- {{{ chunk-then-batch on an over-long text yields multiple chunk vectors
76-- Uses the real /tokenize counter and the exact computed budget — the same path
77-- production takes — so this also validates make_token_counter + the budget math.
78local long = string.rep("This is a sentence about the weather and the wind. ", 400)
79local count_fn = fuzzy.make_token_counter()
80local budget = fuzzy.embedding_chunk_budget()
81local chunks = chunk.chunk_text_by_tokens(long, count_fn, budget)
82check("over-long text splits into >1 chunk", #chunks > 1, "chunks=" .. #chunks)
83local chunk_vecs = fuzzy.get_embeddings_batch(chunks, model)
84check("batch embeds every chunk", chunk_vecs and #chunk_vecs == #chunks)
85local weights = {}
86for i, c in ipairs(chunks) do weights[i] = #c end
87local combined = chunk.combine_chunk_vectors(chunk_vecs, weights)
88check("recombined vector matches embedding dimension",
89 combined and #combined == (one and #one or 0))
90-- }}}
91
92print(failures == 0 and "\nall live checks passed" or ("\n" .. failures .. " FAILED"))
93os.exit(failures == 0 and 0 or 1)
94
95-- vim: set foldmethod=marker:
96