"""Deterministic local providers for tests and offline Docker verification only.""" from __future__ import annotations import hashlib import math import re import time from collections.abc import Sequence from app.ports.model_providers import ( EmbeddingResult, ModelProviderError, ProviderErrorKind, ProviderUsage, RankedItem, RerankResult, ) _TOKEN_PATTERN = re.compile(r"[\u3400-\u9fff]+|[a-zA-Z0-9_.%+-]+") def invalid_input(operation: str, code: str) -> ModelProviderError: return ModelProviderError( operation=operation, kind=ProviderErrorKind.INVALID_REQUEST, provider_code=code, ) def lexical_features(text: str) -> tuple[str, ...]: """Create stable character n-grams/words without pretending to be a tokenizer.""" features: list[str] = [] for token in _TOKEN_PATTERN.findall(text.lower()): if "\u3400" <= token[0] <= "\u9fff": features.extend(token) features.extend(token[index : index + 2] for index in range(len(token) - 1)) else: features.append(token) return tuple(features) class FakeEmbeddingProvider: """Feature-hashing embedding used to validate plumbing without cloud calls.""" def __init__(self, dimension: int = 1024) -> None: if dimension < 1: raise ValueError("dimension must be positive") self.dimension = dimension def _vector(self, text: str) -> tuple[float, ...]: vector = [0.0] * self.dimension for feature in lexical_features(text): digest = hashlib.sha256(feature.encode("utf-8")).digest() index = int.from_bytes(digest[:4], "big") % self.dimension sign = 1.0 if digest[4] & 1 else -1.0 vector[index] += sign norm = math.sqrt(sum(value * value for value in vector)) if norm == 0: vector[0] = 1.0 norm = 1.0 return tuple(value / norm for value in vector) async def _embed(self, texts: Sequence[str]) -> EmbeddingResult: if isinstance(texts, (str, bytes)) or not isinstance(texts, Sequence): raise invalid_input("fake.embedding", "invalid_input_collection") if not texts: raise invalid_input("fake.embedding", "empty_input") if len(texts) > 10: raise invalid_input("fake.embedding", "batch_size_exceeded") token_counts = [] for text in texts: if not isinstance(text, str) or not text: raise invalid_input("fake.embedding", "invalid_input_text") token_count = len(text.encode("utf-8")) if token_count > 8_192: raise invalid_input("fake.embedding", "text_token_limit_exceeded") token_counts.append(token_count) if sum(token_counts) > 33_000: raise invalid_input("fake.embedding", "request_token_limit_exceeded") started = time.perf_counter() vectors = tuple(self._vector(text) for text in texts) return EmbeddingResult( vectors=vectors, model="fake-feature-hash-v1", request_id=None, usage=ProviderUsage(input_tokens=sum(len(lexical_features(text)) for text in texts)), elapsed_ms=(time.perf_counter() - started) * 1000, ) async def embed_documents(self, texts: Sequence[str]) -> EmbeddingResult: return await self._embed(texts) async def embed_query(self, text: str) -> EmbeddingResult: return await self._embed([text]) class FakeReranker: """Lexical-overlap reranker for deterministic offline flow tests.""" async def rerank( self, query: str, documents: Sequence[str], *, top_n: int, instruct: str | None = None, ) -> RerankResult: del instruct if not isinstance(query, str) or not query: raise invalid_input("fake.rerank", "invalid_query") if isinstance(documents, (str, bytes)) or not isinstance(documents, Sequence): raise invalid_input("fake.rerank", "invalid_document_collection") if not documents: raise invalid_input("fake.rerank", "empty_documents") if len(documents) > 500: raise invalid_input("fake.rerank", "document_count_exceeded") if isinstance(top_n, bool) or not isinstance(top_n, int) or top_n <= 0: raise invalid_input("fake.rerank", "invalid_top_n") query_tokens = len(query.encode("utf-8")) if query_tokens > 4_000: raise invalid_input("fake.rerank", "query_token_limit_exceeded") document_tokens = [] for document in documents: if not isinstance(document, str) or not document: raise invalid_input("fake.rerank", "invalid_document") count = len(document.encode("utf-8")) if count > 4_000: raise invalid_input("fake.rerank", "document_token_limit_exceeded") document_tokens.append(count) if query_tokens * len(documents) + sum(document_tokens) > 120_000: raise invalid_input("fake.rerank", "request_token_limit_exceeded") started = time.perf_counter() query_features = set(lexical_features(query)) ranked: list[RankedItem] = [] for index, document in enumerate(documents): document_features = set(lexical_features(document)) union = query_features | document_features score = len(query_features & document_features) / len(union) if union else 0.0 ranked.append(RankedItem(index=index, relevance_score=score, document=document)) ranked.sort(key=lambda item: (-item.relevance_score, item.index)) return RerankResult( items=tuple(ranked[:top_n]), model="fake-lexical-rerank-v1", request_id=None, usage=ProviderUsage( input_tokens=len(query_features) + sum(len(lexical_features(document)) for document in documents) ), elapsed_ms=(time.perf_counter() - started) * 1000, )