pipeline.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. from __future__ import annotations
  2. from collections.abc import Sequence
  3. from typing import Protocol
  4. from app.core.knowledge.access import KnowledgeAccessContext
  5. from app.core.knowledge.retrieval.contracts import KnowledgeEvidence, SearchResult
  6. from app.core.knowledge.retrieval.fusion import reciprocal_rank_fusion
  7. from app.core.knowledge.retrieval.router import route_query
  8. class Retriever(Protocol):
  9. def retrieve(
  10. self,
  11. query: str,
  12. context: KnowledgeAccessContext,
  13. limit: int,
  14. ) -> Sequence[KnowledgeEvidence]: ...
  15. class KnowledgeRetrievalPipeline:
  16. def __init__(
  17. self,
  18. *,
  19. lexical: Retriever,
  20. vector: Retriever,
  21. graph: Retriever | None = None,
  22. lightrag: Retriever | None = None,
  23. ):
  24. self._retrievers = {
  25. "lexical": lexical,
  26. "vector": vector,
  27. "graph": graph,
  28. "lightrag": lightrag,
  29. }
  30. def search(
  31. self,
  32. query: str,
  33. *,
  34. context: KnowledgeAccessContext,
  35. mode: str = "auto",
  36. limit: int = 20,
  37. ) -> SearchResult:
  38. resolved_mode = route_query(query) if mode == "auto" else mode
  39. names = ["lexical", "vector"]
  40. if resolved_mode == "relationship" and self._retrievers["graph"] is not None:
  41. names.append("graph")
  42. if resolved_mode == "global" and self._retrievers["lightrag"] is not None:
  43. names.append("lightrag")
  44. ranked: dict[str, Sequence[KnowledgeEvidence]] = {}
  45. degraded: list[str] = []
  46. for name in names:
  47. retriever = self._retrievers[name]
  48. if retriever is None:
  49. continue
  50. try:
  51. ranked[name] = retriever.retrieve(query, context, limit)
  52. except Exception:
  53. degraded.append(name)
  54. fused = reciprocal_rank_fusion(ranked, limit=limit)
  55. authorized = tuple(
  56. evidence
  57. for evidence in fused
  58. if context.permits_domain(evidence.business_domain_uid)
  59. and evidence.freshness_status != "stale"
  60. )
  61. return SearchResult(
  62. evidence=authorized,
  63. mode=resolved_mode,
  64. degraded_components=tuple(degraded),
  65. )