A webhook service for automated GitLab Merge Request (MR) reviews using AI/LLM analysis.
- FastAPI Webhook Service: Receives GitLab events (
/webhook/gitlab) and orchestrates analysis - Modular Pipeline: Context generators → Analyzers (LLM/rule-based) → Notifiers
- Repository-Specific Prompts: Centrally managed, customizable per repo pattern
- Automatic Cleanup: Temporary directories (git clones) are always cleaned up
framework/webhook_service.py- FastAPI entrypoint and event handlersframework/config/prompts.py- Repository-specific prompt configurationframework/pipeline.py- Pipeline orchestrationframework/context/- Context generators (git clone, dbt parse, etc.)framework/analysis/- LLM analyzers (Gemini/VertexAI)framework/notification/- GitLab MR comment posting
- Python 3.8+
- dbt-core 1.8.x and dbt-snowflake
- Google Cloud service account with VertexAI access
- GitLab API token with project access
git clone <your-repo-url>
cd gitlab-mr-reviewer/framework
pip install -r requirements.txtSet these environment variables:
GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account.json
GITLAB_API_TOKEN=your_gitlab_token
WEBHOOK_SECRET=your_webhook_secret
GITLAB_API_BASE_URL=https://gitlab.example.com # Your GitLab instanceuvicorn framework.webhook_service:app --host 0.0.0.0 --port 8000 --reloadcurl http://localhost:8000/healthGitCloneContextGenerator: Clones repository to temporary directory, provides{repo_dir}DBTParseContextGenerator: Runsdbt parse, provides{manifest_path}and{manifest_json}ManifestSummaryContextGenerator: Analyzes manifest for model counts and documentation completeness, provides{manifest_summary}CISummaryContextGenerator: Parses.gitlab-ci.yml, provides{ci_summary}MRChangesContextGenerator: Extracts MR file changes, provides{mr_changes}
- Standard MR Analysis: All repos get basic MR summary using webhook context
- Promotion Detection: Only
dataproduct-configrepos check for data product promotions - Deep Analysis: When promotion detected, clones target dbt repo and runs comprehensive analysis
Edit framework/config/prompts.py and add your repository to the _repo_prompts dictionary:
# In PromptManager.__init__()
self._repo_prompts: Dict[str, PromptConfig] = {
# Existing repos...
# Your new repository (exact match)
"gitlab.example.com/your-org/your-repo": PromptConfig(
mr_summary="Your custom MR summary prompt here...",
),
# Or use regex pattern for multiple repos
r"^gitlab\.example\.com/your-org/.*-api$": PromptConfig(
mr_summary="Prompt for all API repos...",
),
}Available prompt types:
mr_summary- Main MR analysis and review (required)manifest- dbt manifest analysis (for dbt repos)ci- CI/CD pipeline analysis (for dbt repos)promotion- Data product promotion detection (for dataproduct-config repo)
Template variables available:
{title}- MR title{description}- MR description{source_branch}- Source branch name{target_branch}- Target branch name{commit_sha}- Latest commit SHA{diff_content}- Full MR diff content{project_id}- GitLab project ID{mr_iid}- MR internal ID
"gitlab.example.com/data-team/analytics-models": PromptConfig(
mr_summary=(
"You are a senior data analyst reviewing analytics models. "
"Focus on data accuracy, business logic, and stakeholder impact.\n\n"
"## Business Logic Review\n"
"- Verify calculations align with business requirements\n"
"- Check for potential data quality issues\n\n"
"## Stakeholder Impact\n"
"- Identify which dashboards/reports might be affected\n"
"- Assess breaking changes for downstream consumers\n\n"
"MR Diff:\n```diff\n{diff_content}\n```"
),
)# In a Python shell or test script
from framework.config.prompts import prompt_manager
# Test your repo URL matches
repo_url = "gitlab.example.com/your-org/your-repo"
try:
prompt = prompt_manager.get_prompt(repo_url, "mr_summary")
print("✅ Prompt found:", prompt[:100] + "...")
except KeyError as e:
print("❌ No prompt found:", e)Create a test payload file test_payload.json:
{
"object_kind": "merge_request",
"project": {
"id": 12345,
"web_url": "https://gitlab.example.com/your-org/your-repo"
},
"object_attributes": {
"iid": 123,
"title": "Test MR",
"description": "Test description",
"source_branch": "feature/test",
"target_branch": "main",
"action": "open"
}
}Test locally:
curl -X POST http://localhost:8000/webhook/gitlab \
-H "Content-Type: application/json" \
-H "X-Gitlab-Event: Merge Request Hook" \
-H "X-Gitlab-Token: your_webhook_secret" \
-d @test_payload.jsonAdd logging to see which prompts are being used:
# Temporarily add to your webhook handler
logger.info(f"Using prompt for {repo_url}: {prompt[:100]}...")docker build -t gitlab-mr-reviewer .docker run -p 8000:8000 \
-v /path/to/service-account.json:/app/service-account.json:ro \
-e GOOGLE_APPLICATION_CREDENTIALS=/app/service-account.json \
-e GITLAB_API_TOKEN=your_token \
-e WEBHOOK_SECRET=your_secret \
gitlab-mr-reviewer# Google Cloud credentials
oc create secret generic gcp-service-account \
--from-file=service-account.json=/path/to/service-account.json
# GitLab token and webhook secret
oc create secret generic gitlab-secrets \
--from-literal=GITLAB_API_TOKEN=your_token \
--from-literal=WEBHOOK_SECRET=your_secretapiVersion: apps/v1
kind: Deployment
metadata:
name: gitlab-mr-reviewer
spec:
replicas: 1
selector:
matchLabels:
app: gitlab-mr-reviewer
template:
metadata:
labels:
app: gitlab-mr-reviewer
spec:
containers:
- name: gitlab-mr-reviewer
image: gitlab-mr-reviewer:latest
ports:
- containerPort: 8000
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /app/service-account.json
- name: GITLAB_API_TOKEN
valueFrom:
secretKeyRef:
name: gitlab-secrets
key: GITLAB_API_TOKEN
- name: WEBHOOK_SECRET
valueFrom:
secretKeyRef:
name: gitlab-secrets
key: WEBHOOK_SECRET
volumeMounts:
- name: gcp-creds
mountPath: /app/service-account.json
subPath: service-account.json
- name: tmp-volume
mountPath: /tmp
volumes:
- name: gcp-creds
secret:
secretName: gcp-service-account
- name: tmp-volume
emptyDir: {}oc expose service gitlab-mr-reviewer
oc patch route gitlab-mr-reviewer -p '{"spec":{"tls":{"termination":"edge"}}}'- Go to your GitLab project → Settings → Webhooks
- Add webhook URL:
https://your-app-url/webhook/gitlab - Set secret token (same as
WEBHOOK_SECRET) - Enable triggers:
- ✅ Merge request events
- ✅ Comments (for
/analyze-promotioncommands)
- Test the webhook
- Context Generators: Add to
framework/context/ - Analyzers: Add to
framework/analysis/ - Notifiers: Add to
framework/notification/ - Follow the base class interfaces in
framework/interfaces.py
MIT License