#!/usr/bin/env python # -*- coding: utf-8 -*- """知识库入库脚本:分块 → LLM 生成问答 → 双路向量索引 → 落盘 kb_index.json / kb_qa.json。 知识源 = backend/knowledge/ 下全部 *.md(park.md / opc.md / park_data.md …)。 - 幂等:缓存有效时直接加载(不重新构建);--force 强制删除缓存重建。 - 首次运行会调用 DashScope(text-embedding-v4 向量化 + qwen-plus 生成问答), 耗时约数分钟,仅构建一次,之后命中缓存秒开。 用法(在 backend/ 目录下执行): python scripts/ingest_kb.py # 有缓存则加载,无缓存则构建 python scripts/ingest_kb.py --force # 强制重建(任一知识文件变更后) """ import argparse import sys from pathlib import Path BACKEND_DIR = Path(__file__).resolve().parent.parent if str(BACKEND_DIR) not in sys.path: sys.path.insert(0, str(BACKEND_DIR)) def main() -> None: parser = argparse.ArgumentParser(description="知识库入库(分块 + 问答 + 双路向量索引)") parser.add_argument("--force", action="store_true", help="删除缓存并强制重建") parser.add_argument("--dry", action="store_true", help="只做分块预览,不调 API、不落盘") args = parser.parse_args() from app import rag if args.dry: chunks = rag._chunk_md(rag._kb_text()) print(f"[dry] 知识源 {len(list(rag.KB_DIR.glob(rag.KB_GLOB)))} 个文件,全库 " f"{len(rag._kb_text())} 字 → {len(chunks)} 块(最长 {max(map(len, chunks))} 字)") return if args.force: for p in (rag.META_FILE, rag.VECTOR_FILE, rag.QA_FILE, rag.LEGACY_INDEX_FILE): if p.exists(): p.unlink() print(f"[ingest] 已删除缓存 {p.name}") print("[ingest] 知识源:") for f in sorted(rag.KB_DIR.glob(rag.KB_GLOB)): print(f" - {f.name} ({f.stat().st_size} B)") idx = rag._load_index() n_chunk = len(idx["chunks"]) n_vec = len(idx["embed_texts"]) dim = idx["embeddings"].shape[1] n_q = sum(1 for qs in idx["questions"] for _ in qs) print("[ingest] 入库完成:") print(f" - 分块数: {n_chunk}") print(f" - 生成问句数: {n_q}") print(f" - 向量数: {n_vec}(dim={dim},含原句+问句双路)") print(f" - 向量矩阵: {rag.VECTOR_FILE}") print(f" - 元数据: {rag.META_FILE}") print(f" - 问答缓存: {rag.QA_FILE}") print("[ingest] 成功。") if __name__ == "__main__": main()