Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-rag/src/lexigram/ai/rag/synthesis/synthesizers/hybrid.py: 35%

26 statements  

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

1"""Hybrid synthesizer implementation. 

2 

3Combines extractive and abstractive approaches for robust synthesis. 

4""" 

5 

6from __future__ import annotations 

7 

8from lexigram.ai.rag.synthesis.synthesizers.abstractive import ( 

9 AbstractiveSynthesizer, 

10) 

11from lexigram.ai.rag.synthesis.synthesizers.base import AbstractSynthesizer 

12from lexigram.ai.rag.synthesis.synthesizers.extractive import ( 

13 ExtractiveSynthesizer, 

14) 

15from lexigram.ai.rag.synthesis.types import ( 

16 ContextChunk, 

17 SynthesisResult, 

18 SynthesisStrategy, 

19) 

20from lexigram.contracts import ( 

21 LLMClientProtocol, 

22) 

23 

24 

25class HybridSynthesizer(AbstractSynthesizer): 

26 """Hybrid extractive + abstractive synthesizer. 

27 

28 This synthesizer first uses extractive methods to reduce and rank context, 

29 then uses abstractive generation for the final response. 

30 

31 Attributes: 

32 extractive: Extractive synthesizer for context reduction 

33 abstractive: Abstractive synthesizer for final generation 

34 extraction_ratio: Ratio of content to extract (0-1) 

35 """ 

36 

37 def __init__( 

38 self, 

39 llm_client: LLMClientProtocol, 

40 extractive_weight: float = 0.3, 

41 abstractive_weight: float = 0.7, 

42 min_extractive_score: float = 0.5, 

43 extraction_ratio: float = 0.5, 

44 max_extractive_sentences: int = 5, 

45 ): 

46 """Initialize the hybrid synthesizer. 

47 

48 Args: 

49 llm_client: LLM client implementing LLMClientProtocol protocol 

50 extractive_weight: Weight for extractive component (0-1) 

51 abstractive_weight: Weight for abstractive component (0-1) 

52 min_extractive_score: Minimum score to use extractive results 

53 extraction_ratio: Fraction of content to extract before abstraction 

54 max_extractive_sentences: Max sentences to extract in extractive phase 

55 """ 

56 self.extractive_weight = extractive_weight 

57 self.abstractive_weight = abstractive_weight 

58 self.min_extractive_score = min_extractive_score 

59 self.extraction_ratio = extraction_ratio 

60 

61 self.extractive = ExtractiveSynthesizer( 

62 max_sentences=max_extractive_sentences, 

63 reorder_sentences=True, 

64 ) 

65 self.abstractive = AbstractiveSynthesizer( 

66 llm_client=llm_client, 

67 temperature=0.3, # Default temperature 

68 ) 

69 

70 async def _synthesize_internal( 

71 self, 

72 query: str, 

73 context_chunks: list[ContextChunk], 

74 **kwargs, 

75 ) -> SynthesisResult: 

76 """Synthesize response using hybrid approach. 

77 

78 Args: 

79 query: The user query 

80 context_chunks: Retrieved context chunks 

81 **kwargs: Additional parameters 

82 

83 Returns: 

84 SynthesisResult with hybrid-synthesized response 

85 

86 Raises: 

87 ValueError: If query is empty or no context chunks provided 

88 """ 

89 if not query: 

90 msg = "Query cannot be empty" 

91 raise ValueError(msg) 

92 if not context_chunks: 

93 msg = "No context chunks provided" 

94 raise ValueError(msg) 

95 

96 # Phase 1: Extractive reduction 

97 # Extract most relevant sentences to reduce context size 

98 extractive_result = await self.extractive._synthesize_internal( 

99 query=query, 

100 context_chunks=context_chunks, 

101 ) 

102 

103 # Create condensed chunks from extracted content 

104 condensed_chunk = ContextChunk( 

105 text=extractive_result.response, 

106 source="extracted_content", 

107 score=1.0, 

108 metadata={ 

109 "num_sentences": extractive_result.metadata.get("num_sentences", 0), 

110 "original_chunks": len(context_chunks), 

111 }, 

112 ) 

113 

114 # Phase 2: Abstractive synthesis 

115 # Use LLM to generate final response from condensed content 

116 abstractive_result = await self.abstractive._synthesize_internal( 

117 query=query, 

118 context_chunks=[condensed_chunk], 

119 **kwargs, 

120 ) 

121 

122 # Combine metadata from both phases 

123 metadata = { 

124 "extraction_phase": extractive_result.metadata, 

125 "abstraction_phase": abstractive_result.metadata, 

126 "original_chunks": len(context_chunks), 

127 "extractive_weight": self.extractive_weight, 

128 "abstractive_weight": self.abstractive_weight, 

129 } 

130 

131 return SynthesisResult( 

132 query=query, 

133 response=abstractive_result.response, 

134 strategy=SynthesisStrategy.HYBRID, 

135 context_chunks=extractive_result.context_chunks, # Original chunks used 

136 citations=abstractive_result.citations, 

137 metadata=metadata, 

138 )