0Pricing
AI Agents · Lektion

Vektor- und Graphen-Retrieval kombinieren

Hybrides Retrieval: Vektorähnlichkeit + Traversierung von Graphpfaden für reichhaltigeren Kontext.

Vektor- und Graphen-Retrieval kombinieren ist eine kostenlose AI Agents-Lektion auf CoddyKit. Dies ist Lektion 3 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des AI Agents-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der AI Agents-Kurs umfasst insgesamt 4 Lektionen.

Warum hybrides Retrieval?

Die Vektorsuche findet semantisch ähnliche Inhalte, übersieht jedoch strukturierte Beziehungen. Der Graphdurchlauf erfasst Beziehungen, hat aber Schwierigkeiten mit semantischer Ähnlichkeit. Hybrides Retrieval kombiniert beides und liefert dadurch einen umfassenderen Kontext.

Zusammenfassung der Vektorsuche

Bei der Vektorsuche werden Abfragen und Dokumente in Embeddings (dichte Vektoren) umgewandelt. Anschließend werden Dokumente mit hoher Kosinusähnlichkeit gefunden. Sie beantwortet die Frage Welche Dokumente behandeln dasselbe Thema?

import openai
import numpy as np

client = openai.OpenAI(api_key='sk-...')

def embed(text: str) -> list:
    response = client.embeddings.create(
        model='text-embedding-3-small',
        input=text
    )
    return response.data[0].embedding

def cosine_similarity(a: list, b: list) -> float:
    a_arr = np.array(a)
    b_arr = np.array(b)
    return float(np.dot(a_arr, b_arr) / (np.linalg.norm(a_arr) * np.linalg.norm(b_arr)))

# Simple in-memory vector store
class SimpleVectorStore:
    def __init__(self):
        self.documents = []
    
    def add(self, text: str, metadata: dict):
        embedding = embed(text)
        self.documents.append({'text': text, 'embedding': embedding, 'metadata': metadata})
    
    def search(self, query: str, top_k: int = 5) -> list:
        query_emb = embed(query)
        scored = [
            (cosine_similarity(query_emb, doc['embedding']), doc)
            for doc in self.documents
        ]
        scored.sort(key=lambda x: x[0], reverse=True)
        return [doc for _, doc in scored[:top_k]]

Zusammenfassung des Graph-Retrievals

Graph-Retrieval beantwortet relationale Fragen: Wer ist mit X verbunden?, Welche Unternehmen kennt diese Person? Es verwendet explizite Kanten statt semantischer Ähnlichkeit.

from neo4j import GraphDatabase

driver = GraphDatabase.driver('bolt://localhost:7687', auth=('neo4j', 'password'))

def get_entity_context(entity_name: str) -> dict:
    with driver.session() as session:
        # Get node properties
        result = session.run(
            'MATCH (n {name: $name}) RETURN n, labels(n) AS labels LIMIT 1',
            name=entity_name
        )
        record = result.single()
        if not record:
            return {}
        
        node_data = dict(record['n'])
        node_labels = record['labels']
        
        # Get connected entities
        conn_result = session.run(
            'MATCH (n {name: $name})-[r]-(connected) '
            'RETURN type(r) AS rel_type, connected.name AS connected_name, labels(connected) AS connected_labels '
            'LIMIT 20',
            name=entity_name
        )
        connections = [dict(r) for r in conn_result]
        
        return {
            'name': entity_name,
            'labels': node_labels,
            'properties': node_data,
            'connections': connections
        }

Ergebnisse verschachteln

Eine Fusionsstrategie besteht darin, Vektor- und Graphergebnisse abwechselnd einzufügen: Nehmen Sie das beste Ergebnis der Vektorsuche, dann das beste Graphergebnis, anschließend das zweitbeste Vektorergebnis und so weiter. Dadurch tragen beide Quellen bei.

def interleave_results(vector_results: list, graph_results: list) -> list:
    combined = []
    v_idx, g_idx = 0, 0
    
    while v_idx < len(vector_results) or g_idx < len(graph_results):
        if v_idx < len(vector_results):
            item = vector_results[v_idx]
            item['source'] = 'vector'
            combined.append(item)
            v_idx += 1
        
        if g_idx < len(graph_results):
            item = graph_results[g_idx]
            item['source'] = 'graph'
            combined.append(item)
            g_idx += 1
    
    return combined

# Example
vector_docs = [
    {'text': 'Alice led the machine learning initiative at Acme', 'score': 0.92},
    {'text': 'Machine learning best practices guide', 'score': 0.85},
]
graph_context = [
    {'name': 'Alice', 'type': 'Person', 'connections': ['Acme Corp', 'Bob']},
]

fused = interleave_results(vector_docs, graph_context)
for item in fused:
    print(f"[{item['source']}]", item.get('text') or item.get('name'))

Gewichtete Kombination

Bewerten Sie jedes Ergebnis mit einer kombinierten Punktzahl: final_score = alpha * vector_score + (1-alpha) * graph_score. Stimmen Sie alpha darauf ab, ob für Ihren Anwendungsfall die semantische Ähnlichkeit oder der relationale Kontext wichtiger ist.

def weighted_fusion(vector_results: list, graph_results: list, alpha: float = 0.6) -> list:
    '''
    alpha: weight for vector results (0.0 = pure graph, 1.0 = pure vector)
    '''
    all_results = []
    
    # Normalize vector scores (already in 0-1 range for cosine)
    for i, res in enumerate(vector_results):
        # Positional score: first result gets highest
        positional_score = 1.0 - (i / max(len(vector_results), 1))
        combined = alpha * res.get('score', positional_score)
        all_results.append({
            'content': res,
            'source': 'vector',
            'final_score': combined
        })
    
    # Graph results: score by relevance (e.g., connection count)
    for i, res in enumerate(graph_results):
        positional_score = 1.0 - (i / max(len(graph_results), 1))
        combined = (1 - alpha) * positional_score
        all_results.append({
            'content': res,
            'source': 'graph',
            'final_score': combined
        })
    
    # Sort by final score
    all_results.sort(key=lambda x: x['final_score'], reverse=True)
    return all_results

print('Weighted fusion function defined (alpha=0.6 favors vector)')

Reciprocal Rank Fusion

Reciprocal Rank Fusion (RRF) ist eine robuste Methode zum Kombinieren sortierter Listen, für die keine normalisierten Punktzahlen erforderlich sind. Jedes Dokument erhält über alle Listen hinweg die Punktzahl sum(1 / (k + rank)).

def reciprocal_rank_fusion(result_lists: list, k: int = 60) -> list:
    '''
    result_lists: list of lists, each containing dicts with an 'id' field
    k: constant to reduce impact of high rankings (typically 60)
    '''
    scores = {}
    all_items = {}
    
    for result_list in result_lists:
        for rank, item in enumerate(result_list):
            item_id = item.get('id') or item.get('text', '')[:50]
            if item_id not in scores:
                scores[item_id] = 0.0
                all_items[item_id] = item
            scores[item_id] += 1.0 / (k + rank + 1)
    
    sorted_ids = sorted(scores.keys(), key=lambda x: scores[x], reverse=True)
    return [
        {**all_items[id_], 'rrf_score': scores[id_]}
        for id_ in sorted_ids
    ]

vector_list = [{'id': 'doc1', 'text': 'About Alice'}, {'id': 'doc3', 'text': 'About AI'}]
graph_list = [{'id': 'doc2', 'text': 'Alice connections'}, {'id': 'doc1', 'text': 'About Alice'}]

fused = reciprocal_rank_fusion([vector_list, graph_list])
for item in fused:
    print(f"{item['id']}: RRF score {item['rrf_score']:.4f}")

Entitätsverankertes hybrides Retrieval

Ein leistungsfähiger hybrider Ansatz: Extrahieren Sie Entitäten aus der Abfrage, verwenden Sie den Graphen, um Kontext zu diesen Entitäten abzurufen, und nutzen Sie diesen Kontext anschließend, um die Abfrage für die Vektorsuche zu erweitern.

import spacy

nlp = spacy.load('en_core_web_sm')

def entity_anchored_retrieval(query: str, vector_store, graph_driver) -> dict:
    # Step 1: Extract entities from query
    doc = nlp(query)
    entities = [ent.text for ent in doc.ents if ent.label_ in ['PERSON', 'ORG', 'GPE']]
    
    # Step 2: Get graph context for entities
    graph_contexts = {}
    for entity in entities:
        context = get_entity_context(entity)
        if context:
            graph_contexts[entity] = context
    
    # Step 3: Enrich query with graph context
    enriched_query = query
    if graph_contexts:
        context_str = ' '.join([
            f"{name} works at {', '.join([c['connected_name'] for c in ctx.get('connections', [])[:3]])}"
            for name, ctx in graph_contexts.items()
        ])
        enriched_query = f'{query} Context: {context_str}'
    
    # Step 4: Vector search with enriched query
    vector_results = vector_store.search(enriched_query, top_k=5)
    
    return {
        'entities_found': entities,
        'graph_contexts': graph_contexts,
        'vector_results': vector_results
    }

Ein Kontextpaket erstellen

Im letzten Retrieval-Schritt wird der gesamte Kontext (Vektorergebnisse und Graphdaten) für das LLM in einer strukturierten Zeichenkette zusammengefasst. Das LLM verwendet ihn, um eine umfassende Antwort zu generieren.

def build_context_package(vector_results: list, graph_contexts: dict, max_tokens: int = 3000) -> str:
    sections = []
    
    # Graph entity context section
    if graph_contexts:
        graph_section = ['## Entity Context from Knowledge Graph']
        for entity_name, context in graph_contexts.items():
            connections = context.get('connections', [])
            conn_summary = ', '.join([
                f"{c['connected_name']} ({c['rel_type']})"
                for c in connections[:5]
            ])
            graph_section.append(f'**{entity_name}**: connected to {conn_summary}')
        sections.append('\n'.join(graph_section))
    
    # Vector search results section
    if vector_results:
        vector_section = ['## Relevant Documents']
        for i, doc in enumerate(vector_results[:5]):
            text = doc.get('text', '')[:500]  # Truncate long docs
            vector_section.append(f'{i+1}. {text}')
        sections.append('\n'.join(vector_section))
    
    context_package = '\n\n'.join(sections)
    # Rough token estimate (1 token ~ 4 chars)
    if len(context_package) > max_tokens * 4:
        context_package = context_package[:max_tokens * 4]
    
    return context_package

if __name__ == '__main__':
    demo_vector = [{'text': 'Refunds are processed within 5 business days of approval.'}]
    demo_graph = {'Acme Corp': {'connections': [{'connected_name': 'Jane Doe', 'rel_type': 'employs'}]}}
    print(build_context_package(demo_vector, demo_graph))

Asynchrones paralleles Retrieval

Führen Sie Vektor- und Graph-Retrieval parallel mit asyncio.gather aus, um die Gesamtlatenz zu minimieren. Die Ergebnisse stehen gleichzeitig bereit.

import asyncio
from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(max_workers=4)

async def async_vector_search(query: str, vector_store) -> list:
    loop = asyncio.get_event_loop()
    return await loop.run_in_executor(executor, vector_store.search, query, 5)

async def async_graph_lookup(entities: list) -> dict:
    loop = asyncio.get_event_loop()
    results = {}
    for entity in entities:
        context = await loop.run_in_executor(executor, get_entity_context, entity)
        if context:
            results[entity] = context
    return results

async def hybrid_retrieval_async(query: str, entities: list, vector_store) -> dict:
    # Run vector search and graph lookup in parallel
    vector_task = async_vector_search(query, vector_store)
    graph_task = async_graph_lookup(entities)
    
    vector_results, graph_contexts = await asyncio.gather(vector_task, graph_task)
    
    return {
        'vector': vector_results,
        'graph': graph_contexts
    }

print('Async parallel retrieval functions defined')

Retrieval-Ergebnisse zwischenspeichern

Speichern Sie sowohl die Ergebnisse der Vektorsuche als auch Graphabfragen im Cache, um wiederholte API-Aufrufe zu vermeiden. Verwenden Sie eine kurze TTL (Minuten bis Stunden), da sich Wissensbasen langsam, aber nicht sofort ändern.

import hashlib
import time

class HybridRetrievalCache:
    def __init__(self, vector_ttl: int = 300, graph_ttl: int = 600):
        self.vector_cache = {}
        self.graph_cache = {}
        self.vector_ttl = vector_ttl
        self.graph_ttl = graph_ttl
    
    def _key(self, value: str) -> str:
        return hashlib.md5(value.encode()).hexdigest()[:12]
    
    def get_vector(self, query: str):
        k = self._key(query)
        entry = self.vector_cache.get(k)
        if entry and time.time() - entry['ts'] < self.vector_ttl:
            return entry['data']
        return None
    
    def set_vector(self, query: str, results: list):
        self.vector_cache[self._key(query)] = {'data': results, 'ts': time.time()}
    
    def get_graph(self, entity: str):
        k = self._key(entity)
        entry = self.graph_cache.get(k)
        if entry and time.time() - entry['ts'] < self.graph_ttl:
            return entry['data']
        return None
    
    def set_graph(self, entity: str, context: dict):
        self.graph_cache[self._key(entity)] = {'data': context, 'ts': time.time()}

cache = HybridRetrievalCache()
print('Hybrid retrieval cache initialized')

Retrieval-Gewichte auswählen

Stimmen Sie den Parameter alpha (Gewichtung von Vektor- gegenüber Graph-Retrieval) auf den Abfragetyp ab:

  • Fragen zum Nachschlagen von Fakten (Wer hat OpenAI gegründet?) → höheres Gewicht für den Graphen
  • Fragen zur semantischen Ähnlichkeit (Finden Sie Dokumente über KI-Sicherheit) → höheres Gewicht für den Vektor
  • Gemischte Fragen → ausgewogene Gewichtung (alpha=0.5)
def auto_tune_alpha(query: str) -> float:
    query_lower = query.lower()
    
    # High graph weight for relational questions
    relational_keywords = [
        'who', 'founded', 'works at', 'connected to',
        'related to', 'partner', 'owns', 'acquired'
    ]
    
    # High vector weight for content questions
    content_keywords = [
        'explain', 'describe', 'what is', 'how does',
        'tell me about', 'documents about', 'find information'
    ]
    
    relational_count = sum(1 for kw in relational_keywords if kw in query_lower)
    content_count = sum(1 for kw in content_keywords if kw in query_lower)
    
    if relational_count > content_count:
        return 0.3  # Graph-heavy
    elif content_count > relational_count:
        return 0.7  # Vector-heavy
    else:
        return 0.5  # Balanced

queries = [
    'Who founded Tesla?',
    'Explain transformer architecture',
    'What companies is Elon Musk connected to?'
]
for q in queries:
    print(f'alpha={auto_tune_alpha(q):.1f} for: {q}')

Wissenscheck: Hybrides Retrieval

Überprüfen Sie Ihr Verständnis der Kombination von Vektor- und Graph-Retrieval.

Zusammenfassung des hybriden Retrievals

Effektives hybrides Retrieval kombiniert: Vektorsuche für semantische Ähnlichkeit, Graphdurchlauf für relationalen Kontext, Entitätsextraktion zur Verankerung von Abfragen im Graphen, Fusionsstrategien (Verschachteln, Gewichtung, RRF) zum Zusammenführen von Ergebnissen und asynchrone parallele Ausführung zur Minimierung der Latenz. Das Ergebnis ist ein umfassenderer Kontext für Antworten eines LLM.

Häufig gestellte Fragen

Ist die Lektion „Vektor- und Graphen-Retrieval kombinieren“ kostenlos?

Ja — der vollständige Text von „Vektor- und Graphen-Retrieval kombinieren“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des AI Agents-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der AI Agents-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Vektor- und Graphen-Retrieval kombinieren“?

Hybrides Retrieval: Vektorähnlichkeit + Traversierung von Graphpfaden für reichhaltigeren Kontext. Du übst AI Agents mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um AI Agents zu starten?

Keine Vorkenntnisse erforderlich. AI Agents auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 3 von 4.

Wie lange dauert die Lektion „Vektor- und Graphen-Retrieval kombinieren“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser AI Agents-Lektion Code schreiben und ausführen?

Ja. Jede AI Agents-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Entitätsextraktion für Wissensgraphen
  2. Neo4j-Abfragen aus Agent-Tools
  3. Vektor- und Graphen-Retrieval kombinieren
  4. Einen wissensgestützten Agenten erstellen
← Zurück zu AI Agents