Coverage for src / lexigram / contracts / ai / vector.py: 0%

53 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:57 +0800

1"""Vector store and document protocols.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable 

7 

8if TYPE_CHECKING: 

9 from lexigram.contracts.core import HealthCheckResult 

10 from lexigram.contracts.core.result import Result 

11 from lexigram.contracts.data.vector.exceptions import VectorError 

12 

13 

14@runtime_checkable 

15class DocumentProtocol(Protocol): 

16 """Structural protocol for unified document representation. 

17 

18 Structural protocol satisfied by any class with the required 

19 fields — used across lexigram-vector and lexigram-ai-rag. 

20 """ 

21 

22 @property 

23 def id(self) -> str | None: 

24 """Unique document identifier, or None if unassigned.""" 

25 ... 

26 

27 @property 

28 def text(self) -> str: 

29 """Text content of the document.""" 

30 ... 

31 

32 @property 

33 def metadata(self) -> dict[str, Any]: 

34 """Arbitrary metadata key-value pairs.""" 

35 ... 

36 

37 

38@dataclass(frozen=True) 

39class EmbeddingResult: 

40 """Result of an embedding operation.""" 

41 

42 vectors: list[list[float]] 

43 model: str 

44 tokens: int 

45 

46 

47@runtime_checkable 

48class SearchResultProtocol(Protocol): 

49 """Protocol for a vector search hit.""" 

50 

51 @property 

52 def text(self) -> str: 

53 """The text content of the search result.""" 

54 ... 

55 

56 @property 

57 def document(self) -> DocumentProtocol: 

58 """The retrieved document.""" 

59 ... 

60 

61 @property 

62 def score(self) -> float: 

63 """Relevance score.""" 

64 ... 

65 

66 @property 

67 def metadata(self) -> dict[str, Any]: 

68 """Additional search-specific metadata.""" 

69 ... 

70 

71 

72@runtime_checkable 

73class BatchProcessorProtocol(Protocol): 

74 """Protocol for batch operations on documents.""" 

75 

76 async def process_batch( 

77 self, 

78 documents: list[DocumentProtocol], 

79 **kwargs: Any, 

80 ) -> Result[int, VectorError]: 

81 """Process a batch of documents.""" 

82 ... 

83 

84 

85@runtime_checkable 

86class DocumentVectorStoreProtocol(Protocol): 

87 """Document-centric vector store API for RAG and AI packages. 

88 

89 This is the AI-layer contract for add/search/delete against embedded 

90 documents. For connection lifecycle and collection management, use 

91 :class:`~lexigram.contracts.data.vector.protocols.VectorStoreProtocol` 

92 (infrastructure layer). 

93 

94 All vector database drivers (Qdrant, ChromaDB, PGVector, etc.) are adapted 

95 to this shape via ``lexigram-vector`` so callers can swap backends. 

96 """ 

97 

98 async def add( 

99 self, documents: list[DocumentProtocol] 

100 ) -> Result[list[str], VectorError]: 

101 """Add documents to the vector store. 

102 

103 Args: 

104 documents: List of documents with text and metadata. 

105 

106 Returns: 

107 ``Ok(list[str])`` with document IDs on success, or 

108 ``Err(VectorStoreError)`` on failure. 

109 """ 

110 ... 

111 

112 async def batch_upsert( 

113 self, 

114 documents: list[DocumentProtocol], 

115 batch_size: int = 100, 

116 ) -> Result[int, VectorError]: 

117 """Upsert documents in batches. 

118 

119 Args: 

120 documents: List of documents to upsert. 

121 batch_size: Number of documents per batch. defaults to 100. 

122 

123 Returns: 

124 ``Ok(int)`` with the total count of documents upserted, or 

125 ``Err(VectorStoreError)`` on failure. 

126 """ 

127 ... 

128 

129 async def search( 

130 self, 

131 query: list[float], 

132 *, 

133 top_k: int = 10, 

134 filters: dict[str, Any] | None = None, 

135 score_threshold: float | None = None, 

136 ) -> Result[list[SearchResultProtocol], VectorError]: 

137 """Search for similar documents using a vector. 

138 

139 Args: 

140 query: Query embedding vector. 

141 top_k: Number of results to return. 

142 filters: Metadata filters. 

143 score_threshold: Minimum relevance score. 

144 

145 Returns: 

146 ``Ok(list of search results)`` on success, or 

147 ``Err(VectorStoreError)`` on failure. 

148 """ 

149 ... 

150 

151 async def search_text( 

152 self, 

153 query: str, 

154 *, 

155 top_k: int = 10, 

156 filters: dict[str, Any] | None = None, 

157 ) -> Result[list[SearchResultProtocol], VectorError]: 

158 """Search for similar documents using text. 

159 

160 Args: 

161 query: Query text. 

162 top_k: Number of results to return. 

163 filters: Metadata filters. 

164 

165 Returns: 

166 ``Ok(list of search results)`` on success, or 

167 ``Err(VectorStoreError)`` on failure. 

168 """ 

169 ... 

170 

171 async def delete(self, ids: list[str]) -> Result[int, VectorError]: 

172 """Delete documents by ID. 

173 

174 Args: 

175 ids: List of document IDs to delete. 

176 

177 Returns: 

178 ``Ok(int)`` with the count of deleted documents on success, or 

179 ``Err(VectorStoreError)`` on failure. 

180 """ 

181 ... 

182 

183 async def health_check(self, timeout: float = 5.0) -> HealthCheckResult: 

184 """Perform a lightweight connectivity check. 

185 

186 Returns: 

187 Structured health check result. 

188 """ 

189 ... 

190 

191 

192@runtime_checkable 

193class ChunkerProtocol(Protocol): 

194 """Protocol for document chunking strategies. 

195 

196 Implementations split document text into smaller, overlapping 

197 or non-overlapping chunks suitable for embedding and retrieval. 

198 """ 

199 

200 def chunk( 

201 self, 

202 text: str, 

203 metadata: dict[str, Any] | None = None, 

204 ) -> list[Any]: 

205 """Split text into chunks. 

206 

207 Args: 

208 text: The document text to chunk. 

209 metadata: Optional metadata to attach to each chunk. 

210 

211 Returns: 

212 List of Chunk objects. 

213 """ 

214 ... 

215 

216 

217@dataclass(frozen=True) 

218class Document: 

219 """Concrete document data class shared across AI packages. 

220 

221 Satisfies ``DocumentProtocol``. Use this when constructing documents 

222 in packages that must not depend on ``lexigram-vector``. 

223 """ 

224 

225 text: str 

226 metadata: dict[str, Any] = field(default_factory=dict) 

227 id: str | None = None 

228 embedding: list[float] | None = None 

229 

230 

231@dataclass(frozen=True) 

232class RAGSearchResult: 

233 """Enriched search result for RAG applications. 

234 

235 Unlike the generic SearchResult from the storage layer (data/vector/types.py), 

236 this result is mutable and focused on RAG workflows: 

237 - Wraps a full Document object (not just id/score/vector/content) 

238 - Includes ranking information useful for re-ranking pipelines 

239 - Mutable to allow post-processing 

240 

241 Satisfies ``SearchResultProtocol``. 

242 """ 

243 

244 document: Document 

245 score: float 

246 rank: int = 0 

247 metadata: dict[str, Any] = field(default_factory=dict) 

248 

249 

250__all__ = [ 

251 "BatchProcessorProtocol", 

252 "ChunkerProtocol", 

253 "Document", 

254 "DocumentProtocol", 

255 "DocumentVectorStoreProtocol", 

256 "EmbeddingResult", 

257 "RAGSearchResult", 

258 "SearchResultProtocol", 

259]