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: 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¶
- LlamaIndex Docs — Documentació oficial LlamaIndex 0.10
- LlamaHub — Col·lecció de readers, tools i integracions
- LlamaIndex Blog — Tutorials i casos d'ús avançats