| """ |
| Pre-compute KB section embeddings + build FAISS index. |
| Run during Docker build (and locally). ONNX export optional (--export-onnx). |
| """ |
| import os, sys, json, time, argparse |
| sys.stdout.reconfigure(encoding="utf-8") |
|
|
| parser = argparse.ArgumentParser() |
| parser.add_argument("--export-onnx", action="store_true") |
| args = parser.parse_args() |
|
|
| import numpy as np |
| import faiss |
| import torch |
| from sentence_transformers import SentenceTransformer |
|
|
| os.makedirs("embeddings", exist_ok=True) |
|
|
| |
| t0 = time.time() |
| model = SentenceTransformer("BAAI/bge-m3", trust_remote_code=True) |
| print(f"Model loaded in {time.time()-t0:.1f}s") |
|
|
| |
| paths = ["knowledge_base.json", "knowledge_base_insoles.json"] |
| sections = [] |
| for path in paths: |
| with open(path, encoding="utf-8") as f: |
| data = json.load(f) |
| for name, sec in data["sections"].items(): |
| sections.append({ |
| "id": f"{path}:{name}", |
| "title": sec["title"], |
| "content": sec["content"], |
| }) |
| print(f"Loaded {len(sections)} sections") |
|
|
| |
| texts = [f"{s['title']}\n\n{s['content']}" for s in sections] |
| t1 = time.time() |
| embeddings = model.encode(texts, normalize_embeddings=True, show_progress_bar=True) |
| print(f"Embedded {len(embeddings)} sections in {time.time()-t1:.1f}s") |
|
|
| |
| index = faiss.IndexFlatIP(embeddings.shape[1]) |
| index.add(embeddings.astype(np.float32)) |
| faiss.write_index(index, "embeddings/index.faiss") |
| with open("embeddings/sections.json", "w", encoding="utf-8") as f: |
| json.dump(sections, f, ensure_ascii=False, indent=2) |
| np.save("embeddings/embeddings.npy", embeddings) |
| print(f"FAISS: {index.ntotal} vectors x {embeddings.shape[1]} dim") |
| print(f" index.faiss: {os.path.getsize('embeddings/index.faiss')/1024:.1f} KB") |
| print(f" sections.json: {os.path.getsize('embeddings/sections.json')/1024:.1f} KB") |
|
|
| if not args.export_onnx: |
| print("Skipping ONNX export (not needed at runtime)") |
| print(f"Done in {time.time()-t0:.1f}s") |
| sys.exit(0) |
|
|
| |
| t2 = time.time() |
| print("Exporting transformer to ONNX...") |
| raw_model = model[0].auto_model.eval() |
| tok = model.tokenizer |
| dummy = tok("test query", return_tensors="pt", padding=True, truncation=True, max_length=256) |
| with torch.no_grad(): |
| torch.onnx.export( |
| raw_model, |
| (dummy["input_ids"], dummy["attention_mask"]), |
| "embeddings/bge-m3.onnx", |
| input_names=["input_ids", "attention_mask"], |
| output_names=["last_hidden_state"], |
| dynamic_axes={ |
| "input_ids": {0: "batch", 1: "seq"}, |
| "attention_mask": {0: "batch", 1: "seq"}, |
| }, |
| opset_version=14, |
| ) |
| onnx_size_mb = os.path.getsize("embeddings/bge-m3.onnx") / 1024 / 1024 |
| print(f"ONNX exported in {time.time()-t2:.1f}s, {onnx_size_mb:.1f} MB") |
| print(f"Done in {time.time()-t0:.1f}s") |
|
|