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 ...