Phase 2.6: add per-paper trust summary to synthesizer output — addresses Armin's explainability request
Browse files- src/agents/synthesizer.py +35 -0
src/agents/synthesizer.py
CHANGED
|
@@ -284,6 +284,41 @@ Synthesize a research position on this query using the evidence above."""
|
|
| 284 |
logger.warning(f"Verdict log failed (non-fatal): {e}")
|
| 285 |
# ── End verdict logging ───────────────────────────────────────────────────
|
| 286 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
# Build markdown export
|
| 288 |
export_md = _build_export_md(query, position, summary, claims, state)
|
| 289 |
|
|
|
|
| 284 |
logger.warning(f"Verdict log failed (non-fatal): {e}")
|
| 285 |
# ── End verdict logging ───────────────────────────────────────────────────
|
| 286 |
|
| 287 |
+
# --- Phase 2.6: Trust summary block ---
|
| 288 |
+
reliability_scores = state.get("paper_reliability_scores", {})
|
| 289 |
+
if reliability_scores and papers:
|
| 290 |
+
trust_lines = ["\n\n---\n## Evidence Trust Summary\n"]
|
| 291 |
+
for p in papers[:8]: # same top-8 window synthesizer already uses
|
| 292 |
+
rs = reliability_scores.get(p.paper_id)
|
| 293 |
+
if not rs:
|
| 294 |
+
continue
|
| 295 |
+
# Format author citation
|
| 296 |
+
if p.authors and len(p.authors) > 0:
|
| 297 |
+
first_author = p.authors[0] if isinstance(p.authors, list) else str(p.authors).split(",")[0]
|
| 298 |
+
last_name = first_author.strip().split()[-1] if first_author.strip() else "Unknown"
|
| 299 |
+
citation_label = f"{last_name} et al., {p.year}"
|
| 300 |
+
else:
|
| 301 |
+
citation_label = f"Paper {p.year}"
|
| 302 |
+
|
| 303 |
+
# Signal label to human-readable
|
| 304 |
+
signal_map = {
|
| 305 |
+
"FOUNDATIONAL": "HIGH — foundational work",
|
| 306 |
+
"CURRENT": "HIGH — current",
|
| 307 |
+
"DECLINING": "MEDIUM — declining relevance",
|
| 308 |
+
"SUPERSEDED": "LOW — likely superseded",
|
| 309 |
+
}
|
| 310 |
+
dominant = rs.get("dominant_signal", "DECLINING") if isinstance(rs, dict) else rs.dominant_signal
|
| 311 |
+
score_val = rs.get("score", 0.0) if isinstance(rs, dict) else rs.score
|
| 312 |
+
reason_val = rs.get("reason", "") if isinstance(rs, dict) else rs.reason
|
| 313 |
+
reliability_label = signal_map.get(dominant, f"score={score_val:.2f}")
|
| 314 |
+
|
| 315 |
+
trust_lines.append(
|
| 316 |
+
f"**[{citation_label}]** \n"
|
| 317 |
+
f"Reliability: {reliability_label} \n"
|
| 318 |
+
f"Reason: {reason_val}\n"
|
| 319 |
+
)
|
| 320 |
+
position = position + "".join(trust_lines)
|
| 321 |
+
|
| 322 |
# Build markdown export
|
| 323 |
export_md = _build_export_md(query, position, summary, claims, state)
|
| 324 |
|