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,440 @@
|
|
|
1
|
+
# Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
|
|
2
|
+
# Licensed under Elastic License 2.0 — see LICENSE.md for details.
|
|
3
|
+
"""Report Generator — human-readable provenance reports (§7.14.3).
|
|
4
|
+
|
|
5
|
+
Produces regulator-auditable decision provenance reports in markdown and
|
|
6
|
+
JSON formats. Reports answer: "For each claim in the AI output, what
|
|
7
|
+
evidence supported it and how confident are we in that attribution?"
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ._types import (
|
|
16
|
+
AttributionType,
|
|
17
|
+
ClaimType,
|
|
18
|
+
EntailmentLabel,
|
|
19
|
+
HallucinationRisk,
|
|
20
|
+
OmissionSeverity,
|
|
21
|
+
ProvenanceReport,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Markdown report
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def generate_markdown_report(report: ProvenanceReport) -> str:
|
|
31
|
+
"""Generate a human-readable markdown provenance report.
|
|
32
|
+
|
|
33
|
+
Suitable for regulatory audit, compliance review, or integration into
|
|
34
|
+
documentation. Format follows EU AI Act Article 12 logging requirements.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
report: Complete provenance report from the DPE pipeline.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Markdown-formatted report string.
|
|
41
|
+
"""
|
|
42
|
+
lines: list[str] = []
|
|
43
|
+
|
|
44
|
+
# Header
|
|
45
|
+
lines.append("## Decision Provenance Report")
|
|
46
|
+
session_short = report.session_id[:12] + "..." if len(report.session_id) > 12 else report.session_id
|
|
47
|
+
window_short = report.window_id[:12] + "..." if len(report.window_id) > 12 else report.window_id
|
|
48
|
+
ts = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(report.timestamp))
|
|
49
|
+
lines.append(f"### Session: {session_short} | Window: {window_short} | Generated: {ts}")
|
|
50
|
+
lines.append("")
|
|
51
|
+
|
|
52
|
+
# Output summary
|
|
53
|
+
lines.append("### Output Summary")
|
|
54
|
+
lines.append(f"The model generated output containing **{report.total_claims}** identifiable claims.")
|
|
55
|
+
lines.append(f"- {report.factual_claims} factual claims (require attribution)")
|
|
56
|
+
lines.append(f"- {report.opinion_claims} opinions")
|
|
57
|
+
lines.append(f"- {report.procedural_claims} procedural instructions")
|
|
58
|
+
lines.append(f"- {report.hedge_claims} hedged/qualified statements")
|
|
59
|
+
lines.append(f"- {report.connective_claims} connective/structural")
|
|
60
|
+
lines.append("")
|
|
61
|
+
|
|
62
|
+
# Attribution summary table
|
|
63
|
+
lines.append("### Attribution Summary")
|
|
64
|
+
grounding_pct = f"{report.grounding_ratio * 100:.1f}%" if report.factual_claims > 0 else "N/A"
|
|
65
|
+
|
|
66
|
+
lines.append("")
|
|
67
|
+
lines.append("| # | Claim (preview) | Type | Source | Score | Confidence |")
|
|
68
|
+
lines.append("|---|-----------------|------|--------|-------|------------|")
|
|
69
|
+
|
|
70
|
+
for i, attr in enumerate(report.attributions, 1):
|
|
71
|
+
preview = attr.claim_text[:60] + "..." if len(attr.claim_text) > 60 else attr.claim_text
|
|
72
|
+
preview = preview.replace("|", "\\|")
|
|
73
|
+
source = attr.attribution_type.value if attr.attribution_type != AttributionType.UNCERTAIN else "—"
|
|
74
|
+
score = f"{attr.top_score:.2f}" if attr.top_score > 0 else "—"
|
|
75
|
+
conf = f"{attr.confidence:.2f}" if attr.confidence > 0 else "—"
|
|
76
|
+
lines.append(f"| {i} | {preview} | {attr.claim_type.value} | {source} | {score} | {conf} |")
|
|
77
|
+
|
|
78
|
+
lines.append("")
|
|
79
|
+
|
|
80
|
+
# Grounding statistics
|
|
81
|
+
lines.append("### Grounding Statistics")
|
|
82
|
+
lines.append(f"- **Context-grounded claims:** {report.context_grounded_count}")
|
|
83
|
+
lines.append(f"- **Parametric claims (training data):** {report.parametric_count}")
|
|
84
|
+
lines.append(f"- **Mixed attribution:** {report.mixed_count}")
|
|
85
|
+
lines.append(f"- **Uncertain attribution:** {report.uncertain_count}")
|
|
86
|
+
lines.append(f"- **Grounding ratio:** {grounding_pct}")
|
|
87
|
+
lines.append("")
|
|
88
|
+
|
|
89
|
+
# Provenance chains
|
|
90
|
+
if report.chains:
|
|
91
|
+
lines.append("### Provenance Chains (Top Factual Claims)")
|
|
92
|
+
for chain in report.chains:
|
|
93
|
+
if chain.attribution_type in (AttributionType.CONTEXT_GROUNDED, AttributionType.MIXED):
|
|
94
|
+
claim_preview = chain.claim_text[:80]
|
|
95
|
+
lines.append(f"\n**Claim #{chain.claim_index}:** \"{claim_preview}...\"")
|
|
96
|
+
for link in chain.links:
|
|
97
|
+
indent = " " if link.level != "claim" else ""
|
|
98
|
+
lines.append(f"{indent}← **{link.level.upper()}:** {link.label}")
|
|
99
|
+
lines.append("")
|
|
100
|
+
|
|
101
|
+
# Integrity
|
|
102
|
+
lines.append("### Provenance Chain Integrity")
|
|
103
|
+
status = "✅" if report.chain_verified else "⚠️ Not verified"
|
|
104
|
+
lines.append(f"- Audit chain integrity: {status}")
|
|
105
|
+
lines.append(f"- Envelope facts available: {report.envelope_facts_count}")
|
|
106
|
+
lines.append("")
|
|
107
|
+
|
|
108
|
+
# Regulatory notes
|
|
109
|
+
lines.append("### Regulatory Compliance Notes")
|
|
110
|
+
lines.append("- **EU AI Act Art. 12:** Activity logging ✅")
|
|
111
|
+
if report.factual_claims > 0:
|
|
112
|
+
lines.append(
|
|
113
|
+
f"- {report.context_grounded_count}/{report.factual_claims} factual claims "
|
|
114
|
+
f"grounded in provided context ({grounding_pct})"
|
|
115
|
+
)
|
|
116
|
+
if report.parametric_count > 0:
|
|
117
|
+
lines.append(
|
|
118
|
+
f"- {report.parametric_count} claim(s) flagged as potentially from "
|
|
119
|
+
f"parametric knowledge — may need manual verification"
|
|
120
|
+
)
|
|
121
|
+
if report.uncertain_count > 0:
|
|
122
|
+
lines.append(
|
|
123
|
+
f"- {report.uncertain_count} claim(s) with uncertain attribution "
|
|
124
|
+
f"— manual review recommended"
|
|
125
|
+
)
|
|
126
|
+
lines.append("")
|
|
127
|
+
|
|
128
|
+
# Fidelity verification section
|
|
129
|
+
fid = report.fidelity
|
|
130
|
+
if fid is not None:
|
|
131
|
+
lines.append("### Fidelity Verification")
|
|
132
|
+
lines.append(f"**Fidelity score:** {fid.fidelity_score:.2f} / 1.00")
|
|
133
|
+
lines.append("")
|
|
134
|
+
|
|
135
|
+
if fid.distortions:
|
|
136
|
+
lines.append("#### Distortions Detected")
|
|
137
|
+
lines.append("| # | Claim (preview) | Type | Severity | Detail |")
|
|
138
|
+
lines.append("|---|-----------------|------|----------|--------|")
|
|
139
|
+
for i, d in enumerate(fid.distortions, 1):
|
|
140
|
+
preview = d.claim_text[:50].replace("|", "\\|")
|
|
141
|
+
lines.append(
|
|
142
|
+
f"| {i} | {preview} | {d.distortion_type.value} "
|
|
143
|
+
f"| {d.severity:.2f} | {d.detail[:80]} |"
|
|
144
|
+
)
|
|
145
|
+
lines.append("")
|
|
146
|
+
|
|
147
|
+
if fid.fabrications:
|
|
148
|
+
lines.append("#### Fabrications Detected")
|
|
149
|
+
lines.append("| # | Claim (preview) | Entity | Type | Severity |")
|
|
150
|
+
lines.append("|---|-----------------|--------|------|----------|")
|
|
151
|
+
for i, f in enumerate(fid.fabrications, 1):
|
|
152
|
+
preview = f.claim_text[:50].replace("|", "\\|")
|
|
153
|
+
lines.append(
|
|
154
|
+
f"| {i} | {preview} | `{f.fabricated_entity}` "
|
|
155
|
+
f"| {f.entity_type.value} | {f.severity:.2f} |"
|
|
156
|
+
)
|
|
157
|
+
lines.append("")
|
|
158
|
+
|
|
159
|
+
critical_omissions = [
|
|
160
|
+
o for o in fid.omissions
|
|
161
|
+
if o.severity in (OmissionSeverity.CRITICAL, OmissionSeverity.HIGH)
|
|
162
|
+
]
|
|
163
|
+
if critical_omissions:
|
|
164
|
+
lines.append("#### Critical/High Omissions")
|
|
165
|
+
lines.append("| # | Fact (preview) | Relevance | Severity |")
|
|
166
|
+
lines.append("|---|----------------|-----------|----------|")
|
|
167
|
+
for i, o in enumerate(critical_omissions, 1):
|
|
168
|
+
preview = o.fact_text_preview[:60].replace("|", "\\|")
|
|
169
|
+
lines.append(
|
|
170
|
+
f"| {i} | {preview} | {o.fact_relevance_score:.2f} "
|
|
171
|
+
f"| {o.severity.value} |"
|
|
172
|
+
)
|
|
173
|
+
lines.append("")
|
|
174
|
+
|
|
175
|
+
if fid.contradictions:
|
|
176
|
+
lines.append("#### Contradictions Detected")
|
|
177
|
+
for i, c in enumerate(fid.contradictions, 1):
|
|
178
|
+
lines.append(
|
|
179
|
+
f"{i}. **{c.contradiction_type}** (severity {c.severity:.2f}): "
|
|
180
|
+
f"Claim #{c.claim_a_index} vs Claim #{c.claim_b_index} — {c.detail[:120]}"
|
|
181
|
+
)
|
|
182
|
+
lines.append("")
|
|
183
|
+
|
|
184
|
+
if not (fid.distortions or fid.fabrications or critical_omissions or fid.contradictions):
|
|
185
|
+
lines.append("No fidelity issues detected. ✅")
|
|
186
|
+
lines.append("")
|
|
187
|
+
|
|
188
|
+
# Semantic entailment section
|
|
189
|
+
if report.entailment_results:
|
|
190
|
+
lines.append("### Semantic Entailment Verification")
|
|
191
|
+
contradictions = [
|
|
192
|
+
er for er in report.entailment_results
|
|
193
|
+
if er.label == EntailmentLabel.CONTRADICTION
|
|
194
|
+
]
|
|
195
|
+
if contradictions:
|
|
196
|
+
lines.append(f"**{len(contradictions)} semantic contradiction(s) detected** ⚠️")
|
|
197
|
+
lines.append("")
|
|
198
|
+
lines.append("| # | Claim (preview) | vs Fact | P(contra) | P(entail) | Method |")
|
|
199
|
+
lines.append("|---|-----------------|---------|-----------|-----------|--------|")
|
|
200
|
+
for i, er in enumerate(contradictions, 1):
|
|
201
|
+
preview = er.claim_text[:50].replace("|", "\\|")
|
|
202
|
+
method = "NLI model" if er.used_model else "Heuristic"
|
|
203
|
+
lines.append(
|
|
204
|
+
f"| {i} | {preview} | {er.fact_id[:12]} "
|
|
205
|
+
f"| {er.contradiction_score:.2f} | {er.entailment_score:.2f} "
|
|
206
|
+
f"| {method} |"
|
|
207
|
+
)
|
|
208
|
+
lines.append("")
|
|
209
|
+
else:
|
|
210
|
+
entailed = sum(
|
|
211
|
+
1 for er in report.entailment_results
|
|
212
|
+
if er.label == EntailmentLabel.ENTAILED
|
|
213
|
+
)
|
|
214
|
+
lines.append(
|
|
215
|
+
f"All {len(report.entailment_results)} claim-fact pairs checked. "
|
|
216
|
+
f"{entailed} entailed, 0 contradictions. ✅"
|
|
217
|
+
)
|
|
218
|
+
lines.append("")
|
|
219
|
+
|
|
220
|
+
# Hallucination risk section
|
|
221
|
+
risk = report.risk_report
|
|
222
|
+
if risk is not None and risk.assessments:
|
|
223
|
+
lines.append("### Hallucination Risk Assessment")
|
|
224
|
+
risk_emoji = {
|
|
225
|
+
HallucinationRisk.LOW: "🟢",
|
|
226
|
+
HallucinationRisk.MEDIUM: "🟡",
|
|
227
|
+
HallucinationRisk.HIGH: "🟠",
|
|
228
|
+
HallucinationRisk.CRITICAL: "🔴",
|
|
229
|
+
}
|
|
230
|
+
lines.append(
|
|
231
|
+
f"**Window risk level:** {risk_emoji.get(risk.window_risk_level, '')} "
|
|
232
|
+
f"{risk.window_risk_level.value} "
|
|
233
|
+
f"(mean score: {risk.mean_risk_score:.2f})"
|
|
234
|
+
)
|
|
235
|
+
if risk.critical_risk_count > 0:
|
|
236
|
+
lines.append(f"- **{risk.critical_risk_count}** CRITICAL risk claim(s)")
|
|
237
|
+
if risk.high_risk_count > 0:
|
|
238
|
+
lines.append(f"- **{risk.high_risk_count}** HIGH risk claim(s)")
|
|
239
|
+
lines.append("")
|
|
240
|
+
|
|
241
|
+
# Show high/critical risk claims
|
|
242
|
+
high_risk = [
|
|
243
|
+
a for a in risk.assessments
|
|
244
|
+
if a.risk_level in (HallucinationRisk.HIGH, HallucinationRisk.CRITICAL)
|
|
245
|
+
]
|
|
246
|
+
if high_risk:
|
|
247
|
+
lines.append("#### High/Critical Risk Claims")
|
|
248
|
+
lines.append("| # | Claim (preview) | Risk | Score | Factors |")
|
|
249
|
+
lines.append("|---|-----------------|------|-------|---------|")
|
|
250
|
+
for a in high_risk:
|
|
251
|
+
preview = a.claim_text[:50].replace("|", "\\|")
|
|
252
|
+
factors = "; ".join(a.risk_factors[:3])
|
|
253
|
+
lines.append(
|
|
254
|
+
f"| {a.claim_index} | {preview} "
|
|
255
|
+
f"| {a.risk_level.value} | {a.risk_score:.2f} "
|
|
256
|
+
f"| {factors[:100]} |"
|
|
257
|
+
)
|
|
258
|
+
lines.append("")
|
|
259
|
+
|
|
260
|
+
return "\n".join(lines)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
# JSON report
|
|
265
|
+
# ---------------------------------------------------------------------------
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def generate_json_report(report: ProvenanceReport) -> dict[str, Any]:
|
|
269
|
+
"""Generate a machine-readable JSON provenance report.
|
|
270
|
+
|
|
271
|
+
Suitable for API responses, database storage, or integration with
|
|
272
|
+
compliance management systems.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
report: Complete provenance report from the DPE pipeline.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
JSON-serializable dictionary.
|
|
279
|
+
"""
|
|
280
|
+
return {
|
|
281
|
+
"report_type": "decision_provenance",
|
|
282
|
+
"version": "1.0.0",
|
|
283
|
+
"session_id": report.session_id,
|
|
284
|
+
"window_id": report.window_id,
|
|
285
|
+
"timestamp": report.timestamp,
|
|
286
|
+
"summary": {
|
|
287
|
+
"total_claims": report.total_claims,
|
|
288
|
+
"factual_claims": report.factual_claims,
|
|
289
|
+
"opinion_claims": report.opinion_claims,
|
|
290
|
+
"procedural_claims": report.procedural_claims,
|
|
291
|
+
"hedge_claims": report.hedge_claims,
|
|
292
|
+
"connective_claims": report.connective_claims,
|
|
293
|
+
"context_grounded": report.context_grounded_count,
|
|
294
|
+
"parametric": report.parametric_count,
|
|
295
|
+
"mixed": report.mixed_count,
|
|
296
|
+
"uncertain": report.uncertain_count,
|
|
297
|
+
"grounding_ratio": report.grounding_ratio,
|
|
298
|
+
},
|
|
299
|
+
"attributions": [
|
|
300
|
+
{
|
|
301
|
+
"claim_text": attr.claim_text[:200],
|
|
302
|
+
"claim_index": attr.claim_index,
|
|
303
|
+
"claim_type": attr.claim_type.value,
|
|
304
|
+
"attribution_type": attr.attribution_type.value,
|
|
305
|
+
"top_score": attr.top_score,
|
|
306
|
+
"confidence": attr.confidence,
|
|
307
|
+
"top_facts": [
|
|
308
|
+
{
|
|
309
|
+
"fact_id": fs.fact_id,
|
|
310
|
+
"fact_preview": fs.fact_text_preview,
|
|
311
|
+
"semantic_similarity": fs.semantic_similarity,
|
|
312
|
+
"lexical_overlap": fs.lexical_overlap,
|
|
313
|
+
"composite_score": fs.composite_score,
|
|
314
|
+
"source_window": fs.fact_source_window,
|
|
315
|
+
"extraction_stage": fs.fact_extraction_stage,
|
|
316
|
+
}
|
|
317
|
+
for fs in attr.attributed_facts[:3]
|
|
318
|
+
],
|
|
319
|
+
}
|
|
320
|
+
for attr in report.attributions
|
|
321
|
+
],
|
|
322
|
+
"chains": [
|
|
323
|
+
{
|
|
324
|
+
"claim_text": chain.claim_text[:200],
|
|
325
|
+
"claim_index": chain.claim_index,
|
|
326
|
+
"attribution_type": chain.attribution_type.value,
|
|
327
|
+
"links": [
|
|
328
|
+
{
|
|
329
|
+
"level": link.level,
|
|
330
|
+
"label": link.label,
|
|
331
|
+
"detail": link.detail,
|
|
332
|
+
}
|
|
333
|
+
for link in chain.links
|
|
334
|
+
],
|
|
335
|
+
}
|
|
336
|
+
for chain in report.chains
|
|
337
|
+
],
|
|
338
|
+
"integrity": {
|
|
339
|
+
"chain_verified": report.chain_verified,
|
|
340
|
+
"envelope_facts_count": report.envelope_facts_count,
|
|
341
|
+
"output_token_count": report.output_token_count,
|
|
342
|
+
},
|
|
343
|
+
"fidelity": _serialize_fidelity(report.fidelity),
|
|
344
|
+
"entailment": [
|
|
345
|
+
{
|
|
346
|
+
"claim_index": er.claim_index,
|
|
347
|
+
"claim_text": er.claim_text[:200],
|
|
348
|
+
"fact_id": er.fact_id,
|
|
349
|
+
"label": er.label.value,
|
|
350
|
+
"confidence": er.confidence,
|
|
351
|
+
"entailment_score": er.entailment_score,
|
|
352
|
+
"contradiction_score": er.contradiction_score,
|
|
353
|
+
"neutral_score": er.neutral_score,
|
|
354
|
+
"used_model": er.used_model,
|
|
355
|
+
}
|
|
356
|
+
for er in report.entailment_results
|
|
357
|
+
],
|
|
358
|
+
"risk_assessment": _serialize_risk(report.risk_report),
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _serialize_risk(risk: object) -> dict[str, object] | None:
|
|
363
|
+
"""Serialize HallucinationRiskReport for JSON output."""
|
|
364
|
+
if risk is None:
|
|
365
|
+
return None
|
|
366
|
+
return {
|
|
367
|
+
"window_risk_level": risk.window_risk_level.value, # type: ignore[union-attr]
|
|
368
|
+
"mean_risk_score": risk.mean_risk_score, # type: ignore[union-attr]
|
|
369
|
+
"high_risk_count": risk.high_risk_count, # type: ignore[union-attr]
|
|
370
|
+
"critical_risk_count": risk.critical_risk_count, # type: ignore[union-attr]
|
|
371
|
+
"assessments": [
|
|
372
|
+
{
|
|
373
|
+
"claim_index": a.claim_index,
|
|
374
|
+
"claim_text": a.claim_text[:200],
|
|
375
|
+
"risk_level": a.risk_level.value,
|
|
376
|
+
"risk_score": a.risk_score,
|
|
377
|
+
"attribution_signal": a.attribution_signal,
|
|
378
|
+
"fidelity_signal": a.fidelity_signal,
|
|
379
|
+
"entailment_signal": a.entailment_signal,
|
|
380
|
+
"specificity_signal": a.specificity_signal,
|
|
381
|
+
"risk_factors": a.risk_factors,
|
|
382
|
+
}
|
|
383
|
+
for a in risk.assessments # type: ignore[union-attr]
|
|
384
|
+
],
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _serialize_fidelity(fid: object) -> dict[str, Any] | None:
|
|
389
|
+
"""Serialize FidelityReport for JSON output."""
|
|
390
|
+
if fid is None:
|
|
391
|
+
return None
|
|
392
|
+
# fid is a FidelityReport but we avoid import cycle via duck typing
|
|
393
|
+
return {
|
|
394
|
+
"fidelity_score": fid.fidelity_score, # type: ignore[union-attr]
|
|
395
|
+
"distortion_count": fid.distortion_count, # type: ignore[union-attr]
|
|
396
|
+
"fabrication_count": fid.fabrication_count, # type: ignore[union-attr]
|
|
397
|
+
"critical_omission_count": fid.critical_omission_count, # type: ignore[union-attr]
|
|
398
|
+
"contradiction_count": fid.contradiction_count, # type: ignore[union-attr]
|
|
399
|
+
"distortions": [
|
|
400
|
+
{
|
|
401
|
+
"claim_index": d.claim_index,
|
|
402
|
+
"claim_text": d.claim_text[:200],
|
|
403
|
+
"distortion_type": d.distortion_type.value,
|
|
404
|
+
"severity": d.severity,
|
|
405
|
+
"detail": d.detail,
|
|
406
|
+
"claim_value": d.claim_value,
|
|
407
|
+
"fact_value": d.fact_value,
|
|
408
|
+
}
|
|
409
|
+
for d in fid.distortions # type: ignore[union-attr]
|
|
410
|
+
],
|
|
411
|
+
"fabrications": [
|
|
412
|
+
{
|
|
413
|
+
"claim_index": f.claim_index,
|
|
414
|
+
"claim_text": f.claim_text[:200],
|
|
415
|
+
"fabricated_entity": f.fabricated_entity,
|
|
416
|
+
"entity_type": f.entity_type.value,
|
|
417
|
+
"severity": f.severity,
|
|
418
|
+
}
|
|
419
|
+
for f in fid.fabrications # type: ignore[union-attr]
|
|
420
|
+
],
|
|
421
|
+
"omissions": [
|
|
422
|
+
{
|
|
423
|
+
"fact_id": o.fact_id,
|
|
424
|
+
"fact_text_preview": o.fact_text_preview[:120],
|
|
425
|
+
"relevance_score": o.fact_relevance_score,
|
|
426
|
+
"severity": o.severity.value,
|
|
427
|
+
}
|
|
428
|
+
for o in fid.omissions # type: ignore[union-attr]
|
|
429
|
+
],
|
|
430
|
+
"contradictions": [
|
|
431
|
+
{
|
|
432
|
+
"claim_a_index": c.claim_a_index,
|
|
433
|
+
"claim_b_index": c.claim_b_index,
|
|
434
|
+
"contradiction_type": c.contradiction_type,
|
|
435
|
+
"severity": c.severity,
|
|
436
|
+
"detail": c.detail,
|
|
437
|
+
}
|
|
438
|
+
for c in fid.contradictions # type: ignore[union-attr]
|
|
439
|
+
],
|
|
440
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Copyright © 2025 Constantinos Vidiniotis. All rights reserved.
|
|
2
|
+
# Licensed under Elastic License 2.0 — see LICENSE.md for details.
|
|
3
|
+
"""LLM provider adapters — OpenAI, Anthropic, Ollama, llama.cpp, custom.
|
|
4
|
+
|
|
5
|
+
All adapters implement :class:`LLMProvider` and can be passed directly
|
|
6
|
+
to ``crp.Client(provider=...)``.
|
|
7
|
+
|
|
8
|
+
Usage::
|
|
9
|
+
|
|
10
|
+
from crp.providers import OpenAIAdapter, OllamaAdapter
|
|
11
|
+
|
|
12
|
+
provider = OpenAIAdapter(model="gpt-4o")
|
|
13
|
+
# or
|
|
14
|
+
provider = OllamaAdapter(model="llama3.1")
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from crp.providers.base import LLMProvider
|
|
18
|
+
from crp.providers.custom import CustomProvider
|
|
19
|
+
from crp.providers.llamacpp import LlamaCppAdapter
|
|
20
|
+
from crp.providers.ollama import OllamaAdapter
|
|
21
|
+
from crp.providers.openai import OpenAIAdapter
|
|
22
|
+
|
|
23
|
+
# Lazy import for optional dependency: Anthropic
|
|
24
|
+
def __getattr__(name: str):
|
|
25
|
+
if name == "AnthropicAdapter":
|
|
26
|
+
from crp.providers.anthropic import AnthropicAdapter
|
|
27
|
+
return AnthropicAdapter
|
|
28
|
+
raise AttributeError(f"module 'crp.providers' has no attribute {name!r}")
|
|
29
|
+
|
|
30
|
+
# Convenience alias matching docs
|
|
31
|
+
CallableAdapter = CustomProvider
|
|
32
|
+
BaseAdapter = LLMProvider
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"LLMProvider",
|
|
36
|
+
"BaseAdapter",
|
|
37
|
+
"OpenAIAdapter",
|
|
38
|
+
"AnthropicAdapter",
|
|
39
|
+
"OllamaAdapter",
|
|
40
|
+
"LlamaCppAdapter",
|
|
41
|
+
"CustomProvider",
|
|
42
|
+
"CallableAdapter",
|
|
43
|
+
]
|