""" Retrieval Strategy Schemas for AI Service. [AC-AISVC-RES-01~15] Request/Response models aligned with OpenAPI contract. """ from enum import Enum from typing import Any from pydantic import BaseModel, Field, model_validator class StrategyType(str, Enum): DEFAULT = "default" ENHANCED = "enhanced" class ReactMode(str, Enum): REACT = "react" NON_REACT = "non_react" class RolloutMode(str, Enum): OFF = "off" PERCENTAGE = "percentage" ALLOWLIST = "allowlist" class ValidationCheckType(str, Enum): METADATA_CONSISTENCY = "metadata_consistency" EMBEDDING_PREFIX = "embedding_prefix" RRF_CONFIG = "rrf_config" PERFORMANCE_BUDGET = "performance_budget" class RolloutConfig(BaseModel): """ [AC-AISVC-RES-03] Grayscale rollout configuration. """ mode: RolloutMode = Field(..., description="Rollout mode: off, percentage, or allowlist") percentage: float | None = Field( default=None, ge=0, le=100, description="Percentage of traffic for grayscale (0-100)", ) allowlist: list[str] | None = Field( default=None, description="List of tenant IDs in allowlist", ) @model_validator(mode="after") def validate_rollout_config(self) -> "RolloutConfig": if self.mode == RolloutMode.PERCENTAGE and self.percentage is None: raise ValueError("percentage is required when mode is 'percentage'") if self.mode == RolloutMode.ALLOWLIST and ( self.allowlist is None or len(self.allowlist) == 0 ): raise ValueError("allowlist is required when mode is 'allowlist'") return self class RetrievalStrategyStatus(BaseModel): """ [AC-AISVC-RES-01] Current retrieval strategy status. """ active_strategy: StrategyType = Field( ..., alias="active_strategy", description="Current active strategy: default or enhanced", ) react_mode: ReactMode = Field( ..., alias="react_mode", description="ReAct mode: react or non_react", ) rollout: RolloutConfig = Field(..., description="Grayscale rollout configuration") model_config = {"populate_by_name": True} class RetrievalStrategySwitchRequest(BaseModel): """ [AC-AISVC-RES-02, AC-AISVC-RES-03, AC-AISVC-RES-05] Request to switch retrieval strategy. """ target_strategy: StrategyType = Field( ..., alias="target_strategy", description="Target strategy to switch to", ) react_mode: ReactMode | None = Field( default=None, alias="react_mode", description="ReAct mode to use", ) rollout: RolloutConfig | None = Field( default=None, description="Grayscale rollout configuration", ) reason: str | None = Field( default=None, description="Reason for strategy switch", ) model_config = {"populate_by_name": True} class RetrievalStrategySwitchResponse(BaseModel): """ [AC-AISVC-RES-02] Response after strategy switch. """ previous: RetrievalStrategyStatus = Field(..., description="Previous strategy status") current: RetrievalStrategyStatus = Field(..., description="Current strategy status") class RetrievalStrategyValidationRequest(BaseModel): """ [AC-AISVC-RES-04, AC-AISVC-RES-06, AC-AISVC-RES-08] Request to validate strategy. """ strategy: StrategyType = Field(..., description="Strategy to validate") react_mode: ReactMode | None = Field( default=None, description="ReAct mode to validate", ) checks: list[ValidationCheckType] | None = Field( default=None, description="List of checks to perform", ) model_config = {"populate_by_name": True} class ValidationResult(BaseModel): """ [AC-AISVC-RES-06] Single validation check result. """ check: str = Field(..., description="Check name") passed: bool = Field(..., description="Whether the check passed") message: str | None = Field(default=None, description="Additional message") class RetrievalStrategyValidationResponse(BaseModel): """ [AC-AISVC-RES-06] Validation response with all check results. """ passed: bool = Field(..., description="Whether all checks passed") results: list[ValidationResult] = Field(..., description="Individual check results") class RetrievalStrategyRollbackResponse(BaseModel): """ [AC-AISVC-RES-07] Response after strategy rollback. """ current: RetrievalStrategyStatus = Field(..., description="Current strategy status before rollback") rollback_to: RetrievalStrategyStatus = Field(..., description="Strategy status after rollback") class StrategyAuditLog(BaseModel): """ [AC-AISVC-RES-07] Audit log entry for strategy operations. """ timestamp: str = Field(..., description="ISO timestamp of the operation") operation: str = Field(..., description="Operation type: switch, rollback, validate") previous_strategy: str | None = Field(default=None, description="Previous strategy") new_strategy: str | None = Field(default=None, description="New strategy") previous_react_mode: str | None = Field(default=None, description="Previous react mode") new_react_mode: str | None = Field(default=None, description="New react mode") reason: str | None = Field(default=None, description="Reason for the operation") operator: str | None = Field(default=None, description="Operator who performed the operation") tenant_id: str | None = Field(default=None, description="Tenant ID if applicable") metadata: dict[str, Any] | None = Field(default=None, description="Additional metadata") class StrategyMetrics(BaseModel): """ [AC-AISVC-RES-03, AC-AISVC-RES-08] Metrics for strategy operations. """ strategy: StrategyType = Field(..., description="Current strategy") react_mode: ReactMode = Field(..., description="Current react mode") total_requests: int = Field(default=0, description="Total requests count") successful_requests: int = Field(default=0, description="Successful requests count") failed_requests: int = Field(default=0, description="Failed requests count") avg_latency_ms: float = Field(default=0.0, description="Average latency in ms") p99_latency_ms: float = Field(default=0.0, description="P99 latency in ms") direct_route_count: int = Field(default=0, description="Direct route count") react_route_count: int = Field(default=0, description="React route count") auto_route_count: int = Field(default=0, description="Auto route count") fallback_count: int = Field(default=0, description="Fallback to default count") last_updated: str | None = Field(default=None, description="Last update timestamp")