Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-rag/src/lexigram/ai/rag/pipeline/stages/retrieval.py: 78%

37 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Retrieval stage for fetching relevant context chunks.""" 

2 

3from __future__ import annotations 

4 

5from lexigram.ai.rag.config import RetrievalConfig 

6from lexigram.ai.rag.pipeline.types import PipelineContext 

7from lexigram.ai.rag.synthesis import ContextChunk 

8from lexigram.contracts.ai.vector import DocumentVectorStoreProtocol 

9from lexigram.logging import ( 

10 get_logger, 

11) 

12 

13logger = get_logger(__name__) 

14 

15 

16class RetrievalStage: 

17 """Pipeline stage for retrieving relevant context chunks. 

18 

19 This stage retrieves relevant chunks based on the query, 

20 using vector search, knowledge graph, or multi-hop reasoning 

21 as configured. 

22 """ 

23 

24 def __init__( 

25 self, 

26 config: RetrievalConfig, 

27 vector_store: DocumentVectorStoreProtocol | None = None, 

28 ): 

29 """Initialize the retrieval stage. 

30 

31 Args: 

32 config: Retrieval configuration 

33 vector_store: Optional vector store for similarity search 

34 """ 

35 self.config = config 

36 self.vector_store = vector_store 

37 

38 @property 

39 def name(self) -> str: 

40 """Get stage name.""" 

41 return "retrieval" 

42 

43 async def process(self, context: PipelineContext) -> PipelineContext: 

44 """Process the retrieval stage. 

45 

46 Args: 

47 context: Pipeline context with query and chunks 

48 

49 Returns: 

50 Updated context with retrieved chunks 

51 """ 

52 if not self.config.enabled: 

53 logger.info("Retrieval stage disabled, skipping") 

54 return context 

55 

56 try: 

57 logger.info( 

58 "Starting retrieval", 

59 extra={ 

60 "request_id": context.request_id, 

61 "query": context.query, 

62 "available_chunks": len(context.chunks), 

63 }, 

64 ) 

65 

66 # For now, use a simple mock retrieval 

67 # In a full implementation, this would use: 

68 # - Vector store for similarity search 

69 # - Knowledge graph for entity-based retrieval 

70 # - Multi-hop reasoning for complex queries 

71 

72 retrieved_chunks = await self._retrieve_chunks(context) 

73 

74 context.retrieved_chunks = retrieved_chunks 

75 

76 logger.info( 

77 "Retrieval completed", 

78 extra={ 

79 "request_id": context.request_id, 

80 "retrieved_count": len(retrieved_chunks), 

81 }, 

82 ) 

83 

84 except Exception as e: 

85 logger.exception( 

86 "Retrieval failed", 

87 extra={ 

88 "request_id": context.request_id, 

89 "error": str(e), 

90 }, 

91 ) 

92 raise 

93 

94 return context 

95 

96 async def _retrieve_chunks( 

97 self, 

98 context: PipelineContext, 

99 ) -> list[ContextChunk]: 

100 """Retrieve relevant chunks based on query. 

101 

102 Args: 

103 context: Pipeline context 

104 

105 Returns: 

106 List of retrieved context chunks 

107 """ 

108 # Simple mock retrieval: convert chunks to ContextChunks 

109 # In a real implementation, this would: 

110 # 1. Compute query embeddings 

111 # 2. Search vector store 

112 # 3. Rank by relevance 

113 # 4. Apply filtering 

114 

115 if not context.chunks: 

116 logger.warning("No chunks available for retrieval") 

117 return [] 

118 

119 # Convert to ContextChunks 

120 retrieved: list[ContextChunk] = [] 

121 

122 for i, chunk in enumerate(context.chunks[: self.config.top_k]): 

123 md = chunk.metadata or {} 

124 context_chunk = ContextChunk( 

125 text=chunk.text, 

126 source=md.get("source", f"chunk_{i}"), 

127 score=1.0 - (i * 0.1), # Mock score 

128 metadata=md, 

129 rank=i, 

130 ) 

131 retrieved.append(context_chunk) 

132 

133 return retrieved