feat: Intelligence Pipeline v3 — full implementation

Multi-stage evidence-grounded inference architecture replacing the
monolithic 9B model extraction pipeline. CPU-first specialist services
handle routine extraction while the 9B vLLM model is preserved for
semantic adjudication of ambiguous cases.

Key components:
- Capability-aware inference gateway (OpenAI-compatible + Ollama)
- Endpoint registry with DB migrations and REST API
- Sentence-aware document segmenter (property tests)
- Deterministic financial parsing with offset integrity
- Symbol resolution with ambiguity detection
- Specialist service (GLiNER2, dynamic batching, K8s deployment)
- Company-specific sentiment (FinBERT, calibration)
- Retrieval-based novelty and duplicate detection
- Confidence calibration pipeline
- Deterministic routing engine (property tests)
- 9B adjudication layer with VRAM gating
- Stock-specific impact model (features, labels, baseline, trained)
- Pipeline orchestrator (state machine, queues, leases, feature flags)
- Bounded parallelism (async workers, semaphore, load shedding)
- Observability (tracing, metrics, alerts)
- Compatibility adapter (v3→v2 golden mapping tests)
- Shadow/canary promotion framework
- Active learning and fine-tuning pipeline

Test results: 1,161 tests pass, ruff lint clean.
All 282 spec tasks completed.
This commit is contained in:
Celes Renata
2026-07-13 02:14:59 +00:00
parent 84634a365e
commit a72f336ad1
227 changed files with 50403 additions and 0 deletions
@@ -0,0 +1,37 @@
"""Retrieval-based novelty and duplicate detection.
Replaces model-generated novelty with deterministic fingerprinting,
semantic embeddings, and similarity-based scoring against a recent
history window.
"""
from services.intelligence_pipeline_v3.novelty.embeddings import (
EmbeddingBackend,
MockEmbeddingBackend,
SentenceTransformerBackend,
cosine_similarity,
)
from services.intelligence_pipeline_v3.novelty.fingerprints import (
compute_exact_fingerprint,
compute_simhash,
hamming_distance,
is_near_duplicate,
)
from services.intelligence_pipeline_v3.novelty.index import Match, NoveltyIndex
from services.intelligence_pipeline_v3.novelty.models import NoveltyResult
from services.intelligence_pipeline_v3.novelty.scorer import NoveltyScorer
__all__ = [
"EmbeddingBackend",
"Match",
"MockEmbeddingBackend",
"NoveltyIndex",
"NoveltyResult",
"NoveltyScorer",
"SentenceTransformerBackend",
"compute_exact_fingerprint",
"compute_simhash",
"cosine_similarity",
"hamming_distance",
"is_near_duplicate",
]
@@ -0,0 +1,165 @@
"""Replaceable compact embedding backend for novelty retrieval.
Provides:
- EmbeddingBackend protocol for pluggable embedding models
- MockEmbeddingBackend for deterministic testing
- SentenceTransformerBackend stub for production (all-MiniLM-L6-v2)
- cosine_similarity utility function
"""
from __future__ import annotations
import hashlib
import math
import struct
from typing import Protocol, runtime_checkable
@runtime_checkable
class EmbeddingBackend(Protocol):
"""Protocol for embedding backends.
Implementations must produce fixed-dimension vectors for a batch of texts.
The backend is designed to be replaceable: swap between mock, local model,
and remote API backends without changing scoring logic.
"""
@property
def dimension(self) -> int:
"""Return the embedding dimension produced by this backend."""
...
def embed(self, texts: list[str]) -> list[list[float]]:
"""Embed a batch of texts into dense vectors.
Args:
texts: List of text strings to embed.
Returns:
List of embedding vectors, one per input text.
Each vector has length == self.dimension.
"""
...
class MockEmbeddingBackend:
"""Deterministic hash-based embedding backend for testing.
Produces consistent embeddings using text hashing. Useful for
unit tests and integration tests that need repeatable results
without loading a real model.
"""
def __init__(self, dimension: int = 384) -> None:
self._dimension = dimension
@property
def dimension(self) -> int:
return self._dimension
def embed(self, texts: list[str]) -> list[list[float]]:
"""Generate deterministic embeddings from text hashes.
Uses SHA-256 expanded to fill the dimension. Normalizes to unit length
for compatibility with cosine similarity.
"""
results = []
for text in texts:
raw = self._hash_to_vector(text)
norm = math.sqrt(sum(x * x for x in raw))
if norm > 0:
normalized = [x / norm for x in raw]
else:
normalized = raw
results.append(normalized)
return results
def _hash_to_vector(self, text: str) -> list[float]:
"""Expand text hash into a vector of the target dimension."""
vector = []
# Generate enough hash bytes to fill dimension
chunk_idx = 0
while len(vector) < self._dimension:
data = f"{text}:{chunk_idx}".encode("utf-8")
digest = hashlib.sha256(data).digest()
# Convert 32 bytes to 8 floats (4 bytes each)
for i in range(0, 32, 4):
if len(vector) >= self._dimension:
break
# Unpack as float in [-1, 1] range
raw_int = struct.unpack("<I", digest[i : i + 4])[0]
value = (raw_int / (2**32 - 1)) * 2.0 - 1.0
vector.append(value)
chunk_idx += 1
return vector[: self._dimension]
class SentenceTransformerBackend:
"""Production embedding backend using sentence-transformers.
Wraps all-MiniLM-L6-v2 (384-dimensional) for compact, fast embeddings.
The model loads lazily on first call to avoid startup cost when not needed.
Note: Requires `sentence-transformers` package to be installed.
This is a stub—actual model loading is deferred to production deployment.
"""
MODEL_NAME = "all-MiniLM-L6-v2"
def __init__(self) -> None:
self._model = None
self._dimension = 384
@property
def dimension(self) -> int:
return self._dimension
def embed(self, texts: list[str]) -> list[list[float]]:
"""Embed texts using the sentence-transformers model.
Lazily loads the model on first invocation.
Raises:
ImportError: If sentence-transformers is not installed.
"""
if self._model is None:
self._load_model()
embeddings = self._model.encode(texts, normalize_embeddings=True)
return [emb.tolist() for emb in embeddings]
def _load_model(self) -> None:
"""Load the sentence-transformers model."""
try:
from sentence_transformers import SentenceTransformer
except ImportError as e:
raise ImportError(
"sentence-transformers package is required for SentenceTransformerBackend. "
"Install with: pip install sentence-transformers"
) from e
self._model = SentenceTransformer(self.MODEL_NAME)
def cosine_similarity(a: list[float], b: list[float]) -> float:
"""Compute cosine similarity between two embedding vectors.
Args:
a: First embedding vector.
b: Second embedding vector (must be same dimension as a).
Returns:
Cosine similarity in range [-1, 1]. Returns 0.0 for zero vectors.
Raises:
ValueError: If vectors have different dimensions.
"""
if len(a) != len(b):
raise ValueError(f"Vectors must have same dimension: {len(a)} != {len(b)}")
dot = sum(x * y for x, y in zip(a, b))
norm_a = math.sqrt(sum(x * x for x in a))
norm_b = math.sqrt(sum(x * x for x in b))
if norm_a == 0.0 or norm_b == 0.0:
return 0.0
return dot / (norm_a * norm_b)
@@ -0,0 +1,119 @@
"""Exact and near-duplicate fingerprinting for document deduplication.
Provides:
- SHA-256 exact fingerprint on normalized text
- SimHash for near-duplicate detection with configurable threshold
- Hamming distance comparison between SimHash values
"""
from __future__ import annotations
import hashlib
import re
import struct
def _normalize_text(text: str) -> str:
"""Normalize text for fingerprinting.
Lowercases, collapses whitespace, strips leading/trailing space.
This ensures minor formatting differences don't defeat deduplication.
"""
text = text.lower()
text = re.sub(r"\s+", " ", text)
return text.strip()
def compute_exact_fingerprint(text: str) -> str:
"""Compute SHA-256 fingerprint of normalized text.
Args:
text: Raw document text.
Returns:
Hex-encoded SHA-256 digest of normalized text.
"""
normalized = _normalize_text(text)
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
def _tokenize(text: str) -> list[str]:
"""Split normalized text into tokens for SimHash computation."""
normalized = _normalize_text(text)
return normalized.split()
def _hash_token(token: str) -> int:
"""Hash a single token to a 64-bit integer using MD5 truncation."""
digest = hashlib.md5(token.encode("utf-8")).digest() # noqa: S324
return struct.unpack("<Q", digest[:8])[0]
def compute_simhash(text: str) -> int:
"""Compute 64-bit SimHash of text for near-duplicate detection.
SimHash produces locality-sensitive fingerprints: similar documents
will have SimHash values with low Hamming distance.
Args:
text: Raw document text.
Returns:
64-bit SimHash integer value.
"""
tokens = _tokenize(text)
if not tokens:
return 0
# Accumulator for each bit position
v = [0] * 64
for token in tokens:
token_hash = _hash_token(token)
for i in range(64):
if token_hash & (1 << i):
v[i] += 1
else:
v[i] -= 1
# Build final hash from accumulator signs
fingerprint = 0
for i in range(64):
if v[i] > 0:
fingerprint |= 1 << i
return fingerprint
def hamming_distance(a: int, b: int) -> int:
"""Compute Hamming distance between two 64-bit SimHash values.
Args:
a: First SimHash value.
b: Second SimHash value.
Returns:
Number of differing bits (0-64).
"""
xor = a ^ b
# Count set bits (Brian Kernighan's algorithm)
distance = 0
while xor:
xor &= xor - 1
distance += 1
return distance
def is_near_duplicate(fp1: int, fp2: int, threshold: int = 3) -> bool:
"""Determine if two SimHash fingerprints indicate near-duplicate content.
Args:
fp1: First SimHash value.
fp2: Second SimHash value.
threshold: Maximum Hamming distance to consider near-duplicate.
Default of 3 is conservative for 64-bit SimHash.
Returns:
True if the documents are near-duplicates.
"""
return hamming_distance(fp1, fp2) <= threshold
@@ -0,0 +1,129 @@
"""In-memory vector index for novelty retrieval.
Provides a simple but effective nearest-neighbor search over document
and company-event embeddings. Designed to be replaceable with a
production vector database (e.g., pgvector, FAISS) without changing
the scoring interface.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from services.intelligence_pipeline_v3.novelty.embeddings import cosine_similarity
@dataclass
class Match:
"""A nearest-neighbor match from the index."""
doc_id: str
similarity_score: float
metadata: dict = field(default_factory=dict)
@dataclass
class _IndexEntry:
"""Internal storage for an indexed document."""
doc_id: str
embedding: list[float]
metadata: dict = field(default_factory=dict)
class NoveltyIndex:
"""In-memory vector index for document and event embeddings.
Supports adding embeddings and searching for nearest neighbors
by cosine similarity. Thread-safe for read-after-write but not
for concurrent writes (use external locking if needed).
For production, replace with pgvector or FAISS. This implementation
is suitable for testing, small corpora, and development.
"""
def __init__(self) -> None:
self._entries: list[_IndexEntry] = []
self._id_set: set[str] = set()
def __len__(self) -> int:
return len(self._entries)
def add(self, doc_id: str, embedding: list[float], metadata: dict | None = None) -> None:
"""Add a document embedding to the index.
Args:
doc_id: Unique document or event identifier.
embedding: Dense embedding vector.
metadata: Optional metadata (e.g., record_type, timestamp).
Note:
If doc_id already exists, it is updated in-place.
"""
if metadata is None:
metadata = {}
if doc_id in self._id_set:
# Update existing entry
for entry in self._entries:
if entry.doc_id == doc_id:
entry.embedding = embedding
entry.metadata = metadata
break
else:
self._entries.append(_IndexEntry(doc_id=doc_id, embedding=embedding, metadata=metadata))
self._id_set.add(doc_id)
def search(self, embedding: list[float], k: int = 5) -> list[Match]:
"""Find the k nearest neighbors to the query embedding.
Args:
embedding: Query embedding vector.
k: Maximum number of results to return.
Returns:
List of Match objects sorted by similarity descending.
Similarity scores are clamped to [0, 1] (negative cosine
similarities are treated as 0 for novelty purposes).
"""
if not self._entries:
return []
scored: list[tuple[float, _IndexEntry]] = []
for entry in self._entries:
sim = cosine_similarity(embedding, entry.embedding)
# Clamp to [0, 1] for novelty scoring purposes
sim = max(0.0, min(1.0, sim))
scored.append((sim, entry))
# Sort descending by similarity
scored.sort(key=lambda x: x[0], reverse=True)
results = []
for sim, entry in scored[:k]:
results.append(
Match(doc_id=entry.doc_id, similarity_score=sim, metadata=entry.metadata)
)
return results
def remove(self, doc_id: str) -> bool:
"""Remove a document from the index.
Args:
doc_id: Document identifier to remove.
Returns:
True if the document was found and removed.
"""
if doc_id not in self._id_set:
return False
self._entries = [e for e in self._entries if e.doc_id != doc_id]
self._id_set.discard(doc_id)
return True
def clear(self) -> None:
"""Remove all entries from the index."""
self._entries.clear()
self._id_set.clear()
@@ -0,0 +1,75 @@
"""Pydantic models for novelty and duplicate detection."""
from __future__ import annotations
from pydantic import BaseModel, Field, field_validator
class NearestMatch(BaseModel):
"""A single nearest-neighbor match from the novelty index."""
doc_id: str = Field(description="Document or event identifier of the match")
similarity_score: float = Field(
ge=0.0, le=1.0, description="Cosine similarity score (0=unrelated, 1=identical)"
)
metadata: dict = Field(default_factory=dict, description="Optional metadata about the match")
class NoveltyResult(BaseModel):
"""Result of novelty scoring for a document or event.
Novelty is scored from 0 (exact duplicate) to 1 (completely novel).
The formula version tracks which scoring algorithm produced the result.
"""
document_novelty: float = Field(
ge=0.0, le=1.0, description="Document-level novelty (0=duplicate, 1=novel)"
)
event_novelty: float = Field(
ge=0.0, le=1.0, description="Event-level novelty (0=duplicate event, 1=novel event)"
)
combined_novelty: float = Field(
ge=0.0, le=1.0, description="Combined novelty score used downstream"
)
nearest_matches: list[NearestMatch] = Field(
default_factory=list, description="Nearest matches for explainability"
)
formula_version: str = Field(description="Versioned identifier for the novelty formula used")
is_exact_duplicate: bool = Field(
default=False, description="Whether an exact content fingerprint match was found"
)
is_near_duplicate: bool = Field(
default=False, description="Whether a near-duplicate fingerprint match was found"
)
@field_validator("nearest_matches")
@classmethod
def matches_sorted_descending(cls, v: list[NearestMatch]) -> list[NearestMatch]:
"""Ensure nearest matches are sorted by similarity descending."""
return sorted(v, key=lambda m: m.similarity_score, reverse=True)
class FingerprintRecord(BaseModel):
"""Stored fingerprint for a document."""
doc_id: str = Field(description="Document identifier")
exact_fingerprint: str = Field(description="SHA-256 of normalized text")
simhash: int = Field(description="SimHash value for near-duplicate detection")
metadata: dict = Field(default_factory=dict, description="Additional metadata")
class EmbeddingRecord(BaseModel):
"""Stored embedding vector for a document or event."""
doc_id: str = Field(description="Document or event identifier")
embedding: list[float] = Field(description="Dense embedding vector")
record_type: str = Field(description="Type: 'document' or 'company_event'")
metadata: dict = Field(default_factory=dict, description="Additional metadata")
@field_validator("record_type")
@classmethod
def valid_record_type(cls, v: str) -> str:
valid = {"document", "company_event"}
if v not in valid:
raise ValueError(f"record_type must be one of {valid}, got '{v}'")
return v
@@ -0,0 +1,137 @@
"""Novelty scoring formula implementation.
Combines fingerprint-based duplicate detection with embedding-based
semantic novelty to produce a versioned, deterministic novelty score.
Formula v1: novelty = 1 - max_similarity (clamped to [0, 1])
"""
from __future__ import annotations
from services.intelligence_pipeline_v3.novelty.index import Match, NoveltyIndex
from services.intelligence_pipeline_v3.novelty.models import NearestMatch, NoveltyResult
# Current formula version — bump when the scoring algorithm changes
FORMULA_VERSION = "v1.0"
class NoveltyScorer:
"""Computes novelty scores from embedding similarity and fingerprints.
The scorer queries the novelty index for nearest neighbors and applies
a versioned formula to produce document-level, event-level, and combined
novelty scores.
Formula v1.0:
document_novelty = 1 - max_document_similarity
event_novelty = 1 - max_event_similarity
combined_novelty = min(document_novelty, event_novelty)
All values clamped to [0, 1].
"""
def __init__(self, k: int = 5, formula_version: str = FORMULA_VERSION) -> None:
"""Initialize the scorer.
Args:
k: Number of nearest neighbors to retrieve for scoring.
formula_version: Version string for the scoring formula.
"""
self.k = k
self.formula_version = formula_version
def compute_novelty(
self,
document_embedding: list[float],
event_embedding: list[float],
index: NoveltyIndex,
is_exact_duplicate: bool = False,
is_near_duplicate: bool = False,
) -> NoveltyResult:
"""Compute novelty for a document and its primary event.
Args:
document_embedding: Embedding of the document content.
event_embedding: Embedding of the canonical company-event.
index: NoveltyIndex containing recent document/event embeddings.
is_exact_duplicate: Whether an exact fingerprint match exists.
is_near_duplicate: Whether a near-duplicate fingerprint match exists.
Returns:
NoveltyResult with document, event, and combined novelty scores.
"""
# If exact duplicate, novelty is zero
if is_exact_duplicate:
doc_matches = index.search(document_embedding, k=self.k)
return NoveltyResult(
document_novelty=0.0,
event_novelty=0.0,
combined_novelty=0.0,
nearest_matches=self._to_nearest_matches(doc_matches),
formula_version=self.formula_version,
is_exact_duplicate=True,
is_near_duplicate=True,
)
# Search for similar documents
doc_matches = index.search(document_embedding, k=self.k)
event_matches = index.search(event_embedding, k=self.k)
# Compute novelty from max similarity
document_novelty = self._compute_novelty_from_matches(doc_matches)
event_novelty = self._compute_novelty_from_matches(event_matches)
# If near-duplicate detected via fingerprint, cap document novelty
if is_near_duplicate:
document_novelty = min(document_novelty, 0.2)
# Combined novelty: conservative (take the minimum)
combined_novelty = min(document_novelty, event_novelty)
# Merge matches for explainability, deduplicated by doc_id
all_matches = self._merge_matches(doc_matches, event_matches)
return NoveltyResult(
document_novelty=document_novelty,
event_novelty=event_novelty,
combined_novelty=combined_novelty,
nearest_matches=all_matches,
formula_version=self.formula_version,
is_exact_duplicate=is_exact_duplicate,
is_near_duplicate=is_near_duplicate,
)
def _compute_novelty_from_matches(self, matches: list[Match]) -> float:
"""Apply the v1 formula: novelty = 1 - max_similarity."""
if not matches:
return 1.0 # No history = fully novel
max_sim = max(m.similarity_score for m in matches)
novelty = 1.0 - max_sim
return max(0.0, min(1.0, novelty))
def _to_nearest_matches(self, matches: list[Match]) -> list[NearestMatch]:
"""Convert internal Match objects to NearestMatch models."""
return [
NearestMatch(
doc_id=m.doc_id,
similarity_score=m.similarity_score,
metadata=m.metadata,
)
for m in matches
]
def _merge_matches(
self, doc_matches: list[Match], event_matches: list[Match]
) -> list[NearestMatch]:
"""Merge document and event matches, keeping highest similarity per doc_id."""
seen: dict[str, NearestMatch] = {}
for m in doc_matches + event_matches:
if m.doc_id not in seen or m.similarity_score > seen[m.doc_id].similarity_score:
seen[m.doc_id] = NearestMatch(
doc_id=m.doc_id,
similarity_score=m.similarity_score,
metadata=m.metadata,
)
return sorted(seen.values(), key=lambda x: x.similarity_score, reverse=True)