""" Retrieval Strategy Integration for Dialogue Flow. [AC-AISVC-RES-01~15] Integrates StrategyRouter and ModeRouter into dialogue pipeline. Usage: from app.services.retrieval.strategy_integration import RetrievalStrategyIntegration integration = RetrievalStrategyIntegration() result = await integration.execute(ctx) """ from __future__ import annotations import logging import time from dataclasses import dataclass, field from typing import TYPE_CHECKING, Any from app.services.retrieval.routing_config import ( RagRuntimeMode, StrategyType, RoutingConfig, StrategyContext, StrategyResult, ) from app.services.retrieval.strategy_router import ( StrategyRouter, get_strategy_router, ) from app.services.retrieval.mode_router import ( ModeRouter, ModeRouteResult, get_mode_router, ) if TYPE_CHECKING: from app.services.retrieval.base import RetrievalResult logger = logging.getLogger(__name__) @dataclass class RetrievalStrategyResult: """Combined result from strategy and mode routing.""" retrieval_result: "RetrievalResult | None" final_answer: str | None strategy: StrategyType mode: RagRuntimeMode should_fallback: bool = False fallback_reason: str | None = None mode_route_result: ModeRouteResult | None = None diagnostics: dict[str, Any] = field(default_factory=dict) duration_ms: int = 0 class RetrievalStrategyIntegration: """ [AC-AISVC-RES-01~15] Integration layer for retrieval strategy. Combines StrategyRouter and ModeRouter to provide a unified interface for the dialogue pipeline. Flow: 1. StrategyRouter selects default or enhanced strategy 2. ModeRouter selects direct, react, or auto mode 3. Execute retrieval with selected strategy and mode 4. Handle fallback scenarios """ def __init__( self, config: RoutingConfig | None = None, strategy_router: StrategyRouter | None = None, mode_router: ModeRouter | None = None, ): self._config = config or RoutingConfig() self._strategy_router = strategy_router or get_strategy_router() self._mode_router = mode_router or get_mode_router() @property def config(self) -> RoutingConfig: """Get current configuration.""" return self._config def update_config(self, new_config: RoutingConfig) -> None: """ [AC-AISVC-RES-15] Update all routing configurations. """ self._config = new_config self._strategy_router.update_config(new_config) self._mode_router.update_config(new_config) logger.info( f"[AC-AISVC-RES-15] RetrievalStrategyIntegration config updated: " f"strategy={new_config.strategy.value}, mode={new_config.rag_runtime_mode.value}" ) async def execute( self, ctx: StrategyContext, ) -> RetrievalStrategyResult: """ Execute retrieval with strategy and mode routing. Args: ctx: Strategy context with tenant, query, metadata, etc. Returns: RetrievalStrategyResult with retrieval results and diagnostics """ start_time = time.time() strategy_result = self._strategy_router.route(ctx) mode_result = self._mode_router.route(ctx) logger.info( f"[AC-AISVC-RES-01~15] Strategy routing: " f"strategy={strategy_result.strategy.value}, mode={mode_result.mode.value}, " f"tenant={ctx.tenant_id}, query_len={len(ctx.query)}" ) retrieval_result = None final_answer = None should_fallback = False fallback_reason = None try: if mode_result.mode == RagRuntimeMode.DIRECT: retrieval_result, answer, mode_result = await self._mode_router.execute_with_fallback(ctx) if answer is not None: final_answer = answer should_fallback = mode_result.should_fallback_to_react fallback_reason = mode_result.fallback_reason elif mode_result.mode == RagRuntimeMode.REACT: answer, retrieval_result, react_ctx = await self._mode_router.execute_react(ctx) final_answer = answer else: retrieval_result, answer, mode_result = await self._mode_router.execute_with_fallback(ctx) if answer is not None: final_answer = answer should_fallback = mode_result.should_fallback_to_react fallback_reason = mode_result.fallback_reason except Exception as e: logger.error( f"[AC-AISVC-RES-07] Retrieval strategy execution failed: {e}" ) if strategy_result.strategy == StrategyType.ENHANCED: self._strategy_router.rollback( reason=str(e), tenant_id=ctx.tenant_id, ) from app.services.retrieval.optimized_retriever import get_optimized_retriever from app.services.retrieval.base import RetrievalContext retriever = await get_optimized_retriever() retrieval_ctx = RetrievalContext( tenant_id=ctx.tenant_id, query=ctx.query, metadata_filter=ctx.metadata_filter, kb_ids=ctx.kb_ids, ) retrieval_result = await retriever.retrieve(retrieval_ctx) should_fallback = True fallback_reason = str(e) else: raise duration_ms = int((time.time() - start_time) * 1000) return RetrievalStrategyResult( retrieval_result=retrieval_result, final_answer=final_answer, strategy=strategy_result.strategy, mode=mode_result.mode, should_fallback=should_fallback, fallback_reason=fallback_reason, mode_route_result=mode_result, diagnostics={ "strategy_diagnostics": strategy_result.diagnostics, "mode_diagnostics": mode_result.diagnostics, "duration_ms": duration_ms, }, duration_ms=duration_ms, ) def get_current_strategy(self) -> StrategyType: """Get current active strategy.""" return self._strategy_router.current_strategy def get_rollback_records(self, limit: int = 10) -> list[dict[str, Any]]: """Get recent rollback records.""" records = self._strategy_router.get_rollback_records(limit) return [ { "timestamp": r.timestamp, "from_strategy": r.from_strategy.value, "to_strategy": r.to_strategy.value, "reason": r.reason, "tenant_id": r.tenant_id, } for r in records ] def validate_config(self) -> tuple[bool, list[str]]: """Validate current configuration.""" return self._config.validate() _integration: RetrievalStrategyIntegration | None = None def get_retrieval_strategy_integration() -> RetrievalStrategyIntegration: """Get or create RetrievalStrategyIntegration singleton.""" global _integration if _integration is None: _integration = RetrievalStrategyIntegration() return _integration def reset_retrieval_strategy_integration() -> None: """Reset RetrievalStrategyIntegration singleton (for testing).""" global _integration _integration = None