From 466ce3ca82fa293018f4966c50aa86138a052bd4 Mon Sep 17 00:00:00 2001 From: Lars Date: Sun, 7 Dec 2025 16:57:01 +0100 Subject: [PATCH] angepasster retriever --- app/core/retriever.py | 106 +++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 49 deletions(-) diff --git a/app/core/retriever.py b/app/core/retriever.py index 771689f..38b86c0 100644 --- a/app/core/retriever.py +++ b/app/core/retriever.py @@ -131,9 +131,8 @@ def _build_explanation( ) -> Explanation: """ Erstellt ein detailliertes Explanation-Objekt für einen Treffer. - Analysiert Scores, Typen und eingehende Kanten. + Analysiert Scores, Typen und Kanten (Incoming & Outgoing). """ - # 1. Weights & Config laden sem_w, edge_w, cent_w = _get_scoring_weights() try: @@ -143,7 +142,6 @@ def _build_explanation( note_type = payload.get("type", "unknown") - # 2. Score Breakdown berechnen breakdown = ScoreBreakdown( semantic_contribution=(sem_w * semantic_score * type_weight), edge_contribution=(edge_w * edge_bonus), @@ -157,76 +155,87 @@ def _build_explanation( reasons: List[Reason] = [] edges_dto: List[EdgeDTO] = [] - # 3. Semantische Gründe + # 1. Semantische Gründe if semantic_score > 0.85: reasons.append(Reason( kind="semantic", - message="Sehr hohe textuelle Übereinstimmung mit der Anfrage.", + message="Sehr hohe textuelle Übereinstimmung.", score_impact=breakdown.semantic_contribution )) - elif semantic_score > 0.75: + elif semantic_score > 0.70: reasons.append(Reason( kind="semantic", message="Gute textuelle Übereinstimmung.", score_impact=breakdown.semantic_contribution )) - # 4. Typ-Gründe - if type_weight > 1.0: + # 2. Typ-Gründe + if type_weight != 1.0: + msg = "Bevorzugt" if type_weight > 1.0 else "Leicht abgewertet" reasons.append(Reason( kind="type", - message=f"Bevorzugt aufgrund des Typs '{note_type}' (Gewicht: {type_weight}).", - score_impact=(sem_w * semantic_score * (type_weight - 1.0)) # Delta - )) - elif type_weight < 1.0: - reasons.append(Reason( - kind="type", - message=f"Abgewertet aufgrund des Typs '{note_type}' (Gewicht: {type_weight}).", - score_impact=None + message=f"{msg} aufgrund des Typs '{note_type}' (Gewicht: {type_weight}).", + score_impact=(sem_w * semantic_score * (type_weight - 1.0)) )) - # 5. Graph-Gründe (Incoming Edges) + # 3. Graph-Gründe (Edges) if subgraph and node_key and edge_bonus > 0: - # Wir suchen nach eingehenden Kanten, die diesen Bonus verursacht haben. - # graph_adapter.py (v0.4.0) muss get_incoming_edges bereitstellen. + # Wir sammeln die stärksten Kanten (egal ob rein oder raus), + # die zum Score beitragen. + + # A) Outgoing (Ich verweise auf...) - Das ist oft der Hub-Score + if hasattr(subgraph, "get_outgoing_edges"): + outgoing = subgraph.get_outgoing_edges(node_key) + for edge in outgoing: + target = edge.get("target", "Unknown") + kind = edge.get("kind", "edge") + weight = edge.get("weight", 0.0) + + # Nur relevante Kanten aufnehmen + if weight > 0.05: + edges_dto.append(EdgeDTO( + id=f"{node_key}->{target}:{kind}", + kind=kind, source=node_key, target=target, weight=weight, direction="out" + )) + + # B) Incoming (Ich werde verwiesen von...) if hasattr(subgraph, "get_incoming_edges"): incoming = subgraph.get_incoming_edges(node_key) - - # Sortieren nach Gewicht (stärkste zuerst) - incoming_sorted = sorted(incoming, key=lambda e: e.get("weight", 0.0), reverse=True) - - # Top-3 Gründe extrahieren - for idx, edge in enumerate(incoming_sorted[:3]): + for edge in incoming: src = edge.get("source", "Unknown") kind = edge.get("kind", "edge") weight = edge.get("weight", 0.0) - msg = f"Verbunden mit '{src}' via '{kind}'" - reasons.append(Reason( - kind="edge", - message=msg, - score_impact=(edge_w * weight), - details={"source": src, "kind": kind, "weight": weight} - )) - - # EdgeDTO für die API - edges_dto.append(EdgeDTO( - id=f"{src}->{node_key}:{kind}", # Synthetische ID für Anzeige - kind=kind, - source=src, - target=node_key, - weight=weight, - direction="in" - )) - else: - # Fallback, falls GraphAdapter noch alt - reasons.append(Reason(kind="edge", message="Knoten ist im Kontext-Graphen vernetzt.")) + if weight > 0.05: + edges_dto.append(EdgeDTO( + id=f"{src}->{node_key}:{kind}", + kind=kind, source=src, target=node_key, weight=weight, direction="in" + )) - # 6. Centrality Gründe - if cent_bonus > 0.05: + # Sortieren nach Gewicht und Top-3 als Reasons generieren + all_edges = sorted(edges_dto, key=lambda e: e.weight, reverse=True) + + for top_edge in all_edges[:3]: + # Impact schätzen (grob, da Edge-Bonus eine Summe ist) + impact = edge_w * top_edge.weight + + if top_edge.direction == "out": + msg = f"Verweist auf '{top_edge.target}' via '{top_edge.kind}'" + else: + msg = f"Referenziert von '{top_edge.source}' via '{top_edge.kind}'" + + reasons.append(Reason( + kind="edge", + message=msg, + score_impact=impact, + details={"kind": top_edge.kind, "weight": top_edge.weight} + )) + + # 4. Centrality + if cent_bonus > 0.01: reasons.append(Reason( kind="centrality", - message="Knoten ist ein zentraler Hub im Kontext der Anfrage.", + message="Knoten liegt zentral im Kontext.", score_impact=breakdown.centrality_contribution )) @@ -235,7 +244,6 @@ def _build_explanation( reasons=reasons, related_edges=edges_dto if edges_dto else None ) - # --- End Explanation Logic ---