惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

有赞技术团队
有赞技术团队
量子位
B
Blog RSS Feed
Schneier on Security
Schneier on Security
L
LINUX DO - 最新话题
博客园 - 三生石上(FineUI控件)
Recent Announcements
Recent Announcements
Hacker News: Ask HN
Hacker News: Ask HN
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google DeepMind News
Google DeepMind News
N
News | PayPal Newsroom
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
大猫的无限游戏
大猫的无限游戏
PCI Perspectives
PCI Perspectives
aimingoo的专栏
aimingoo的专栏
D
Docker
T
The Exploit Database - CXSecurity.com
Last Week in AI
Last Week in AI
W
WeLiveSecurity
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
Vercel News
Vercel News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
J
Java Code Geeks
O
OpenAI News
C
Cisco Blogs
Hacker News - Newest:
Hacker News - Newest: "LLM"
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Threat Research - Cisco Blogs
Cisco Talos Blog
Cisco Talos Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Help Net Security
Help Net Security
Scott Helme
Scott Helme
The Hacker News
The Hacker News
Y
Y Combinator Blog
A
Arctic Wolf
V
V2EX
P
Proofpoint News Feed
Simon Willison's Weblog
Simon Willison's Weblog
A
About on SuperTechFans
S
Securelist
G
Google Developers Blog
Cyberwarzone
Cyberwarzone
The GitHub Blog
The GitHub Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
RAG Series (16): Graph RAG — Using Knowledge Graphs to Solve Multi-Hop Reasoning
WonderLab · 2026-05-15 · via DEV Community

The Relationship Blindspot in Vector Retrieval

Every optimization in this series so far — better chunking, reranking, query rewriting, CRAG — works within a fundamental assumption: retrieval is about finding similar text. But a whole category of questions doesn't fit that assumption:

Questions that require reasoning across multiple entities.

Consider:

"bge-large-zh-v1.5 and bge-reranker-v2-m3 both come from the same organization. What is that organization, and what role does each model play in a RAG pipeline?"

Vector search will find documents mentioning BAAI or BGE — that part works. But retrieval only returns text chunks. The LLM then has to figure out "these two models are both from BAAI" by reading disconnected paragraphs. The relationship isn't in the retrieval result; it's implicit in the text.

Or consider:

"Trace the evolution of retrieval quality evaluation from basic RAG to CRAG."

This requires placing RAG → Rerank → Self-RAG → CRAG in order along a progression chain. Vector search returns the top-4 most similar documents — not necessarily the nodes on that chain in the right sequence.

Graph RAG's insight: instead of hoping that semantic similarity happens to capture the right relationships, extract the entities and relationships explicitly, build a knowledge graph, and traverse it at query time. Finding "what connects X to Y" becomes a graph traversal problem, not a similarity problem.


Knowledge Graph Fundamentals

The atomic unit of a knowledge graph is the triple:

(head entity, relation, tail entity)

Enter fullscreen mode Exit fullscreen mode

For example:

BAAI           --[developed]-->  bge-large-zh-v1.5
BAAI           --[developed]-->  bge-reranker-v2-m3
bge-large-zh-v1.5  --[used for]--> vector retrieval
bge-reranker-v2-m3 --[used for]--> reranking

Enter fullscreen mode Exit fullscreen mode

Assembled into a directed graph (NetworkX DiGraph), the question "what models did BAAI develop?" becomes: find the BAAI node, list all outgoing edges. No semantic similarity needed — pure structure.


The Graph RAG Pipeline

Build phase (offline):
  documents → LLM triple extraction → NetworkX directed graph
  documents → embedding → ChromaDB vector index (baseline)

Query phase (online):
  question
    ↓
  LLM extracts question entities (e.g., BAAI, bge-large-zh-v1.5)
    ↓
  Fuzzy-match entities against graph nodes (substring match)
    ↓
  BFS 2-hop traversal: seed_nodes → neighbors → neighbors' neighbors
    ↓
  Assemble triple context (graph traversal result + top-2 vector docs as fallback)
    ↓
  LLM generates answer

Enter fullscreen mode Exit fullscreen mode

A critical design decision: Graph RAG is not pure graph retrieval. It's a hybrid of graph traversal context + vector retrieval. Pure graph traversal has a hard "entity boundary" — if a relevant entity wasn't extracted into the graph, it's unreachable. The top-2 vector documents serve as a safety net for those gaps.


Implementation: Triple Extraction

The Dead End First

LangChain ships LLMGraphTransformer, purpose-built for extracting graph structure from text. I started there:

from langchain_experimental.graph_transformers import LLMGraphTransformer
graph_transformer = LLMGraphTransformer(llm=llm)
graph_docs = graph_transformer.convert_to_graph_documents([doc])

Enter fullscreen mode Exit fullscreen mode

All 12 documents failed:

Invalid JSON: invalid number at line 1 column 2
[type=json_invalid, input_value='- Node: RAG (Retrieval-A...']

Enter fullscreen mode Exit fullscreen mode

LLMGraphTransformer requires the LLM to return structured JSON. GLM-4-flash was returning text-formatted lists (- Node: xxx). Pydantic validation failed every time.

Custom Prompt with a Simpler Format

The fix: stop requiring JSON. Use a pipe-delimited format instead — Entity A | relation | Entity B, one triple per line, parsed with split("|"). No JSON parsing, no Pydantic validation, high fault tolerance:

TRIPLE_EXTRACT_PROMPT = ChatPromptTemplate.from_messages([
    ("system",
     "Extract entities and relations from the text below. Output a list of triples.\n"
     "Required format: one triple per line, strictly: Entity A | relation | Entity B\n"
     "Rules:\n"
     "- Entities: noun phrases, no brackets or quotes\n"
     "- Relations: verb phrases, e.g., uses, contains, proposed by, applies to, outperforms\n"
     "- Output ONLY triples, no numbers, no explanations, nothing else\n"
     "- 8-15 triples per document\n\n"
     "Example output:\n"
     "RAG | uses | vector retrieval\n"
     "RAGAS | proposed by | Es et al.\n"
     "Chroma | suitable for | local development"),
    ("human", "Text:\n{text}"),
])

def extract_triples(text: str) -> list[tuple[str, str, str]]:
    raw = triple_chain.invoke({"text": text})
    triples = []
    for line in raw.strip().splitlines():
        parts = [p.strip() for p in line.split("|")]
        if len(parts) == 3 and all(parts):
            triples.append((parts[0], parts[1], parts[2]))
    return triples

Enter fullscreen mode Exit fullscreen mode

Building the NetworkX Graph

KG = nx.DiGraph()

for doc in DOCUMENTS:
    triples = extract_triples(doc.page_content)
    for head, rel, tail in triples:
        KG.add_node(head, source=doc.metadata["source"])
        KG.add_node(tail, source=doc.metadata["source"])
        KG.add_edge(head, tail, relation=rel)

Enter fullscreen mode Exit fullscreen mode

12 documents → 176 nodes, 139 edges.


Implementation: BFS Graph Traversal

def graph_retrieve(question: str, graph: nx.DiGraph, hops: int = 2):
    # Step 1: Extract entities from question
    entities = extract_entities(question)  # LLM, one entity per line

    # Step 2: Fuzzy-match against graph nodes (substring matching)
    seed_nodes = []
    for entity in entities:
        entity_lower = entity.lower()
        for node in graph.nodes:
            if entity_lower in node.lower() or node.lower() in entity_lower:
                seed_nodes.append(node)

    if not seed_nodes:
        return []  # No match — fall back to pure vector retrieval

    # Step 3: BFS k-hop traversal
    visited = set(seed_nodes)
    frontier = set(seed_nodes)
    for _ in range(hops):
        next_frontier = set()
        for node in frontier:
            neighbors = set(graph.successors(node)) | set(graph.predecessors(node))
            next_frontier |= neighbors - visited
        visited |= next_frontier
        frontier = next_frontier

    # Step 4: Format triples as context
    triples = [
        f"{u} --[{data['relation']}]--> {v}"
        for u, v, data in graph.edges(data=True)
        if u in visited or v in visited
    ]

    return [Document(page_content=
        f"[Graph entities]: {', '.join(list(visited)[:20])}\n\n"
        f"[Graph relationships]:\n" + "\n".join(triples[:40])
    )]

Enter fullscreen mode Exit fullscreen mode

BFS at 2 hops: from seed entities, collect direct neighbors (1 hop) and their neighbors (2 hops). For "what models did BAAI develop?", 1 hop is enough. For "where do bge-large-zh-v1.5 and bge-reranker-v2-m3 come from, and what are they each used for?", 2 hops connects BAAI → both models → their respective uses.


Experimental Results

Test Set Design

8 questions, two categories:

Type Count Example
Single-hop factual 2 "What are the four RAGAS metrics?"
Multi-hop relational 6 "Which org do bge-large-zh-v1.5 and bge-reranker-v2-m3 come from, and what does each do?"

The multi-hop questions are Graph RAG's home turf — and vector search's weak spot.

RAGAS Metrics

======================================================================
  RAGAS Metrics Comparison (Vector RAG vs Graph RAG)
======================================================================

  Metric               Vector RAG    Graph RAG       Delta
  ──────────────────────────────────────────────────────────
  context_recall            0.812        0.750     ↓-0.062
  context_precision         0.729        0.948     ↑+0.219  ◀
  faithfulness              0.865        0.883     ↑+0.018
  answer_relevancy          0.536        0.465     ↓-0.071
======================================================================

Enter fullscreen mode Exit fullscreen mode

context_precision +0.219 — one of the largest precision gaps between vector and non-vector approaches in this series.


Reading the Results

Why Does context_precision Jump So Much?

context_precision measures how many of the documents sent to the LLM are actually relevant.

Vector search returns the top-4 semantically similar chunks — similar doesn't mean precise. Ask "what two models did BAAI build?", and vector search pulls back every paragraph mentioning BAAI or BGE: model dimensions, benchmark rankings, usage scenarios — lots of content the question didn't ask for.

Graph traversal starts at the BAAI node and walks directly to its connected subgraph:

BAAI --[developed]--> bge-large-zh-v1.5
BAAI --[developed]--> bge-reranker-v2-m3
bge-large-zh-v1.5 --[used for]--> vector embedding
bge-reranker-v2-m3 --[used for]--> reranking

Enter fullscreen mode Exit fullscreen mode

Four triples, all directly relevant. No noise. context_precision naturally approaches 1.0 for these questions.

Why Does context_recall Drop Slightly?

Graph traversal has a hard entity boundary: it can only expand from entities that were extracted into the graph. If a relevant fact exists in the text but its corresponding entity wasn't captured during triple extraction, that information is permanently unreachable by graph traversal.

Vector search has no such boundary. Semantic similarity doesn't require prior structuralization — it can surface relevant content even when the exact entity name doesn't appear in the query. That's why vector search has higher recall: broader reach, even if less precise.

This is the classic precision-recall tradeoff. Graph RAG deliberately takes the precision side: return less, but make what's returned count.

Why Does answer_relevancy Drop?

Graph traversal context is a structured triple list:

bge-large-zh-v1.5 --[developed by]--> BAAI

Enter fullscreen mode Exit fullscreen mode

Vector retrieval returns full semantic paragraphs with natural language flow. When the LLM synthesizes an answer from triples, the output is technically correct but slightly less fluent than summarizing a natural paragraph — which slightly depresses the answer_relevancy score.

This is an inherent limitation of the triple format as context. It can be partially addressed by adding a prompt instruction: "First convert these triples into a coherent paragraph, then answer the question."


When to Use Graph RAG

Strong fit:

  • Complex relationship networks in documents: technical docs, knowledge bases, product manuals where entities have many explicit connections
  • Questions that cross entity boundaries: "what connects X and Y", "what family does Z belong to", "trace the evolution of A"
  • Answers distributed across documents: knowledge you need to assemble from multiple sources

Caveats:

  • Triple extraction cost: every document requires an LLM call to extract triples. Build phase is significantly more expensive than a vector-only index
  • Extraction quality is the ceiling: if the LLM misses a key entity during triple extraction, graph traversal can never recover it
  • Limited gain for single-hop QA: if most questions are "what is X?", vector recall advantage outweighs graph precision advantage
  • Answer fluency: triple-format context produces slightly stiffer answers; prompt engineering can compensate

Full Code

Complete code is open-sourced at:

https://github.com/chendongqi/llm-in-action/tree/main/16-graph-rag

Key file:

  • graph_rag.py — Full implementation: graph build, BFS retrieval, RAGAS evaluation

How to run:

git clone https://github.com/chendongqi/llm-in-action
cd 16-graph-rag
cp .env.example .env
pip install -r requirements.txt
python graph_rag.py

Enter fullscreen mode Exit fullscreen mode


Summary

This article implemented Graph RAG from scratch. Key findings:

  1. context_precision +0.219 is a direct result of graph traversal's "retrieve by path, not by similarity" approach — only edges directly connected to the question's entities are returned
  2. context_recall -0.062 is the cost of the entity boundary — what wasn't extracted into the graph is invisible to traversal, while vector search has no such hard limit
  3. The most important implementation decision: abandon LLMGraphTransformer, switch to a custom Entity | relation | Entity format — solves GLM-4-flash's unstable JSON output with a simpler, more robust parsing strategy
  4. The hybrid approach is the right call: graph traversal + vector fallback. Pure graph traversal leaves gaps; pure vector search misses relationships. The combination covers both.

A pattern that's been accumulating across this series: there is no universally dominant RAG strategy. Every approach gains on one dimension while conceding on another. Vector search has strong recall but weak precision for relational questions. Reranking improves precision but adds latency. Graph RAG achieves high precision but sacrifices recall coverage. Which one to deploy depends entirely on what your knowledge base looks like and what questions your users actually ask.


References