Ultima attività 3 weeks ago

Genera un documento hipotético con Ollama a partir de una consulta, lo embeddea con bge-m3, y busca por similitud coseno en la BD. Técnica del paper de Gao et al. 2022. Episodio 821 de atareao con Linux.

Revisione 4772c75ccd5e450d064322e67f47cba5c5849ac1

rag_hyde.py Raw
1#!/usr/bin/env python3
2"""
3rag_hyde.py - HyDE (Hypothetical Document Embeddings) para RAG
4
5Genera un documento hipotético con Ollama a partir de una consulta,
6lo embeddea con bge-m3, y busca por similitud coseno en la BD.
7
8Uso:
9 python rag_hyde.py "¿qué es una variable de entorno?"
10 python rag_hyde.py "explica la configuración de red" --top 10
11"""
12
13import argparse
14import sqlite3
15import sys
16import numpy as np
17from pathlib import Path
18
19DB_PATH = Path.home() / ".cerebro" / "rag_conocimiento.db"
20OLLAMA_MODEL = "llama3.2"
21EMBED_MODEL = "bge-m3"
22
23HYDE_PROMPT = """Eres un asistente experto en Linux y administración de sistemas.
24Dada una pregunta, genera un documento hipotético de 3-5 párrafos que RESPONDA
25a la pregunta como si fuera un artículo del blog o la documentación oficial.
26
27Sé técnico pero claro. Usa markdown si es necesario.
28No digas "basado en" ni "hipotético". Responde DIRECTAMENTE.
29
30Pregunta: {consulta}
31
32Documento:"""
33
34
35def _ollama():
36 try:
37 import ollama
38 except ImportError:
39 print("Necesitas instalar ollama: pip install ollama")
40 sys.exit(1)
41 return ollama
42
43
44def generar_doc_hipotetico(consulta: str) -> str:
45 ollama = _ollama()
46 prompt = HYDE_PROMPT.format(consulta=consulta)
47 respuesta = ollama.generate(model=OLLAMA_MODEL, prompt=prompt)
48 return respuesta["response"]
49
50
51def embed_text(texto: str) -> list[float]:
52 ollama = _ollama()
53 respuesta = ollama.embed(model=EMBED_MODEL, input=texto)
54 return respuesta["embeddings"][0]
55
56
57def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
58 return float(np.dot(a, b))
59
60
61def cargar_embeddings_db() -> list[tuple[int, str, np.ndarray]]:
62 if not DB_PATH.exists():
63 print(f"BD no encontrada: {DB_PATH}")
64 sys.exit(1)
65 conn = sqlite3.connect(str(DB_PATH))
66 cursor = conn.cursor()
67 cursor.execute("""SELECT c.id, c.content, e.vector
68 FROM chunks c JOIN embeddings e ON e.chunk_id = c.id""")
69 filas = cursor.fetchall()
70 conn.close()
71 if not filas:
72 print("No hay chunks con embeddings en la BD.")
73 sys.exit(1)
74 return [(chunk_id, contenido, np.frombuffer(blob, dtype=np.float32).copy())
75 for chunk_id, contenido, blob in filas]
76
77
78def buscar_semantica(query_emb: np.ndarray, chunks: list, top_k: int = 5) -> list:
79 query_norm = query_emb / (np.linalg.norm(query_emb) + 1e-10)
80 scores = []
81 for chunk_id, contenido, vector in chunks:
82 vec_norm = vector / (np.linalg.norm(vector) + 1e-10)
83 sim = cosine_sim(query_norm, vec_norm)
84 scores.append((chunk_id, contenido, sim))
85 scores.sort(key=lambda x: x[2], reverse=True)
86 return scores[:top_k]
87
88
89def main():
90 parser = argparse.ArgumentParser(description="HyDE: genera un doc hipotético y busca en la BD")
91 parser.add_argument("consulta", help="Consulta en lenguaje natural")
92 parser.add_argument("--top", type=int, default=5, help="Resultados a mostrar")
93 args = parser.parse_args()
94
95 doc_hipotetico = generar_doc_hipotetico(args.consulta)
96 print(f"Documento generado:\n{ * 50}\n{doc_hipotetico[:500]}...\n{ * 50}\n")
97
98 query_emb = np.array(embed_text(doc_hipotetico), dtype=np.float32)
99 chunks = cargar_embeddings_db()
100 resultados = buscar_semantica(query_emb, chunks, args.top)
101
102 for i, (chunk_id, contenido, score) in enumerate(resultados, 1):
103 preview = contenido[:150].replace("\n", " ")
104 print(f" {i}. [{score:.4f}] (chunk #{chunk_id})")
105 print(f" {preview}...")
106
107
108if __name__ == "__main__":
109 main()