Files
Nexus.Reader/.agent/skills/km-rag-methodology/artifacts/deep-research-report-rag.md
T

41 KiB
Raw Blame History

Mapa Wiedzy i kontrola w RAG: jak wdrożyć „nowe podejście” w sposób inżynieryjny

Executive summary

Autor posta (entity["people","Vladimir Alekseichenko","dataworkshop ceo"], entity["organization","DataWorkshop","ml/ai training poland"]) kontrastuje „klasyczny” RAG oparty o mechaniczne chunkowanie i wektoryzację z podejściem, w którym buduje się Mapę Wiedzy: „graf z metadanymi, powiązaniami i odniesieniami do źródeł” (w kontekście praktyki na danych z entity["organization","Giełda Papierów Wartościowych w Warszawie","warsaw stock exchange"]). citeturn2view0turn2view1

W tym raporcie formalizuję tę ideę jako KnowledgeMap RAG (KMRAG): RAG, w którym warstwa „R” nie jest tylko wyszukiwaniem semantycznym po losowych fragmentach, ale kontrolowanym wyborem jednostek wiedzy (sekcja, tabela, rekord, definicja, reguła) powiązanych grafowo, z pełną proweniencją (skąd to jest), politykami dostępu, wersjonowaniem i testowalnością. To jest spójne z tezą autora, że „R w RAG” to przede wszystkim ryzyko: jeśli retrieval jest błędny, model będzie „pewnie” odpowiadał na podstawie złego kontekstu. citeturn2view0turn6view0

Ponieważ nie podałeś ograniczeń (skala, budżet, SLA/latencja), przyjmuję brak specyficznych constraintów i podaję warianty: od małych wdrożeń (Postgres/pgvector) po architektury wielotenancy (Qdrant/Pinecone/Weaviate) oraz hybrydy graf + wektory. citeturn12search2turn14search1turn14search16turn14search0turn14search2

Najważniejsze rekomendacje wdrożeniowe:

Po pierwsze, zastąp „losowe chunki” jednostkami sensu: segmentacją strukturalną (nagłówki/sekcje/tabele) i/lub semantyczną, z metadanymi i relacjami (poprzedni/następny, należy do sekcji, cytuje, definiuje). citeturn6view0turn11search1turn11search29

Po drugie, zbuduj Mapę Wiedzy jako graf (property graph) + indeksy (wektorowy i leksykalny/hybrydowy). Praktycznie: graf przechowuje relacje i proweniencję, a wektory dają tani „candidate generation”; dopiero potem używasz grafu do „dociągnięcia” brakujących kontekstów i do audytu. To jest zgodne z rodziną podejść GraphRAG (np. publikacja entity["company","Microsoft","tech company"] o GraphRAG: graf encji + „community summaries” dla lepszych odpowiedzi na pytania globalne). citeturn0search1turn3search4turn3search20

Po trzecie, „kontrola zamiast nadziei” oznacza: (a) mierniki retrieval i generation, (b) automatyczne testy regresji i audyt ścieżki źródeł, (c) monitoring i alerty driftu oraz incydentów bezpieczeństwa (prompt injection, data leakage). W praktyce: RAGAS/TruLens + OWASP LLM Top 10 jako checklisty, plus logowanie „trace” (kontekst → odpowiedź → cytowania). citeturn4search1turn4search2turn4search6turn4search13turn4search7

Definicja podejścia „Mapa Wiedzy zamiast losowych chunków”

W poście autor opisuje Mapę Wiedzy jako artefakt, który budujesz w 3 dni: „graf z metadanymi, powiązaniami i odniesieniami do źródeł” (wspomina też kontekst narzędziowy: repozytorium na entity["company","GitHub","code hosting platform"] i notatki w entity["company","Obsidian","note-taking app company"]). citeturn2view1

Jednocześnie w dłuższym materiale autor rozwija intuicję, dlaczego „chunking + vector DB” bywa drogą donikąd: mechaniczne cięcie rozrywa jednostki sensu (akapit, tabela), a model językowy zwykle nie weryfikuje kontekstu odpowiada w oparciu o to, co mu dostarczysz, nawet jeśli kontekst jest sprzeczny (stąd losowość i halucynacje). citeturn6view0turn7view1

Precyzyjna definicja operacyjna (KMRAG)

KnowledgeMap RAG (KMRAG) to architektura RAG, w której warstwa „R” jest realizowana przez:

Reprezentację wiedzy: dokumenty są przekształcane do zbioru jednostek wiedzy (Knowledge Units) o stabilnej proweniencji (ID, wersja, lokalizacja w źródle) i spójnej semantyce (sekcja definicji, tabela, rozdział, procedura), a nie losowych wycinków znaków. citeturn6view0turn11search9turn16search0

Mapę (graf) zależności: jednostki są węzłami grafu (np. DOCUMENT → SECTION → UNIT; ENTITY ↔ UNIT; UNIT ↔ UNIT przez „refers_to/next/derives_from”), a krawędzie niosą informację ułatwiającą retrieval i audyt (np. „to jest definicja terminu X”, „to jest wyjątek od reguły”). citeturn2view1turn10search3turn3search4

Polityki retrieval: zapytanie jest mapowane na intencję i encje, a retrieval wykonuje plan: generuje kandydatów (wektory/keyword/hybrid), następnie rozszerza kontekst grafowo (np. sekcja nadrzędna, definicje encji, powiązane tabele), na końcu dokonuje selekcji (rerank/pruning) i buduje kontekst z cytowaniami. citeturn12search3turn12search11turn10search6turn10search31

Kontrolę i audytowalność: system jest projektowany tak, aby można było odpowiedzieć na pytania: „Dlaczego ten fragment?”, „Czy użytkownik miał uprawnienia?”, „Jaka wersja źródła?”, „Czy odpowiedź jest ugruntowana (grounded) w kontekście?”. Autor wprost wiąże „mapę wiedzy” z uszczelnianiem rozwiązań, wymaganiami prawnymi/bezpieczeństwa oraz audytowalnością. citeturn7view1turn14search2

Dlaczego „losowe chunki” są słabą abstrakcją inżynieryjną

Mechaniczne chunkowanie jest często liczone w znakach/tokenach; nawet z overlapem rozrywa strukturę i wymusza „magiczne” heurystyki (większy chunk_size, więcej chunków w kontekście), które łatwo psują wcześniej działające przypadki i utrudniają stabilną ewaluację. citeturn6view0

Z perspektywy governance kluczowy problem jest też bezpieczeństwo: w jednym dokumencie mogą być fragmenty o różnych poziomach dostępu, więc „wrzucanie wszystkiego do jednego kontekstu” łamie zasady separacji i komplikuje zgodność (ten motyw pojawia się u autora wprost). citeturn7view1turn14search2

Architektura referencyjna i komponenty

Poniżej przedstawiam architekturę komponentową KMRAG, obejmującą: ingestion, mapę wiedzy, strategie segmentacji, embeddingi i wektory, retrievery i rerankery, prompt engineering i grounding, oraz kontrolę halucynacji i ewaluację.

Diagram architektury

flowchart LR
  subgraph Ingestion
    A[Źródła: PDF/HTML/DOCX/DB] --> B[Parsing + normalizacja]
    B --> C[Jednostki wiedzy: sekcje, tabele, rekordy]
    C --> D[Metadane: źródło, wersja, ACL, lokalizacja]
    C --> E[Ekstrakcja encji/relacji]
    E --> G[(Graf / Mapa Wiedzy)]
    C --> F[Embedding + indeks]
    F --> V[(Vector DB)]
  end

  subgraph QueryTime
    Q[Zapytanie użytkownika] --> R[Routing/intencja/encje]
    R --> V1[Candidate gen: vector/keyword/hybrid]
    V1 --> V
    V --> K[Top-K kandydatów]
    K --> G1[Graph expansion\n(definicje, zależności, sekcje)]
    G1 --> G
    G --> S[Context assembly + dedup + cytowania]
    S --> L[LLM generacja\n(z zasadą "answer from sources")]
    L --> O[Odpowiedź + cytowania + confidence]
  end

  subgraph Control
    O --> M[Logi/trace]
    M --> EV[Ewaluacja offline/online]
    M --> MON[Monitoring KPI + alerty]
  end

Model ten jest kompatybilny zarówno z „klasycznym RAG” w sensie pracy na wektorach (RAG w ujęciu Lewis et al. zakłada połączenie pamięci parametrycznej i nieparametrycznej poprzez retrieval z indeksu wektorowego), jak i z odmianami grafowymi (GraphRAG: budowa grafu encji i „community summaries” jako warstwa indeksu). citeturn0search2turn0search5turn0search1turn3search4

image_group{"layout":"carousel","aspect_ratio":"16:9","query":["GraphRAG architecture diagram","knowledge graph retrieval augmented generation diagram","vector database similarity search diagram","Neo4j graph visualization example"],"num_per_query":1}

Ingestion: parsowanie, normalizacja i jednostki wiedzy

W KMRAG ingestion nie kończy się na „wyciągnij tekst z PDF”. Kluczowe jest zachowanie/rekonstrukcja struktury: tytuły, listy, tabele, numer stron, sekcje. Biblioteka entity["company","Unstructured","document processing company"] wprost opisuje „partitioning” jako ekstrakcję ustrukturyzowanych elementów (Title/NarrativeText/ListItem itd.), aby móc decydować, co zachować. citeturn16search0turn16search8turn16search4

Jeśli pracujesz na bardzo różnych formatach lub potrzebujesz także metadanych i obsługi np. zaszyfrowanych PDF, narzędzia z ekosystemu entity["organization","Apache Software Foundation","open source foundation"] (Apache Tika) podkreślają możliwość parsowania PDF, w tym obsługi dokumentów szyfrowanych przy podaniu hasła. citeturn16search1turn16search30

Wniosek projektowy: „Jednostka wiedzy” w KMRAG to obiekt typu np.:

  • unit_type: section, definition, table, row, procedure_step, policy_rule
  • canonical_text (tekst do embeddingu i rerankingu)
  • rendered_context (tekst/fragment do wklejenia do prompta)
  • provenance: source_id, page, section_path, span_offsets
  • governance: acl_tags, pii_class, retention_class
  • links: prev/next, references, same_topic

Taki model danych bezpośrednio adresuje problem autora: model nie „weźmie odpowiedzialności” za konfliktujący kontekst, więc to system ma pilnować jakości kontekstu i jego zaufania. citeturn6view0turn7view1

Strategie segmentacji: od „chunków” do „węzłów” (Nodes)

Jeżeli musisz działać na tekście, i tak będziesz coś „dzielił” różnica polega na tym, czy są to losowe fragmenty znaków, czy węzły semantyczne.

  • W ekosystemie entity["company","LangChain","llm app framework company"] często proponuje się RecursiveCharacterTextSplitter jako „solidny default” dla wielu przypadków, ale to nadal jest heurystyka bazująca na znakach i separatorach. citeturn11search8turn11search0
  • entity["company","LlamaIndex","llm data framework company"] oferuje semantyczne parsowanie węzłów: SemanticSplitterNodeParser dzieli tekst na grupy zdań powiązane semantycznie (z użyciem embeddingów), a dokumentacja podkreśla, że to alternatywa dla stałego rozmiaru chunków. citeturn11search1turn11search9turn11search29

KMRAG traktuje segmentację jako element modelowania danych: węzły mają typ, hierarchię i relacje.

Embeddingi i Vector DB: candidate generation + filtrowanie po metadanych

Embeddingi są nadal bardzo użyteczne, ale w KMRAG pełnią rolę „szybkiego generatora kandydatów”, a nie „wyroczni”.

Otwartoźródłowo, entity["company","Hugging Face","ml model hub company"] utrzymuje Sentence Transformers, które dostarcza zarówno modele embeddingowe (bi-encoders), jak i rerankery (cross-encoders). citeturn12search38turn12search3

Warstwa metadanych jest w KMRAG krytyczna: np. do ograniczania domeny, wersji dokumentu, języka, daty wejścia w życie, uprawnień.

  • entity["company","Qdrant","vector database company"] opisuje payload/metadata i filtrowanie oraz zaleca indeksowanie pól payload dla efektywności filtrowania. citeturn11search2turn11search6turn11search37
  • entity["company","Pinecone","vector database company"] opisuje filtrowanie po metadanych oraz pokazuje wzorzec multitenancy przez namespaces. citeturn11search7turn14search16turn14search12
  • entity["company","Weaviate","vector database company"] opisuje hybrydę BM25F + wektory (fuzja wyników i wagi są konfigurowalne) oraz posiada natywną wielodzierżawność (tenant per request). citeturn12search0turn14search0
  • entity["company","Milvus","vector database project"] dokumentuje hybrydę sparse+dense i wskazuje scenariusze, w których połączenie poprawia wyniki (semantyka + dopasowanie słów kluczowych). citeturn12search1turn12search5

W KMRAG niemal zawsze warto rozważyć hybrid retrieval (dense + sparse), bo ogranicza „semantic drift” i poprawia precyzję przy terminach domenowych (np. numery, nazwy własne). Jest to wspólny wątek w dokumentacji Weaviate i Pinecone, opisującej fuzję wyników i podejścia do hybrydy. citeturn12search0turn11search3turn11search19

Retrievery, rerankery i kontrola halucynacji

KMRAG rozdziela retrieval na etapy:

Candidate generation (tani): dense retriever (np. dual-encoder) i/lub sparse (BM25). Klasyczna praca o dense retrieval (DPR) pokazuje dual-encoder jako praktyczny mechanizm retrieval i porównuje go do BM25 w QA. citeturn8search0turn8search4

Reranking (droższy): cross-encoder reranker znacząco poprawia ranking, ale jest kosztowny, bo ocenia pary (query, doc) wspólnie w modelu. Sentence Transformers opisuje retrieve&rerank pipeline oraz rolę CrossEncodera. citeturn12search11turn12search19

Graph expansion (precyzja i kompletność): graf dostarcza „brakujących mostów” (definicje, zależności, wyjątki, kontekst sekcji) oraz daje audyt to jest sedno „Mapy Wiedzy”. W wariantach GraphRAG (Microsoft) graf jest budowany z encji i relacji, a następnie grupowany w społeczności i streszczany, co poprawia odpowiedzi na pytania „globalne” (np. „jakie są główne tematy w korpusie?”), gdzie naiwny RAG zawodzi. citeturn0search1turn0search13turn3search4turn3search20

Halucynacje i „kontrola”: literatura proponuje pętle weryfikacji (np. ChainofVerification: draft → pytania weryfikacyjne → niezależne odpowiedzi → final) i mechanizmy samorefleksji (SelfRAG) oraz korekty retrieval (CRAG). Są to techniki „kontroli” na poziomie architektury, a nie tylko promptu. citeturn8search3turn9search1turn9search2

Opcje projektowe i tradeoffy

Porównanie: klasyczny RAG vs KMRAG

Wymiar Klasyczny „chunk + vector DB” KMRAG (Mapa Wiedzy) Konsekwencja praktyczna
Jednostka indeksowania fragment znaków/tokenów jednostka sensu: sekcja/tabela/rekord + typ mniej „urwanych” kontekstów, mniej przypadkowości
Reprezentacja embedding + (czasem) metadata embedding + metadata + graf relacji + proweniencja lepsza ścieżka audytu i „dlaczego to”
Retrieval topk similarity plan retrieval: hybrid + graf expansion + rerank wyższa precyzja i odporność na trudne pytania
Zmiany w danych częsty reindex, ryzyko regresji wersjonowanie, testy regresji per typ jednostki stabilniejsze wdrożenia i migracje
Bezpieczeństwo/ACL łatwo mieszać fragmenty o różnych uprawnieniach ACL na poziomie jednostki i ścieżki grafu mniejsze ryzyko wycieku kontekstu
Debuggowanie „dlaczego takie chunki?” „jaki węzeł, z jakiego źródła, jaka relacja?” szybsze RCA i audyt

Uzasadnienie co do problemów chunkingu i „model ufa kontekstowi” pochodzi z materiału autora; definicja Mapy Wiedzy jako grafu z metadanymi i odniesieniami jest wprost w poście. citeturn6view0turn2view1turn7view1

Wybory technologiczne: wektory, graf, hybryda

Poniżej pokazuję typowe opcje i kompromisy (bez narzuconych constraintów dobór zależy od QPS, wolumenu i wymagań bezpieczeństwa).

Vector store

  • Qdrant: mocne filtrowanie payload + mechanizmy multitenancy (w tym „tiered multitenancy”). citeturn11search6turn14search1turn14search18
  • Pinecone: proste multitenancy przez namespaces; dobrze opisane podejścia do hybrid search (single hybrid index vs osobne indeksy, z plusami i minusami). citeturn14search16turn11search3
  • Weaviate: wbudowany hybrid BM25F + wektor, oraz multitenancy z tenantem w operacjach. citeturn12search0turn14search0
  • Milvus: rozbudowane podejścia do sparse+dense i multivector, z dokumentacją dla hybrydy. citeturn12search1turn12search5turn12search33
  • pgvector: dobre, gdy chcesz „mniej systemów” i akceptujesz kompromisy wydajności; repo dokumentuje różnice IVFFlat vs HNSW (build time/memory vs speedrecall). citeturn12search2turn12search14
  • Elasticsearch: istotny, gdy potrzebujesz „enterprise security” (RBAC, field/documentlevel security) i hybrydowego wyszukiwania w jednej platformie. citeturn14search2turn14search15

Graph / Knowledge Map store

  • Neo4j: bogate wzorce GraphRAG (graph traversal, fulltext, vector, text2cypher). Neo4j publikuje GraphRAG field guide i pakiet GraphRAG dla Pythona. citeturn10search18turn10search14turn10search31turn10search2
  • Microsoft GraphRAG: gotowy pipeline budowy grafowego indeksu (encje → społeczności → streszczenia), opensource na GitHubie + dokumentacja „Getting started”. citeturn3search0turn3search31turn3search20turn0search1
  • LlamaIndex KnowledgeGraphIndex: praktyczna automatyzacja budowy grafu z tekstu i query po encjach. citeturn10search3turn10search11

Kompromisy

  • Skalowalność: graf może zmniejszać liczbę „strzałów” w LLM (np. przez prestreszczenia społeczności w GraphRAG) kosztem cięższego ingestion i większej złożoności danych. citeturn0search1turn3search4
  • Latencja: rerankery crossencoder podnoszą jakość, ale zwiększają czas (N par do oceny); dlatego standardem jest retrieval → rerank topN, nie rerank całego korpusu. citeturn12search11turn12search19
  • Koszt: hybryda i graf często zwiększają koszt ingest (LLM do ekstrakcji encji/relacji), ale zmniejszają koszt „ratowania” jakości w runtime przez kolejne heurystyki. To jest w duchu argumentu autora o „dokładaniu miniklocków” versus poprawa fundamentu. citeturn6view0turn7view1
  • Maintainability: mniej „magicznych” parametrów chunk_size; więcej jawnych typów jednostek i testów per typ. citeturn7view1turn13search3
  • Security/data governance: najlepiej wspierać permissionaware retrieval już w retrieverze (prefilter), bo wtedy model nie ma czego „wyciec”. Dokumentacja Elastic i wektor DB pokazuje mechanizmy RBAC/DLS, namespaces/tenants i filtrowanie po metadanych. citeturn14search2turn14search16turn14search0turn11search6

Migracja z klasycznego RAG do KMRAG

Migracja jest łatwiejsza, jeśli potraktujesz ją jak refactoring warstwy danych i retrieval, a nie „przepisanie wszystkiego od zera”.

Ścieżka migracji krok po kroku

Krok pierwszy: ustal bazową prawdę (baseline) i testy.
Bez ewaluacji będziesz „liczyć na cud” wprost przeciwieństwo postulatu „kontrola zamiast nadziei”. Zacznij od małego zestawu pytań i oczekiwań (golden set) oraz logowania kontekstu i odpowiedzi. W praktyce możesz użyć RAGAS (metryki retrieval i faithfulness bez konieczności pełnych anotacji) oraz TruLens (RAG triad: context relevance, groundedness, answer relevance). citeturn4search1turn4search2turn4search6

Krok drugi: dołóż metadane i proweniencję zanim dołożysz graf.
W klasycznym RAG często brakuje stabilnych ID i lokalizacji w źródle; tymczasem autor wiąże mapę wiedzy z odniesieniami do źródeł. Minimalny zestaw to: source_id, version, page/section, timestamp, acl_tags. Mechanizmy filtrowania po metadanych są standardem m.in. w Pinecone i Qdrant. citeturn2view1turn11search7turn11search6

Krok trzeci: zamień chunki na węzły o typach i relacjach.
Zamiast „1000 znaków”, twórz: SectionNode, TableNode, DefinitionNode, PolicyNode. Jeśli nie możesz od razu, przejdź etapowo przez semantyczne node parsers (LlamaIndex) lub segmentację po strukturze dokumentu (partitioning). citeturn11search9turn16search0turn11search1

Krok czwarty: zbuduj Mapę Wiedzy (graf) i zacznij od najtańszego użycia w runtime.
Nie musisz od razu robić pełnego „GraphRAG global”. Najpierw używaj grafu do: (a) definicji i wyjątków, (b) dołączania kontekstu „nadrzędna sekcja” / „poprzedninastępny”, (c) audytu ścieżki cytowań. Dopiero potem dokładaj stricte grafowe retrievery. citeturn10search6turn10search31turn3search4

Krok piąty: wprowadź gating i rollout.
Zgodnie z najlepszymi praktykami ewaluacji: iteruj, porównuj wersje, ustaw continuous evaluation i progi akceptacji. citeturn13search3turn13search35

Proponowana sekwencja wdrożenia

Faza Co dostarczasz Typowy czas (brak constraintów) Kryterium „done”
Audit RAG logi + golden set + baseline metryk 12 tyg. masz mierzalne recall/faithfulness + top failure modes
Metadata-first proweniencja + filtry + ACL 12 tyg. brak „orphan” chunków bez źródła; prefiltrowanie działa
Nodes & map węzły typowane + relacje 24 tyg. stable IDs, relacje prev/next/contains/refers_to
Hybrid + rerank dense+sparse + rerank topN 13 tyg. poprawa metryk retrieval bez wzrostu halucynacji
Graph expansion dołączanie kontekstu grafem 24 tyg. poprawa trudnych pytań „łączących fakty”
Produkcja monitoring KPI + procedury incydentów ciągłe CE + alerty + playbook audytu

Metryki i praktykę continuous evaluation wspiera dokumentacja OpenAI (zalecenia dot. progów context recall/precision i pipelineu ewaluacji), co jest spójne z „kontrolą” jako procesem, nie jednorazową konfiguracją. citeturn13search3turn13search27

Implementacje przykładowe

Poniższe implementacje są „szkieletami” (reference implementations). W obu wariantach zakładam brak narzuconych wymagań co do skali, więc pokazuję rozwiązania, które da się skalować horyzontalnie (wektor DB) i/lub uprościć (pgvector zamiast osobnej bazy).

Stack A: opensource embeddings + opensource Vector DB (Sentence Transformers + Qdrant) + graf w Neo4j

Kiedy wybrać: gdy chcesz uniezależnić embeddingi od dostawcy, mieć pełną kontrolę nad danymi i implementować multitenancy/filtry wprost w wektor DB. Payload/filtry i multitenancy są natywnie wspierane w Qdrant. citeturn11search6turn14search1turn14search7

Zależności (przykład): sentence-transformers, qdrant-client, neo4j, parser dokumentów (unstructured lub Tika).

# --- Ingestion: parse -> units -> embeddings -> Qdrant + graph ---
from dataclasses import dataclass
from typing import Iterable, Optional
import hashlib
import time

from sentence_transformers import SentenceTransformer, CrossEncoder
from qdrant_client import QdrantClient, models as qmodels
from neo4j import GraphDatabase

@dataclass
class KnowledgeUnit:
    unit_id: str
    source_id: str
    version: str
    unit_type: str              # e.g. "section", "definition", "table"
    text: str                   # canonical text for embedding
    page: Optional[int] = None
    section_path: Optional[str] = None
    acl: str = "public"         # e.g. role/tenant tag

def stable_id(source_id: str, version: str, unit_type: str, page: str, text: str) -> str:
    raw = f"{source_id}|{version}|{unit_type}|{page}|{text}".encode("utf-8")
    return hashlib.sha256(raw).hexdigest()[:24]

# 1) Embeddings (bi-encoder) + reranker (cross-encoder)
embed_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")  # example
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")              # example

# 2) Vector DB: Qdrant
qdrant = QdrantClient(url="http://localhost:6333", timeout=30)
COLLECTION = "kmrag_units"

DIM = embed_model.get_sentence_embedding_dimension()
qdrant.recreate_collection(
    collection_name=COLLECTION,
    vectors_config=qmodels.VectorParams(size=DIM, distance=qmodels.Distance.COSINE),
)

# Index payload fields frequently used in filters (performance)
qdrant.create_payload_index(
    collection_name=COLLECTION,
    field_name="acl",
    field_schema=qmodels.PayloadSchemaType.KEYWORD,
)
qdrant.create_payload_index(
    collection_name=COLLECTION,
    field_name="source_id",
    field_schema=qmodels.PayloadSchemaType.KEYWORD,
)

# 3) Graph DB: Neo4j (property graph)
neo4j_driver = GraphDatabase.driver(
    "neo4j://localhost:7687", auth=("neo4j", "password")
)

def upsert_units(units: Iterable[KnowledgeUnit]) -> None:
    batch = list(units)
    # embeddings
    vectors = embed_model.encode([u.text for u in batch], normalize_embeddings=True)

    # upsert into Qdrant with payload metadata (provenance + ACL)
    qdrant.upsert(
        collection_name=COLLECTION,
        points=[
            qmodels.PointStruct(
                id=u.unit_id,
                vector=vectors[i].tolist(),
                payload={
                    "source_id": u.source_id,
                    "version": u.version,
                    "unit_type": u.unit_type,
                    "page": u.page,
                    "section_path": u.section_path,
                    "acl": u.acl,
                    "ingested_at": int(time.time()),
                },
            )
            for i, u in enumerate(batch)
        ],
    )

    # build/update graph nodes + relationships
    cypher = """
    UNWIND $rows AS r
    MERGE (d:Document {source_id: r.source_id, version: r.version})
    MERGE (u:Unit {unit_id: r.unit_id})
      SET u.unit_type = r.unit_type,
          u.page = r.page,
          u.section_path = r.section_path
    MERGE (d)-[:HAS_UNIT]->(u)
    """
    with neo4j_driver.session() as s:
        s.run(cypher, rows=[u.__dict__ for u in batch])

# --- Query-time retrieval: vector -> graph expansion -> rerank -> context ---
def retrieve(query: str, acl: str, top_k: int = 30, rerank_k: int = 8):
    qvec = embed_model.encode([query], normalize_embeddings=True)[0].tolist()

    # 1) Candidate generation with metadata filter (permission-aware)
    hits = qdrant.search(
        collection_name=COLLECTION,
        query_vector=qvec,
        limit=top_k,
        query_filter=qmodels.Filter(
            must=[qmodels.FieldCondition(key="acl", match=qmodels.MatchValue(value=acl))]
        ),
    )
    candidate_ids = [h.id for h in hits]

    # 2) Graph expansion: pull neighbor units from same document/section (simple example)
    expand_cypher = """
    MATCH (u:Unit) WHERE u.unit_id IN $ids
    OPTIONAL MATCH (d:Document)-[:HAS_UNIT]->(u)
    OPTIONAL MATCH (d)-[:HAS_UNIT]->(u2:Unit)
    WHERE u2.section_path = u.section_path
    RETURN DISTINCT u2.unit_id AS unit_id
    LIMIT 200
    """
    with neo4j_driver.session() as s:
        rows = s.run(expand_cypher, ids=candidate_ids).data()
    expanded_ids = list({r["unit_id"] for r in rows}) or candidate_ids

    # 3) Fetch texts for reranking (here: from Qdrant payload 'text' not stored; you'd load from your storage)
    # In production: keep canonical text in your doc store; Qdrant payload keeps provenance only.
    # For demo: assume we can map id->text elsewhere:
    id_to_text = load_texts(expanded_ids)  # implement in your system

    pairs = [(query, id_to_text[i]) for i in expanded_ids]
    scores = reranker.predict(pairs)
    ranked = sorted(zip(expanded_ids, scores), key=lambda x: x[1], reverse=True)[:rerank_k]

    return ranked  # list of (unit_id, score) + you can also return provenance from payload

def load_texts(unit_ids):
    # Placeholder: pull canonical text from your document store / data lake
    raise NotImplementedError

Co w tym szkielecie jest „Mapą Wiedzy”: Neo4j przechowuje relacje (Document→Unit, a dalej możesz dodać Entity↔Unit, REFERENCES, NEXT), a Qdrant przechowuje wektory + payload do filtrowania; filtrowanie i indeksowanie payload jest sformalizowane w dokumentacji Qdrant. citeturn11search6turn11search2turn14search7

Rerank to klasyczny krok „retrievethenrerank” opisywany przez Sentence Transformers, gdzie CrossEncoder podnosi jakość finalnych wyników kosztem obliczeń. citeturn12search11turn12search19

Stack B: managed LLM + Vector DB (OpenAI + Pinecone) + graf (Neo4j GraphRAG / Text2Cypher)

Kiedy wybrać: gdy zależy Ci na szybkości iteracji, jakości modeli oraz gotowych mechanizmach „structured output”, a retrieval chcesz oprzeć o managed vector DB z namespaces i hybrid search. citeturn13search1turn14search16turn11search3

W wariancie managed sensownie jest też wykorzystać Structured Outputs do wymuszenia formatu odpowiedzi (np. answer + citations[]), co jest elementem „kontroli” i audytu. OpenAI opisuje Structured Outputs jako mechanizm gwarantujący zgodność odpowiedzi z JSON Schema. citeturn13search1turn13search8

# --- Managed stack: OpenAI embeddings + Pinecone + structured outputs + graph retrieval ---
from openai import OpenAI
from pinecone import Pinecone
from neo4j_graphrag import GraphRAG  # example usage; adjust to actual package API

OPENAI_MODEL_EMB = "text-embedding-3-large"
OPENAI_MODEL_GEN = "gpt-5.4-mini"   # example; choose by latency/cost needs

client = OpenAI()
pc = Pinecone(api_key="PINECONE_API_KEY")
index = pc.Index("kmrag")

def embed(texts):
    resp = client.embeddings.create(model=OPENAI_MODEL_EMB, input=texts)
    return [d.embedding for d in resp.data]

def upsert_to_pinecone(units, namespace):
    vecs = embed([u["text"] for u in units])
    index.upsert(
        vectors=[
            (u["unit_id"], vecs[i], {
                "source_id": u["source_id"],
                "version": u["version"],
                "unit_type": u["unit_type"],
                "page": u.get("page"),
                "section_path": u.get("section_path"),
                "acl": u.get("acl"),
            })
            for i, u in enumerate(units)
        ],
        namespace=namespace,   # multitenancy / workspace isolation
    )

def retrieve_candidates(query, namespace, acl, top_k=30):
    qvec = embed([query])[0]
    res = index.query(
        vector=qvec,
        top_k=top_k,
        include_metadata=True,
        namespace=namespace,
        filter={"acl": {"$eq": acl}},
    )
    return res["matches"]

# Optional: graph retrieval pattern via Text2Cypher (Neo4j GraphRAG package)
# The idea: use graph schema + question -> generated Cypher -> execute -> return records as extra grounded context.
gr = GraphRAG(neo4j_uri="neo4j+s://...", user="neo4j", password="...")

ANSWER_SCHEMA = {
  "name": "kmrag_answer",
  "schema": {
    "type": "object",
    "properties": {
      "answer": {"type": "string"},
      "citations": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "unit_id": {"type": "string"},
            "source_id": {"type": "string"},
            "quote": {"type": "string"}
          },
          "required": ["unit_id", "source_id"]
        }
      },
      "confidence": {"type": "number", "minimum": 0, "maximum": 1}
    },
    "required": ["answer", "citations", "confidence"]
  }
}

def answer(query, namespace, acl):
    hits = retrieve_candidates(query, namespace, acl)
    text_context = "\n\n".join(
        f"[{m['id']}] ({m['metadata'].get('source_id')}) {load_unit_text(m['id'])}"
        for m in hits[:8]
    )

    graph_context = gr.text2cypher_retrieve(query)  # e.g. definitions, relationships

    system = (
        "Odpowiadasz wyłącznie na podstawie kontekstu i grafu.\n"
        "Jeśli brakuje danych, powiedz wprost, czego nie wiesz.\n"
        "Zwróć cytowania do unit_id/source_id."
    )

    resp = client.responses.create(
        model=OPENAI_MODEL_GEN,
        input=[
            {"role": "system", "content": system},
            {"role": "user", "content": f"Pytanie: {query}\n\nKontekst:\n{text_context}\n\nGraf:\n{graph_context}"}
        ],
        text={
            "format": {
                "type": "json_schema",
                "json_schema": {**ANSWER_SCHEMA, "strict": True}
            }
        }
    )
    return resp.output_text

def load_unit_text(unit_id):
    # fetch canonical unit text from your storage
    raise NotImplementedError

Źródła dla tego stosu: OpenAI opisuje nowe modele embeddingowe (text-embedding-3-small/large) i guide embeddings, a także Structured Outputs i evaluation best practices. citeturn13search2turn13search1turn13search3turn13search9
Pinecone opisuje hybrydę oraz filtrowanie po metadanych i multitenancy przez namespaces. citeturn11search3turn11search7turn14search16
Wzorzec Text2Cypher tłumaczenie pytania + schematu grafu na Cypher i wykonanie query jest opisany w materiałach Neo4j. citeturn10search2turn10search6turn10search10

Kontrola jakości, audyt i monitoring

„Kontrola zamiast nadziei” warto potraktować jako trzy warstwy: (A) kontrola danych i retrieval, (B) kontrola generacji, (C) kontrola procesu (ewaluacja i monitoring).

Metryki i ewaluacja

Ewaluacja retrieval (czyt. „czy przynosimy właściwy kontekst”)

  • Recall@K / Precision@K / MRR / NDCG: standardowe metryki IR; w pracach o retrieval z grafami i/lub KG są one explicite używane do oceny retrieval (np. praca o RAG+KG dla customer service raportuje MRR/Recall@K/NDCG@K). citeturn10search1turn10search5
  • Offline test set buduj iteracyjnie na podstawie prawdziwych porażek (failure traces) to jest zgodne z podejściem „evaluation flywheel” i continuous evaluation. citeturn13search35turn13search3

Ewaluacja generation (czyt. „czy odpowiedź jest ugruntowana w źródłach”)

  • RAGAS: framework do „referencefree evaluation” RAG, mierzący różne wymiary retrieval i generation. citeturn4search1turn4search5
  • TruLens: RAG triad context relevance, groundedness, answer relevance jako praktyczny zestaw ocen dla halucynacji. citeturn4search2turn4search6

Progi jakości (przykład)
OpenAI w evaluation best practices podaje przykładowe targety (np. context recall ≥ 0.85, context precision > 0.7) jako część praktyk ewaluacji i porównywania wersji. Traktuj to jako punkt startowy, nie prawo natury. citeturn13search3

Checklist audytu KMRAG

Dane i ingestion

  • Czy parser zachowuje strukturę (sekcje/tabele/numery stron) i czy masz testy parsera na „trudnych dokumentach” (tabele, wielokolumnowe layouty)? citeturn16search0turn16search10turn6view0
  • Czy każda jednostka wiedzy ma stabilne source_id, version, lokalizację i politykę retencji/PII? citeturn7view1turn14search2

Mapa Wiedzy

  • Czy graf ma jasno zdefiniowane typy węzłów i relacje (HAS_UNIT, DEFINES, EXCEPTION_OF, REFERENCES, NEXT), oraz czy masz reguły walidacji (np. brak cykli w „NEXT”, spójność sekcji)? citeturn2view1turn10search31
  • Czy ekstrakcja encji/relacji jest mierzalna (precision/recall) i odporna na duplikaty/rozbieżności nazw? (w praktyce: canonicalization + entity resolution). Koncepcja grafu encji jako indeksu jest centralna w GraphRAG. citeturn0search1turn0search13

Retrieval

  • Czy stosujesz prefilter po ACL/tenant (permission-aware retrieval), zanim cokolwiek trafi do prompta? (mechanizmy namespaces/tenants i DLS/RBAC istnieją w narzędziach retrieval). citeturn14search16turn14search0turn14search2
  • Czy masz hybrydę dense+sparse tam, gdzie słowa kluczowe są krytyczne (regulacje, numery, tickery)? Pinecone i Weaviate opisują hybrydę jako fuzję wyników. citeturn11search3turn12search0
  • Czy reranking działa na topN, a nie na setkach wyników (koszt/latencja), i czy jest mierzony? citeturn12search11turn12search19

Generacja i grounding

  • Czy model ma jasną instrukcję „answer from sources” oraz czy odpowiedź wymusza strukturę (JSON schema) i cytowania? Structured Outputs jest mechanizmem wspierającym niezawodność formatu. citeturn13search1turn13search8
  • Czy masz mechanizm „I dont know / insufficient evidence” zamiast konfabulacji (np. minimalny próg evidence coverage)? Podejścia typu CoVe/SelfRAG/CRAG pokazują, że pętle weryfikacji i korekty podnoszą factuality. citeturn8search3turn9search1turn9search2

Bezpieczeństwo

  • Czy testujesz prompt injection na poziomie aplikacji, nie tylko promptu? OWASP opisuje prompt injection jako manipulację zachowaniem modelu przez wejście, a cheat sheet sugeruje praktyki obrony. citeturn4search3turn4search7turn4search13
  • Czy masz kontrolę kosztu (rate limits, timeouts, budżety tokenów) to też „kontrola”, bo DoS na LLM to realny wektor ryzyka (OWASP LLM Top 10 zawiera kategorie dot. DoS i supply chain). citeturn4search13turn13search12

KPI i monitoring w produkcji

Rekomendowany zestaw KPI (z podziałem na warstwy):

Retrieval KPI

  • Context Recall@K / Context Precision@K (offline i online na próbie logów). citeturn13search3turn4search1
  • % zapytań, w których retrieval zwraca „pustkę” lub tylko niskie score (sugeruje routing lub brak danych).

Generation KPI

  • Faithfulness/groundedness (TruLens/RAGAS). citeturn4search1turn4search6
  • Citation coverage: % zdań mających przypisane źródło, oraz „citation accuracy” (czy cytat faktycznie zawiera wspierający fragment). SelfRAG raportuje poprawę citation accuracy w długich generacjach jako jeden z efektów frameworku. citeturn9search1turn9search9

Ops KPI

  • Latencja p95/p99 per etap (retrieval, rerank, LLM).
  • Koszt per zapytanie (tokeny, liczba wywołań modeli) + alerty „unbounded consumption”. OpenAI publikuje production best practices i evaluation tooling jako część przejścia prototyp → produkcja. citeturn13search12turn13search3

Narzędzia do obserwowalności

  • RAGAS opisuje łączenie ewaluacji z tracingiem/analizą (np. Phoenix). citeturn4search34
  • TruLens ma integracje i dokumentację quickstart dla trace + feedback. citeturn4search2turn4search27
  • Jeśli używasz OpenAI, masz też guidance dot. ewaluacji i ciągłego monitorowania regresji. citeturn13search3turn13search6

Typowe failure modes KMRAG i mitigacje

„Graf rośnie w chaos” (sprawl, duplikaty encji, zła kanonikalizacja).
Mitigacja: wprowadź entity resolution, reguły normalizacji nazw, walidację schematu grafu i testy na podzbiorze; zacznij od grafu dokumentsekcjaunit, dopiero potem dodawaj encje/relacje automatyczne. GraphRAG wprost zaczyna od grafu encji jako indeksu, ale też pipelineu budowy i transformacji danych, co sugeruje konieczność procesu, nie jednorazowego prompta. citeturn3search0turn0search1

„Retrieval jest poprawny semantycznie, ale zły merytorycznie” (conflicts).
Mitigacja: hybryda dense+sparse + rerank + kontrola jakości źródeł + mechanizmy korekty (CRAG: evaluator jakości retrieval i akcje naprawcze). citeturn9search2turn11search3turn12search0

„Źródła przenoszą instrukcje (prompt injection z dokumentów)”
Mitigacja: separacja „instructions vs data”, sanitation, polityki „nie wykonuj instrukcji z kontekstu”, oraz przede wszystkim permission-aware retrieval (prefilter). OWASP opisuje prompt injection i praktyki obrony. citeturn4search3turn4search7turn14search2

„Latency/cost eksploduje przez reranking i graf”
Mitigacja: ogranicz N rerankowanych kandydatów; cache embeddingów; cache wyników graf expansion; prestreszczenia (GraphRAG community summaries) dla klas pytań globalnych. citeturn12search11turn0search1turn3search4

„Zgodność i audyt”
Mitigacja: loguj trace: query → (filtry ACL) → dokumenty → fragmenty → odpowiedź; uzupełnij o standardy zarządzania ryzykiem i bezpieczeństwem (entity["organization","NIST","us standards institute"] AI RMF; entity["organization","ISO","international standards body"]/IEC 27001; entity["organization","OWASP","security foundation"] LLM Top 10). Zapewnia to język kontroli dla audytu, nawet jeśli implementacje są różne. citeturn15search1turn15search2turn4search13turn15search3

Źródła priorytetowe do dalszej pracy

Najbardziej „nośne” (loadbearing) źródła do wdrożenia KMRAG, w kolejności praktycznej użyteczności:

Źródła autora: definicja Mapy Wiedzy (graf + metadane + odniesienia) oraz argument o „R jako ryzyku” i potrzebie kontroli retrieval. citeturn2view1turn7view1

Podstawy RAG: praca Lewis et al. (RAG jako retrieval + generacja z nieparametrycznej pamięci) jako fundament terminologiczny. citeturn0search2turn0search5

GraphRAG: publikacja i repozytorium Microsoft (graf encji, społeczności, streszczenia) jako referencyjny wariant Mapy Wiedzy w postaci pipelineu. citeturn0search1turn3search0turn3search4turn3search20

KGRAG / hybrydy: prace o łączeniu KG i RAG (np. HybridRAG; RAG+KG w customer service) pokazują, że graf zmniejsza skutki segmentacji i poprawia retrieval. citeturn10search0turn10search1

Ewaluacja i kontrola jakości: RAGAS + TruLens + best practices ewaluacji jako praktyczny „system kontroli”. citeturn4search1turn4search2turn13search3

Bezpieczeństwo: OWASP prompt injection i LLM Top 10 jako checklisty dla warstwy „R” i integracji z danymi. citeturn4search3turn4search13turn4search7