A multi-agent PR code review system built with Google ADK (Agent Development Kit) and Gemini 2.5 Flash. It analyzes Git diffs and produces structured, actionable code review reports covering bugs, style violations, security vulnerabilities, and improvement suggestions — all in parallel.
The bot implements a Fan-Out / Fan-In multi-agent pipeline:
User Input (raw git diff)
│
▼
┌─────────────────────────────────────────────┐
│ SequentialAgent: CodeReviewPipeline │
│ │
│ Step 1 ── DiffParser (LlmAgent) │
│ calls parse_github_diff() │
│ → state["parsed_diff"] │
│ │
│ Step 2 ── ReviewerPanel (ParallelAgent) │
│ ├── BugDetector ──────────┐ │
│ ├── StyleChecker ──────────┤ │
│ ├── SuggestionAgent ──────────┤ │
│ └── SecurityScanner ──────────┘ │
│ (all run simultaneously) │
│ │
│ Step 3 ── Synthesizer (LlmAgent) │
│ merges all reports │
│ → structured JSON output │
└─────────────────────────────────────────────┘
│
▼
ReviewOutput (Pydantic-validated JSON)
Each specialist agent focuses on a single concern and writes to its own state key. The Synthesizer reads all four reports, deduplicates findings, and produces a final ReviewOutput with:
- An executive summary
- All findings sorted by severity (
critical → warning → info) - An approval recommendation (
APPROVE/REQUEST_CHANGES/COMMENT) - Finding counts by severity
code_review_bot/
├── agent.py # ADK entry point — defines root_agent
├── run.py # Standalone async runner (for CI/CD or scripting)
├── __init__.py
│
├── agents/
│ ├── bug_detector.py # Finds logic errors, crashes, resource leaks
│ ├── style_checker.py # Enforces naming, formatting, PEP 8, etc.
│ ├── suggestion_agent.py # Proposes idiomatic improvements and DRY fixes
│ ├── security_scanner.py # Detects SQLi, XSS, hardcoded secrets, OWASP Top 10
│ └── synthesizer.py # Merges all reports into structured ReviewOutput
│
├── tools/
│ ├── diff_parser.py # Pure-function git diff parser (no LLM)
│ └── github_client.py # GitHub API integration (fetch live PR diffs)
│
├── schemas/
│ └── review_models.py # Pydantic models: ReviewOutput, ReviewFinding, etc.
│
├── callbacks/
│ └── guardrails.py # Input validation (rejects diffs > 5000 lines)
│
├── tests/
│ ├── test_diff_parser.py # Unit tests for the diff parser
│ ├── test_agents.py # Smoke tests for agent wiring and schemas
│ └── fixtures/
│ └── sample_diff.txt # Sample diff with intentional vulnerabilities
│
├── .env # GOOGLE_API_KEY (not committed)
└── pyproject.toml
- Python 3.11+
- A Google AI Studio API key (free tier works)
git clone https://github.com/your-username/code_review_bot.git
cd code_review_botpython -m venv .venv
source .venv/bin/activate # macOS/Linux
.venv\Scripts\activate # Windowspip install google-adk pydantic python-dotenv requestsecho 'GOOGLE_API_KEY=your-gemini-api-key-here' > .envGet a free key at https://aistudio.google.com/apikey.
# From the PARENT directory of code_review_bot/
cd ..
adk web .Open http://127.0.0.1:8000, select code_review_bot from the dropdown, paste a git diff into the chat, and send.
The UI shows the full agent trace: tool calls, each specialist's response, and the final structured JSON — all in real time.
# From inside code_review_bot/
python run.py # uses built-in sample diff
python run.py path/to/diff.txt # review any diff file# From inside code_review_bot/
python -m pytest tests/ -vtests/test_diff_parser.py::test_parse_simple_diff PASSED
tests/test_diff_parser.py::test_parse_multi_file_diff PASSED
tests/test_diff_parser.py::test_parse_empty_diff PASSED
tests/test_diff_parser.py::test_parse_language_detection PASSED
tests/test_diff_parser.py::test_parse_unknown_language PASSED
tests/test_diff_parser.py::test_parse_hunk_lines PASSED
tests/test_agents.py::test_agent_imports PASSED
tests/test_agents.py::test_root_agent_wiring PASSED
tests/test_agents.py::test_diff_parser_has_tool PASSED
tests/test_agents.py::test_output_keys PASSED
tests/test_agents.py::test_review_output_schema PASSED
Input — a diff with SQL injection and a hardcoded secret:
{
"summary": "This PR introduces two critical security vulnerabilities in the authentication layer: a SQL injection in the login query and a hardcoded JWT secret. Changes should not be merged until both issues are resolved.",
"findings": [
{
"file": "src/auth/login.py",
"line": 18,
"severity": "critical",
"category": "security",
"title": "SQL Injection (CWE-89)",
"description": "User input is directly interpolated into a SQL query string. An attacker can manipulate the username field to execute arbitrary SQL.",
"suggestion": "Use parameterized queries: db.execute('SELECT * FROM users WHERE username = ?', (username,))"
},
{
"file": "src/auth/login.py",
"line": 20,
"severity": "critical",
"category": "security",
"title": "Hardcoded JWT Secret",
"description": "The string 'secret123' is used as the JWT signing key. Anyone with access to the source code can forge valid tokens.",
"suggestion": "Load the secret from an environment variable: os.environ['JWT_SECRET']"
}
],
"approval_recommendation": "REQUEST_CHANGES",
"stats": { "critical": 2, "warning": 0, "info": 0 }
}| Decision | Rationale |
|---|---|
| Multi-agent fan-out | Each specialist focuses on one concern — better results than one big prompt |
| Parallel reviewers | BugDetector, StyleChecker, SuggestionAgent, SecurityScanner run simultaneously, reducing wall-clock time by ~4x |
| Deterministic diff parser | Parsing is a pure function — no LLM tokens wasted on what regex solves |
Pydantic output_schema |
Guarantees valid, typed JSON from the Synthesizer — no brittle string parsing |
output_key as state bus |
Agents communicate through session.state — no shared globals, no coupling |
| Guardrail callback | Rejects diffs > 5000 lines before any LLM call, capping token usage |
- Google ADK — multi-agent orchestration (
SequentialAgent,ParallelAgent,LlmAgent) - Gemini 2.5 Flash — LLM for all agents (fast, cost-efficient)
- Pydantic v2 — structured output schema and data validation
- Python 3.11+