retriever.py 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. from langchain_chroma import Chroma
  2. from langchain_openai import ChatOpenAI
  3. from app.config import settings
  4. from app.rag.embeddings import get_embeddings
  5. from app.tools.java_client import JavaClient
  6. from typing import Optional
  7. import logging
  8. logger = logging.getLogger(__name__)
  9. class RagRetriever:
  10. """简单的向量检索器 - 基于 ChromaDB"""
  11. def __init__(self, collection_name: str = "cfc_knowledge"):
  12. embeddings = get_embeddings()
  13. self.vectorstore = Chroma(
  14. collection_name=collection_name,
  15. embedding_function=embeddings,
  16. persist_directory=settings.chroma_db_path,
  17. )
  18. self.java_client = JavaClient()
  19. async def initialize(self):
  20. """初始化向量库连接(不写入任何内容)。
  21. 内容统一由 app.tasks.knowledge_sync.sync_knowledge_base 维护(article/microbiome/dan_knowledge 三源)。
  22. 旧实现在此额外写入一版粗粒度文章索引(metadata.type=article,标题+摘要+标签),
  23. 与同步任务写入的 metadata.source=article 分块索引(标题+摘要+正文)构成同一批文章的
  24. 双份向量,且每次容器重启都会追加,导致 HNSW 索引无限膨胀。治理后统一移除。
  25. """
  26. try:
  27. count = self.vectorstore._collection.count()
  28. logger.info("知识库初始化完成:collection 当前 %d 个文档(内容同步由 knowledge_sync 维护)", count)
  29. except Exception as e:
  30. logger.warning("知识库初始化失败:%s", e)
  31. async def retrieve(
  32. self,
  33. query: str,
  34. filters: Optional[dict] = None,
  35. k: int = 5,
  36. ) -> list[dict]:
  37. """向量相似度检索"""
  38. results = []
  39. # 向量检索
  40. filter_query = {"user_id": filters["user_id"]} if filters and "user_id" in filters else None
  41. docs = self.vectorstore.similarity_search(
  42. query,
  43. k=k * 2, # 多取一些
  44. filter=filter_query,
  45. )
  46. # 格式化结果并去重
  47. seen = set()
  48. for doc in docs:
  49. content_hash = hash(doc.page_content[:100])
  50. if content_hash in seen:
  51. continue
  52. seen.add(content_hash)
  53. results.append({
  54. "content": doc.page_content,
  55. "metadata": doc.metadata,
  56. "score": doc.metadata.get("score", 0) if hasattr(doc, "metadata") else 0,
  57. })
  58. if len(results) >= k:
  59. break
  60. return results[:k]