Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Self-RAG

Sistema de perguntas e respostas sobre documentos PDF (apostilas de lógica de programação e algoritmos) usando Retrieval-Augmented Generation com uma etapa de auto-crítica sobre a própria resposta, e avaliação automática de qualidade via RAGAS.

Contextualização

RAG (Retrieval-Augmented Generation) é o padrão de recuperar trechos relevantes de uma base documental e injetá-los no prompt de um LLM para fundamentar a resposta. "Self-RAG" (Asai et al.) é o nome de uma família de técnicas em que o próprio modelo participa da decisão de quando recuperar, do julgamento da relevância dos documentos e da crítica da resposta gerada, tipicamente usando tokens de reflexão treinados especificamente para isso.

Este projeto implementa uma versão simplificada desse conceito, baseada em auto-crítica pós-geração via prompting (sem tokens de reflexão treinados, sem grading de documentos individuais e sem decisão adaptativa de "recuperar ou não" — o retrieval é sempre executado). O fluxo é:

  1. Recupera contexto (top-5 chunks por similaridade vetorial).
  2. Gera uma resposta com base nesse contexto.
  3. Pergunta ao próprio LLM, em uma segunda chamada, se a resposta está fundamentada no contexto (resposta binária SIM/NAO).
  4. Se a crĂ­tica for "NAO", refaz a resposta uma Ăşnica vez com um prompt de refinamento.

Arquitetura do pipeline

docs/*.pdf
    │  PyPDFLoader + DirectoryLoader
    â–Ľ
Documentos (1 por página)
    │  RecursiveCharacterTextSplitter (chunk_size=800, overlap=100)
    â–Ľ
Chunks
    │  OpenAIEmbeddings (text-embedding-3-large)
    â–Ľ
Chroma (persistente, batches de 500)
    │  as_retriever(k=5)
    â–Ľ
Contexto recuperado
    │
    â–Ľ
① Prompt de geração ──► ChatOpenAI ──► Resposta
    │
    â–Ľ
② Prompt de auto-crítica (SIM/NAO) ──► ChatOpenAI
    │
    ├── SIM ──► resposta final = resposta do passo ①
    │
    └── NAO ──► ③ Prompt de refinamento ──► ChatOpenAI ──► resposta final
                                                                │
                                                                â–Ľ
                                                    Avaliação RAGAS (5 rodadas)
                                                                │
                                                                â–Ľ
                                                results/self-rag-run-N_i.csv
Etapa Função / arquivo
Configuração de ambiente configure_environment() — rag_settings.py:36-45
Ingestão + chunking + indexação build_vectorstore() — main.py:68-100
Embeddings build_embeddings() — rag_settings.py:67-71
Retrieval vectordb.as_retriever(search_kwargs={"k": 5}) — main.py:154
Geração + auto-crítica + refino self_rag() — main.py:103-144
Rastreamento (LangSmith) @traceable em self_rag_traced() — main.py:147-149
Avaliação run_ragas() — rag_settings.py:277-298
Persistência dos resultados salvar() — rag_settings.py:301-336
Orquestração / loop principal main() — main.py:152-185

Detalhes técnicos

Prompts

Todos definidos dentro de self_rag() (main.py:103-144).

1. Geração (main.py:109-117):

Contexto:
{context}

Pergunta:
{query}

Responda usando apenas o contexto.

2. Auto-crĂ­tica (main.py:122-128):

Pergunta: {query}
Resposta: {response}

A resposta está fundamentada no contexto?
Responda apenas SIM ou NAO.

A decisĂŁo de refazer a resposta Ă© feita por checagem literal de string: if "NAO" in critique.upper(): (main.py:132).

3. Refinamento (usado sĂł se a crĂ­tica retornar "NAO", main.py:133-141):

Refaça a resposta usando melhor o contexto.

Contexto:
{context}

Pergunta:
{query}

Não há prompt de grading de documentos individuais nem de decisão prévia de "recuperar ou não" — o retrieval é sempre executado incondicionalmente antes da geração.

Chunking

  • Biblioteca: langchain_text_splitters.RecursiveCharacterTextSplitter.
  • chunk_size=800, chunk_overlap=100 (main.py:81-84).
  • Carregamento via DirectoryLoader(DOCS_DIR, glob="**/*.pdf", loader_cls=PyPDFLoader), DOCS_DIR configurável (default ./docs/).
  • Inserção no Chroma em batches de 500 chunks.

Embeddings

def build_embeddings():
    return OpenAIEmbeddings(
        model=os.getenv("OPENAI_EMBEDDING_MODEL", "text-embedding-3-large"),
        api_key=get_openai_api_key(),
    )

Modelo padrĂŁo text-embedding-3-large (OpenAI), dimensĂŁo nativa 3072 nĂŁo fixada explicitamente no cĂłdigo.

Banco vetorial

  • ChromaDB, persistido em disco: persist_directory=CHROMA_PERSIST_DIR (default ./chroma_self_db_openai), collection_name=CHROMA_COLLECTION_NAME (default self_rag_contexts_openai).
  • IngestĂŁo idempotente (vectordb._collection.count() == 0 decide se popula ou pula).

Este projeto nĂŁo usa nenhum grafo de conhecimento.

Parâmetros de recuperação

  • retriever = vectordb.as_retriever(search_kwargs={"k": 5}) — top-5 por similaridade vetorial padrĂŁo do Chroma.
  • CritĂ©rio de re-tentativa: string matching simples ("NAO" in critique.upper()), sem threshold numĂ©rico.
  • NĂşmero máximo de refinamentos: 1 (um Ăşnico if, nĂŁo Ă© um loop iterativo atĂ© convergĂŞncia).
  • Sem MMR, sem reranking, sem filtros de metadata.

Versões das bibliotecas

requirements.txt não fixa versões exatas (só um mínimo):

Biblioteca VersĂŁo
langchain nĂŁo pinada
langchain-community nĂŁo pinada
langchain-openai >=1.1.11
langchain-text-splitters nĂŁo pinada
openai nĂŁo pinada
chromadb nĂŁo pinada
pypdf nĂŁo pinada
datasets nĂŁo pinada
python-dotenv nĂŁo pinada
ragas nĂŁo pinada
langsmith nĂŁo pinada

Não há pyproject.toml nem lockfile.

Requisitos

  • Python 3.10+
  • Conta OpenAI com acesso Ă  API
  • Conta LangSmith, para rastreamento (tracing) do fluxo e cálculo de uso de tokens

Replicabilidade / Instalação

python -m venv .venv
source .venv/Scripts/activate   # Windows Git Bash
pip install -r requirements.txt

Configuração

Crie um .env a partir de .env.example:

OPENAI_API_KEY=sk-sua_chave_openai
OPENAI_MODEL=gpt-5.5
OPENAI_EMBEDDING_MODEL=text-embedding-3-large
OPENAI_REASONING_EFFORT=medium
DOCS_DIR=./docs/
CHROMA_PERSIST_DIR=./chroma_self_db_openai
CHROMA_COLLECTION_NAME=self_rag_contexts_openai
LANGCHAIN_TRACING_V2=false
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_API_KEY=
LANGCHAIN_PROJECT=benchmark-self-rag
Variável Default Descrição
OPENAI_API_KEY — (obrigatória) Chave da API OpenAI.
OPENAI_MODEL gpt-5.5 Modelo usado para geração, auto-crítica, refinamento e avaliação RAGAS. Reportado como está no código/.env.example.
OPENAI_EMBEDDING_MODEL text-embedding-3-large Modelo de embeddings.
OPENAI_REASONING_EFFORT medium Parâmetro reasoning_effort do ChatOpenAI (Responses API, use_responses_api=True).
DOCS_DIR ./docs/ Pasta com os PDFs a indexar.
CHROMA_PERSIST_DIR ./chroma_self_db_openai DiretĂłrio de persistĂŞncia do Ă­ndice vetorial.
CHROMA_COLLECTION_NAME self_rag_contexts_openai Nome da coleção no Chroma.
LANGCHAIN_TRACING_V2 false Ativa tracing no LangSmith.
LANGSMITH_ENDPOINT https://api.smith.langchain.com Endpoint do LangSmith.
LANGCHAIN_API_KEY — Chave do LangSmith.
LANGCHAIN_PROJECT benchmark-self-rag Nome do projeto no LangSmith.

Uso

Coloque os PDFs em docs/ (já populada com 10 apostilas/livros sobre algoritmos, estruturas de dados e lógica de programação) e execute:

python main.py

O script:

  1. Indexa os PDFs de docs/ no Chroma (pula se a coleção já existir).
  2. Roda 5 rodadas das mesmas 10 perguntas de benchmark fixas no cĂłdigo (test_queries/ground_truths em main.py).
  3. Para cada pergunta, recupera os 5 chunks mais relevantes, gera a resposta, aplica auto-crítica e, se necessário, refina a resposta uma vez.
  4. Avalia cada rodada com RAGAS e salva um CSV por rodada em results/ (ou results_2/, results_3/... se a pasta já existir).

Estrutura do projeto

self-rag/
├── .env.example
├── README.md
├── requirements.txt
├── main.py              # pipeline Self-RAG + benchmark RAGAS (versão oficial)
├── rag_settings.py       # utilitários compartilhados: env, LLM/embeddings, tracking de uso, RAGAS, salvar CSV
├── main.ipynb            # variante histórica (ver Notas)
├── benchmark.ipynb       # ferramenta de análise pós-hoc via LangSmith (ver Notas)
└── docs/                 # 10 PDFs (livros de algoritmos/estruturas de dados/lógica de programação em PT-BR)

Gerados em runtime (fora do controle de versĂŁo): chroma_self_db_openai/ (Ă­ndice vetorial) e results*/ (CSVs).

Avaliação e resultados

Métricas RAGAS calculadas a cada rodada: faithfulness, answer_relevancy, context_precision, context_recall. Cada linha do CSV também traz answer_response_time_seconds, answer_input_tokens, answer_output_tokens e answer_total_tokens, medidos por pergunta via TokenUsageTracker.

Notas e limitações

  • O retrieval Ă© sempre executado (nĂŁo há decisĂŁo adaptativa de "recuperar ou nĂŁo"), e nĂŁo há grading individual de documentos recuperados — a auto-crĂ­tica avalia apenas a resposta final, nĂŁo os chunks.
  • O refinamento Ă© limitado a uma Ăşnica tentativa; nĂŁo há loop iterativo atĂ© a crĂ­tica retornar "SIM".
  • main.ipynb Ă© uma variante histĂłrica que usa HuggingFaceEmbeddings (sentence-transformers/all-MiniLM-L6-v2) e um LLM via endpoint compatĂ­vel com OpenAI hospedado na DigitalOcean, em vez da stack OpenAI usada em main.py — nĂŁo Ă© equivalente ao pipeline oficial e roda uma Ăşnica vez (sem 5 rodadas, sem tracking de tokens, sem exportação em CSV).
  • benchmark.ipynb nĂŁo faz parte do pipeline Self-RAG em si: Ă© uma ferramenta separada que consulta a API do LangSmith para comparar tokens mĂ©dios e latĂŞncia de execuções já rastreadas (requer LANGCHAIN_TRACING_V2=true em execuções anteriores) e salva um gráfico comparativo (rag_benchmark_chart.png).
  • DependĂŞncias em requirements.txt nĂŁo sĂŁo pinadas (exceto o mĂ­nimo de langchain-openai).

About

🔍 Implementation of Self RAG with RAGAS for evaluate metrics.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages