knowledge-base

star 0

Base de conocimiento vectorial para proyectos — indexa archivos, docs y código para búsqueda semántica instantánea. Usa cuando necesites que el agente "conozca" un proyecto entero o una colección de documentos y pueda responder preguntas sobre él.

garri333 By garri333 schedule Updated 2/18/2026

name: knowledge-base version: 1.0.0 description: Base de conocimiento vectorial para proyectos — indexa archivos, docs y código para búsqueda semántica instantánea. Usa cuando necesites que el agente "conozca" un proyecto entero o una colección de documentos y pueda responder preguntas sobre él. tags: [knowledge-base, embeddings, rag, vector-search, lancedb, chromadb, semantic-search] author: garri333 license: MIT source: https://skills.sh/

Knowledge Base Skill

Cuándo usar esta skill

  • Proyecto con muchos archivos y necesitas "preguntar" sobre el código
  • Documentación extensa (manuales, PDFs, wikis) que el agente debe conocer
  • Implementar un chatbot con conocimiento sobre tu empresa/producto
  • Sistema de onboarding que responde preguntas sobre el proyecto
  • Búsqueda semántica sobre notas, emails, transcripciones

Arquitectura de una Knowledge Base

Documentos  →  Chunking  →  Embeddings  →  Vector DB  →  Búsqueda semántica
  (texto)      (trozos)    (OpenAI/local)  (LanceDB)     (query → top-k docs)
                                                              ↓
                                                        LLM + contexto = Respuesta

Setup inicial con LanceDB (local, sin servidor)

# pip install lancedb openai pypdf2 python-docx
import lancedb
import openai
import os
from pathlib import Path

# Configurar embedding
client = openai.OpenAI(api_key=os.getenv("OPENAI_API_KEY"))

def embed(texts: list[str]) -> list[list[float]]:
    """Generar embeddings con OpenAI text-embedding-3-small"""
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=texts
    )
    return [e.embedding for e in response.data]

# Crear/conectar base de datos
db = lancedb.connect("./knowledge-base")

Indexar un directorio de proyectos

import hashlib
from typing import Generator

def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
    """Dividir texto en chunks con overlap"""
    words = text.split()
    chunks = []
    
    for i in range(0, len(words), chunk_size - overlap):
        chunk = " ".join(words[i:i + chunk_size])
        if chunk:
            chunks.append(chunk)
    
    return chunks


def load_file(path: Path) -> str | None:
    """Cargar contenido de un archivo según su extensión"""
    try:
        suffix = path.suffix.lower()
        
        if suffix in [".txt", ".md", ".py", ".ts", ".js", ".json", ".yaml", ".yml", ".env.example"]:
            return path.read_text(encoding="utf-8")
        
        elif suffix == ".pdf":
            import PyPDF2
            reader = PyPDF2.PdfReader(str(path))
            return "\n".join(page.extract_text() for page in reader.pages)
        
        elif suffix in [".docx"]:
            from docx import Document
            doc = Document(str(path))
            return "\n".join(p.text for p in doc.paragraphs)
        
        return None
    
    except Exception as e:
        print(f"[warn] No se pudo leer {path}: {e}")
        return None


def index_directory(
    directory: str,
    db: lancedb.LanceDBConnection,
    table_name: str = "knowledge",
    exclude: list[str] = None,
    extensions: list[str] = None,
) -> int:
    """
    Indexar todos los archivos de un directorio.
    Retorna el número de chunks indexados.
    """
    exclude = exclude or ["node_modules", ".git", "__pycache__", ".venv", "dist", "build"]
    extensions = extensions or [".md", ".txt", ".py", ".ts", ".js", ".json", ".pdf", ".docx"]
    
    records = []
    
    for path in Path(directory).rglob("*"):
        # Saltar directorios excluidos
        if any(ex in path.parts for ex in exclude):
            continue
        
        if path.is_file() and path.suffix.lower() in extensions:
            content = load_file(path)
            if not content or len(content.strip()) < 50:
                continue
            
            chunks = chunk_text(content)
            
            for i, chunk in enumerate(chunks):
                records.append({
                    "text": chunk,
                    "source": str(path),
                    "chunk_index": i,
                    "file_type": path.suffix.lower(),
                    "doc_id": hashlib.md5(f"{path}{i}".encode()).hexdigest(),
                })
    
    if not records:
        print("No se encontraron documentos para indexar.")
        return 0
    
    # Generar embeddings en lotes
    BATCH_SIZE = 100
    all_embeddings = []
    
    for i in range(0, len(records), BATCH_SIZE):
        batch = records[i:i + BATCH_SIZE]
        texts = [r["text"] for r in batch]
        embeddings = embed(texts)
        all_embeddings.extend(embeddings)
        print(f"  Embeddings: {i + len(batch)}/{len(records)}")
    
    # Añadir embeddings a los records
    for record, embedding in zip(records, all_embeddings):
        record["vector"] = embedding
    
    # Crear/actualizar tabla en LanceDB
    if table_name in db.table_names():
        table = db.open_table(table_name)
        table.add(records)
    else:
        table = db.create_table(table_name, data=records)
    
    print(f"✅ {len(records)} chunks indexados desde {directory}")
    return len(records)

Búsqueda semántica

def search(
    query: str,
    db: lancedb.LanceDBConnection,
    table_name: str = "knowledge",
    top_k: int = 5,
    filter: str = None,  # Ej: "file_type = '.py'"
) -> list[dict]:
    """
    Buscar en la knowledge base por similitud semántica.
    """
    table = db.open_table(table_name)
    query_embedding = embed([query])[0]
    
    results = (
        table.search(query_embedding)
        .limit(top_k)
        .where(filter, prefilter=True) if filter else
        table.search(query_embedding).limit(top_k)
    ).to_list()
    
    # Deduplicar por source + chunk_index
    seen = set()
    unique_results = []
    for r in results:
        key = r["doc_id"]
        if key not in seen:
            seen.add(key)
            unique_results.append(r)
    
    return unique_results


def format_context(results: list[dict]) -> str:
    """Formatear resultados como contexto para el LLM"""
    context_parts = []
    
    for i, result in enumerate(results, 1):
        source = result["source"].replace("\\", "/")
        context_parts.append(
            f"[Fuente {i}: {source}]\n{result['text']}\n"
        )
    
    return "\n---\n".join(context_parts)

Pipeline RAG completo (Retrieval-Augmented Generation)

def answer_with_rag(
    question: str,
    db: lancedb.LanceDBConnection,
    table_name: str = "knowledge",
    system_prompt: str = None,
) -> str:
    """
    Responder una pregunta usando RAG sobre la knowledge base.
    """
    # 1. Recuperar contexto relevante
    results = search(question, db, table_name, top_k=5)
    context = format_context(results)
    
    if not results:
        return "No encontré información relevante en la knowledge base."
    
    # 2. Generar respuesta con contexto
    system = system_prompt or (
        "Eres un asistente experto en este proyecto. "
        "Responde SOLO basándote en el contexto dado. "
        "Si la información no está en el contexto, dilo claramente. "
        "Cita la fuente cuando uses información específica."
    )
    
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {"role": "system", "content": system},
            {
                "role": "user",
                "content": f"Contexto de la knowledge base:\n\n{context}\n\n---\n\nPregunta: {question}"
            }
        ],
        temperature=0.1,  # Respuestas más fieles al contexto
    )
    
    return response.choices[0].message.content


# Uso
if __name__ == "__main__":
    db = lancedb.connect("./knowledge-base")
    
    # Primera vez: indexar
    index_directory("./my-project", db)
    
    # Responder preguntas
    answer = answer_with_rag(
        "¿Cómo se configura la autenticación en este proyecto?",
        db
    )
    print(answer)

Alternativa con ChromaDB

# pip install chromadb
import chromadb
from chromadb.utils import embedding_functions

# Usar embeddings de OpenAI
openai_ef = embedding_functions.OpenAIEmbeddingFunction(
    api_key=os.getenv("OPENAI_API_KEY"),
    model_name="text-embedding-3-small"
)

chroma_client = chromadb.PersistentClient(path="./chroma-db")
collection = chroma_client.get_or_create_collection(
    name="knowledge",
    embedding_function=openai_ef
)

# Añadir documentos
collection.add(
    documents=["Documento 1 content...", "Documento 2 content..."],
    ids=["doc1", "doc2"],
    metadatas=[{"source": "file1.md"}, {"source": "file2.md"}]
)

# Buscar
results = collection.query(
    query_texts=["¿Qué es X?"],
    n_results=5
)

Alternativa local (sin API de OpenAI) con sentence-transformers

# pip install sentence-transformers lancedb
from sentence_transformers import SentenceTransformer

# Modelo local — no requiere API key — 80MB en disco
model = SentenceTransformer("all-MiniLM-L6-v2")  # Rápido
# model = SentenceTransformer("BAAI/bge-m3")  # Multilingüe, mejor calidad

def embed_local(texts: list[str]) -> list[list[float]]:
    return model.encode(texts, convert_to_numpy=True).tolist()

CLI para gestionar la knowledge base

# kb.py — gestión de knowledge base
import argparse

def main():
    parser = argparse.ArgumentParser(description="Knowledge Base manager")
    subparsers = parser.add_subparsers(dest="command")
    
    # Subcomando: index
    idx = subparsers.add_parser("index", help="Indexar directorio")
    idx.add_argument("directory", help="Directorio a indexar")
    idx.add_argument("--table", default="knowledge")
    idx.add_argument("--rebuild", action="store_true", help="Borrar y reconstruir")
    
    # Subcomando: search
    srch = subparsers.add_parser("search", help="Buscar en la knowledge base")
    srch.add_argument("query", help="Texto a buscar")
    srch.add_argument("--top-k", type=int, default=5)
    srch.add_argument("--table", default="knowledge")
    
    # Subcomando: ask
    ask = subparsers.add_parser("ask", help="Pregunta con RAG")
    ask.add_argument("question", help="Pregunta a responder")
    ask.add_argument("--table", default="knowledge")
    
    args = parser.parse_args()
    db = lancedb.connect("./knowledge-base")
    
    if args.command == "index":
        if args.rebuild and args.table in db.table_names():
            db.drop_table(args.table)
        index_directory(args.directory, db, args.table)
    
    elif args.command == "search":
        results = search(args.query, db, args.table, top_k=args.top_k)
        for r in results:
            print(f"\n[{r['source']}]\n{r['text'][:200]}...")
    
    elif args.command == "ask":
        answer = answer_with_rag(args.question, db, args.table)
        print(f"\n{answer}")

if __name__ == "__main__":
    main()

Casos de uso comunes

# 1. Knowledge base del proyecto (para onboarding)
index_directory("./my-project", db, table_name="project")
answer_with_rag("¿Qué framework se usa y por qué?", db, "project")

# 2. Knowledge base de documentación
index_directory("./docs", db, table_name="docs")
answer_with_rag("¿Cómo se hace un deploy a producción?", db, "docs")

# 3. Knowledge base personal (notas, emails)
index_directory("~/Obsidian", db, table_name="personal")
answer_with_rag("¿Qué decisiones tomé sobre X el año pasado?", db, "personal")

# 4. Búsqueda filtrada por tipo de archivo
results = search("autenticación JWT", db, filter="file_type = '.ts'")

Referencias

Install via CLI
npx skills add https://github.com/garri333/Skills --skill knowledge-base
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator