crprotocol 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crp/__init__.py +126 -0
- crp/__main__.py +8 -0
- crp/_typing.py +27 -0
- crp/_version.py +5 -0
- crp/adapters.py +31 -0
- crp/advanced/__init__.py +40 -0
- crp/advanced/auto_ingest.py +400 -0
- crp/advanced/cqs.py +235 -0
- crp/advanced/cross_window.py +477 -0
- crp/advanced/curator.py +265 -0
- crp/advanced/feedback.py +146 -0
- crp/advanced/hierarchical.py +211 -0
- crp/advanced/meta_learning.py +401 -0
- crp/advanced/parallel.py +98 -0
- crp/advanced/review_cycle.py +329 -0
- crp/advanced/scale_mode.py +129 -0
- crp/advanced/source_grounding.py +207 -0
- crp/ckf/__init__.py +35 -0
- crp/ckf/community.py +377 -0
- crp/ckf/fabric.py +445 -0
- crp/ckf/gc.py +175 -0
- crp/ckf/graph_walk.py +87 -0
- crp/ckf/merge.py +133 -0
- crp/ckf/pattern_query.py +122 -0
- crp/ckf/pubsub.py +128 -0
- crp/ckf/semantic.py +207 -0
- crp/cli/__init__.py +7 -0
- crp/cli/main.py +329 -0
- crp/cli/sidecar.py +929 -0
- crp/cli/startup.py +272 -0
- crp/continuation/__init__.py +103 -0
- crp/continuation/completion.py +348 -0
- crp/continuation/degradation.py +157 -0
- crp/continuation/document_map.py +160 -0
- crp/continuation/flow.py +109 -0
- crp/continuation/gap.py +419 -0
- crp/continuation/manager.py +484 -0
- crp/continuation/quality_monitor.py +179 -0
- crp/continuation/stitch.py +419 -0
- crp/continuation/trigger.py +142 -0
- crp/continuation/voice.py +157 -0
- crp/core/__init__.py +69 -0
- crp/core/batch.py +77 -0
- crp/core/circuit_breaker.py +116 -0
- crp/core/config.py +377 -0
- crp/core/context_tools.py +540 -0
- crp/core/dispatch_router.py +3977 -0
- crp/core/errors.py +128 -0
- crp/core/extraction_facade.py +384 -0
- crp/core/facilitator.py +713 -0
- crp/core/idempotency.py +215 -0
- crp/core/orchestrator.py +1435 -0
- crp/core/relay_strategies.py +613 -0
- crp/core/security_manager.py +140 -0
- crp/core/session.py +134 -0
- crp/core/task_intent.py +36 -0
- crp/core/window.py +363 -0
- crp/envelope/__init__.py +30 -0
- crp/envelope/builder.py +288 -0
- crp/envelope/decomposer.py +236 -0
- crp/envelope/formatter.py +168 -0
- crp/envelope/packer.py +211 -0
- crp/envelope/reranker.py +209 -0
- crp/envelope/scoring.py +310 -0
- crp/extraction/__init__.py +45 -0
- crp/extraction/complexity.py +96 -0
- crp/extraction/contradiction.py +132 -0
- crp/extraction/pipeline.py +360 -0
- crp/extraction/quality_gate.py +237 -0
- crp/extraction/stage1_regex.py +173 -0
- crp/extraction/stage2_statistical.py +244 -0
- crp/extraction/stage3_gliner.py +210 -0
- crp/extraction/stage4_uie.py +183 -0
- crp/extraction/stage5_discourse.py +175 -0
- crp/extraction/stage6_llm.py +178 -0
- crp/extraction/structured_output.py +219 -0
- crp/extraction/types.py +299 -0
- crp/license_guard.py +722 -0
- crp/observability/__init__.py +30 -0
- crp/observability/audit.py +118 -0
- crp/observability/events.py +233 -0
- crp/observability/metrics.py +264 -0
- crp/observability/quality.py +135 -0
- crp/observability/structured_logging.py +81 -0
- crp/observability/telemetry.py +117 -0
- crp/provenance/__init__.py +314 -0
- crp/provenance/_embeddings.py +97 -0
- crp/provenance/_types.py +378 -0
- crp/provenance/attribution_scorer.py +252 -0
- crp/provenance/claim_detector.py +229 -0
- crp/provenance/contradiction_detector.py +243 -0
- crp/provenance/distortion_detector.py +397 -0
- crp/provenance/entailment_verifier.py +358 -0
- crp/provenance/fabrication_detector.py +203 -0
- crp/provenance/hallucination_scorer.py +320 -0
- crp/provenance/omission_analyzer.py +106 -0
- crp/provenance/provenance_chain.py +205 -0
- crp/provenance/report_generator.py +440 -0
- crp/providers/__init__.py +43 -0
- crp/providers/anthropic.py +270 -0
- crp/providers/base.py +135 -0
- crp/providers/custom.py +63 -0
- crp/providers/diagnostic.py +251 -0
- crp/providers/llamacpp.py +224 -0
- crp/providers/manager.py +139 -0
- crp/providers/ollama.py +243 -0
- crp/providers/openai.py +628 -0
- crp/providers/tokenizers.py +48 -0
- crp/py.typed +0 -0
- crp/resources/__init__.py +53 -0
- crp/resources/adaptive_allocator.py +525 -0
- crp/resources/cost_model.py +388 -0
- crp/resources/overhead_manager.py +217 -0
- crp/resources/resource_manager.py +262 -0
- crp/schemas/__init__.py +20 -0
- crp/schemas/cost-estimate.json +33 -0
- crp/schemas/crp-error.json +43 -0
- crp/schemas/envelope-preview.json +40 -0
- crp/schemas/persisted-state-header.json +27 -0
- crp/schemas/quality-report.json +94 -0
- crp/schemas/session-handle.json +33 -0
- crp/schemas/session-status.json +57 -0
- crp/schemas/stream-event.json +18 -0
- crp/schemas/task-intent.json +42 -0
- crp/security/__init__.py +93 -0
- crp/security/audit_trail.py +392 -0
- crp/security/binding.py +192 -0
- crp/security/compliance.py +813 -0
- crp/security/consent.py +593 -0
- crp/security/embedding_defense.py +161 -0
- crp/security/encryption.py +202 -0
- crp/security/injection.py +335 -0
- crp/security/integrity.py +267 -0
- crp/security/privacy.py +662 -0
- crp/security/quarantine.py +249 -0
- crp/security/rbac.py +221 -0
- crp/security/validation.py +164 -0
- crp/state/__init__.py +31 -0
- crp/state/cold_storage.py +258 -0
- crp/state/compaction.py +263 -0
- crp/state/critical_state.py +104 -0
- crp/state/event_log.py +313 -0
- crp/state/fact.py +189 -0
- crp/state/serialization.py +189 -0
- crp/state/session_cleanup.py +77 -0
- crp/state/snapshot.py +290 -0
- crp/state/warm_store.py +346 -0
- crprotocol-2.0.0.dist-info/METADATA +1295 -0
- crprotocol-2.0.0.dist-info/RECORD +153 -0
- crprotocol-2.0.0.dist-info/WHEEL +4 -0
- crprotocol-2.0.0.dist-info/entry_points.txt +2 -0
- crprotocol-2.0.0.dist-info/licenses/LICENSE.md +170 -0
- crprotocol-2.0.0.dist-info/licenses/NOTICE +18 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
|
|
2
|
+
# Licensed under Elastic License 2.0 — see LICENSE.md for details.
|
|
3
|
+
"""Shared semantic embedding module for DPE components (§7.14.3).
|
|
4
|
+
|
|
5
|
+
Provides lazy-loaded sentence-transformer embeddings used by:
|
|
6
|
+
- attribution_scorer (G-1): real semantic similarity vs hash-BOW
|
|
7
|
+
- entailment_verifier (P-1): ML-driven fallback when NLI unavailable
|
|
8
|
+
- distortion_detector (P-3): semantic drift detection
|
|
9
|
+
|
|
10
|
+
The model (all-MiniLM-L6-v2, ~80 MB, 384-dim, CPU-only) is loaded
|
|
11
|
+
once and shared across all components to avoid duplicate memory usage.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import math
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Module-level model cache (lazy singleton)
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
_model: Any = None
|
|
27
|
+
_model_load_failed: bool = False
|
|
28
|
+
_DEFAULT_MODEL = "all-MiniLM-L6-v2"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_embedder(*, model_name: str = _DEFAULT_MODEL) -> Any:
|
|
32
|
+
"""Lazy-load the SentenceTransformer model (singleton).
|
|
33
|
+
|
|
34
|
+
Returns the model object or None if loading fails.
|
|
35
|
+
"""
|
|
36
|
+
global _model, _model_load_failed # noqa: PLW0603
|
|
37
|
+
|
|
38
|
+
if _model_load_failed:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
if _model is not None:
|
|
42
|
+
return _model
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
from sentence_transformers import SentenceTransformer # type: ignore[import-untyped]
|
|
46
|
+
logger.info("Loading embedding model: %s", model_name)
|
|
47
|
+
_model = SentenceTransformer(model_name)
|
|
48
|
+
return _model
|
|
49
|
+
except Exception as exc:
|
|
50
|
+
logger.warning(
|
|
51
|
+
"Embedding model unavailable (%s), semantic features degraded: %s",
|
|
52
|
+
model_name, exc,
|
|
53
|
+
)
|
|
54
|
+
_model_load_failed = True
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def encode_texts(
|
|
59
|
+
texts: list[str],
|
|
60
|
+
*,
|
|
61
|
+
_model_override: Any = None,
|
|
62
|
+
) -> list[list[float]] | None:
|
|
63
|
+
"""Encode texts into dense embeddings.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
texts: List of strings to encode.
|
|
67
|
+
_model_override: Injected model for testing (skips singleton).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
List of embedding vectors (as lists of floats), or None if
|
|
71
|
+
the model is unavailable.
|
|
72
|
+
"""
|
|
73
|
+
model = _model_override or get_embedder()
|
|
74
|
+
if model is None:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
embeddings = model.encode(texts, convert_to_numpy=True)
|
|
79
|
+
return [emb.tolist() for emb in embeddings]
|
|
80
|
+
except Exception as exc:
|
|
81
|
+
logger.warning("Embedding encode failed: %s", exc)
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cosine_similarity(a: list[float], b: list[float]) -> float:
|
|
86
|
+
"""Compute cosine similarity between two vectors."""
|
|
87
|
+
dot = sum(x * y for x, y in zip(a, b))
|
|
88
|
+
norm_a = math.sqrt(sum(x * x for x in a)) or 1e-12
|
|
89
|
+
norm_b = math.sqrt(sum(x * x for x in b)) or 1e-12
|
|
90
|
+
return dot / (norm_a * norm_b)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def reset_cache() -> None:
|
|
94
|
+
"""Reset the module-level model cache (for testing)."""
|
|
95
|
+
global _model, _model_load_failed # noqa: PLW0603
|
|
96
|
+
_model = None
|
|
97
|
+
_model_load_failed = False
|
crp/provenance/_types.py
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
# Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
|
|
2
|
+
# Licensed under Elastic License 2.0 — see LICENSE.md for details.
|
|
3
|
+
"""Decision Provenance Engine — data types (§7.14.3).
|
|
4
|
+
|
|
5
|
+
Dataclasses for claim attribution, fact scoring, provenance chains,
|
|
6
|
+
and provenance reports used by the DPE pipeline.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from enum import Enum
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Claim classification
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ClaimType(str, Enum):
|
|
22
|
+
"""Classification of individual sentences/claims in LLM output."""
|
|
23
|
+
|
|
24
|
+
FACTUAL_CLAIM = "FACTUAL_CLAIM" # Verifiable factual assertion
|
|
25
|
+
OPINION = "OPINION" # Subjective view or judgment
|
|
26
|
+
PROCEDURAL = "PROCEDURAL" # Action or instruction
|
|
27
|
+
HEDGE = "HEDGE" # Qualified/uncertain statement
|
|
28
|
+
CONNECTIVE = "CONNECTIVE" # Structural/transitional text
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AttributionType(str, Enum):
|
|
32
|
+
"""How a claim maps to its knowledge source."""
|
|
33
|
+
|
|
34
|
+
CONTEXT_GROUNDED = "CONTEXT_GROUNDED" # Supported by envelope fact(s)
|
|
35
|
+
PARAMETRIC = "PARAMETRIC" # Likely from model training data
|
|
36
|
+
MIXED = "MIXED" # Partial context + parametric
|
|
37
|
+
UNCERTAIN = "UNCERTAIN" # Low-confidence attribution
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
# Fidelity verification — did the model FAITHFULLY represent its sources?
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class DistortionType(str, Enum):
|
|
46
|
+
"""How the model distorted a source fact in its output."""
|
|
47
|
+
|
|
48
|
+
NUMBER_CHANGED = "NUMBER_CHANGED" # 10% → 15%
|
|
49
|
+
NEGATION_FLIP = "NEGATION_FLIP" # "is safe" → "is not safe"
|
|
50
|
+
QUALIFIER_DROPPED = "QUALIFIER_DROPPED" # "approximately 10" → "10"
|
|
51
|
+
QUALIFIER_ADDED = "QUALIFIER_ADDED" # "10" → "always 10"
|
|
52
|
+
SCOPE_CHANGED = "SCOPE_CHANGED" # "in Q3" → "annually"
|
|
53
|
+
ENTITY_SUBSTITUTED = "ENTITY_SUBSTITUTED" # "Company A" → "Company B"
|
|
54
|
+
SEMANTIC_DRIFT = "SEMANTIC_DRIFT" # Meaning-level deviation despite lexical similarity
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class FabricationType(str, Enum):
|
|
58
|
+
"""Category of fabricated entity in a claim."""
|
|
59
|
+
|
|
60
|
+
NUMBER = "NUMBER" # Invented number not in any fact
|
|
61
|
+
PERCENTAGE = "PERCENTAGE" # Invented percentage
|
|
62
|
+
DATE = "DATE" # Invented date/year
|
|
63
|
+
PROPER_NOUN = "PROPER_NOUN" # Invented name/entity
|
|
64
|
+
CITATION = "CITATION" # Invented citation/reference
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class OmissionSeverity(str, Enum):
|
|
68
|
+
"""How important the omitted fact was."""
|
|
69
|
+
|
|
70
|
+
CRITICAL = "CRITICAL" # Top-quartile relevance, omitted entirely
|
|
71
|
+
HIGH = "HIGH" # High relevance, omitted
|
|
72
|
+
MEDIUM = "MEDIUM" # Medium relevance, omitted
|
|
73
|
+
LOW = "LOW" # Low relevance, omitted (expected)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------------------
|
|
77
|
+
# Scoring results
|
|
78
|
+
# ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class FactScore:
|
|
83
|
+
"""Attribution score between a single claim and a single envelope fact."""
|
|
84
|
+
|
|
85
|
+
fact_id: str = ""
|
|
86
|
+
fact_text_preview: str = "" # First 120 chars of fact text
|
|
87
|
+
semantic_similarity: float = 0.0 # Bag-of-words cosine similarity
|
|
88
|
+
lexical_overlap: float = 0.0 # N-gram token overlap ratio
|
|
89
|
+
composite_score: float = 0.0 # Weighted combination
|
|
90
|
+
fact_source_window: str = "" # Which window created this fact
|
|
91
|
+
fact_extraction_stage: int = 0 # Which pipeline stage (1-6)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class ClaimAttribution:
|
|
96
|
+
"""Attribution result for a single claim in the LLM output."""
|
|
97
|
+
|
|
98
|
+
claim_text: str = ""
|
|
99
|
+
claim_index: int = 0 # Position in output (0-based)
|
|
100
|
+
claim_type: ClaimType = ClaimType.CONNECTIVE
|
|
101
|
+
attributed_facts: list[FactScore] = field(default_factory=list)
|
|
102
|
+
top_score: float = 0.0 # Highest composite score
|
|
103
|
+
attribution_type: AttributionType = AttributionType.UNCERTAIN
|
|
104
|
+
confidence: float = 0.0 # Overall attribution confidence
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------------------------------------
|
|
108
|
+
# Provenance chain
|
|
109
|
+
# ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class ProvenanceLink:
|
|
114
|
+
"""Single link in a provenance chain — traces from claim → source."""
|
|
115
|
+
|
|
116
|
+
level: str = "" # "claim", "fact", "window", "envelope", "task"
|
|
117
|
+
label: str = "" # Human-readable label for this link
|
|
118
|
+
detail: dict[str, Any] = field(default_factory=dict)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass
|
|
122
|
+
class ProvenanceChain:
|
|
123
|
+
"""Full provenance chain for one claim — linked list from output to input."""
|
|
124
|
+
|
|
125
|
+
claim_text: str = ""
|
|
126
|
+
claim_index: int = 0
|
|
127
|
+
attribution_type: AttributionType = AttributionType.UNCERTAIN
|
|
128
|
+
links: list[ProvenanceLink] = field(default_factory=list)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ---------------------------------------------------------------------------
|
|
132
|
+
# Provenance report
|
|
133
|
+
# ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass
|
|
137
|
+
class ProvenanceReport:
|
|
138
|
+
"""Complete provenance report for a single dispatch window."""
|
|
139
|
+
|
|
140
|
+
session_id: str = ""
|
|
141
|
+
window_id: str = ""
|
|
142
|
+
timestamp: float = 0.0
|
|
143
|
+
|
|
144
|
+
# Counts
|
|
145
|
+
total_claims: int = 0
|
|
146
|
+
factual_claims: int = 0
|
|
147
|
+
opinion_claims: int = 0
|
|
148
|
+
procedural_claims: int = 0
|
|
149
|
+
hedge_claims: int = 0
|
|
150
|
+
connective_claims: int = 0
|
|
151
|
+
|
|
152
|
+
# Attribution summary
|
|
153
|
+
context_grounded_count: int = 0
|
|
154
|
+
parametric_count: int = 0
|
|
155
|
+
mixed_count: int = 0
|
|
156
|
+
uncertain_count: int = 0
|
|
157
|
+
grounding_ratio: float = 0.0 # context_grounded / factual_claims
|
|
158
|
+
|
|
159
|
+
# Payload
|
|
160
|
+
attributions: list[ClaimAttribution] = field(default_factory=list)
|
|
161
|
+
chains: list[ProvenanceChain] = field(default_factory=list)
|
|
162
|
+
|
|
163
|
+
# Integrity
|
|
164
|
+
chain_verified: bool = False
|
|
165
|
+
output_token_count: int = 0
|
|
166
|
+
envelope_facts_count: int = 0
|
|
167
|
+
|
|
168
|
+
# Fidelity verification (None until fidelity checks run)
|
|
169
|
+
fidelity: FidelityReport | None = None
|
|
170
|
+
|
|
171
|
+
# Semantic entailment verification (None until entailment runs)
|
|
172
|
+
entailment_results: list[EntailmentResult] = field(default_factory=list)
|
|
173
|
+
|
|
174
|
+
# Hallucination risk assessment (None until risk scoring runs)
|
|
175
|
+
risk_report: HallucinationRiskReport | None = None
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# Fidelity verification results
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
@dataclass
|
|
184
|
+
class DistortionResult:
|
|
185
|
+
"""A specific distortion found between a grounded claim and its source fact."""
|
|
186
|
+
|
|
187
|
+
claim_index: int = 0
|
|
188
|
+
claim_text: str = ""
|
|
189
|
+
source_fact_id: str = ""
|
|
190
|
+
source_fact_preview: str = ""
|
|
191
|
+
distortion_type: DistortionType = DistortionType.NUMBER_CHANGED
|
|
192
|
+
severity: float = 0.0 # 0.0-1.0
|
|
193
|
+
detail: str = "" # Human-readable explanation
|
|
194
|
+
claim_value: str = "" # The value in the claim
|
|
195
|
+
fact_value: str = "" # The value in the source fact
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@dataclass
|
|
199
|
+
class FabricationResult:
|
|
200
|
+
"""A specific fabrication found in a claim — an entity not in any source fact."""
|
|
201
|
+
|
|
202
|
+
claim_index: int = 0
|
|
203
|
+
claim_text: str = ""
|
|
204
|
+
fabricated_entity: str = ""
|
|
205
|
+
entity_type: FabricationType = FabricationType.NUMBER
|
|
206
|
+
severity: float = 0.0 # 0.0-1.0
|
|
207
|
+
detail: str = ""
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@dataclass
|
|
211
|
+
class OmissionResult:
|
|
212
|
+
"""An envelope fact that the model ignored entirely."""
|
|
213
|
+
|
|
214
|
+
fact_id: str = ""
|
|
215
|
+
fact_text_preview: str = ""
|
|
216
|
+
fact_relevance_score: float = 0.0 # Original packing score
|
|
217
|
+
max_attribution_score: float = 0.0 # Highest score any claim gave this fact
|
|
218
|
+
severity: OmissionSeverity = OmissionSeverity.LOW
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass
|
|
222
|
+
class ContradictionResult:
|
|
223
|
+
"""Two claims that contradict each other."""
|
|
224
|
+
|
|
225
|
+
claim_a_index: int = 0
|
|
226
|
+
claim_a_text: str = ""
|
|
227
|
+
claim_b_index: int = 0
|
|
228
|
+
claim_b_text: str = ""
|
|
229
|
+
contradiction_type: str = "" # "NEGATION" | "NUMBER_CONFLICT" | "SEMANTIC"
|
|
230
|
+
severity: float = 0.0 # 0.0-1.0
|
|
231
|
+
detail: str = ""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@dataclass
|
|
235
|
+
class FidelityReport:
|
|
236
|
+
"""Complete fidelity verification results for a dispatch window.
|
|
237
|
+
|
|
238
|
+
Answers: "Given source attribution, did the model faithfully represent
|
|
239
|
+
the sources, or did it distort, fabricate, omit, or contradict?"
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
distortions: list[DistortionResult] = field(default_factory=list)
|
|
243
|
+
fabrications: list[FabricationResult] = field(default_factory=list)
|
|
244
|
+
omissions: list[OmissionResult] = field(default_factory=list)
|
|
245
|
+
contradictions: list[ContradictionResult] = field(default_factory=list)
|
|
246
|
+
|
|
247
|
+
# Aggregate counts
|
|
248
|
+
distortion_count: int = 0
|
|
249
|
+
fabrication_count: int = 0
|
|
250
|
+
critical_omission_count: int = 0
|
|
251
|
+
contradiction_count: int = 0
|
|
252
|
+
|
|
253
|
+
# Composite fidelity score (1.0 = perfect fidelity, 0.0 = severe issues)
|
|
254
|
+
fidelity_score: float = 1.0
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# ---------------------------------------------------------------------------
|
|
258
|
+
# Semantic Entailment — ML-powered claim↔fact verification
|
|
259
|
+
# ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class EntailmentLabel(str, Enum):
|
|
263
|
+
"""NLI classification of a claim against its source fact."""
|
|
264
|
+
|
|
265
|
+
ENTAILED = "ENTAILED" # Claim logically follows from fact
|
|
266
|
+
CONTRADICTION = "CONTRADICTION" # Claim contradicts the fact
|
|
267
|
+
NEUTRAL = "NEUTRAL" # Claim is unrelated to the fact
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@dataclass
|
|
271
|
+
class EntailmentResult:
|
|
272
|
+
"""Semantic entailment verdict for a single claim↔fact pair.
|
|
273
|
+
|
|
274
|
+
Uses Natural Language Inference (NLI) to determine whether a claim
|
|
275
|
+
is *semantically* supported by its attributed source fact — beyond
|
|
276
|
+
lexical overlap.
|
|
277
|
+
"""
|
|
278
|
+
|
|
279
|
+
claim_index: int = 0
|
|
280
|
+
claim_text: str = ""
|
|
281
|
+
fact_id: str = ""
|
|
282
|
+
fact_text_preview: str = ""
|
|
283
|
+
label: EntailmentLabel = EntailmentLabel.NEUTRAL
|
|
284
|
+
confidence: float = 0.0 # P(predicted_label)
|
|
285
|
+
entailment_score: float = 0.0 # P(entailed)
|
|
286
|
+
contradiction_score: float = 0.0 # P(contradiction)
|
|
287
|
+
neutral_score: float = 0.0 # P(neutral)
|
|
288
|
+
used_model: bool = False # True if NLI model ran; False if heuristic
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# ---------------------------------------------------------------------------
|
|
292
|
+
# Hallucination Risk — composite per-claim risk assessment
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class HallucinationRisk(str, Enum):
|
|
297
|
+
"""Per-claim hallucination risk level."""
|
|
298
|
+
|
|
299
|
+
LOW = "LOW" # Well-sourced, semantically faithful
|
|
300
|
+
MEDIUM = "MEDIUM" # Partially sourced or minor fidelity issues
|
|
301
|
+
HIGH = "HIGH" # Poorly sourced or semantic mismatch
|
|
302
|
+
CRITICAL = "CRITICAL" # Contradicts source or fabricated content
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@dataclass
|
|
306
|
+
class ClaimRiskAssessment:
|
|
307
|
+
"""Hallucination risk assessment for a single claim.
|
|
308
|
+
|
|
309
|
+
Aggregates four independent signals into one auditable risk score:
|
|
310
|
+
1. Attribution signal — how well-sourced is this claim?
|
|
311
|
+
2. Fidelity signal — did lexical checks find distortions?
|
|
312
|
+
3. Entailment signal — does NLI confirm semantic support?
|
|
313
|
+
4. Specificity signal — how specific (and thus risky) is the claim?
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
claim_index: int = 0
|
|
317
|
+
claim_text: str = ""
|
|
318
|
+
risk_level: HallucinationRisk = HallucinationRisk.LOW
|
|
319
|
+
risk_score: float = 0.0 # 0.0=safe, 1.0=critical
|
|
320
|
+
# Individual signals (all 0.0-1.0; higher = safer)
|
|
321
|
+
attribution_signal: float = 0.0
|
|
322
|
+
fidelity_signal: float = 1.0
|
|
323
|
+
entailment_signal: float = 0.0
|
|
324
|
+
specificity_signal: float = 0.0 # Higher = more specific = riskier
|
|
325
|
+
risk_factors: list[str] = field(default_factory=list)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@dataclass
|
|
329
|
+
class HallucinationRiskReport:
|
|
330
|
+
"""Window-level hallucination risk report.
|
|
331
|
+
|
|
332
|
+
Answers: \"For each claim, how likely is it that the model hallucinated,
|
|
333
|
+
and what is the overall risk profile of this output?\"
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
assessments: list[ClaimRiskAssessment] = field(default_factory=list)
|
|
337
|
+
high_risk_count: int = 0
|
|
338
|
+
critical_risk_count: int = 0
|
|
339
|
+
mean_risk_score: float = 0.0
|
|
340
|
+
window_risk_level: HallucinationRisk = HallucinationRisk.LOW
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# ---------------------------------------------------------------------------
|
|
344
|
+
# Configuration
|
|
345
|
+
# ---------------------------------------------------------------------------
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
@dataclass
|
|
349
|
+
class ProvenanceConfig:
|
|
350
|
+
"""Configuration for the Decision Provenance Engine."""
|
|
351
|
+
|
|
352
|
+
enabled: bool = True
|
|
353
|
+
|
|
354
|
+
# Claim detection
|
|
355
|
+
min_claim_length: int = 10 # Skip very short fragments
|
|
356
|
+
max_claims_per_output: int = 50 # Safety limit
|
|
357
|
+
|
|
358
|
+
# Attribution scoring
|
|
359
|
+
similarity_threshold: float = 0.50 # Below this → PARAMETRIC
|
|
360
|
+
mixed_threshold: float = 0.35 # Between mixed and parametric
|
|
361
|
+
lexical_weight: float = 0.40 # Weight for lexical overlap
|
|
362
|
+
semantic_weight: float = 0.60 # Weight for semantic similarity
|
|
363
|
+
|
|
364
|
+
# Report generation
|
|
365
|
+
generate_report: bool = True
|
|
366
|
+
fact_preview_length: int = 120 # Chars of fact text in previews
|
|
367
|
+
|
|
368
|
+
# Semantic entailment
|
|
369
|
+
entailment_enabled: bool = True
|
|
370
|
+
entailment_model: str = "cross-encoder/nli-MiniLM2-L6-H768"
|
|
371
|
+
entailment_contradiction_threshold: float = 0.70 # P(contradiction) above this → flag
|
|
372
|
+
|
|
373
|
+
# Hallucination risk scoring
|
|
374
|
+
risk_scoring_enabled: bool = True
|
|
375
|
+
risk_weight_attribution: float = 0.30
|
|
376
|
+
risk_weight_fidelity: float = 0.25
|
|
377
|
+
risk_weight_entailment: float = 0.30
|
|
378
|
+
risk_weight_specificity: float = 0.15
|