rag_hyde.py
· 3.5 KiB · Python
Ham
#!/usr/bin/env python3
"""
rag_hyde.py - HyDE (Hypothetical Document Embeddings) para RAG
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.
Uso:
python rag_hyde.py "¿qué es una variable de entorno?"
python rag_hyde.py "explica la configuración de red" --top 10
"""
import argparse
import sqlite3
import sys
import numpy as np
from pathlib import Path
DB_PATH = Path.home() / ".cerebro" / "rag_conocimiento.db"
OLLAMA_MODEL = "llama3.2"
EMBED_MODEL = "bge-m3"
HYDE_PROMPT = """Eres un asistente experto en Linux y administración de sistemas.
Dada una pregunta, genera un documento hipotético de 3-5 párrafos que RESPONDA
a la pregunta como si fuera un artículo del blog o la documentación oficial.
Sé técnico pero claro. Usa markdown si es necesario.
No digas "basado en" ni "hipotético". Responde DIRECTAMENTE.
Pregunta: {consulta}
Documento:"""
def _ollama():
try:
import ollama
except ImportError:
print("Necesitas instalar ollama: pip install ollama")
sys.exit(1)
return ollama
def generar_doc_hipotetico(consulta: str) -> str:
ollama = _ollama()
prompt = HYDE_PROMPT.format(consulta=consulta)
respuesta = ollama.generate(model=OLLAMA_MODEL, prompt=prompt)
return respuesta["response"]
def embed_text(texto: str) -> list[float]:
ollama = _ollama()
respuesta = ollama.embed(model=EMBED_MODEL, input=texto)
return respuesta["embeddings"][0]
def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
return float(np.dot(a, b))
def cargar_embeddings_db() -> list[tuple[int, str, np.ndarray]]:
if not DB_PATH.exists():
print(f"BD no encontrada: {DB_PATH}")
sys.exit(1)
conn = sqlite3.connect(str(DB_PATH))
cursor = conn.cursor()
cursor.execute("""SELECT c.id, c.content, e.vector
FROM chunks c JOIN embeddings e ON e.chunk_id = c.id""")
filas = cursor.fetchall()
conn.close()
if not filas:
print("No hay chunks con embeddings en la BD.")
sys.exit(1)
return [(chunk_id, contenido, np.frombuffer(blob, dtype=np.float32).copy())
for chunk_id, contenido, blob in filas]
def buscar_semantica(query_emb: np.ndarray, chunks: list, top_k: int = 5) -> list:
query_norm = query_emb / (np.linalg.norm(query_emb) + 1e-10)
scores = []
for chunk_id, contenido, vector in chunks:
vec_norm = vector / (np.linalg.norm(vector) + 1e-10)
sim = cosine_sim(query_norm, vec_norm)
scores.append((chunk_id, contenido, sim))
scores.sort(key=lambda x: x[2], reverse=True)
return scores[:top_k]
def main():
parser = argparse.ArgumentParser(description="HyDE: genera un doc hipotético y busca en la BD")
parser.add_argument("consulta", help="Consulta en lenguaje natural")
parser.add_argument("--top", type=int, default=5, help="Resultados a mostrar")
args = parser.parse_args()
doc_hipotetico = generar_doc_hipotetico(args.consulta)
print(f"Documento generado:\n{─ * 50}\n{doc_hipotetico[:500]}...\n{─ * 50}\n")
query_emb = np.array(embed_text(doc_hipotetico), dtype=np.float32)
chunks = cargar_embeddings_db()
resultados = buscar_semantica(query_emb, chunks, args.top)
for i, (chunk_id, contenido, score) in enumerate(resultados, 1):
preview = contenido[:150].replace("\n", " ")
print(f" {i}. [{score:.4f}] (chunk #{chunk_id})")
print(f" {preview}...")
if __name__ == "__main__":
main()
| 1 | #!/usr/bin/env python3 |
| 2 | """ |
| 3 | rag_hyde.py - HyDE (Hypothetical Document Embeddings) para RAG |
| 4 | |
| 5 | Genera un documento hipotético con Ollama a partir de una consulta, |
| 6 | lo embeddea con bge-m3, y busca por similitud coseno en la BD. |
| 7 | |
| 8 | Uso: |
| 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 | |
| 13 | import argparse |
| 14 | import sqlite3 |
| 15 | import sys |
| 16 | import numpy as np |
| 17 | from pathlib import Path |
| 18 | |
| 19 | DB_PATH = Path.home() / ".cerebro" / "rag_conocimiento.db" |
| 20 | OLLAMA_MODEL = "llama3.2" |
| 21 | EMBED_MODEL = "bge-m3" |
| 22 | |
| 23 | HYDE_PROMPT = """Eres un asistente experto en Linux y administración de sistemas. |
| 24 | Dada una pregunta, genera un documento hipotético de 3-5 párrafos que RESPONDA |
| 25 | a la pregunta como si fuera un artículo del blog o la documentación oficial. |
| 26 | |
| 27 | Sé técnico pero claro. Usa markdown si es necesario. |
| 28 | No digas "basado en" ni "hipotético". Responde DIRECTAMENTE. |
| 29 | |
| 30 | Pregunta: {consulta} |
| 31 | |
| 32 | Documento:""" |
| 33 | |
| 34 | |
| 35 | def _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 | |
| 44 | def 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 | |
| 51 | def 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 | |
| 57 | def cosine_sim(a: np.ndarray, b: np.ndarray) -> float: |
| 58 | return float(np.dot(a, b)) |
| 59 | |
| 60 | |
| 61 | def 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 | |
| 78 | def 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 | |
| 89 | def 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 | |
| 108 | if __name__ == "__main__": |
| 109 | main() |