1 de marzo de 2026

GitLab MR Review Agent

Activo

Bot de code review con LLM usando FastAPI, Qdrant, Claude API y RAG sobre Confluence. Redujo el turnaround de review en torno al 40%.

Contexto

En AlfaStrakhovanie el equipo de data engineering procesa más de 50 merge requests por semana para Spark jobs, modelos dbt y Airflow DAGs. Los engineers senior dedicaban 4-6 horas semanales a comentarios repetitivos: revisar naming, detectar patrones SQL, comprobar que los modelos nuevos usaran los data contracts correctos.

El objetivo era simple: automatizar el 60% rutinario del feedback de review para que las personas pasaran más tiempo en arquitectura y lógica, no en checks repetitivos.

Arquitectura

GitLab Webhook (MR event)


FastAPI service (webhook handler)

        ├── fetch MR diff via GitLab API
        ├── chunk diff by file/hunk


Qdrant retrieval (RAG)

        ├── query: Confluence data contracts
        ├── query: internal coding standards docs
        ├── query: similar past MR reviews


Claude API

        ├── system prompt: role + company context
        ├── retrieved context (top-k chunks)
        ├── MR diff


Structured JSON output (per-file comments)


GitLab API → post inline comments on MR

Trade-offs

RAG vs fine-tuning. Fine-tuning quedó fuera rápido: la base de código y las reglas internas cambian más rápido que cualquier ciclo de retraining. RAG sobre documentación actual de Confluence daba contexto vivo sin retraining.

Claude vs GPT-4o. Probamos ambos. Claude producía reviews más aplicables y cuidadosos en patrones Spark/SQL, y generaba menos ruido con false positives.

Tamaño de chunks del diff. El punto útil estuvo alrededor de 150 líneas. Archivos completos empeoraban recall y diluían el contexto.

Fragmento clave

async def retrieve_context(diff_chunk: str, k: int = 8) -> list[str]:
    embedding = await embed(diff_chunk)
 
    results = await qdrant_client.search(
        collection_name="internal_docs",
        query_vector=embedding,
        limit=k,
        with_payload=True,
    )
 
    return [hit.payload["text"] for hit in results]
async def generate_review(diff: str, context: list[str]) -> ReviewOutput:
    response = await anthropic.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2048,
        system=REVIEW_SYSTEM_PROMPT,
        messages=[
            {
                "role": "user",
                "content": f"<context>\n{chr(10).join(context)}\n</context>\n\n<diff>\n{diff}\n</diff>",
            }
        ],
    )
    return parse_review(response.content[0].text)

Resultados

  • Tiempo hasta el primer review: de 18h promedio a 11h
  • False positive rate: alrededor de 8% en validación manual sobre 200 reviews
  • Adoption: 3 equipos en producción, 2 más en onboarding
  • Coste: aproximadamente $0.04 por MR review

Qué mejoraría después

  1. Chunking por límites semánticos, no por número de líneas.
  2. Confidence score explícito para cada comentario.
  3. Human-in-the-loop para pipelines críticos, donde un approve del agente no debería verse como decisión final.

Discusión

¿Te resultó útil este post?

Inicia sesión para dar like y comentar.

Tu nombre y avatar del proveedor elegido se guardan en la base de datos propia del sitio para mostrar tu actividad.