kb_check_host.py 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. # -*- coding: utf-8 -*-
  2. """宿主机会话内批量自检:逐句对知识库做 cosine 检索(复刻 verify 逻辑)。
  3. - 数据:/tmp/cv4(生产 chroma 一致性副本,生产文件零接触)
  4. - embedding:生产同款 BAAI/bge-large-zh-v1.5(siliconflow)
  5. - 首次 query 需加载 HNSW 索引(约 80s),后续每句约 1s
  6. 用法:cd /app/cfc/cfc-langgraph && .venv/bin/python /tmp/kb_check_host.py <sentences.json> [out.json]
  7. """
  8. import json
  9. import math
  10. import sys
  11. import time
  12. from langchain_chroma import Chroma
  13. from app.rag.embeddings import get_embeddings
  14. CHROMA_PATH = "/tmp/cv4"
  15. SOURCE = "microbiome"
  16. K = 3
  17. THRESHOLD = 0.7
  18. def _cosine(a, b) -> float:
  19. a = list(a)
  20. b = list(b)
  21. if not a or not b or len(a) != len(b):
  22. return 0.0
  23. dot = sum(x * y for x, y in zip(a, b))
  24. na = math.sqrt(sum(float(x) * float(x) for x in a))
  25. nb = math.sqrt(sum(float(y) * float(y) for y in b))
  26. if na == 0.0 or nb == 0.0:
  27. return 0.0
  28. return float(dot) / (float(na) * float(nb))
  29. def main():
  30. in_path = sys.argv[1]
  31. out_path = sys.argv[2] if len(sys.argv) > 2 else "/tmp/kb_check_result.json"
  32. with open(in_path, encoding="utf-8") as f:
  33. data = json.load(f)
  34. sentences = data["sentences"] if isinstance(data, dict) else data
  35. t0 = time.time()
  36. store = Chroma(collection_name="cfc_knowledge",
  37. embedding_function=get_embeddings(),
  38. persist_directory=CHROMA_PATH)
  39. embedder = get_embeddings()
  40. print("store ready", round(time.time() - t0, 1), "s", flush=True)
  41. results = []
  42. all_passed = True
  43. for i, s in enumerate(sentences, 1):
  44. text = (s or "").strip()
  45. best = {"max_similarity": 0.0, "matched_title": None, "matched_chunk": None}
  46. if text:
  47. try:
  48. cands = store.similarity_search_with_score(text, k=K,
  49. filter={"source": SOURCE})
  50. query_vec = embedder.embed_query(text)
  51. for doc, _dist in cands:
  52. meta = doc.metadata or {}
  53. doc_id = meta.get("id")
  54. if not doc_id:
  55. continue
  56. got = store._collection.get(ids=[doc_id], include=["embeddings"])
  57. vecs = got.get("embeddings")
  58. if vecs is None or len(vecs) == 0:
  59. continue
  60. sim = _cosine(query_vec, vecs[0])
  61. if sim > best["max_similarity"]:
  62. best = {"max_similarity": sim,
  63. "matched_title": meta.get("title"),
  64. "matched_chunk": (doc.page_content[:120]
  65. if doc.page_content else None)}
  66. except Exception as e:
  67. print(f" !! sentence {i} error: {e}", flush=True)
  68. best["id"] = f"s{i}"
  69. best["text"] = text
  70. best["passed"] = best["max_similarity"] >= THRESHOLD
  71. results.append(best)
  72. if not best["passed"]:
  73. all_passed = False
  74. flag = "OK " if best["passed"] else "FAIL"
  75. print(f"[{i:2d}/{len(sentences)}] {flag} sim={best['max_similarity']:.4f} "
  76. f"| {best['matched_title']} | {text[:40]}", flush=True)
  77. print(f"\nall_passed={all_passed} elapsed={round(time.time()-t0,1)}s")
  78. with open(out_path, "w", encoding="utf-8") as f:
  79. json.dump({"results": results, "all_passed": all_passed}, f,
  80. ensure_ascii=False, indent=2)
  81. return 0 if all_passed else 1
  82. if __name__ == "__main__":
  83. sys.exit(main())