| import os |
| import faiss |
| from sentence_transformers import SentenceTransformer |
| from transformers import AutoTokenizer, AutoModelForCausalLM |
|
|
| DATA_PATH = "data/feng_family.txt" |
| INDEX_DIR = "index" |
| FAISS_PATH = os.path.join(INDEX_DIR, "family.faiss") |
| BLOCKS_PATH = os.path.join(INDEX_DIR, "blocks.txt") |
|
|
| embed_model = SentenceTransformer("BAAI/bge-small-zh-v1.5") |
|
|
| def build_index_if_needed(): |
| if os.path.exists(FAISS_PATH) and os.path.exists(BLOCKS_PATH): |
| return |
|
|
| with open(DATA_PATH, "r", encoding="utf-8") as f: |
| text = f.read() |
|
|
| blocks = [b.strip() for b in text.split("--- PERSON ---") if b.strip()] |
| embeddings = embed_model.encode(blocks, normalize_embeddings=True) |
|
|
| index = faiss.IndexFlatIP(embeddings.shape[1]) |
| index.add(embeddings) |
|
|
| os.makedirs(INDEX_DIR, exist_ok=True) |
| faiss.write_index(index, FAISS_PATH) |
|
|
| with open(BLOCKS_PATH, "w", encoding="utf-8") as f: |
| for b in blocks: |
| f.write(b.replace("\n", " ") + "\n===\n") |
|
|
| |
| build_index_if_needed() |
|
|
| index = faiss.read_index(FAISS_PATH) |
| with open(BLOCKS_PATH, "r", encoding="utf-8") as f: |
| blocks = f.read().split("===") |
|
|
| llm_name = "Qwen/Qwen2.5-1.5B" |
| tokenizer = AutoTokenizer.from_pretrained(llm_name) |
| model = AutoModelForCausalLM.from_pretrained(llm_name) |
|
|
| SYSTEM_PROMPT = "你是冯氏家谱机器人,只能根据家谱资料回答,没有记载就说未记载。" |
|
|
| def ask(question: str) -> str: |
| q_emb = embed_model.encode([question], normalize_embeddings=True) |
| _, I = index.search(q_emb, 1) |
| context = blocks[I[0][0]] |
|
|
| prompt = f"{SYSTEM_PROMPT}\n\n【家谱资料】\n{context}\n\n【问题】{question}\n回答:" |
|
|
| inputs = tokenizer(prompt, return_tensors="pt") |
| outputs = model.generate(**inputs, max_new_tokens=200) |
| return tokenizer.decode(outputs[0], skip_special_tokens=True) |
|
|