Erweiterung der Kanten um Abschnittsinformationen
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s

This commit is contained in:
Lars 2025-12-28 11:20:30 +01:00
parent 876ee898d8
commit c5f29ab4ae

View File

@ -200,14 +200,20 @@ class GraphExplorerService:
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchAny(any=note_ids))) shoulds.append(models.FieldCondition(key="target_id", match=models.MatchAny(any=note_ids)))
# Case C: Edge zeigt auf unseren Titel (Wikilinks) - auch wenn note_title None ist, versuchen wir es mit den Titeln der Notes # Case C: Edge zeigt auf unseren Titel (Wikilinks) - auch wenn note_title None ist, versuchen wir es mit den Titeln der Notes
# WICHTIG: Wir müssen auch nach "Titel#Abschnitt" Format suchen!
note_titles_to_search = []
if note_title: if note_title:
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchValue(value=note_title))) note_titles_to_search.append(note_title)
else: else:
# Fallback: Lade Titel der Notes, wenn note_title nicht übergeben wurde # Fallback: Lade Titel der Notes, wenn note_title nicht übergeben wurde
for nid in note_ids: for nid in note_ids:
note = self._fetch_note_cached(nid) note = self._fetch_note_cached(nid)
if note and note.get("title"): if note and note.get("title"):
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchValue(value=note.get("title")))) note_titles_to_search.append(note.get("title"))
# Für jeden Titel: Suche nach exaktem Match
for title in note_titles_to_search:
shoulds.append(models.FieldCondition(key="target_id", match=models.MatchValue(value=title)))
if shoulds: if shoulds:
in_filter = models.Filter( in_filter = models.Filter(
@ -218,6 +224,34 @@ class GraphExplorerService:
res_in, _ = self.client.scroll(self.edges_col, scroll_filter=in_filter, limit=2000, with_payload=True) res_in, _ = self.client.scroll(self.edges_col, scroll_filter=in_filter, limit=2000, with_payload=True)
results.extend(res_in) results.extend(res_in)
# Case D: ZUSÄTZLICHE Suche für "Titel#Abschnitt" Format
# Da Qdrant keine Wildcard-Suche hat, müssen wir breiter suchen und clientseitig filtern
# Wir suchen nach allen Kanten, die mit einem unserer Titel beginnen
if note_titles_to_search:
# Erweiterte Suche: Lade alle relevanten Kanten und filtere clientseitig
# Dies ist notwendig, weil "Titel#Abschnitt" nicht exakt mit "Titel" übereinstimmt
# OPTIMIERUNG: Nur wenn wir Titel haben, und mit begrenztem Limit
extended_filter = models.Filter(
must=[models.FieldCondition(key="kind", match=models.MatchExcept(**{"except": SYSTEM_EDGES}))]
)
# Lade Kanten für die clientseitige Filterung (Limit basierend auf Anzahl der Titel)
# Für jeden Titel könnten mehrere "Titel#Abschnitt" Varianten existieren
res_extended, _ = self.client.scroll(self.edges_col, scroll_filter=extended_filter, limit=3000, with_payload=True)
# Clientseitige Filterung: Finde Kanten, deren target_id mit einem unserer Titel beginnt
# Erstelle Set der bereits gefundenen Edge-IDs für schnelle Deduplizierung
existing_edge_ids = {r.id for r in results}
for edge in res_extended:
tgt_id = edge.payload.get("target_id", "")
if tgt_id and edge.id not in existing_edge_ids:
# Prüfe, ob target_id mit einem unserer Titel beginnt (für "Titel#Abschnitt" Format)
for title in note_titles_to_search:
if tgt_id.startswith(title + "#") or tgt_id == title:
results.append(edge)
existing_edge_ids.add(edge.id)
break # Nur einmal hinzufügen, auch wenn mehrere Titel passen
return results return results
def _find_connected_edges_batch(self, note_ids): def _find_connected_edges_batch(self, note_ids):
@ -296,10 +330,10 @@ class GraphExplorerService:
"""Löst eine ID (Chunk, Note oder Titel) zu einer Note Payload auf.""" """Löst eine ID (Chunk, Note oder Titel) zu einer Note Payload auf."""
if not ref_str: return None if not ref_str: return None
# Fall A: Chunk ID (enthält #) # Fall A: Chunk ID oder Titel#Abschnitt (enthält #)
if "#" in ref_str: if "#" in ref_str:
try: try:
# Versuch 1: Chunk ID direkt # Versuch 1: Chunk ID direkt (Format: note_id#c01)
res = self.client.retrieve(self.chunks_col, ids=[ref_str], with_payload=True) res = self.client.retrieve(self.chunks_col, ids=[ref_str], with_payload=True)
if res and res[0].payload: if res and res[0].payload:
note_id = res[0].payload.get("note_id") note_id = res[0].payload.get("note_id")
@ -309,11 +343,26 @@ class GraphExplorerService:
except Exception: except Exception:
pass pass
# Versuch 2: NoteID#Section (Hash abtrennen) # Versuch 2: NoteID#Section (Hash abtrennen und als Note-ID versuchen)
possible_note_id = ref_str.split("#")[0] possible_note_id = ref_str.split("#")[0]
note = self._fetch_note_cached(possible_note_id) note = self._fetch_note_cached(possible_note_id)
if note: return note if note: return note
# Versuch 3: Titel#Abschnitt (Hash abtrennen und als Titel suchen)
# Dies ist wichtig für Wikilinks im Format [[Titel#Abschnitt]]
possible_title = ref_str.split("#")[0]
try:
res, _ = self.client.scroll(
collection_name=self.notes_col,
scroll_filter=models.Filter(must=[models.FieldCondition(key="title", match=models.MatchValue(value=possible_title))]),
limit=1, with_payload=True
)
if res and res[0].payload:
self._note_cache[res[0].payload['note_id']] = res[0].payload
return res[0].payload
except Exception:
pass
# Fall B: Note ID direkt # Fall B: Note ID direkt
note = self._fetch_note_cached(ref_str) note = self._fetch_note_cached(ref_str)
if note: return note if note: return note