Salta el contingut

5.2 LlamaIndex: RAG i Agents d'Alt Nivell

Versió de Referència

LlamaIndex 0.10.x (també conegut com llama-index). LlamaIndex s'ha especialitzat en RAG i indexació de dades, mentre que LangChain ha evolucionat cap a orquestració d'agents. Tots dos es poden usar conjuntament.


🦙 Filosofia de LlamaIndex

LlamaIndex es centra en connectar LLMs amb les teves dades de forma eficient. La seva arquitectura s'organitza al voltant de:

Documents → Nodes → Index → Query Engine → Response
  • Documents: la informació crua (PDFs, webs, bases de dades, APIs...)
  • Nodes: fragments indexables dels documents
  • Index: estructura de dades per a cerca eficient
  • Query Engine: interfície per fer preguntes sobre l'índex
  • Response: resposta sintetitzada pel LLM

🚀 Instal·lació i Configuració

pip install llama-index==0.10.65 \
            llama-index-llms-openai \
            llama-index-embeddings-openai \
            llama-index-vector-stores-chroma \
            llama-index-readers-file \
            chromadb
from llama_index.core import Settings
from llama_index.llms.openai import OpenAI
from llama_index.embeddings.openai import OpenAIEmbedding

# Configuració global (s'aplica a totes les operacions)
Settings.llm = OpenAI(model="gpt-4o-mini", temperature=0)
Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small")
Settings.chunk_size = 512
Settings.chunk_overlap = 50

📚 Ingesta i Indexació de Documents

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.core.node_parser import SentenceSplitter

# ── Carregar documents ─────────────────────────────────────────

# Opció A: Directori de fitxers
documents = SimpleDirectoryReader(
    input_dir="./docs/",
    required_exts=[".pdf", ".txt", ".md"],
    recursive=True
).load_data()

# Opció B: Fitxer específic
from llama_index.readers.file import PDFReader
documents = PDFReader().load_data(file="manual_cisco.pdf")

# Opció C: URL
from llama_index.readers.web import SimpleWebPageReader
documents = SimpleWebPageReader(html_to_text=True).load_data(
    urls=["https://docs.python.org/3/"]
)

print(f"Carregats {len(documents)} documents")

# ── Chunking personalitzat ─────────────────────────────────────

splitter = SentenceSplitter(
    chunk_size=512,
    chunk_overlap=50,
    paragraph_separator="\n\n",
)
nodes = splitter.get_nodes_from_documents(documents)
print(f"Dividit en {len(nodes)} nodes")

# ── Crear l'índex ──────────────────────────────────────────────

# Índex en memòria (s'ha de recrear cada cop)
index = VectorStoreIndex(nodes)

# Índex persistent amb ChromaDB
import chromadb
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import StorageContext

chroma_client = chromadb.PersistentClient(path="./chroma_data")
chroma_collection = chroma_client.get_or_create_collection("docs")

vector_store = ChromaVectorStore(chroma_collection=chroma_collection)
storage_context = StorageContext.from_defaults(vector_store=vector_store)

index = VectorStoreIndex(
    nodes,
    storage_context=storage_context
)
print("✅ Índex creat i persistit")

🔍 Query Engines: Fer Preguntes

# ── Query Engine bàsic ─────────────────────────────────────────

query_engine = index.as_query_engine(
    similarity_top_k=4,             # Recuperar els 4 nodes més similars
    response_mode="compact",        # compact/tree_summarize/refine
)

response = query_engine.query(
    "Quin és el procediment per configurar VLANs en un switch Cisco?"
)
print(response.response)

# Veure els nodes fonts (per verificació)
for node in response.source_nodes:
    print(f"  Score: {node.score:.3f} | {node.text[:100]}...")

# ── Query Engine amb filtres de metadades ──────────────────────

from llama_index.core.vector_stores import MetadataFilters, MetadataFilter, FilterOperator

# Filtrar per autor o per secció específica del document
filters = MetadataFilters(filters=[
    MetadataFilter(key="categoria", value="xarxes", operator=FilterOperator.EQ),
])

query_engine_filtrat = index.as_query_engine(
    similarity_top_k=4,
    filters=filters
)

# ── Sub-Question Query Engine ──────────────────────────────────
# Per a preguntes complexes que requereixen consultar múltiples sub-preguntes

from llama_index.core.query_engine import SubQuestionQueryEngine
from llama_index.core.tools import QueryEngineTool

# Crear eines a partir de múltiples índexs
tool_asix = QueryEngineTool.from_defaults(
    query_engine=index_asix,
    name="docs_asix",
    description="Documentació tècnica d'ASIX: xarxes, sistemes, seguretat"
)
tool_daw = QueryEngineTool.from_defaults(
    query_engine=index_daw,
    name="docs_daw",
    description="Documentació de DAW: HTML, CSS, JavaScript, frameworks web"
)

# L'engine descompon preguntes complexes en sub-preguntes per a cada eina
sub_question_engine = SubQuestionQueryEngine.from_defaults(
    query_engine_tools=[tool_asix, tool_daw],
    verbose=True
)

response = sub_question_engine.query(
    "Quin és el rol dels protocols HTTPS en tant que ASIX com en el desenvolupament web?"
)

🤖 Agents amb LlamaIndex

from llama_index.core.agent import ReActAgent
from llama_index.core.tools import FunctionTool, QueryEngineTool

# ── Definir eines ──────────────────────────────────────────────

def calcular_subxarxa(ip_cidr: str) -> str:
    """
    Calcula informació de subxarxa a partir d'una notació CIDR.
    Exemples: '192.168.1.0/24', '10.0.0.0/8'
    """
    import ipaddress
    try:
        net = ipaddress.IPv4Network(ip_cidr, strict=False)
        return (
            f"Xarxa: {net.network_address}\n"
            f"Màscara: {net.netmask}\n"
            f"Broadcast: {net.broadcast_address}\n"
            f"Nombre d'hosts: {net.num_addresses - 2}\n"
            f"Rang d'hosts: {list(net.hosts())[0]} - {list(net.hosts())[-1]}\n"
            f"Wildcard: {net.hostmask}"
        )
    except ValueError as e:
        return f"Error: {e}"

subxarxa_tool = FunctionTool.from_defaults(
    fn=calcular_subxarxa,
    name="calcular_subxarxa",
    description="Calcula informació de subxarxa (màscara, hosts, broadcast) a partir de notació CIDR."
)

docs_tool = QueryEngineTool.from_defaults(
    query_engine=query_engine,
    name="documentacio_tecnica",
    description="Consulta la documentació tècnica interna de l'institut."
)

# ── Crear l'agent ──────────────────────────────────────────────

from llama_index.llms.openai import OpenAI as LlamaOpenAI

agent = ReActAgent.from_tools(
    tools=[subxarxa_tool, docs_tool],
    llm=LlamaOpenAI(model="gpt-4o", temperature=0),
    verbose=True,
    max_iterations=10,
)

# ── Executar tasques ───────────────────────────────────────────

response = agent.chat(
    "Divideix la xarxa 192.168.10.0/24 en 4 subxarxes iguals. "
    "Per a cada una, indica el rang d'adreces d'host."
)
print(response.response)

🔄 Streaming de Respostes

# LlamaIndex suporta streaming natiu
query_engine_stream = index.as_query_engine(streaming=True)

streaming_response = query_engine_stream.query(
    "Explica el procés complert de la handshake TCP en 5 passos"
)

# Mostrar la resposta token a token
for text in streaming_response.response_gen:
    print(text, end="", flush=True)
print()  # Nova línia al final

✅ Activitats

Exercici 5.2.1 — RAG sobre Documentació Tècnica

Indexa la documentació oficial de Cisco Networking Basics (o qualsevol doc tècnica de xarxes que tinguis) amb LlamaIndex. Fes 10 preguntes i avalua la qualitat de les respostes. Compara el mode compact vs tree_summarize.

Exercici 5.2.2 — Agent de Càlcul de Xarxes

Crea un agent LlamaIndex amb almenys 4 eines: - calcular_subxarxa(cidr) — Informació de subxarxa - sumaritzar_cidr(ips) — Resumir llista d'IPs en CIDRs - convertir_mascara(mascara) — Convertir entre notació decimal i CIDR - docs_xarxes — Query engine sobre documentació de xarxes

Prova tasques que requereixin combinar diverses eines.

Exercici 5.2.3 — Comparativa LangChain vs LlamaIndex

Implementa el mateix pipeline RAG (documents → índex → query) tant amb LangChain com amb LlamaIndex. Compara: temps de setup, qualitat de recuperació, facilitat d'ús, i casos on cada un brilla més.


📚 Documentació de Referència