Files
ai-agent-book/book-es/gen_ch3_figs.py
T
liqiang b119135836
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
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

742 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Generate all Chapter 3 figures in Spanish (Knowledge Base & RAG)."""
import math
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))
from svg_lib import (
SVG, COLORS, FS_TITLE, FS_BODY, FS_SMALL, FS_TINY, STROKE_W,
)
OUT = os.path.join(os.path.dirname(__file__), 'images')
def fig3_1():
"""Knowledge map of this chapter — Figure 3-1."""
w, h = 860, 580
svg = SVG(w, h)
svg.text(w / 2, 32, "Capítulo 3: Base de Conocimiento y RAG — Mapa de Conocimiento", size=FS_TITLE, bold=True)
r1_y = 70
svg.rect(30, r1_y, 800, 130, fill='white', stroke='border', dash=True)
svg.text(80, r1_y + 20, "Fundamentos de RAG", size=FS_BODY, bold=True, anchor='start')
boxes_r1 = [
("Incrustación densa", 50, "Word2Vec → BGE-M3"),
("Incrustación dispersa", 230, "TF-IDF / BM25"),
("Búsqueda híbrida + Reclasificación", 410, "Dos torres + Cross-Encoder"),
("Inferencia multimodal", 650, "Nativo / Texto / Herramienta"),
]
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')
svg.arrow(w / 2, r1_y + 130, w / 2, r1_y + 160)
r2_y = 230
svg.rect(30, r2_y, 800, 100, fill='white', stroke='border', dash=True)
svg.text(80, r2_y + 20, "Aprendizaje desde Conocimiento Existente", size=FS_BODY, bold=True, anchor='start')
boxes_r2 = [
("RAPTOR\n Ín. Jerárquico Árbol", 50),
("GraphRAG\n Grafo Entidad-Relación", 230),
("RAG agéntico\n Búsqueda como Herramienta", 410),
("Búsqueda Consciente de Ctx\n Enriquecimiento por Prefijo", 590),
]
for label, bx in boxes_r2:
svg.box(bx, r2_y + 35, 160, 55, label, fill='medium', font_size=FS_SMALL)
svg.arrow(w / 2, r2_y + 100, w / 2, r2_y + 130)
r3_y = 360
svg.rect(30, r3_y, 800, 100, fill='white', stroke='border', dash=True)
svg.text(80, r3_y + 20, "Aprendizaje por Exploración Autónoma", size=FS_BODY, bold=True, anchor='start')
boxes_r3 = [
("Posentrenamiento\n RL → Memoria Muscular", 100),
("Aprendizaje en Contexto\n Inferencia Suave", 330),
("Aprendizaje Externalizado\n Base Conocimiento + Herramientas", 560),
]
for label, bx in boxes_r3:
svg.box(bx, r3_y + 35, 200, 55, label, fill='light', font_size=FS_SMALL)
svg.rect(180, 490, 500, 44, fill='dark')
svg.text(w / 2, 512, "Lección Clave: Búsqueda + Aprendizaje = Método General", 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'))
def fig3_2():
"""RAG End-to-End Pipeline — Figure 3-2."""
w, h = 880, 440
svg = SVG(w, h)
svg.text(w / 2, 30, "Canalización RAG de extremo a extremo", size=FS_TITLE, bold=True)
svg.box(20, 65, 180, 55, "① Consulta Usuario", fill='medium', bold=True, font_size=FS_BODY)
svg.text(110, 145, '"¿Pena por homicidio intencional?"', size=FS_SMALL, fill='text_light')
svg.arrow(200, 92, 238, 92)
svg.box(240, 65, 180, 55, "② Búsqueda", fill='light', bold=True, font_size=FS_BODY)
svg.text(330, 140, "Búsqueda densa + BM25", size=FS_SMALL, fill='text_light')
svg.text(330, 160, "→ Top K fragmentos", size=FS_SMALL, fill='text_light')
svg.arrow(420, 92, 458, 92)
svg.box(460, 65, 180, 55, "③ Aumentación", fill='light', bold=True, font_size=FS_BODY)
svg.text(550, 140, "Consulta + Resultados", size=FS_SMALL, fill='text_light')
svg.text(550, 160, "→ Construir prompt final", size=FS_SMALL, fill='text_light')
svg.arrow(640, 92, 678, 92)
svg.box(680, 65, 180, 55, "④ Generación", fill='medium', bold=True, font_size=FS_BODY)
svg.text(770, 140, "LLM sintetiza contexto", size=FS_SMALL, fill='text_light')
svg.text(770, 160, "→ Generar respuesta", size=FS_SMALL, fill='text_light')
svg.line(20, 195, 860, 195, color='dark', dash=True)
svg.text(w / 2, 215, "Ejemplo de flujo de datos", size=FS_BODY, bold=True)
svg.rect(20, 235, 400, 90, fill='code_bg', stroke='dark', rx=4)
svg.text(220, 253, "Fragmentos recuperados", size=FS_SMALL, bold=True)
svg.mono(30, 278, "Código Penal Art 232: El que intencionalmente matare a otro,", size=FS_TINY)
svg.mono(30, 298, "será castigado con pena de prisión de 10 a 25 años...", size=FS_TINY)
svg.rect(440, 235, 420, 90, fill='code_bg', stroke='dark', rx=4)
svg.text(650, 253, "Prompt aumentante", size=FS_SMALL, bold=True)
svg.mono(450, 278, "Responde la pregunta basándote en el código penal:", size=FS_TINY)
svg.mono(450, 298, "[Código Penal Art 232...] P: ¿Pena por homicidio intencional?", size=FS_TINY)
svg.rect(20, 345, 840, 80, fill='light', stroke='border')
svg.text(w / 2, 363, "Respuesta generada", size=FS_SMALL, bold=True)
svg.mono(30, 390, "Según el Código Penal Art 232, el homicidio intencional se castiga con prisión de 10 a 25 años;", size=FS_TINY)
svg.mono(30, 412, "en circunstancias atenuantes, la pena puede reducirse de 3 a 10 años.", size=FS_TINY)
svg.save(os.path.join(OUT, 'fig3-2.svg'))
def fig3_3():
"""Evolution of dense embedding techniques — Figure 3-3."""
w, h = 860, 340
svg = SVG(w, h)
svg.text(w / 2, 30, "Evolución de las técnicas de incrustación densa", size=FS_TITLE, bold=True)
items = [
("Word2Vec", "2013", "300D\nVectores de palabra estáticos", "Coocurrencia\nEntrenamiento predictivo"),
("GloVe", "2014", "300D\nEstadísticas globales", "Factorización matriz\n+ Coocurrencia"),
("BERT", "2018", "768D\nConsciente del contexto", "Transformer\nPreentrenamiento MLM"),
("Sentence-BERT", "2019", "768D\nIncrustación de oraciones", "Red siamesa\nAprendizaje contrastivo"),
("BGE-M3", "2024", "1024D\nTextos largos multilingües", "Multietapa\nEntrenamiento híbrido"),
]
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')
svg.text(pad_l + gap * 0.5, h - 18,
"Vectores de palabra estáticos (un vector por palabra)", size=FS_SMALL, fill='text_light')
svg.text(pad_l + gap * 3.5, h - 18,
"Incrustaciones conscientes del contexto (múltiples vectores por palabra)", 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'))
def fig3_4():
"""HNSW index structure — Figure 3-4."""
w, h = 750, 440
svg = SVG(w, h)
svg.text(w / 2, 30, "Estructura del índice HNSW", size=FS_TITLE, bold=True)
layers = [
("Capa 2 (dispersa · conexiones de larga distancia)", 70, 3),
("Capa 1 (densidad media)", 185, 6),
("Capa 0 (densa · todos los nodos)", 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')
svg.arrow(w / 2, 130, w / 2 - 50, 165, color='border')
svg.text(w / 2 + 80, 148, "La búsqueda comienza en capa superior", size=FS_SMALL, fill='text_light')
svg.arrow(w / 2 - 50, 245, w / 2 - 80, 280, color='border')
svg.text(w / 2 + 60, 263, "Se refina capa por capa hacia abajo", size=FS_SMALL, fill='text_light')
svg.rect(50, h - 45, 300, 32, fill='light')
svg.text(200, h - 29, "Admite actualizaciones incrementales · Alto 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, "Complejidad de consulta O(log N)", size=FS_SMALL)
svg.save(os.path.join(OUT, 'fig3-4.svg'))
def fig3_5():
"""BM25 scoring mechanism — Figure 3-5."""
w, h = 800, 380
svg = SVG(w, h)
svg.text(w / 2, 30, "Mecanismo de puntuación BM25", size=FS_TITLE, bold=True)
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)
boxes = [
("Saturación frec. término (TF)", 40, 'light', [
"k₁ controla velocidad saturación",
"TF ↑ pero contribución decae",
"Ejemplo: 5→10 repeticiones",
"Puntaje solo sube ~20%",
]),
("Frec. inversa doc. (IDF)", 290, 'light', [
"Mide rareza de palabra",
"\"el\" → IDF ≈ 0",
"\"sanción\" → IDF ≈ 5.2",
"Palabra rara peso >> común",
]),
("Normalización longitud (b)", 540, 'light', [
"b ∈ [0,1] fuerza normalización",
"b=0: ignorar longitud",
"b=1: normalización total",
"Evita sesgo a docs largos",
]),
]
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')
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, "Puntaje final = Σ (Saturación TF × Peso IDF × Normalización longitud)", size=FS_BODY, bold=True)
svg.save(os.path.join(OUT, 'fig3-5.svg'))
def fig3_6():
"""Hybrid retrieval and re-ranking pipeline — Figure 3-6."""
w, h = 880, 480
svg = SVG(w, h)
svg.text(w / 2, 30, "Canalización de búsqueda híbrida y reclasificación", size=FS_TITLE, bold=True)
svg.rect(30, 55, 160, 50, fill='medium')
svg.text(110, 73, "Consulta del usuario", size=FS_BODY, bold=True)
svg.mono(110, 93, '"conducta de gatitos"', size=FS_TINY, anchor='middle')
svg.arrow(190, 68, 238, 68)
svg.box(240, 50, 180, 50, "Búsqueda densa", fill='light', bold=True, font_size=FS_BODY)
svg.text(330, 118, "Coincidencia semántica: gatito ≈ gato", size=FS_SMALL, fill='text_light')
dense_results = [
("doc3: \"hábitos felinos y juego de gatos...\"", "cos=0.87"),
("doc7: \"patrones de aseo en gatos...\"", "cos=0.82"),
("doc1: \"conceptos básicos de mascotas...\"", "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')
svg.arrow(190, 90, 238, 270)
svg.box(240, 250, 180, 50, "Búsqueda dispersa (BM25)", fill='light', bold=True, font_size=FS_BODY)
svg.text(330, 318, "Coincidencia exacta: clave \"gatitos\"", size=FS_SMALL, fill='text_light')
sparse_results = [
("doc5: \"entrenamiento de gatitos...\"", "BM25=8.4"),
("doc9: \"guía de adopción de gatitos...\"", "BM25=6.1"),
("doc2: \"consejos de salud gatitos...\"", "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')
svg.arrow(770, 180, 808, 220)
svg.arrow(770, 370, 808, 330)
svg.rect(790, 215, 70, 120, fill='medium')
svg.text(825, 250, "Fusionar", size=FS_BODY, bold=True)
svg.text(825, 275, "Deduplicar", 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'))
def fig3_7():
"""RAPTOR tree structure — Figure 3-7."""
w, h = 800, 440
svg = SVG(w, h)
svg.text(w / 2, 30, "Índice jerárquico de árbol RAPTOR", size=FS_TITLE, bold=True)
svg.box(300, 55, 200, 50, "Resumen general", fill='dark', bold=True, font_size=FS_BODY)
svg.text(300 + 200 + 15, 80, "← Nodo raíz", size=FS_SMALL, fill='text_light', anchor='start')
mid_nodes = [("Resumen clúster A", 80), ("Resumen clúster B", 320), ("Resumen clúster 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, "Capa media ↑", size=FS_SMALL, fill='text_light', anchor='start')
chunks = [
[(40, "Fragmento 1"), (140, "Fragmento 2"), (240, "Fragmento 3")],
[(360, "Fragmento 4"), (460, "Fragmento 5")],
[(560, "Fragmento 6"), (660, "Fragmento 7")],
]
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, "Capa hoja ↑", size=FS_SMALL, fill='text_light', anchor='start')
svg.rect(40, 320, 720, 55, fill='white', stroke='dark', dash=True)
svg.text(400, 340, "Documento original", size=FS_BODY, fill='text_light')
for bx in range(60, 720, 110):
svg.rect(bx, 350, 90, 16, fill='light')
svg.text(w / 2, h - 20, "Abstracción recursiva de abajo a arriba: detalles → temas → vista general", size=FS_BODY, fill='text_light')
svg.save(os.path.join(OUT, 'fig3-7.svg'))
def fig3_8():
"""GraphRAG relational network — Figure 3-8."""
w, h = 750, 430
svg = SVG(w, h)
svg.text(w / 2, 28, "Grafo de conocimiento entidad-relación GraphRAG", size=FS_TITLE, bold=True)
nodes = [
("Intel", 375, 100, 'medium'),
("SSE", 150, 190, 'light'),
("AVX", 550, 190, 'light'),
("Reg. XMM", 100, 320, 'light'),
("ADDPS", 280, 340, 'light'),
("Reg. YMM", 520, 320, 'light'),
("Ops FP", 375, 250, 'light'),
]
node_r = 42
svg.rect(50, 275, 300, 110, fill='none', stroke='border', dash=True)
svg.text(200, 395, "Comunidad: Conjunto instrucciones SSE", 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, "Desarrollo"), (0, 2, "Desarrollo"),
(1, 3, "Uso"), (1, 6, ""), (1, 4, "Incluye"),
(2, 5, "Uso"), (2, 6, "Ejecución"),
(6, 3, ""), (6, 5, "Operación"),
]
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'))
def fig3_9():
"""Agentic RAG vs Non-Agentic RAG — Figure 3-9."""
w, h = 880, 560
svg = SVG(w, h)
col_w = 400
lx, rx = 20, 460
svg.rect(lx, 50, col_w, 45, fill='medium')
svg.text(lx + col_w / 2, 73, "RAG No Agéntico", size=FS_BODY, bold=True)
steps_l = [
("Consulta: \"¿Pena por lesiones graves negligentes \nsi el acusado tiene antecedentes de robo?\"", 'light'),
("Búsqueda única:\n\"Sanción por lesiones graves negligentes\"", 'light'),
("Resultado: Solo artículos básicos hallados\n(falta contexto relevante)", 'code_bg'),
("Generación directa: Faltan factores de\n\"alcohol\" y \"antecedentes\"", '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, "Paso único · Información incompleta", size=FS_BODY, fill='text_light')
svg.line(440, 50, 440, h - 5, color='dark', dash=True)
svg.rect(rx, 50, col_w, 45, fill='medium')
svg.text(rx + col_w / 2, 73, "RAG Agéntico (ReAct)", size=FS_BODY, bold=True)
steps_r = [
("Pensamiento: Descomponer en 3 subpreguntas", 'light'),
("Búsqueda ①: \"Sanción lesiones graves negligentes\"\nBúsqueda ②: \"Responsabilidad penal por embriaguez\"\nBúsqueda ③: \"Efecto de antecedentes por robo\"", 'code_bg'),
("Observación: Normas básicas halladas pero falta\nconexión entre \"antecedentes\" y \"lesiones\"", 'light'),
("Búsqueda ④: \"Interpretación judicial de\nreincidencia en distintos delitos\"", 'code_bg'),
("Síntesis: Respuesta completa con todas las\nnormas aplicables y análisis penal", '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)
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, "Iteración", size=FS_SMALL, fill='text_light',
anchor='start')
svg.text(rx + col_w / 2, h - 15, "Iteración multirronda · Información completa", size=FS_BODY, fill='text_light')
svg.save(os.path.join(OUT, 'fig3-9.svg'))
def fig3_10():
"""Agentic RAG System Architecture — Figure 3-10."""
w, h = 880, 500
svg = SVG(w, h)
svg.text(w / 2, 30, "Experimento 3.6: Arquitectura del sistema RAG agéntico", size=FS_TITLE, bold=True)
svg.rect(220, 55, 440, 200, fill='white', stroke='border')
svg.text(440, 78, "Agente (Bucle ReAct)", size=FS_BODY, bold=True)
react_items = [
("① Pensamiento", 240, 100, 180, 45, 'light'),
("② Acción", 460, 100, 180, 45, 'medium'),
("③ Observación", 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')
svg.text(360, 165, "Bucle hasta que la información sea suficiente", size=FS_TINY, fill='text_light')
svg.box(20, 95, 160, 55, "Consulta usuario", fill='medium', bold=True, font_size=FS_BODY)
svg.arrow(180, 122, 218, 122)
svg.box(700, 95, 160, 55, "Respuesta final", fill='medium', bold=True, font_size=FS_BODY)
svg.arrow(660, 122, 698, 122)
svg.rect(100, 290, 680, 85, fill='white', stroke='border', dash=True)
svg.text(440, 312, "Capa de herramientas", 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)
svg.rect(100, 400, 680, 85, fill='white', stroke='dark', dash=True)
svg.text(440, 420, "Backend de base de conocimiento (intercambiable)", size=FS_BODY, bold=True)
backends = [
("retrieval-pipeline\nBúsqueda híbrida", 120),
("structured-index\nRAPTOR/GraphRAG", 340),
("contextual-retrieval\nConsciente del contexto", 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'))
def fig3_11():
"""Context-aware retrieval — Figure 3-11."""
w, h = 880, 430
svg = SVG(w, h)
svg.text(w / 2, 30, "Búsqueda consciente del contexto", size=FS_TITLE, bold=True)
svg.rect(20, 55, 400, 170, fill='white', stroke='border')
svg.text(220, 78, "Fragmentado tradicional (sin contexto)", size=FS_BODY, bold=True)
svg.rect(40, 95, 360, 50, fill='code_bg', stroke='dark', rx=4)
svg.mono(50, 112, "Los ingresos del segundo trimestre subieron 3%,", size=FS_TINY)
svg.mono(50, 132, "debido principalmente a nuevas líneas de productos.", size=FS_TINY)
svg.text(220, 170, "Pregunta: ¿Qué \"empresa\"? ¿Qué año?", size=FS_SMALL, fill='text_light')
svg.text(220, 195, "→ Búsqueda coincide con datos de ingresos de empresas no relacionadas", size=FS_SMALL, fill='text_light')
svg.rect(460, 55, 400, 170, fill='white', stroke='border')
svg.text(660, 78, "Fragmentado consciente del contexto", size=FS_BODY, bold=True)
svg.rect(480, 95, 360, 35, fill='medium')
svg.mono(490, 113, "[Informe Financiero ACME Q2 2025 · Indicadores Clave]", size=FS_TINY)
svg.rect(480, 130, 360, 50, fill='code_bg', stroke='dark', rx=4)
svg.mono(490, 148, "Los ingresos del segundo trimestre subieron 3%,", size=FS_TINY)
svg.mono(490, 168, "debido principalmente a nuevas líneas de productos.", size=FS_TINY)
svg.text(660, 200, "→ Coincidencia exacta ACME + Q2 + aumento ingresos", size=FS_SMALL, fill='text_light')
svg.text(440, 140, "→", size=FS_TITLE, bold=True)
svg.line(20, 250, 860, 250, color='dark', dash=True)
svg.text(w / 2, 275, "Fase de indexación: el LLM genera el prefijo de contexto", size=FS_BODY, bold=True)
flow_y = 300
svg.box(30, flow_y, 180, 55, "Documento original", 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, "Fragmentación", 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 genera prefijo\n(caché de prompt)", 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, "Prefijo + texto orig.\n→ Índice", fill='light', font_size=FS_SMALL, bold=True)
svg.text(w / 2, h - 20,
"Impacto: Tasa de fallos ↓49% (+BM25), ↓67% (+reclasificación) — datos de Anthropic",
size=FS_SMALL, fill='text_light')
svg.save(os.path.join(OUT, 'fig3-11.svg'))
def fig3_12():
"""Structured knowledge extraction pipeline — Figure 3-12."""
w, h = 880, 510
svg = SVG(w, h)
svg.text(w / 2, 30, "Experimento 3.10: Extracción de conocimiento estructurado (precedentes)", size=FS_TITLE, bold=True)
svg.rect(20, 55, 840, 200, fill='white', stroke='border')
svg.text(440, 78, "Fase 1: Extracción y estructuración de conocimiento", size=FS_BODY, bold=True)
svg.rect(40, 95, 180, 65, fill='code_bg', stroke='dark', rx=4)
svg.text(130, 113, "Sentencias originales", size=FS_SMALL, bold=True)
svg.mono(50, 138, "Conjunto datos CAIL2018", size=FS_TINY)
svg.arrow(220, 127, 258, 127)
svg.rect(260, 95, 180, 65, fill='medium')
svg.text(350, 113, "Descubrimiento factores LLM", size=FS_SMALL, bold=True)
svg.text(350, 138, "Esquema de abajo a arriba", size=FS_SMALL, fill='text_light')
svg.arrow(440, 127, 478, 127)
svg.rect(480, 95, 200, 65, fill='code_bg', stroke='dark', rx=4)
svg.text(580, 113, "JSON estructurado", 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)
svg.rect(40, 170, 400, 70, fill='light')
svg.text(240, 188, "Esquema de datos modular", size=FS_SMALL, bold=True)
svg.text(240, 212, "Esquema básico (entrega voluntaria/indemnización/antecedentes) + esquema por delito", size=FS_SMALL, fill='text_light')
svg.text(240, 232, "(robo→monto involucrado, lesiones→nivel de lesión)", size=FS_SMALL, fill='text_light')
svg.rect(20, 270, 840, 200, fill='white', stroke='border')
svg.text(440, 293, "Fase 2: Análisis de factores y modelado de conocimiento", size=FS_BODY, bold=True)
svg.rect(40, 310, 200, 65, fill='light')
svg.text(140, 328, "Vectorización de características", size=FS_SMALL, bold=True)
svg.text(140, 350, "Codificación one-hot + multi-hot", size=FS_SMALL, fill='text_light')
svg.text(140, 370, "+ transf. logarítmica + estandariz.", size=FS_SMALL, fill='text_light')
svg.arrow(240, 342, 278, 342)
svg.rect(280, 310, 200, 65, fill='medium')
svg.text(380, 328, "Agrupamiento KMeans", size=FS_SMALL, bold=True)
svg.text(380, 350, "Descubrimiento \"prototipos de caso\"", size=FS_SMALL, fill='text_light')
svg.text(380, 370, "p. ej., \"riña sin armas, lesión leve\"", size=FS_SMALL, fill='text_light')
svg.arrow(480, 342, 518, 342)
svg.rect(520, 310, 200, 65, fill='light')
svg.text(620, 328, "Modelo importancia factores", size=FS_SMALL, bold=True)
svg.text(620, 350, "Cuantificar peso de cada factor", size=FS_SMALL, fill='text_light')
svg.text(620, 370, "Construir lógica de sentencia", size=FS_SMALL, fill='text_light')
svg.arrow(620, 375, 620, 400)
svg.rect(40, 400, 720, 60, fill='light')
svg.text(400, 420, "Aplicación: Agente de consulta legal conversacional", size=FS_BODY, bold=True)
svg.text(400, 445, "Preguntar según importancia → recuperar prototipos similares → análisis penal basado en datos",
size=FS_SMALL, fill='text_light')
svg.save(os.path.join(OUT, 'fig3-12.svg'))
def fig3_13():
"""Externalized learning loop — Figure 3-13."""
w, h = 880, 490
svg = SVG(w, h)
svg.text(w / 2, 30, "Aprendizaje externalizado: bucle cerrado de experiencia a capacidad", size=FS_TITLE, bold=True)
cx, cy = 440, 210
svg.circle(cx, cy, 55, fill='medium', label="Agente", font_size=FS_BODY)
steps = [
("① Ejecutar tarea", 120, 100, "Procesar reembolso\nLlamar a API de servicio"),
("② Recibir retroalim.", 680, 100, "$45 reembolsados con éxito\nSe requiere verificar últimos 4 dígitos"),
("③ Reflexionar y resumir", 680, 310, "El LLM resume la experiencia:\n\"Reembolso Comp. A requiere verificación\""),
("④ Guardar en base conoc.", 340, 380, "Experiencia → índice vectorizado\nProceso → código de herramienta"),
("⑤ Recuperar y reutilizar", 120, 310, "Tarea similar → recuperar experiencia\nUsar estrategia exitosa directamente"),
]
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))
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')
svg.rect(30, 395, 180, 28, fill='dark')
svg.text(120, 409, "Conocimiento: resumen", size=FS_SMALL, fill='white')
svg.rect(670, 395, 180, 28, fill='dark')
svg.text(760, 409, "Herramienta: proceso→código", size=FS_SMALL, fill='white')
svg.save(os.path.join(OUT, 'fig3-13.svg'))
def fig3_14():
"""GAIA experience learning system — Figure 3-14."""
w, h = 880, 510
svg = SVG(w, h)
svg.text(w / 2, 30, "Experimento 3.11: Sistema de aprendizaje de experiencia GAIA", size=FS_TITLE, bold=True)
box_h = 60
step_gap = 75
base_y = 100
lx = 20
svg.rect(lx, 55, 400, 420, fill='white', stroke='border')
svg.text(lx + 200, 80, "Modo de aprendizaje", size=FS_BODY, bold=True)
learn_steps = [
("Tarea GAIA", 'medium', "Problema complejo multipaso"),
("Ejecución del Agente", 'light', "Navegador + archivo + intérprete código"),
("¿Éxito en tarea?", 'light', "Evaluación automática (AWorld)"),
("Reflexión y resumen LLM", 'medium', "Extraer resumen de estrategia"),
("Experiencia → Vectorización", 'light', "Guardar en base de experiencia"),
]
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)
rx = 460
svg.rect(rx, 55, 400, 420, fill='white', stroke='border')
svg.text(rx + 200, 80, "Modo de aplicación", size=FS_BODY, bold=True)
apply_steps = [
("Nueva Tarea GAIA", 'medium', "Recibir nueva pregunta"),
("Búsqueda semántica de exp.", 'light', "Buscar tarea similar en base de exp."),
("Inyectar en prompt del sist.", 'medium', "Estrategias pasadas como ejemplos"),
("Ejecución del Agente", 'light', "Resolución más eficiente basada en exp."),
("Éxito ↑ Eficiencia ↑", 'dark', "Autoevolución: fortalece con el tiempo"),
]
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)
kb_cy = base_y + 2 * step_gap + box_h / 2
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, "Base de Experiencia", size=FS_SMALL, fill='white', bold=True)
svg.text((kb_x1 + kb_x2) / 2, kb_cy + 12, "(Índice Vectorial)", size=FS_TINY, fill='white')
last_y = base_y + 4 * step_gap + box_h / 2
svg.arrow(lx + 350, last_y, kb_x1 - 2, kb_cy + 10)
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'))
if __name__ == '__main__':
os.makedirs(OUT, exist_ok=True)
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()
print("Chapter 3 figures generated.")