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:
@@ -0,0 +1,80 @@
|
||||
"""Hard ambiguity and conflict rules for routing decisions.
|
||||
|
||||
These rules check structural markers in extraction output that indicate
|
||||
the document *requires* semantic reasoning by the 9B adjudicator. Any
|
||||
triggered rule forces ADJUDICATION regardless of confidence scores.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from services.intelligence_pipeline_v3.routing.reasons import RoutingReason
|
||||
|
||||
|
||||
def evaluate_hard_rules(
|
||||
confidence_features: dict[str, Any],
|
||||
ambiguity_markers: dict[str, Any],
|
||||
) -> list[RoutingReason]:
|
||||
"""Evaluate hard ambiguity/conflict rules against extraction output.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
confidence_features:
|
||||
Field-level confidence features from the confidence pipeline.
|
||||
Expected keys include:
|
||||
- ``evidence_coverage``: float 0-1
|
||||
- ``material_fields_present``: bool
|
||||
- ``cross_chunk_relations``: bool (relations span multiple chunks)
|
||||
|
||||
ambiguity_markers:
|
||||
Structural ambiguity markers from candidate generation and resolution.
|
||||
Expected keys include:
|
||||
- ``unresolved_aliases``: int (count of unresolved entity aliases)
|
||||
- ``primary_company_count``: int (number of primary companies detected)
|
||||
- ``contradictory_numeric_facts``: bool
|
||||
- ``conflicting_sentiment``: bool
|
||||
- ``implied_causal_impact``: bool
|
||||
- ``guidance_vs_consensus``: bool
|
||||
- ``long_document_cross_chunk``: bool
|
||||
|
||||
Returns
|
||||
-------
|
||||
list[RoutingReason]
|
||||
List of triggered reasons. Empty list means no hard rules triggered.
|
||||
"""
|
||||
triggered: list[RoutingReason] = []
|
||||
|
||||
# Unresolved entity aliases require contextual disambiguation
|
||||
if ambiguity_markers.get("unresolved_aliases", 0) > 0:
|
||||
triggered.append(RoutingReason.UNRESOLVED_ALIAS)
|
||||
|
||||
# Multiple primary companies need reasoning about which is the subject
|
||||
if ambiguity_markers.get("primary_company_count", 0) > 1:
|
||||
triggered.append(RoutingReason.MULTIPLE_PRIMARY_COMPANIES)
|
||||
|
||||
# Contradictory numeric facts (e.g., conflicting revenue figures)
|
||||
if ambiguity_markers.get("contradictory_numeric_facts", False):
|
||||
triggered.append(RoutingReason.CONTRADICTORY_NUMERIC_FACTS)
|
||||
|
||||
# Conflicting sentiment across evidence groups for the same company
|
||||
if ambiguity_markers.get("conflicting_sentiment", False):
|
||||
triggered.append(RoutingReason.CONFLICTING_SENTIMENT)
|
||||
|
||||
# Implied causal impact requiring reasoning (not explicit statement)
|
||||
if ambiguity_markers.get("implied_causal_impact", False):
|
||||
triggered.append(RoutingReason.IMPLIED_CAUSAL_IMPACT)
|
||||
|
||||
# Guidance vs consensus comparison requires model reasoning
|
||||
if ambiguity_markers.get("guidance_vs_consensus", False):
|
||||
triggered.append(RoutingReason.GUIDANCE_VS_CONSENSUS_REQUIRES_REASONING)
|
||||
|
||||
# Material fields missing from extraction output
|
||||
if not confidence_features.get("material_fields_present", True):
|
||||
triggered.append(RoutingReason.MATERIAL_FIELD_MISSING)
|
||||
|
||||
# Cross-chunk relations in long documents need broader context
|
||||
if ambiguity_markers.get("long_document_cross_chunk", False):
|
||||
triggered.append(RoutingReason.LONG_DOCUMENT_CROSS_CHUNK_RELATION)
|
||||
|
||||
return triggered
|
||||
Reference in New Issue
Block a user