Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
861 lines
36 KiB
Python
861 lines
36 KiB
Python
#!/usr/bin/env python3
|
||
"""Generate all SVG illustrations for Chapter 3 (Knowledge Base & RAG).
|
||
|
||
Figures (14 total):
|
||
fig3-1: Chapter roadmap
|
||
fig3-2: RAG end-to-end pipeline (concrete example)
|
||
fig3-3: Dense embedding evolution (with dimensions & training)
|
||
fig3-4: HNSW index structure (enlarged)
|
||
fig3-5: BM25 scoring mechanism (enlarged)
|
||
fig3-6: Hybrid retrieval + reranking (with scores)
|
||
fig3-7: RAPTOR tree structure (enlarged)
|
||
fig3-8: GraphRAG relation network (enlarged)
|
||
fig3-9: Agentic vs Non-Agentic RAG (concrete queries)
|
||
fig3-10: Agentic RAG system architecture (Exp 3.6)
|
||
fig3-11: Contextual retrieval (concrete prefix example)
|
||
fig3-12: Structured knowledge extraction pipeline (Exp 3.10)
|
||
fig3-13: Externalized learning loop (concrete)
|
||
fig3-14: GAIA experience learning (Exp 3.11)
|
||
"""
|
||
|
||
import math
|
||
import os
|
||
import sys
|
||
|
||
sys.path.insert(0, os.path.dirname(__file__))
|
||
from svg_lib import (
|
||
SVG, COLORS, FONT, MONO, STROKE_W, CORNER_R, _escape, _marker_def,
|
||
FS_TITLE, FS_BODY, FS_SMALL, FS_TINY, FS_LABEL,
|
||
)
|
||
|
||
OUT = os.path.join(os.path.dirname(__file__), 'images')
|
||
|
||
|
||
# ──────────────────────── Helpers ────────────────────────
|
||
|
||
def _pill(svg, x, y, w, h, label, fill='light', font_size=FS_SMALL, bold=False):
|
||
"""Rounded pill / tag shape."""
|
||
svg.rect(x, y, w, h, fill=fill, rx=h // 2)
|
||
c = 'white' if fill in ('dark', 'darker') else 'text'
|
||
svg.text(x + w / 2, y + h / 2, label, size=font_size, fill=c, bold=bold)
|
||
|
||
|
||
# ──────────────────────── fig3-1 ────────────────────────
|
||
|
||
def fig3_1():
|
||
"""Knowledge map of this chapter"""
|
||
w, h = 860, 580
|
||
svg = SVG(w, h)
|
||
|
||
svg.text(w / 2, 32, "Chapter 3: Knowledge Base & RAG — Knowledge Map", size=FS_TITLE, bold=True)
|
||
|
||
# --- Row 1: RAG foundations ---
|
||
r1_y = 70
|
||
svg.rect(30, r1_y, 800, 130, fill='white', stroke='border', dash=True)
|
||
svg.text(80, r1_y + 20, "RAG Basics", size=FS_BODY, bold=True, anchor='start')
|
||
|
||
boxes_r1 = [
|
||
("Dense Embedding", 50, "Word2Vec → BGE-M3"),
|
||
("Sparse Embedding", 230, "TF-IDF / BM25"),
|
||
("Hybrid Retrieval + Reranking", 410, "Two-tower Retrieval + Cross-Encoder"),
|
||
("Multimodal Extraction", 650, "Native / Text / Tool"),
|
||
]
|
||
for label, bx, sub in boxes_r1:
|
||
svg.box(bx, r1_y + 38, 160, 50, label, fill='light', bold=True, font_size=FS_SMALL)
|
||
svg.text(bx + 80, r1_y + 38 + 50 + 18, sub, size=FS_TINY, fill='text_light')
|
||
|
||
# --- Arrow down ---
|
||
svg.arrow(w / 2, r1_y + 130, w / 2, r1_y + 160)
|
||
|
||
# --- Row 2: Advanced knowledge structuring ---
|
||
r2_y = 230
|
||
svg.rect(30, r2_y, 800, 100, fill='white', stroke='border', dash=True)
|
||
svg.text(80, r2_y + 20, "Learning from Existing Knowledge", size=FS_BODY, bold=True, anchor='start')
|
||
|
||
boxes_r2 = [
|
||
("RAPTOR\n Tree Hierarchical Index", 50),
|
||
("GraphRAG\n Entity Relation Graph", 230),
|
||
("Agentic RAG\n Retrieval as Tool", 410),
|
||
("Context-Aware Retrieval\n Prefix Summary Enhancement", 590),
|
||
]
|
||
for label, bx in boxes_r2:
|
||
svg.box(bx, r2_y + 35, 160, 55, label, fill='medium', font_size=FS_SMALL)
|
||
|
||
# --- Arrow down ---
|
||
svg.arrow(w / 2, r2_y + 100, w / 2, r2_y + 130)
|
||
|
||
# --- Row 3: Learning from experience ---
|
||
r3_y = 360
|
||
svg.rect(30, r3_y, 800, 100, fill='white', stroke='border', dash=True)
|
||
svg.text(80, r3_y + 20, "Learning from Autonomous Exploration", size=FS_BODY, bold=True, anchor='start')
|
||
|
||
boxes_r3 = [
|
||
("Post-training\n RL → Muscle Memory", 100),
|
||
("In-Context Learning\n Inference-time Soft Retrieval", 330),
|
||
("Externalized Learning\n Knowledge Base + Tool Generation", 560),
|
||
]
|
||
for label, bx in boxes_r3:
|
||
svg.box(bx, r3_y + 35, 200, 55, label, fill='light', font_size=FS_SMALL)
|
||
|
||
# --- Bottom: core insight ---
|
||
svg.rect(180, 490, 500, 44, fill='dark')
|
||
svg.text(w / 2, 512, "Bitter Lesson: Search + Learning = General Method", size=FS_BODY, fill='white', bold=True)
|
||
svg.arrow(w / 2, r3_y + 100, w / 2, 488)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-1.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-2 ────────────────────────
|
||
|
||
def fig3_2():
|
||
"""RAG End-to-End Pipeline (Concrete Example)"""
|
||
w, h = 880, 440
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "RAG End-to-End Pipeline", size=FS_TITLE, bold=True)
|
||
|
||
# Step 1: User query
|
||
svg.box(20, 65, 180, 55, "① User Query", fill='medium', bold=True, font_size=FS_BODY)
|
||
q_lines = ['"How many years for intentional homicide?"']
|
||
svg.text(110, 145, q_lines[0], size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(200, 92, 238, 92)
|
||
|
||
# Step 2: Retrieval
|
||
svg.box(240, 65, 180, 55, "② Retrieval", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.text(330, 140, "Dense Retrieval + BM25", size=FS_SMALL, fill='text_light')
|
||
svg.text(330, 160, "→ Top-K Text Chunks", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(420, 92, 458, 92)
|
||
|
||
# Step 3: Augmentation
|
||
svg.box(460, 65, 180, 55, "③ Augment", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.text(550, 140, "Query + Retrieved Results", size=FS_SMALL, fill='text_light')
|
||
svg.text(550, 160, "→ Construct Full Prompt", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(640, 92, 678, 92)
|
||
|
||
# Step 4: Generation
|
||
svg.box(680, 65, 180, 55, "④ Generate", fill='medium', bold=True, font_size=FS_BODY)
|
||
svg.text(770, 140, "LLM synthesizes context", size=FS_SMALL, fill='text_light')
|
||
svg.text(770, 160, "→ Generate response", size=FS_SMALL, fill='text_light')
|
||
|
||
# Concrete data flow example
|
||
svg.line(20, 195, 860, 195, color='dark', dash=True)
|
||
svg.text(w / 2, 215, "Example data flow", size=FS_BODY, bold=True)
|
||
|
||
# Retrieved chunks
|
||
svg.rect(20, 235, 400, 90, fill='code_bg', stroke='dark', rx=4)
|
||
svg.text(220, 253, "Retrieved text chunks", size=FS_SMALL, bold=True)
|
||
svg.mono(30, 278, "Article 232 of the Criminal Law: Whoever intentionally kills another shall be sentenced to death,", size=FS_TINY)
|
||
svg.mono(30, 298, "life imprisonment or fixed-term imprisonment of not less than ten years...", size=FS_TINY)
|
||
|
||
# Augmented prompt
|
||
svg.rect(440, 235, 420, 90, fill='code_bg', stroke='dark', rx=4)
|
||
svg.text(650, 253, "Augmented Prompt", size=FS_SMALL, bold=True)
|
||
svg.mono(450, 278, "Answer the question based on the following legal provisions:", size=FS_TINY)
|
||
svg.mono(450, 298, "[Article 232 of the Criminal Law...] Q: What is the sentence for intentional homicide?", size=FS_TINY)
|
||
|
||
# Generated answer
|
||
svg.rect(20, 345, 840, 80, fill='light', stroke='border')
|
||
svg.text(w / 2, 363, "Generated response", size=FS_SMALL, bold=True)
|
||
svg.mono(30, 390, "According to Article 232 of the Criminal Law, the crime of intentional homicide is punishable by death, life imprisonment, or fixed-term imprisonment of not less than ten years;", size=FS_TINY)
|
||
svg.mono(30, 412, "if the circumstances are minor, the sentence is fixed-term imprisonment of not less than three years but not more than ten years.", size=FS_TINY)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-2.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-3 ────────────────────────
|
||
|
||
def fig3_3():
|
||
"""Evolution of dense embedding techniques"""
|
||
w, h = 860, 340
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Evolution of dense embedding techniques", size=FS_TITLE, bold=True)
|
||
|
||
items = [
|
||
("Word2Vec", "2013", "300D\nStatic word vectors", "Co-occurrence\nPredictive training"),
|
||
("GloVe", "2014", "300D\nGlobal statistics", "Matrix factorization\n+ Co-occurrence"),
|
||
("BERT", "2018", "768D\nContext-aware", "Transformer\nMLM pre-training"),
|
||
("Sentence-BERT", "2019", "768D\nSentence-level embeddings", "Siamese network\nContrastive learning"),
|
||
("BGE-M3", "2024", "1024D\nMultilingual long texts", "Multi-stage\nHybrid training"),
|
||
]
|
||
n = len(items)
|
||
pad_l, pad_r = 80, 80
|
||
usable = w - pad_l - pad_r
|
||
gap = usable / (n - 1)
|
||
line_y = 90
|
||
|
||
svg.line(pad_l - 30, line_y, w - pad_r + 30, line_y, color='dark')
|
||
svg.elems.append(
|
||
f'<polygon points="{w - pad_r + 30},{line_y - 6} {w - pad_r + 42},{line_y} '
|
||
f'{w - pad_r + 30},{line_y + 6}" fill="{COLORS["dark"]}"/>'
|
||
)
|
||
|
||
for i, (name, year, dims, training) in enumerate(items):
|
||
x = pad_l + i * gap
|
||
svg.circle(x, line_y, 8, fill='dark')
|
||
svg.text(x, line_y - 30, name, size=FS_BODY, bold=True)
|
||
svg.text(x, line_y + 28, year, size=FS_SMALL, fill='text_light')
|
||
|
||
svg.rect(x - 65, line_y + 50, 130, 55, fill='light')
|
||
for j, dl in enumerate(dims.split('\n')):
|
||
svg.text(x, line_y + 68 + j * 22, dl, size=FS_SMALL)
|
||
|
||
svg.rect(x - 65, line_y + 115, 130, 55, fill='code_bg', stroke='dark', rx=4)
|
||
for j, tl in enumerate(training.split('\n')):
|
||
svg.text(x, line_y + 133 + j * 22, tl, size=FS_SMALL, fill='text_light')
|
||
|
||
# Bottom labels
|
||
svg.text(pad_l + gap * 0.5, h - 18,
|
||
"Static word vectors (one vector per word)", size=FS_SMALL, fill='text_light')
|
||
svg.text(pad_l + gap * 3.5, h - 18,
|
||
"Context-aware embeddings (multiple vectors per word)", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.line(pad_l + gap * 1.5, 75, pad_l + gap * 1.5, h - 35, color='dark', dash=True)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-3.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-4 ────────────────────────
|
||
|
||
def fig3_4():
|
||
"""HNSW index structure"""
|
||
w, h = 750, 440
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "HNSW index structure", size=FS_TITLE, bold=True)
|
||
|
||
layers = [
|
||
("Layer 2 (sparse · long-range connections)", 70, 3),
|
||
("Layer 1 (medium density)", 185, 6),
|
||
("Layer 0 (dense · all nodes)", 300, 10),
|
||
]
|
||
for label, base_y, count in layers:
|
||
svg.rect(30, base_y - 30, w - 60, 90, fill='white', stroke='dark', dash=True)
|
||
svg.text(100, base_y - 14, label, size=FS_SMALL, fill='text_light', anchor='start')
|
||
spacing = (w - 140) / (count + 1)
|
||
positions = []
|
||
for j in range(count):
|
||
cx = 70 + spacing * (j + 1)
|
||
cy = base_y + 25
|
||
svg.circle(cx, cy, 14, fill='light')
|
||
positions.append((cx, cy))
|
||
for j in range(count - 1):
|
||
skip = 1 if count <= 6 else (2 if j % 2 == 0 else 1)
|
||
if j + skip < count:
|
||
x1, y1 = positions[j]
|
||
x2, y2 = positions[j + skip]
|
||
svg.line(x1 + 14, y1, x2 - 14, y2, color='dark')
|
||
|
||
# Search path arrows
|
||
svg.arrow(w / 2, 130, w / 2 - 50, 165, color='border')
|
||
svg.text(w / 2 + 80, 148, "Search starts from the top level", size=FS_SMALL, fill='text_light')
|
||
svg.arrow(w / 2 - 50, 245, w / 2 - 80, 280, color='border')
|
||
svg.text(w / 2 + 60, 263, "Refine layer by layer downward", size=FS_SMALL, fill='text_light')
|
||
|
||
# Key properties
|
||
svg.rect(50, h - 45, 300, 32, fill='light')
|
||
svg.text(200, h - 29, "Supports incremental updates · High recall", size=FS_SMALL, bold=True)
|
||
svg.rect(400, h - 45, 300, 32, fill='code_bg', stroke='dark', rx=4)
|
||
svg.text(550, h - 29, "O(log N) query complexity", size=FS_SMALL)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-4.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-5 ────────────────────────
|
||
|
||
def fig3_5():
|
||
"""BM25 scoring mechanism"""
|
||
w, h = 800, 380
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "BM25 scoring mechanism", size=FS_TITLE, bold=True)
|
||
|
||
# Formula
|
||
svg.rect(40, 50, w - 80, 50, fill='code_bg', stroke='dark', rx=4)
|
||
svg.mono(60, 75,
|
||
"Score(Q,D) = Σ IDF(qi) × TF(qi,D)×(k1+1) / (TF + k1×(1-b+b×|D|/avgdl))",
|
||
size=FS_SMALL)
|
||
|
||
# Three components
|
||
boxes = [
|
||
("Term frequency saturation (TF)", 40, 'light', [
|
||
"k₁ controls saturation speed",
|
||
"TF ↑ but contribution diminishes",
|
||
"Example: 5→10 occurrences",
|
||
"Score increases only ~20%",
|
||
]),
|
||
("Inverse document frequency (IDF)", 290, 'light', [
|
||
"Measures word rarity",
|
||
"\"的\" → IDF ≈ 0",
|
||
"\"量刑\" → IDF ≈ 5.2",
|
||
"Rare word weight >> common word",
|
||
]),
|
||
("Length normalization (b)", 540, 'light', [
|
||
"b ∈ [0,1] normalization strength",
|
||
"b=0: ignore length",
|
||
"b=1: full normalization",
|
||
"Avoid bias towards long documents",
|
||
]),
|
||
]
|
||
for title, bx, fill, details in boxes:
|
||
svg.rect(bx, 120, 220, 170, fill=fill)
|
||
svg.text(bx + 110, 148, title, size=FS_BODY, bold=True)
|
||
svg.line(bx + 20, 163, bx + 200, 163, color='dark')
|
||
for k, line in enumerate(details):
|
||
svg.text(bx + 110, 190 + k * 28, line, size=FS_SMALL, fill='text_light')
|
||
|
||
# Result bar
|
||
for bx in [150, 400, 650]:
|
||
svg.line(bx, 290, bx, 315, color='dark')
|
||
svg.rect(40, 315, w - 80, 48, fill='medium')
|
||
svg.text(w / 2, 339, "Final score = Σ (TF saturation × IDF weighting × length normalization)", size=FS_BODY, bold=True)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-5.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-6 ────────────────────────
|
||
|
||
def fig3_6():
|
||
"""Hybrid retrieval and re-ranking pipeline (with score examples)"""
|
||
w, h = 880, 480
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Hybrid retrieval and re-ranking pipeline", size=FS_TITLE, bold=True)
|
||
|
||
# Query
|
||
svg.rect(30, 55, 160, 50, fill='medium')
|
||
svg.text(110, 73, "User query", size=FS_BODY, bold=True)
|
||
svg.mono(110, 93, '"kitty behavior"', size=FS_TINY, anchor='middle')
|
||
|
||
# Dense retrieval
|
||
svg.arrow(190, 68, 238, 68)
|
||
svg.box(240, 50, 180, 50, "Dense retrieval", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.text(330, 118, "Semantic matching: kitty ≈ cat", size=FS_SMALL, fill='text_light')
|
||
|
||
dense_results = [
|
||
("doc3: \"feline habits and cat play...\"", "cos=0.87"),
|
||
("doc7: \"cat grooming patterns...\"", "cos=0.82"),
|
||
("doc1: \"pet care basics...\"", "cos=0.71"),
|
||
]
|
||
for i, (doc, score) in enumerate(dense_results):
|
||
y = 140 + i * 32
|
||
svg.mono(250, y, doc, size=FS_TINY)
|
||
svg.text(700, y, score, size=FS_TINY, fill='text_light', anchor='start')
|
||
|
||
# Sparse retrieval
|
||
svg.arrow(190, 90, 238, 270)
|
||
svg.box(240, 250, 180, 50, "Sparse retrieval (BM25)", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.text(330, 318, "Exact match: \"kitty\" keyword", size=FS_SMALL, fill='text_light')
|
||
|
||
sparse_results = [
|
||
("doc5: \"kitty litter training...\"", "BM25=8.4"),
|
||
("doc9: \"kitty adoption guide...\"", "BM25=6.1"),
|
||
("doc2: \"kitten health tips...\"", "BM25=3.2"),
|
||
]
|
||
for i, (doc, score) in enumerate(sparse_results):
|
||
y = 340 + i * 32
|
||
svg.mono(250, y, doc, size=FS_TINY)
|
||
svg.text(700, y, score, size=FS_TINY, fill='text_light', anchor='start')
|
||
|
||
# Merge + rerank
|
||
svg.arrow(770, 180, 808, 220)
|
||
svg.arrow(770, 370, 808, 330)
|
||
|
||
svg.rect(790, 215, 70, 120, fill='medium')
|
||
svg.text(825, 250, "Merge", size=FS_BODY, bold=True)
|
||
svg.text(825, 275, "Deduplicate", size=FS_BODY, bold=True)
|
||
svg.text(825, 300, "6→5", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-6.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-7 ────────────────────────
|
||
|
||
def fig3_7():
|
||
"""RAPTOR tree structure"""
|
||
w, h = 800, 440
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "RAPTOR tree hierarchical index", size=FS_TITLE, bold=True)
|
||
|
||
# Root
|
||
svg.box(300, 55, 200, 50, "Global summary", fill='dark', bold=True, font_size=FS_BODY)
|
||
svg.text(300 + 200 + 15, 80, "← Root node", size=FS_SMALL, fill='text_light', anchor='start')
|
||
|
||
# Mid-level
|
||
mid_nodes = [("Cluster summary A", 80), ("Cluster summary B", 320), ("Cluster summary C", 560)]
|
||
for label, x in mid_nodes:
|
||
svg.box(x, 150, 160, 48, label, fill='medium', font_size=FS_BODY)
|
||
svg.line(400, 105, 160, 150, color='border')
|
||
svg.line(400, 105, 400, 150, color='border')
|
||
svg.line(400, 105, 640, 150, color='border')
|
||
svg.text(35, 230, "Middle layer ↑", size=FS_SMALL, fill='text_light', anchor='start')
|
||
|
||
# Leaf nodes — 7 boxes evenly distributed, narrower to avoid overlap
|
||
chunks = [
|
||
[(40, "Text chunk 1"), (140, "Text chunk 2"), (240, "Text chunk 3")], # Cluster A → cluster center ~160
|
||
[(360, "Text chunk 4"), (460, "Text chunk 5")], # Cluster B → cluster center ~410
|
||
[(560, "Text chunk 6"), (660, "Text chunk 7")], # Cluster C → cluster center ~640
|
||
]
|
||
leaf_w = 88
|
||
mid_cxs = [160, 400, 640]
|
||
for gi, group in enumerate(chunks):
|
||
for cx, label in group:
|
||
svg.box(cx, 250, leaf_w, 40, label, fill='light', font_size=FS_SMALL)
|
||
svg.line(cx + leaf_w / 2, 250, mid_cxs[gi], 198, color='dark')
|
||
svg.text(35, 295, "Leaf layer ↑", size=FS_SMALL, fill='text_light', anchor='start')
|
||
|
||
# Original document
|
||
svg.rect(40, 320, 720, 55, fill='white', stroke='dark', dash=True)
|
||
svg.text(400, 340, "Original document", size=FS_BODY, fill='text_light')
|
||
for bx in range(60, 720, 110):
|
||
svg.rect(bx, 350, 90, 16, fill='light')
|
||
|
||
# Bottom label
|
||
svg.text(w / 2, h - 20, "Bottom-up recursive abstraction: details → topics → global overview", size=FS_BODY, fill='text_light')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-7.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-8 ────────────────────────
|
||
|
||
def fig3_8():
|
||
"""GraphRAG relational network"""
|
||
w, h = 750, 430
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 28, "GraphRAG entity-relation knowledge graph", size=FS_TITLE, bold=True)
|
||
|
||
nodes = [
|
||
("Intel", 375, 100, 'medium'),
|
||
("SSE", 150, 190, 'light'),
|
||
("AVX", 550, 190, 'light'),
|
||
("XMM reg", 100, 320, 'light'),
|
||
("ADDPS", 280, 340, 'light'),
|
||
("YMM reg", 520, 320, 'light'),
|
||
("FP ops", 375, 250, 'light'),
|
||
]
|
||
node_r = 42
|
||
|
||
# Community box (drawn first, as background layer, to avoid covering subsequent nodes and edges)
|
||
svg.rect(50, 275, 300, 110, fill='none', stroke='border', dash=True)
|
||
svg.text(200, 395, "Community: SSE instruction set", size=FS_SMALL, fill='text_light')
|
||
|
||
for label, x, y, fill in nodes:
|
||
svg.circle(x, y, node_r, fill=fill, label=label, font_size=FS_SMALL)
|
||
|
||
edges = [
|
||
(0, 1, "Development"), (0, 2, "Development"),
|
||
(1, 3, "Usage"), (1, 6, ""), (1, 4, "Contains"),
|
||
(2, 5, "Usage"), (2, 6, "Execute"),
|
||
(6, 3, ""), (6, 5, "Operation"),
|
||
]
|
||
for i, j, elabel in edges:
|
||
x1, y1 = nodes[i][1], nodes[i][2]
|
||
x2, y2 = nodes[j][1], nodes[j][2]
|
||
dx, dy = x2 - x1, y2 - y1
|
||
dist = math.sqrt(dx * dx + dy * dy)
|
||
ux, uy = dx / dist, dy / dist
|
||
ax1 = x1 + ux * (node_r + 3)
|
||
ay1 = y1 + uy * (node_r + 3)
|
||
ax2 = x2 - ux * (node_r + 14)
|
||
ay2 = y2 - uy * (node_r + 14)
|
||
svg.arrow(ax1, ay1, ax2, ay2, label=elabel, color='dark')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-8.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-9 ────────────────────────
|
||
|
||
def fig3_9():
|
||
"""Agentic RAG vs Non-Agentic RAG (Specific Example)"""
|
||
w, h = 880, 560
|
||
svg = SVG(w, h)
|
||
col_w = 400
|
||
lx, rx = 20, 460
|
||
|
||
# --- Left: Non-Agentic ---
|
||
svg.rect(lx, 50, col_w, 45, fill='medium')
|
||
svg.text(lx + col_w / 2, 73, "Non-Agentic RAG", size=FS_BODY, bold=True)
|
||
|
||
steps_l = [
|
||
("Query: \"How to sentence for causing serious injury by negligence while drunk \nand with a previous theft conviction?\"", 'light'),
|
||
("Single retrieval:\n\"Sentencing for causing serious injury by negligence\"", 'light'),
|
||
("Retrieval result: Only found basic provisions for negligent injury\n (incomplete context)", 'code_bg'),
|
||
("Direct generation: Missing \"drunk\"\nand \"previous conviction\" influencing factors", 'light'),
|
||
]
|
||
prev_y = 95
|
||
for i, (s, fill) in enumerate(steps_l):
|
||
y = 110 + i * 108
|
||
svg.box(lx + 30, y, 340, 80, s, fill=fill, font_size=FS_SMALL)
|
||
if i > 0:
|
||
svg.arrow(lx + 200, prev_y + 80 + 2, lx + 200, y - 2)
|
||
prev_y = y
|
||
|
||
svg.text(lx + col_w / 2, h - 15, "Single pass · Incomplete information", size=FS_BODY, fill='text_light')
|
||
|
||
# --- Separator ---
|
||
svg.line(440, 50, 440, h - 5, color='dark', dash=True)
|
||
|
||
# --- Right: Agentic ---
|
||
svg.rect(rx, 50, col_w, 45, fill='medium')
|
||
svg.text(rx + col_w / 2, 73, "Agentic RAG (ReAct)", size=FS_BODY, bold=True)
|
||
|
||
steps_r = [
|
||
("Thought: Need to decompose into 3 sub-questions", 'light'),
|
||
("Search ①: \"Sentencing for causing serious injury by negligence\"\nSearch ②: \"Criminal liability for drunkenness\"\nSearch ③: \"Impact of previous theft conviction\"", 'code_bg'),
|
||
("Observation: Found basic provisions but\nmissing link between \"previous conviction\" and \"negligent injury\"", 'light'),
|
||
("Search ④: \"Recidivism different crimes\njudicial interpretation\"", 'code_bg'),
|
||
("Synthesis: Complete answer including all\nlegal provisions and sentencing analysis", 'medium'),
|
||
]
|
||
ys = []
|
||
for i, (s, fill) in enumerate(steps_r):
|
||
y = 105 + i * 86
|
||
hh = 68
|
||
svg.box(rx + 30, y, 340, hh, s, fill=fill, font_size=FS_SMALL)
|
||
ys.append(y)
|
||
if i > 0:
|
||
svg.arrow(rx + 200, ys[i - 1] + hh + 2, rx + 200, y - 2)
|
||
|
||
# Iteration loop arrow
|
||
loop_x = rx + 370 + 10
|
||
svg.elems.append(
|
||
f'<path d="M {loop_x},{ys[2] + 34} C {loop_x + 28},{ys[2] + 34} '
|
||
f'{loop_x + 28},{ys[1] + 34} {loop_x},{ys[1] + 34}" '
|
||
f'fill="none" stroke="{COLORS["border"]}" stroke-width="{STROKE_W}" '
|
||
f'stroke-dasharray="6,3" marker-end="url(#ah)"/>'
|
||
)
|
||
svg.text(loop_x + 4, (ys[1] + ys[2]) / 2 + 34, "Iteration", size=FS_SMALL, fill='text_light',
|
||
anchor='start')
|
||
|
||
svg.text(rx + col_w / 2, h - 15, "Multi-round iteration · Complete information", size=FS_BODY, fill='text_light')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-9.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-10 ────────────────────────
|
||
|
||
def fig3_10():
|
||
"""Agentic RAG System Architecture (Experiment 3.6)"""
|
||
w, h = 880, 500
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Experiment 3.6: Agentic RAG System Architecture", size=FS_TITLE, bold=True)
|
||
|
||
# Agent core
|
||
svg.rect(220, 55, 440, 200, fill='white', stroke='border')
|
||
svg.text(440, 78, "Agent (ReAct Loop)", size=FS_BODY, bold=True)
|
||
|
||
# ReAct steps inside agent
|
||
react_items = [
|
||
("① Thought", 240, 100, 180, 45, 'light'),
|
||
("② Action", 460, 100, 180, 45, 'medium'),
|
||
("③ Observation", 350, 180, 180, 45, 'light'),
|
||
]
|
||
for label, bx, by, bw, bh, fill in react_items:
|
||
svg.box(bx, by, bw, bh, label, fill=fill, font_size=FS_SMALL, bold=True)
|
||
|
||
svg.arrow(420, 122, 458, 122)
|
||
svg.arrow(640, 130, 530, 178, color='border')
|
||
svg.arrow(350, 202, 280, 145, color='border')
|
||
|
||
# Loop label
|
||
svg.text(360, 165, "Loop until information is sufficient", size=FS_TINY, fill='text_light')
|
||
|
||
# User
|
||
svg.box(20, 95, 160, 55, "User query", fill='medium', bold=True, font_size=FS_BODY)
|
||
svg.arrow(180, 122, 218, 122)
|
||
|
||
# Final answer
|
||
svg.box(700, 95, 160, 55, "Final answer", fill='medium', bold=True, font_size=FS_BODY)
|
||
svg.arrow(660, 122, 698, 122)
|
||
|
||
# Tool layer
|
||
svg.rect(100, 290, 680, 85, fill='white', stroke='border', dash=True)
|
||
svg.text(440, 312, "Tool layer", size=FS_BODY, bold=True)
|
||
tools = [
|
||
("knowledge_base_search", 120, 330, 220),
|
||
("web_search", 370, 330, 140),
|
||
("code_interpreter", 540, 330, 160),
|
||
]
|
||
for label, tx, ty, tw in tools:
|
||
svg.rect(tx, ty, tw, 35, fill='light')
|
||
svg.mono(tx + tw / 2, ty + 17, label, size=FS_TINY, anchor='middle')
|
||
|
||
svg.arrow(440, 255, 440, 288)
|
||
svg.arrow(440, 288, 440, 255)
|
||
|
||
# Knowledge base backends
|
||
svg.rect(100, 400, 680, 85, fill='white', stroke='dark', dash=True)
|
||
svg.text(440, 420, "Knowledge base backend (switchable)", size=FS_BODY, bold=True)
|
||
backends = [
|
||
("retrieval-pipeline\nHybrid retrieval", 120),
|
||
("structured-index\nRAPTOR/GraphRAG", 340),
|
||
("contextual-retrieval\nContext-aware", 560),
|
||
]
|
||
for label, bx in backends:
|
||
svg.box(bx, 435, 180, 45, label, fill='light', font_size=FS_SMALL)
|
||
|
||
svg.arrow(230, 365, 230, 398)
|
||
svg.arrow(440, 375, 440, 398)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-10.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-11 ────────────────────────
|
||
|
||
def fig3_11():
|
||
"""Context-aware retrieval (specific prefix example)"""
|
||
w, h = 880, 430
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Context-aware retrieval", size=FS_TITLE, bold=True)
|
||
|
||
# Left: Traditional chunking
|
||
svg.rect(20, 55, 400, 170, fill='white', stroke='border')
|
||
svg.text(220, 78, "Traditional chunking (no context)", size=FS_BODY, bold=True)
|
||
|
||
svg.rect(40, 95, 360, 50, fill='code_bg', stroke='dark', rx=4)
|
||
svg.mono(50, 112, "The company's second-quarter revenue grew by 3%,", size=FS_TINY)
|
||
svg.mono(50, 132, "mainly driven by new product lines.", size=FS_TINY)
|
||
|
||
svg.text(220, 170, "Question: \"Who is \"the company\"? Which year?", size=FS_SMALL, fill='text_light')
|
||
svg.text(220, 195, "→ Retrieval matches revenue data of many irrelevant companies", size=FS_SMALL, fill='text_light')
|
||
|
||
# Right: Contextual
|
||
svg.rect(460, 55, 400, 170, fill='white', stroke='border')
|
||
svg.text(660, 78, "Context-aware chunking", size=FS_BODY, bold=True)
|
||
|
||
svg.rect(480, 95, 360, 35, fill='medium')
|
||
svg.mono(490, 113, "[ACME Company 2025 Q2 Earnings Report · Key Performance Indicators]", size=FS_TINY)
|
||
|
||
svg.rect(480, 130, 360, 50, fill='code_bg', stroke='dark', rx=4)
|
||
svg.mono(490, 148, "The company's second-quarter revenue grew by 3%,", size=FS_TINY)
|
||
svg.mono(490, 168, "mainly driven by new product lines.", size=FS_TINY)
|
||
|
||
svg.text(660, 200, "→ Exact match ACME + Q2 + revenue growth", size=FS_SMALL, fill='text_light')
|
||
|
||
# Arrow between
|
||
svg.text(440, 140, "→", size=FS_TITLE, bold=True)
|
||
|
||
# Process flow
|
||
svg.line(20, 250, 860, 250, color='dark', dash=True)
|
||
svg.text(w / 2, 275, "Indexing stage: LLM generates context prefix", size=FS_BODY, bold=True)
|
||
|
||
flow_y = 300
|
||
svg.box(30, flow_y, 180, 55, "Original document", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.arrow(210, flow_y + 27, 248, flow_y + 27)
|
||
|
||
svg.box(250, flow_y, 180, 55, "Chunking", fill='light', bold=True, font_size=FS_BODY)
|
||
svg.arrow(430, flow_y + 27, 468, flow_y + 27)
|
||
|
||
svg.box(470, flow_y, 180, 55, "LLM generates prefix\n(prompt caching)", fill='medium',
|
||
font_size=FS_SMALL, bold=True)
|
||
svg.arrow(650, flow_y + 27, 688, flow_y + 27)
|
||
|
||
svg.box(690, flow_y, 170, 55, "Prefix + original text\n→ Index", fill='light', font_size=FS_SMALL, bold=True)
|
||
|
||
# Stats
|
||
svg.text(w / 2, h - 20,
|
||
"Effect: Retrieval failure rate ↓49% (+BM25), ↓67% (+reranking) — Anthropic data",
|
||
size=FS_SMALL, fill='text_light')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-11.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-12 ────────────────────────
|
||
|
||
def fig3_12():
|
||
"""Structured knowledge extraction pipeline (Experiment 3.10)"""
|
||
w, h = 880, 510
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Experiment 3.10: Structured knowledge extraction (judicial precedents)", size=FS_TITLE, bold=True)
|
||
|
||
# Phase 1 header
|
||
svg.rect(20, 55, 840, 200, fill='white', stroke='border')
|
||
svg.text(440, 78, "Phase 1: Knowledge extraction and structuring", size=FS_BODY, bold=True)
|
||
|
||
# Raw cases
|
||
svg.rect(40, 95, 180, 65, fill='code_bg', stroke='dark', rx=4)
|
||
svg.text(130, 113, "Original judgment documents", size=FS_SMALL, bold=True)
|
||
svg.mono(50, 138, "CAIL2018 dataset", size=FS_TINY)
|
||
|
||
svg.arrow(220, 127, 258, 127)
|
||
|
||
# LLM extraction
|
||
svg.rect(260, 95, 180, 65, fill='medium')
|
||
svg.text(350, 113, "LLM factor discovery", size=FS_SMALL, bold=True)
|
||
svg.text(350, 138, "Bottom-up Schema", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(440, 127, 478, 127)
|
||
|
||
# Structured JSON
|
||
svg.rect(480, 95, 200, 65, fill='code_bg', stroke='dark', rx=4)
|
||
svg.text(580, 113, "Structured JSON", size=FS_SMALL, bold=True)
|
||
svg.mono(490, 138, "{voluntary_surrender:true, compensation:500000,", size=FS_TINY)
|
||
svg.mono(490, 155, " injury_level:severe_second_degree}", size=FS_TINY)
|
||
|
||
# Schema detail
|
||
svg.rect(40, 170, 400, 70, fill='light')
|
||
svg.text(240, 188, "Modular data schema", size=FS_SMALL, bold=True)
|
||
svg.text(240, 212, "Core schema (voluntary surrender/compensation/criminal record) + charge extension schema", size=FS_SMALL, fill='text_light')
|
||
svg.text(240, 232, "(theft→amount involved, injury→injury level)", size=FS_SMALL, fill='text_light')
|
||
|
||
# Phase 2 header
|
||
svg.rect(20, 270, 840, 200, fill='white', stroke='border')
|
||
svg.text(440, 293, "Phase 2: Factor analysis and knowledge modeling", size=FS_BODY, bold=True)
|
||
|
||
# Vectorization
|
||
svg.rect(40, 310, 200, 65, fill='light')
|
||
svg.text(140, 328, "Feature vectorization", size=FS_SMALL, bold=True)
|
||
svg.text(140, 350, "One-hot encoding + multi-hot encoding", size=FS_SMALL, fill='text_light')
|
||
svg.text(140, 370, "+ log transformation + standardization", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(240, 342, 278, 342)
|
||
|
||
# Clustering
|
||
svg.rect(280, 310, 200, 65, fill='medium')
|
||
svg.text(380, 328, "KMeans clustering", size=FS_SMALL, bold=True)
|
||
svg.text(380, 350, "discover \"case prototype\"", size=FS_SMALL, fill='text_light')
|
||
svg.text(380, 370, "misal, \"tangan kosong, luka ringan\"", size=FS_SMALL, fill='text_light')
|
||
|
||
svg.arrow(480, 342, 518, 342)
|
||
|
||
# Factor importance
|
||
svg.rect(520, 310, 200, 65, fill='light')
|
||
svg.text(620, 328, "factor importance model", size=FS_SMALL, bold=True)
|
||
svg.text(620, 350, "quantify the weight of each factor", size=FS_SMALL, fill='text_light')
|
||
svg.text(620, 370, "build sentencing decision logic", size=FS_SMALL, fill='text_light')
|
||
|
||
# Application
|
||
svg.arrow(620, 375, 620, 400)
|
||
svg.rect(40, 400, 720, 60, fill='light')
|
||
svg.text(400, 420, "Application: conversational legal advice Agent", size=FS_BODY, bold=True)
|
||
svg.text(400, 445, "guide questions by factor importance → retrieve similar case prototypes → data-driven sentencing analysis",
|
||
size=FS_SMALL, fill='text_light')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-12.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-13 ────────────────────────
|
||
|
||
def fig3_13():
|
||
"""Externalized learning loop (concrete example)"""
|
||
w, h = 880, 490
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Externalized learning: a closed loop from experience to capability", size=FS_TITLE, bold=True)
|
||
|
||
# Central Agent
|
||
cx, cy = 440, 210
|
||
svg.circle(cx, cy, 55, fill='medium', label="Agent", font_size=FS_BODY)
|
||
|
||
# 5 steps around the loop
|
||
steps = [
|
||
("① Execute task", 120, 100, "process refund request\ncall customer service API"),
|
||
("② Get feedback", 680, 100, "successfully refunded $45\nfound need to verify last four digits"),
|
||
("③ Reflect and distill", 680, 310, "LLM summarizes experience:\n\"Company A refund requires verification\""),
|
||
("④ Store in knowledge base", 340, 380, "experience → vectorized index\nprocess → generate tool code"),
|
||
("⑤ Future retrieval and reuse", 120, 310, "similar task → retrieve experience\ndirectly reuse successful strategy"),
|
||
]
|
||
|
||
positions = []
|
||
for label, x, y, detail in steps:
|
||
svg.box(x, y, 200, 80, label + "\n" + detail,
|
||
fill='light', font_size=FS_SMALL)
|
||
positions.append((x + 100, y + 40))
|
||
|
||
# Arrows connecting steps
|
||
arrow_pairs = [
|
||
(0, 1), (1, 2), (2, 3), (3, 4), (4, 0),
|
||
]
|
||
for si, ei in arrow_pairs:
|
||
sx, sy = positions[si]
|
||
ex, ey = positions[ei]
|
||
dx, dy = ex - sx, ey - sy
|
||
dist = math.sqrt(dx * dx + dy * dy)
|
||
ux, uy = dx / dist, dy / dist
|
||
svg.arrow(sx + ux * 105, sy + uy * 45,
|
||
ex - ux * 105, ey - uy * 45, color='dark')
|
||
|
||
# Two output types
|
||
svg.rect(30, 395, 180, 28, fill='dark')
|
||
svg.text(120, 409, "Knowledge: summary/tree summary", size=FS_SMALL, fill='white')
|
||
svg.rect(670, 395, 180, 28, fill='dark')
|
||
svg.text(760, 409, "Tool: process → code", size=FS_SMALL, fill='white')
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-13.svg'))
|
||
|
||
|
||
# ──────────────────────── fig3-14 ────────────────────────
|
||
|
||
def fig3_14():
|
||
"""GAIA experience learning system (Experiment 3.11)"""
|
||
w, h = 880, 510
|
||
svg = SVG(w, h)
|
||
svg.text(w / 2, 30, "Experiment 3.11: GAIA experience learning system", size=FS_TITLE, bold=True)
|
||
|
||
box_h = 60
|
||
step_gap = 75
|
||
base_y = 100
|
||
|
||
# --- Left: Learning Mode ---
|
||
lx = 20
|
||
svg.rect(lx, 55, 400, 420, fill='white', stroke='border')
|
||
svg.text(lx + 200, 80, "Learning Mode", size=FS_BODY, bold=True)
|
||
|
||
learn_steps = [
|
||
("GAIA task", 'medium', "complex multi-step problem"),
|
||
("Agent execution", 'light', "browser + file + code interpreter"),
|
||
("Task successful?", 'light', "Auto Evaluation (AWorld)"),
|
||
("LLM Reflection & Summary", 'medium', "Extract Strategy Summary"),
|
||
("Experience → Vectorization", 'light', "Store in Experience Knowledge Base"),
|
||
]
|
||
for i, (label, fill, sub) in enumerate(learn_steps):
|
||
y = base_y + i * step_gap
|
||
svg.box(lx + 50, y, 300, box_h, label, sublabel=sub, fill=fill, bold=True, font_size=FS_BODY)
|
||
if i > 0:
|
||
svg.arrow(lx + 200, base_y + (i - 1) * step_gap + box_h + 2, lx + 200, y - 2)
|
||
|
||
# --- Right: Apply Mode ---
|
||
rx = 460
|
||
svg.rect(rx, 55, 400, 420, fill='white', stroke='border')
|
||
svg.text(rx + 200, 80, "Apply Mode", size=FS_BODY, bold=True)
|
||
|
||
apply_steps = [
|
||
("New GAIA Task", 'medium', "Receive New Question"),
|
||
("Semantic Retrieval of Experience", 'light', "Search for Similar Tasks in Experience Base"),
|
||
("Inject into System Prompt", 'medium', "Historical Successful Strategies as Examples"),
|
||
("Agent execution", 'light', "Leverage Experience for More Efficient Problem Solving"),
|
||
("Success Rate ↑ Efficiency ↑", 'dark', "Self-Evolution: Getting Stronger Over Time"),
|
||
]
|
||
for i, (label, fill, sub) in enumerate(apply_steps):
|
||
y = base_y + i * step_gap
|
||
svg.box(rx + 50, y, 300, box_h, label, sublabel=sub, fill=fill, bold=True, font_size=FS_BODY)
|
||
if i > 0:
|
||
svg.arrow(rx + 200, base_y + (i - 1) * step_gap + box_h + 2, rx + 200, y - 2)
|
||
|
||
# Arrow from learning to apply: the experience KB (centered vertically)
|
||
kb_cy = base_y + 2 * step_gap + box_h / 2 #Align with Step 3 Center
|
||
kb_x1, kb_x2 = 375, 505
|
||
svg.rect(kb_x1, kb_cy - 25, kb_x2 - kb_x1, 50, fill='dark')
|
||
svg.text((kb_x1 + kb_x2) / 2, kb_cy - 8, "Experience Knowledge Base", size=FS_SMALL, fill='white', bold=True)
|
||
svg.text((kb_x1 + kb_x2) / 2, kb_cy + 12, "(Vector Index)", size=FS_TINY, fill='white')
|
||
|
||
# Last learn step right-middle → KB left
|
||
last_y = base_y + 4 * step_gap + box_h / 2
|
||
svg.arrow(lx + 350, last_y, kb_x1 - 2, kb_cy + 10)
|
||
# KB right → second apply step left-middle
|
||
apply2_y = base_y + 1 * step_gap + box_h / 2
|
||
svg.arrow(kb_x2 + 2, kb_cy - 10, rx + 50, apply2_y)
|
||
|
||
svg.save(os.path.join(OUT, 'fig3-14.svg'))
|
||
|
||
|
||
# ──────────────────────── Main ────────────────────────
|
||
|
||
ALL_FIGS = [
|
||
fig3_1, fig3_2, fig3_3, fig3_4, fig3_5, fig3_6, fig3_7,
|
||
fig3_8, fig3_9, fig3_10, fig3_11, fig3_12, fig3_13, fig3_14,
|
||
]
|
||
|
||
if __name__ == '__main__':
|
||
os.makedirs(OUT, exist_ok=True)
|
||
for fn in ALL_FIGS:
|
||
fn()
|
||
print(f" ✓ {fn.__name__}: {fn.__doc__}")
|
||
print(f"\nDone — {len(ALL_FIGS)} SVGs saved to {OUT}/")
|