A 200-Line Python Flask Service That Posts AI Code Reviews Straight to GitLab MRs
A practical guide to a small Python project written for frontend developers
- # Practical: Building an AI Code Review Automated Pipeline
- # GitLab-Runner + AI Code Review Service + Remote Large Model Full Deployment and Operations Practice
Origin: Why I Wrote These Notes
A few days ago, I built an AI code review service using Python + Flask with about 200 lines of code. It achieves: When GitLab receives an MR, it automatically calls AI to analyze the code diff, then posts the review comments back to the MR comment section.
Several frontend colleagues on the team were curious: "I don't know Python, can I learn this?" So I thought, why not write a set of zero-basics learning notes, recording all the pitfalls I encountered and the process of understanding it.
1. What Exactly Does This Service Do?
Don't worry about the code yet. Let's first clarify what problem it aims to solve:
- Every time someone submits an MR, it requires manual review, which is slow, and some low-level errors (like forgetting to handle null values, non-standard variable naming) keep recurring.
- We want AI to do a first pass, picking out obvious errors, so humans only need to look at complex logic.
So the service flow is:
- GitLab CI triggers a task →
- Calls this Python service (passing the project name, MR number) →
- The Python service pulls the code changes (diff) for this MR from GitLab →
- Sends the diff to a large model (like GPT-4) →
- The large model returns review comments according to our specifications →
- The service then posts these comments to the MR comment section via the GitLab API.
You can think of it as: an AI colleague who can read code, online 24/7, on call anytime.
2. What Technologies Does This Project Use? (Made Understandable for Frontend Devs)
| Technology | Role | Analogy (Frontend Perspective) |
|---|---|---|
| Python 3 | Backend language | Similar to Node.js |
| Flask | Web framework | Similar to Express |
| OpenAI SDK | Calling large models | Similar to calling a third-party API |
| requests | Sending HTTP requests | Similar to axios |
| python-dotenv | Reading .env config |
Similar to the dotenv package |
| git command | Cloning the specification file repository | Just git clone |
So even if you don't know Python, as long as you know JavaScript, understanding these concepts isn't hard—it's just different syntax.
3. Step-by-Step to Get It Running (Mac / Linux)
1. Install Python (if not already present)
Mac users (usually comes pre-installed):
brew install python3
Verify:
python3 --version # Should display 3.9 or higher
2. Download the Code
Save the app.py file above into a folder, e.g., ~/ai-review-service/.
Also create a requirements.txt with the following content:
flask
openai
python-dotenv
requests
3. Install Dependencies
cd ~/ai-review-service
pip3 install -r requirements.txt
If
pip3is not found, trypip. Or usepython3 -m pip install ...
4. Configure Environment Variables (Create .env file)
Create a .env file in the same directory with the following content (replace the angle brackets with your own):
OPENAI_API_KEY=sk-xxxxxxxxxxxxxx
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=gpt-4o-mini
GITLAB_TOKEN=glpat-xxxxxxxxxxxx
GITLAB_URL=https://gitlab.com
REVIEW_SPEC_PATH=./specs/frontend-code-review.md
AUTO_SYNC_SPEC=true
SKILLS_REPO_URL=https://gitlab.com/your-team/skills.git
I will explain the meaning of each field in detail later.
5. Start the Service
python3 app.py
Success looks like the following output:
🚀 AI Review Service starting...
📡 Service address: http://localhost:5001
🔑 Test token: test-token-123
📝 Review endpoint: POST /api/review
4. Detailed Explanation of the .env Configuration File (Must-Read for Beginners)
| Variable | Meaning | Where to Get It |
|---|---|---|
OPENAI_API_KEY |
Key for calling OpenAI (or compatible interface) | Apply on the OpenAI official website or Alibaba Cloud DashScope |
OPENAI_BASE_URL |
API address | Default is OpenAI official; if using Tongyi Qianwen domestically, change to the Alibaba Cloud address |
OPENAI_MODEL |
Model name used | e.g., gpt-4o-mini, qwen3.7-max |
GITLAB_TOKEN |
GitLab personal access token | GitLab → Settings → Access Tokens, check api or read_api permission |
GITLAB_URL |
Your GitLab server address | Your company's internal GitLab address, e.g., https://gitlab.your-company.com |
REVIEW_SPEC_PATH |
Path to store the code review specification file | Can be default; it will be downloaded from the skills repo on startup |
AUTO_SYNC_SPEC |
Whether to auto-sync the specification | true or false, recommended true |
SKILLS_REPO_URL |
Git repository address storing the review specifications | Create a repo yourself, put frontend-code-review/SKILL.md inside |
Special Note:
GITLAB_TOKENmust haveapipermission, otherwise it cannot post comments on MRs.- If just testing, you can skip configuring
GITLAB_TOKENfirst; the service will prompt "Unable to get diff" but won't error out.
5. Code Analysis Section by Section (You don't need to know Python, just understand what it does)
I'll break down app.py into several core functions and explain them in plain language.
1. Import Dependencies and Read Configuration
from flask import Flask, request, jsonify
import os
import subprocess
# ... other imports
load_dotenv() # Automatically reads the .env file
Flaskis like Express'sappobject, used to define routes.load_dotenv()allows you to useos.getenv('OPENAI_API_KEY')in your code to get the configuration.
2. Sync Review Specification Files
def sync_specs_from_git():
# Uses git clone to pull a repository, then copies the SKILL.md inside to local
Why is this needed? Team code specifications may update. We put them separately in a Git repository, and the service automatically pulls the latest version on startup. This way, you don't need to change the code, just the specification file, and the AI will review according to the new rules.
Analogy for frontend: It's like you npm install a dependency package, except here it's git clone for a document.
3. Get MR Diff from GitLab
def get_mr_diff(project_path, mr_iid):
# Calls GitLab API: GET /projects/:id/merge_requests/:iid/changes
# Returns a string containing the code additions and deletions
Core Logic:
project_pathis likegroup/project.mr_iidis the MR number (e.g., the 123 in!123).- Uses the
requestslibrary to send a GET request with thePRIVATE-TOKENheader. - Parses the returned JSON, extracting the
difffield from thechangesarray.
What if it fails? The code has detailed error prompts, e.g., 401 tells you the token permission is insufficient, 403 tells you no permission to access the project.
4. Call AI for Review
def get_real_ai_review(mr_info, code_diff):
# 1. Load the team specification file content
# 2. Construct a very long prompt
# 3. Call the OpenAI API
# 4. Parse the returned JSON
This is the core function. What it does can be understood as: giving instructions to the AI.
The instruction content is roughly:
You are a review expert. Below are the code changes for the MR. Please check according to the following specifications: ... The output format must be JSON, and each issue must indicate the file, line number, priority, and category.
The JSON structure returned by the AI is similar to:
{
"score": 75,
"suggestions": [
{"type": "error", "file": "src/App.vue", "line": 12, "message": "Null pointer risk, suggest adding ?."}
]
}
Note for frontend colleagues: This prompt is your "requirements document". How you want the AI to review can be completely controlled by modifying the prompt. You can even translate ESLint rules into natural language and write them in.
5. Define HTTP Interface (Routes)
@app.route('/api/review', methods=['POST'])
def review_code():
# 1. Verify the Bearer token in the Authorization header
# 2. Parse the request body (JSON)
# 3. Call get_mr_diff to pull the code
# 4. Call get_real_ai_review
# 5. Return the result
@app.routeis similar to Express'sapp.post().- The function name can be anything, but the internal logic must be clear.
- The returned JSON is automatically converted to a response via
jsonify.
6. Health Check and Test Interface
@app.route('/health')
def health_check():
return {"status": "ok", ...}
Purpose: Lets you confirm whether the service is alive and whether the configuration is correct (e.g., checking if the API Key exists).
There is also a /test/gitlab interface to manually test if the GitLab token is valid.
6. How to Call This Service in GitLab CI?
In your frontend project's root directory, modify .gitlab-ci.yml and add a job:
ai-review:
stage: review
tags:
- mac-runner # If you use a local runner
script:
- |
curl -X POST http://localhost:5001/api/review \
-H "Authorization: Bearer test-token-123" \
-H "Content-Type: application/json" \
-d "{
\"project\": \"$CI_PROJECT_PATH\",
\"mr_iid\": $CI_MERGE_REQUEST_IID,
\"source_branch\": \"$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME\",
\"target_branch\": \"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME\"
}"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- Variables like
$CI_PROJECT_PATHare built into GitLab CI, you don't need to fill them in manually. test-token-123is a hardcoded test token in the service; you can change it to a more secure random string.
If you don't want to use a local runner, you can also deploy this service to a server with a public IP, then replace http://localhost:5001 with the corresponding IP.
7. Pitfall Records (Where I Fell)
1. Insufficient Token Permission → 401
- Error:
Failed to get MR changes: 401 - Cause: GitLab Token only had
read_user, notapi. - Solution: Regenerate the Token, checking the
apipermission (or at leastread_api+write_repository).
2. Local Service Running on 5001, but GitLab Runner Cannot Access It
- Error:
curl: (7) Failed to connect to localhost port 5001 - Cause: If the Runner is in Docker mode,
localhostpoints inside the container, not your Mac. - Solution: Either use
host.docker.internal(Mac), or switch to a shell executor (which is what I used above).
3. Python Dependency Installation Failed
- Error:
No module named 'flask' - Cause: You might be using the system's built-in Python 2.7, or forgot to activate the virtual environment.
- Solution: Use
python3 -m pip install, or first create a virtual environment withpython3 -m venv venv.
4. AI Returned JSON Parsing Failed
- Symptom: Service log prompts
JSON parsing failed, raw_response appears in the MR. - Cause: Large models occasionally return JSON with explanatory text, or incomplete JSON.
- Solution: The code already includes logic to "try extracting the ```json code block". If it still fails, you can set the model's temperature lower (e.g., 0.1).
8. Extension Ideas (Features You Can Add Yourself)
- Support more Git platforms: Like GitHub, Gitee. The principle is the same, just different API addresses and Tokens.
- Support local models: Use Ollama to run an open-source CodeLlama, saving API costs.
- Incremental review: Only review the changed parts, not the full diff, to improve speed.
- Auto-fix: Let the AI not only find problems but also generate a fix patch that users can apply with one click.
9. Final Words
As a former frontend developer, I initially found Python very unfamiliar too. But after actually writing it, I discovered that Flask is even simpler than Express: no need to configure middleware, no need to handle route parameter parsing, and not even async/await (it's synchronous by default, which is very suitable for this kind of small tool).
Moreover, the pattern for this kind of "small AI service" is very fixed: Receive request → Call third-party API → Process data → Return JSON. As long as you can write JavaScript, switching to Python is just a difference in dictionary and list syntax.
I hope these notes give you the confidence to modify the code above, or even write your own. If you encounter problems while trying, feel free to leave a comment in the comment section (or raise an issue), and I will add new pitfall experiences.
One last nagging point: The code is not important, the method of solving the problem is. Although this service is only 200 lines, it truly implements the team's code standards, which is the most valuable part.