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

105 statements  

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

1"""Core types for RAG pipeline execution. 

2 

3This module provides the foundational types used throughout the pipeline, 

4including context, errors, and configuration structures. 

5""" 

6 

7from __future__ import annotations 

8 

9from dataclasses import dataclass, field 

10from datetime import UTC, datetime 

11from enum import StrEnum 

12from typing import Any 

13import uuid 

14 

15from lexigram.ai.rag.chunking import Chunk 

16from lexigram.ai.rag.query import TransformedQuery 

17from lexigram.ai.rag.routing import QueryIntent, RoutingDecision 

18from lexigram.ai.rag.synthesis import ( 

19 ContextChunk, 

20 QualityMetrics, 

21 SynthesisResult, 

22) 

23 

24 

25class ErrorStrategy(StrEnum): 

26 """Strategy for handling errors in pipeline stages.""" 

27 

28 RETRY = "retry" # Retry with exponential backoff 

29 FALLBACK = "fallback" # Use fallback implementation 

30 SKIP = "skip" # Skip stage and continue 

31 FAIL_FAST = "fail_fast" # Raise exception immediately 

32 GRACEFUL = "graceful" # Return partial results with warnings 

33 

34 

35class StageStatus(StrEnum): 

36 """Status of a pipeline stage.""" 

37 

38 PENDING = "pending" 

39 RUNNING = "running" 

40 COMPLETED = "completed" 

41 FAILED = "failed" 

42 SKIPPED = "skipped" 

43 

44 

45@dataclass 

46class PipelineError: 

47 """Represents an error that occurred during pipeline execution.""" 

48 

49 stage: str 

50 error: Exception 

51 timestamp: datetime = field(default_factory=lambda: datetime.now(UTC)) 

52 recoverable: bool = True 

53 retry_count: int = 0 

54 

55 

56@dataclass 

57class StageMetrics: 

58 """Metrics for a single pipeline stage.""" 

59 

60 stage_name: str 

61 status: StageStatus 

62 start_time: datetime | None = None 

63 end_time: datetime | None = None 

64 duration_ms: float = 0.0 

65 error: PipelineError | None = None 

66 

67 @property 

68 def duration_seconds(self) -> float: 

69 """Get duration in seconds.""" 

70 return self.duration_ms / 1000.0 

71 

72 

73@dataclass 

74class PipelineContext: 

75 """Context object that flows through pipeline stages. 

76 

77 This context carries all state through the pipeline, accumulating 

78 results from each stage and tracking metrics/errors. 

79 """ 

80 

81 # Required input 

82 query: str 

83 

84 # Optional input documents 

85 documents: list[str] = field(default_factory=list) 

86 document_paths: list[str] = field(default_factory=list) 

87 

88 # Stage results - populated by pipeline stages 

89 transformed_queries: list[TransformedQuery] = field(default_factory=list) 

90 chunks: list[Chunk] = field(default_factory=list) 

91 retrieved_chunks: list[ContextChunk] = field(default_factory=list) 

92 optimized_chunks: list[ContextChunk] = field(default_factory=list) 

93 synthesis_result: SynthesisResult | None = None 

94 quality_metrics: QualityMetrics | None = None 

95 

96 # Routing and intent 

97 routing_decision: RoutingDecision | None = None 

98 intent: QueryIntent | None = None 

99 

100 # Request tracking 

101 request_id: str = field(default_factory=lambda: str(uuid.uuid4())) 

102 created_at: datetime = field(default_factory=lambda: datetime.now(UTC)) 

103 

104 # Custom metadata 

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

106 

107 # Performance metrics 

108 stage_metrics: list[StageMetrics] = field(default_factory=list) 

109 token_usage: dict[str, int] = field(default_factory=dict) 

110 cache_hits: dict[str, bool] = field(default_factory=dict) 

111 

112 # Error tracking 

113 errors: list[PipelineError] = field(default_factory=list) 

114 warnings: list[str] = field(default_factory=list) 

115 

116 @property 

117 def has_errors(self) -> bool: 

118 """Check if any errors occurred.""" 

119 return len(self.errors) > 0 

120 

121 @property 

122 def has_warnings(self) -> bool: 

123 """Check if any warnings were issued.""" 

124 return len(self.warnings) > 0 

125 

126 @property 

127 def total_duration_ms(self) -> float: 

128 """Calculate total pipeline duration in milliseconds.""" 

129 return sum(m.duration_ms for m in self.stage_metrics) 

130 

131 @property 

132 def total_duration_seconds(self) -> float: 

133 """Calculate total pipeline duration in seconds.""" 

134 return self.total_duration_ms / 1000.0 

135 

136 @property 

137 def failed_stages(self) -> list[str]: 

138 """Get list of failed stage names.""" 

139 return [ 

140 m.stage_name for m in self.stage_metrics if m.status == StageStatus.FAILED 

141 ] 

142 

143 @property 

144 def completed_stages(self) -> list[str]: 

145 """Get list of completed stage names.""" 

146 return [ 

147 m.stage_name 

148 for m in self.stage_metrics 

149 if m.status == StageStatus.COMPLETED 

150 ] 

151 

152 def add_error( 

153 self, 

154 stage: str, 

155 error: Exception, 

156 recoverable: bool = True, 

157 retry_count: int = 0, 

158 ) -> None: 

159 """Add an error to the context. 

160 

161 Args: 

162 stage: Name of the stage where error occurred 

163 error: The exception that was raised 

164 recoverable: Whether the error is recoverable 

165 retry_count: Number of times this error has been retried 

166 """ 

167 pipeline_error = PipelineError( 

168 stage=stage, 

169 error=error, 

170 recoverable=recoverable, 

171 retry_count=retry_count, 

172 ) 

173 self.errors.append(pipeline_error) 

174 

175 def add_warning(self, message: str) -> None: 

176 """Add a warning message to the context. 

177 

178 Args: 

179 message: Warning message 

180 """ 

181 self.warnings.append(message) 

182 

183 def start_stage(self, stage_name: str) -> None: 

184 """Mark a stage as started. 

185 

186 Args: 

187 stage_name: Name of the stage 

188 """ 

189 metrics = StageMetrics( 

190 stage_name=stage_name, 

191 status=StageStatus.RUNNING, 

192 start_time=datetime.now(UTC), 

193 ) 

194 self.stage_metrics.append(metrics) 

195 

196 def complete_stage( 

197 self, 

198 stage_name: str, 

199 status: StageStatus = StageStatus.COMPLETED, 

200 error: PipelineError | None = None, 

201 ) -> None: 

202 """Mark a stage as completed. 

203 

204 Args: 

205 stage_name: Name of the stage 

206 status: Final status of the stage 

207 error: Optional error if stage failed 

208 """ 

209 for metrics in reversed(self.stage_metrics): 

210 if ( 

211 metrics.stage_name == stage_name 

212 and metrics.status == StageStatus.RUNNING 

213 ): 

214 metrics.status = status 

215 metrics.end_time = datetime.now(UTC) 

216 if metrics.start_time: 

217 duration = (metrics.end_time - metrics.start_time).total_seconds() 

218 metrics.duration_ms = duration * 1000.0 

219 metrics.error = error 

220 break 

221 

222 def record_token_usage(self, operation: str, tokens: int) -> None: 

223 """Record token usage for an operation. 

224 

225 Args: 

226 operation: Name of the operation (e.g., "embedding", "llm_call") 

227 tokens: Number of tokens used 

228 """ 

229 if operation in self.token_usage: 

230 self.token_usage[operation] += tokens 

231 else: 

232 self.token_usage[operation] = tokens 

233 

234 def record_cache_hit(self, cache_key: str, hit: bool) -> None: 

235 """Record a cache hit or miss. 

236 

237 Args: 

238 cache_key: Key for the cache lookup 

239 hit: Whether the cache was hit 

240 """ 

241 self.cache_hits[cache_key] = hit 

242 

243 def to_dict(self) -> dict[str, Any]: 

244 """Convert context to dictionary for serialization. 

245 

246 Returns: 

247 Dictionary representation of the context 

248 """ 

249 return { 

250 "request_id": self.request_id, 

251 "query": self.query, 

252 "created_at": self.created_at.isoformat(), 

253 "total_duration_ms": self.total_duration_ms, 

254 "completed_stages": self.completed_stages, 

255 "failed_stages": self.failed_stages, 

256 "has_errors": self.has_errors, 

257 "has_warnings": self.has_warnings, 

258 "error_count": len(self.errors), 

259 "warning_count": len(self.warnings), 

260 "token_usage": self.token_usage, 

261 "cache_hits": dict(self.cache_hits), 

262 "metadata": self.metadata, 

263 }