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

27 statements  

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

1"""Base synthesizer protocol and abstract base class. 

2 

3This module defines the internal protocol that all response synthesizers must 

4implement, and the abstract base class that bridges to the contracts-level 

5``SynthesizerProtocol`` by converting ``SearchResultProtocol`` items to 

6internal ``ContextChunk`` objects. 

7""" 

8 

9from __future__ import annotations 

10 

11from abc import ABC, abstractmethod 

12from typing import TYPE_CHECKING, Any, Protocol 

13 

14from lexigram.ai.rag.synthesis.types import ContextChunk, SynthesisResult 

15from lexigram.contracts.ai.exceptions import RAGError 

16from lexigram.contracts.ai.rag import RAGResponse, SynthesisError 

17from lexigram.result import Err, Ok, Result 

18 

19if TYPE_CHECKING: 

20 from lexigram.contracts.ai.vector import SearchResultProtocol 

21 

22 

23class ResponseSynthesizerProtocol(Protocol): 

24 """Protocol for internal response synthesizers. 

25 

26 All synthesizer implementations must provide an async 

27 ``_synthesize_internal`` method that takes a query and context chunks 

28 and returns a :class:`SynthesisResult`. 

29 """ 

30 

31 async def _synthesize_internal( 

32 self, 

33 query: str, 

34 context_chunks: list[ContextChunk], 

35 **kwargs: Any, 

36 ) -> SynthesisResult: 

37 """Synthesize a response from query and context chunks. 

38 

39 Args: 

40 query: The user query. 

41 context_chunks: Retrieved context chunks. 

42 **kwargs: Additional synthesis parameters. 

43 

44 Returns: 

45 SynthesisResult with the synthesized response and metadata. 

46 

47 Raises: 

48 ValueError: If query is empty or no context chunks provided. 

49 """ 

50 ... 

51 

52 

53class AbstractSynthesizer(ABC): 

54 """Abstract base providing a contracts-conformant ``synthesize()`` method. 

55 

56 Subclasses implement :meth:`_synthesize_internal` with the internal 

57 ``ContextChunk``-based signature. This class bridges to the contracts 

58 ``SynthesizerProtocol`` by converting ``SearchResultProtocol`` items to 

59 ``ContextChunk`` objects and wrapping the result in 

60 ``Result[RAGResponse, RAGError]``. 

61 """ 

62 

63 def _to_context_chunk(self, result: SearchResultProtocol) -> ContextChunk: 

64 """Convert a ``SearchResultProtocol`` to an internal ``ContextChunk``. 

65 

66 Args: 

67 result: The search result to convert. 

68 

69 Returns: 

70 An internal ContextChunk populated from the search result. 

71 """ 

72 doc = result.document 

73 source = doc.id or "unknown" 

74 score = max(0.0, min(1.0, float(result.score))) 

75 return ContextChunk( 

76 text=doc.text, 

77 source=source, 

78 score=score, 

79 metadata=dict(result.metadata), 

80 ) 

81 

82 async def synthesize( 

83 self, 

84 query: str, 

85 context: list[SearchResultProtocol], 

86 **kwargs: Any, 

87 ) -> Result[RAGResponse, RAGError]: 

88 """Synthesize an answer conforming to ``SynthesizerProtocol``. 

89 

90 Converts ``SearchResultProtocol`` items to internal ``ContextChunk`` 

91 objects, delegates to :meth:`_synthesize_internal`, and wraps the 

92 outcome in ``Result[RAGResponse, RAGError]``. 

93 

94 Args: 

95 query: The user query. 

96 context: Search results providing context. 

97 **kwargs: Additional synthesis parameters forwarded to the 

98 internal implementation. 

99 

100 Returns: 

101 ``Ok(RAGResponse)`` on success, ``Err(RAGError)`` on failure. 

102 """ 

103 try: 

104 chunks = [self._to_context_chunk(sr) for sr in context] 

105 internal = await self._synthesize_internal(query, chunks, **kwargs) 

106 confidence: float | None = ( 

107 internal.quality_metrics.confidence 

108 if internal.quality_metrics 

109 else None 

110 ) 

111 return Ok( 

112 RAGResponse( 

113 answer=internal.response, 

114 sources=list(context), 

115 citations=internal.citations or None, 

116 confidence=confidence, 

117 ) 

118 ) 

119 except RAGError as exc: 

120 return Err(exc) 

121 except Exception as exc: 

122 return Err(SynthesisError(str(exc))) 

123 

124 @abstractmethod 

125 async def _synthesize_internal( 

126 self, 

127 query: str, 

128 context_chunks: list[ContextChunk], 

129 **kwargs: Any, 

130 ) -> SynthesisResult: 

131 """Perform internal synthesis from context chunks. 

132 

133 Args: 

134 query: The user query. 

135 context_chunks: Retrieved context chunks. 

136 **kwargs: Additional synthesis parameters. 

137 

138 Returns: 

139 SynthesisResult with the synthesized response and metadata. 

140 

141 Raises: 

142 ValueError: If query is empty or no context chunks provided. 

143 """ 

144 ...