← Back to DevBytes

Building a Code Review Agent with AutoGen: Complete Guide

What Is a Code Review Agent?

A Code Review Agent is an autonomous AI-powered assistant built with AutoGen—Microsoft's open-source multi-agent conversation framework—that automatically analyzes source code, identifies potential issues, suggests improvements, and enforces coding standards. Instead of manually reviewing every pull request, developers can delegate the first pass of code review to an intelligent agent that understands context, follows custom guidelines, and provides structured, actionable feedback.

AutoGen enables this by orchestrating conversations between specialized agents: one agent acts as the code author or submitter, another acts as the reviewer, and optionally a third acts as an arbiter or senior reviewer. These agents exchange messages, reason about code quality, and produce comprehensive review reports—all without human intervention until the final report is ready.

Why Automated Code Review Matters

Manual code review is time-consuming, inconsistent, and often becomes a bottleneck in fast-moving development teams. An automated Code Review Agent delivers several critical benefits:

Setting Up Your Environment

Before building the agent, install the required dependencies. You'll need Python 3.10+ and an API key for an LLM provider (OpenAI, Azure OpenAI, or a local model via Ollama/LiteLLM). Create a new project directory and set up a virtual environment:

mkdir code-review-agent
cd code-review-agent
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
pip install autogen-agentchat autogen-ext[openai]
pip install python-dotenv

Create a .env file to store your API credentials securely:

OPENAI_API_KEY=your-openai-api-key-here
# Or for Azure OpenAI:
# AZURE_OPENAI_API_KEY=your-azure-key
# AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
# AZURE_OPENAI_DEPLOYMENT_NAME=your-deployment-name

Create a configuration file config.py that defines the LLM profiles for your agents:

import os
from dotenv import load_dotenv

load_dotenv()

# LLM configuration for agents
llm_config = {
    "config_list": [
        {
            "model": "gpt-4o",
            "api_key": os.getenv("OPENAI_API_KEY"),
            "temperature": 0.2,
        }
    ],
    "timeout": 120,
    "cache_seed": 42,  # For reproducible results during testing
}

# A separate, more deterministic config for the reviewer
reviewer_llm_config = {
    "config_list": [
        {
            "model": "gpt-4o",
            "api_key": os.getenv("OPENAI_API_KEY"),
            "temperature": 0.0,  # Zero temperature for consistent reviews
        }
    ],
    "timeout": 180,
    "cache_seed": None,
}

Building the Code Review Agent: Step by Step

Step 1: Define the Agent Personas and System Prompts

The core of a Code Review Agent lies in well-crafted system prompts. You need at least two agents: a Developer Agent (which submits code and responds to feedback) and a Reviewer Agent (which analyzes the code and produces the review). Optionally, add a Lead Reviewer Agent for final arbitration.

# agent_prompts.py

DEVELOPER_SYSTEM_PROMPT = """You are a professional software developer submitting code for review.
Your responsibilities:
1. Present the code clearly, including the file path and a brief description of what it does.
2. When asked questions by the reviewer, provide honest explanations about design decisions.
3. If the reviewer finds issues, acknowledge them and propose fixes.
4. Do not argue defensively—accept constructive feedback gracefully.
5. When asked to revise code, provide the full corrected version.

Always format code blocks with triple backticks and specify the language.
"""

REVIEWER_SYSTEM_PROMPT = """You are an expert code reviewer with deep experience in software engineering.
Your task is to perform a thorough code review and produce a structured report.

Review criteria to check (but not limited to):
- Correctness: Logic errors, off-by-one bugs, null/None handling, edge cases
- Security: Injection vulnerabilities, hardcoded secrets, unsafe deserialization, missing authentication checks
- Performance: Inefficient algorithms, unnecessary allocations, blocking operations, missing caching opportunities
- Readability: Naming conventions, function length, cyclomatic complexity, comments quality
- Error Handling: Missing try/except, swallowed exceptions, improper error propagation
- Testing: Missing tests, untestable code patterns, improper mocking
- Style & Standards: PEP8/ESLint/etc violations, inconsistent formatting
- Architecture: SOLID violations, tight coupling, missing abstractions, dependency issues

Format your review EXACTLY as follows:

## Code Review Report

**File:** [file path]
**Overall Assessment:** [PASS / NEEDS WORK / FAIL]

### Critical Issues (must fix before merge)
- [Issue description with line numbers and fix suggestion]

### Warnings (should fix)
- [Issue description with rationale]

### Suggestions (nice to have)
- [Optional improvement with rationale]

### Summary
[Brief summary and final recommendation]
"""

LEAD_REVIEWER_PROMPT = """You are the lead reviewer who resolves disagreements between the developer and reviewer.
When a conflict arises, analyze both arguments objectively and make a final, binding decision.
Explain your reasoning clearly and provide the final verdict."""

Step 2: Create the AutoGen Agents

Now wire up the agents using AutoGen's AssistantAgent class. Each agent gets its own configuration and personality:

# agents.py

import autogen
from config import llm_config, reviewer_llm_config
from agent_prompts import (
    DEVELOPER_SYSTEM_PROMPT,
    REVIEWER_SYSTEM_PROMPT,
    LEAD_REVIEWER_PROMPT,
)

def create_agents():
    """Create and return the code review agent team."""
    
    # The Developer Agent - submits code and responds to feedback
    developer = autogen.AssistantAgent(
        name="Developer",
        system_message=DEVELOPER_SYSTEM_PROMPT,
        llm_config=llm_config,
        code_execution_config=False,  # We don't execute code, just review it
    )
    
    # The Reviewer Agent - performs the actual code analysis
    reviewer = autogen.AssistantAgent(
        name="Reviewer",
        system_message=REVIEWER_SYSTEM_PROMPT,
        llm_config=reviewer_llm_config,
        code_execution_config=False,
    )
    
    # The Lead Reviewer - resolves disputes (optional but recommended)
    lead_reviewer = autogen.AssistantAgent(
        name="LeadReviewer",
        system_message=LEAD_REVIEWER_PROMPT,
        llm_config=reviewer_llm_config,
        code_execution_config=False,
    )
    
    # Human proxy agent - the interface for the actual developer using the system
    user_proxy = autogen.UserProxyAgent(
        name="User",
        human_input_mode="NEVER",  # Fully automated mode
        max_consecutive_auto_reply=3,
        code_execution_config=False,
    )
    
    return developer, reviewer, lead_reviewer, user_proxy

Step 3: Orchestrate the Review Conversation

AutoGen provides two primary orchestration patterns: two-agent chat (initiate_chat) and group chat (GroupChat). For code review, a sequential two-agent pattern works best: the User initiates the review, the Reviewer analyzes the code, and the Developer responds to findings. Here's the orchestration logic:

# orchestrator.py

import autogen
from agents import create_agents

def run_code_review(code_submission: str, max_rounds: int = 5):
    """
    Run an automated code review session.
    
    Args:
        code_submission: The code to review (full file content with context)
        max_rounds: Maximum conversation rounds to prevent infinite loops
    
    Returns:
        The complete conversation history and final review report
    """
    developer, reviewer, lead_reviewer, user_proxy = create_agents()
    
    # Customize the initial message that kicks off the review
    initial_message = f"""Please review the following code submission:

{code_submission}

Start by analyzing the code thoroughly, then produce your structured review report.
After the reviewer's report, the developer may respond to any findings.
Continue the discussion until all critical issues are resolved or explained.
"""
    
    # Initiate the conversation: UserProxy sends the code, Reviewer responds
    user_proxy.initiate_chat(
        recipient=reviewer,
        message=initial_message,
        max_turns=max_rounds,
        clear_history=True,
    )
    
    # If the developer needs to respond to reviewer findings,
    # we can chain additional conversation rounds
    # The reviewer's response will be available in the chat history
    
    return user_proxy.chat_messages

def run_code_review_with_rebuttal(code_submission: str, max_rounds: int = 8):
    """
    Advanced: Run a review with full developer-rebuttal cycle.
    The developer can respond to each review finding before the final report.
    """
    developer, reviewer, lead_reviewer, user_proxy = create_agents()
    
    # Create a group chat for multi-agent interaction
    group_chat = autogen.GroupChat(
        agents=[user_proxy, developer, reviewer, lead_reviewer],
        messages=[],
        max_round=max_rounds,
        speaker_selection_method="round_robin",  # Ensures orderly turns
        allow_repeat_speaker=False,
    )
    
    manager = autogen.GroupChatManager(
        groupchat=group_chat,
        llm_config=llm_config,
    )
    
    initial_message = f"""Starting code review session. Here is the code submission:

{code_submission}

Reviewer: Please begin your analysis.
Developer: You may respond to any findings after the reviewer speaks.
LeadReviewer: Intervene only if there is a disagreement that needs resolution.
"""
    
    user_proxy.initiate_chat(
        recipient=manager,
        message=initial_message,
        max_turns=max_rounds,
    )
    
    return user_proxy.chat_messages

Step 4: Parse and Structure the Review Output

Raw conversation history is useful for debugging, but you'll want a structured output for integration with CI/CD pipelines, PR comments, or dashboards. Build a parser that extracts the review report from the conversation:

# report_parser.py

import re
from typing import Dict, List, Optional

def extract_review_report(chat_messages: Dict) -> Dict:
    """
    Parse the AutoGen conversation history to extract the structured review report.
    
    Returns a dictionary with:
    - file_path: str
    - overall_assessment: str (PASS / NEEDS WORK / FAIL)
    - critical_issues: list of dicts
    - warnings: list of dicts
    - suggestions: list of dicts
    - summary: str
    - full_conversation: list of messages
    """
    report = {
        "file_path": None,
        "overall_assessment": "UNKNOWN",
        "critical_issues": [],
        "warnings": [],
        "suggestions": [],
        "summary": "",
        "full_conversation": [],
    }
    
    # Iterate through all messages to find the reviewer's report
    for agent_name, messages in chat_messages.items():
        for msg in messages:
            content = msg.get("content", "")
            report["full_conversation"].append({
                "agent": agent_name,
                "content": content,
            })
            
            # Look for the structured review report pattern
            file_match = re.search(r'\*\*File:\*\*\s*(.+?)(?:\n|$)', content)
            if file_match:
                report["file_path"] = file_match.group(1).strip()
            
            assessment_match = re.search(
                r'\*\*Overall Assessment:\*\*\s*(PASS|NEEDS WORK|FAIL)', 
                content, 
                re.IGNORECASE
            )
            if assessment_match:
                report["overall_assessment"] = assessment_match.group(1).upper()
            
            # Extract critical issues section
            critical_section = re.search(
                r'### Critical Issues.*?\n(.*?)(?=###|\Z)', 
                content, 
                re.DOTALL
            )
            if critical_section:
                issues_text = critical_section.group(1)
                for line in issues_text.strip().split('\n'):
                    line = line.strip()
                    if line.startswith('- ') or line.startswith('* '):
                        report["critical_issues"].append({
                            "description": line[2:].strip(),
                            "severity": "critical"
                        })
            
            # Extract warnings
            warnings_section = re.search(
                r'### Warnings.*?\n(.*?)(?=###|\Z)', 
                content, 
                re.DOTALL
            )
            if warnings_section:
                for line in warnings_section.group(1).strip().split('\n'):
                    line = line.strip()
                    if line.startswith('- ') or line.startswith('* '):
                        report["warnings"].append({
                            "description": line[2:].strip(),
                            "severity": "warning"
                        })
            
            # Extract suggestions
            suggestions_section = re.search(
                r'### Suggestions.*?\n(.*?)(?=###|\Z)', 
                content, 
                re.DOTALL
            )
            if suggestions_section:
                for line in suggestions_section.group(1).strip().split('\n'):
                    line = line.strip()
                    if line.startswith('- ') or line.startswith('* '):
                        report["suggestions"].append({
                            "description": line[2:].strip(),
                            "severity": "suggestion"
                        })
            
            # Extract summary
            summary_match = re.search(
                r'### Summary\s*\n(.*?)(?:\n\n|\Z)', 
                content, 
                re.DOTALL
            )
            if summary_match:
                report["summary"] = summary_match.group(1).strip()
    
    return report

def format_report_for_pr_comment(report: Dict) -> str:
    """Format the parsed report as a markdown PR comment."""
    lines = []
    lines.append(f"## 🤖 Automated Code Review")
    lines.append(f"")
    lines.append(f"**Overall Assessment:** `{report['overall_assessment']}`")
    lines.append(f"")
    
    if report["critical_issues"]:
        lines.append("### 🚨 Critical Issues")
        for issue in report["critical_issues"]:
            lines.append(f"- {issue['description']}")
        lines.append("")
    
    if report["warnings"]:
        lines.append("### ⚠️ Warnings")
        for warning in report["warnings"]:
            lines.append(f"- {warning['description']}")
        lines.append("")
    
    if report["suggestions"]:
        lines.append("### 💡 Suggestions")
        for suggestion in report["suggestions"]:
            lines.append(f"- {suggestion['description']}")
        lines.append("")
    
    if report["summary"]:
        lines.append("### 📝 Summary")
        lines.append(report["summary"])
    
    return "\n".join(lines)

Step 5: Build the Complete Main Entry Point

Combine all components into a single executable script that takes a file path or inline code and runs the full review pipeline:

# main.py

import sys
import json
from pathlib import Path
from orchestrator import run_code_review, run_code_review_with_rebuttal
from report_parser import extract_review_report, format_report_for_pr_comment

def review_file(file_path: str, enable_rebuttal: bool = False) -> None:
    """Review a single source file from disk."""
    path = Path(file_path)
    if not path.exists():
        print(f"Error: File '{file_path}' not found.")
        sys.exit(1)
    
    code_content = path.read_text(encoding="utf-8")
    
    # Add file context to the submission
    code_submission = f"""**File:** {file_path}
**Language:** {path.suffix.lstrip('.')}
**Description:** Code submission for review

{path.suffix.lstrip('.')}
{code_content}
"""
    print(f"🔍 Starting code review for: {file_path}")
    print(f"   Mode: {'with rebuttal' if enable_rebuttal else 'standard'}")
    
    if enable_rebuttal:
        chat_messages = run_code_review_with_rebuttal(code_submission)
    else:
        chat_messages = run_code_review(code_submission)
    
    # Parse and display results
    report = extract_review_report(chat_messages)
    
    print("\n" + "=" * 60)
    print(format_report_for_pr_comment(report))
    print("=" * 60)
    
    # Save full conversation as JSON for debugging
    output_path = path.parent / f"{path.stem}_review_report.json"
    with open(output_path, "w", encoding="utf-8") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n📁 Full report saved to: {output_path}")

def review_inline_code(code: str, language: str = "python") -> None:
    """Review code provided directly as a string."""
    code_submission = f"""**File:** inline_submission.{language}
**Language:** {language}
**Description:** Inline code snippet for review

{language}
{code}
"""
    print("🔍 Starting inline code review...")
    chat_messages = run_code_review(code_submission)
    report = extract_review_report(chat_messages)
    print("\n" + format_report_for_pr_comment(report))

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage:")
        print("  python main.py               # Review a file")
        print("  python main.py  --rebuttal   # Review with developer rebuttal")
        print("  python main.py --inline 'code here'     # Review inline code snippet")
        sys.exit(1)
    
    arg1 = sys.argv[1]
    enable_rebuttal = "--rebuttal" in sys.argv
    
    if arg1 == "--inline" and len(sys.argv) > 2:
        review_inline_code(sys.argv[2])
    else:
        review_file(arg1, enable_rebuttal)

Step 6: Run the Agent on Real Code

Create a sample file to test the agent end-to-end. Save this as sample_code.py:

# sample_code.py - Intentionally contains issues for demonstration

def process_user_data(users, conn):
    results = []
    for user in users:
        query = "SELECT * FROM users WHERE name = '" + user + "'"
        cursor = conn.cursor()
        cursor.execute(query)
        data = cursor.fetchall()
        results.append(data)
    return results

def calculate_discount(price, discount_percent):
    return price - price * discount_percent / 100

# Missing null check and error handling
def get_user_by_id(user_id, db_connection):
    cursor = db_connection.cursor()
    cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
    return cursor.fetchone()

Run the review with:

python main.py sample_code.py --rebuttal

The agent will identify SQL injection vulnerabilities, missing error handling, potential null return values, and suggest parameterized queries—all automatically.

Best Practices for Production-Ready Code Review Agents

1. Craft Granular, Domain-Specific Reviewer Prompts

Generic prompts produce generic reviews. Tailor your reviewer's system prompt to your tech stack. For a Python/TypeScript monorepo, include specific checks:

REVIEWER_SYSTEM_PROMPT += """
Additional domain-specific checks:
- Python: Use dataclasses over plain dicts; prefer pathlib over os.path; use type hints consistently
- TypeScript: Avoid 'any' type; use const assertions for literal types; prefer interface over type where applicable
- Database: Always use parameterized queries; never concatenate user input into SQL strings
- React: Check for missing useEffect dependencies; ensure keys in list renders are stable unique IDs
- API Design: Verify RESTful conventions; check proper HTTP status code usage; validate OpenAPI spec alignment
"""

2. Implement a Severity-Based Review Gate

Not all issues should block a merge. Configure thresholds in your CI pipeline:

def should_block_merge(report: Dict) -> bool:
    """
    Determine if the code should be blocked from merging.
    Only critical issues block the merge; warnings are advisory.
    """
    if report["overall_assessment"] == "FAIL":
        return True
    if len(report["critical_issues"]) > 0:
        return True
    return False

# In CI script:
# if should_block_merge(report):
#     sys.exit(1)  # Fails the pipeline
# else:
#     print("Review passed - merge allowed with advisory warnings.")

3. Cache Reviews for Incremental Changes

Reviewing entire files on every commit wastes tokens and time. Implement diff-aware review that only analyzes changed sections:

# diff_reviewer.py

import subprocess

def get_git_diff(base_branch: str = "main") -> str:
    """Get the git diff between the current branch and base branch."""
    result = subprocess.run(
        ["git", "diff", base_branch, "--", "*.py", "*.ts", "*.js", "*.tsx"],
        capture_output=True,
        text=True,
    )
    return result.stdout

def review_diff_only(base_branch: str = "main"):
    """Review only the changed code in the current branch."""
    diff_content = get_git_diff(base_branch)
    if not diff_content.strip():
        print("No changes to review.")
        return
    
    code_submission = f"""**Context:** Git diff against {base_branch}
**Description:** Review only the changed code sections shown in the diff below.
Focus on new code (lines starting with '+') and modified code.
Ignore unchanged context lines except where needed for understanding.

diff
{diff_content}
"""
    chat_messages = run_code_review(code_submission, max_rounds=3)
    report = extract_review_report(chat_messages)
    return report

4. Add Retrieval-Augmented Generation (RAG) for Project Context

For large codebases, augment the reviewer with project-specific context—coding standards documents, architecture decision records, and past review patterns:

# rag_context.py

from typing import List

class ProjectContextProvider:
    """Provides project-specific context to the reviewer agent."""
    
    def __init__(self, docs_path: str):
        self.coding_standards = self._load_docs(docs_path)
    
    def _load_docs(self, path: str) -> str:
        """Load coding standards and architecture docs."""
        from pathlib import Path
        docs_dir = Path(path)
        content = []
        for doc in docs_dir.glob("*.md"):
            content.append(doc.read_text(encoding="utf-8"))
        return "\n\n".join(content)
    
    def augment_review_prompt(self, base_prompt: str) -> str:
        """Inject project context into the reviewer's system prompt."""
        context_block = f"""
## Project-Specific Coding Standards
The following standards MUST be applied during review:
{self.coding_standards}

When violations of these standards are found, mark them as Warnings or Critical Issues
depending on whether the standard explicitly states it is mandatory.
"""
        return base_prompt + context_block

# Usage:
# provider = ProjectContextProvider("./docs/standards")
# reviewer_system_prompt = provider.augment_review_prompt(REVIEWER_SYSTEM_PROMPT)

5. Build a Feedback Loop for Continuous Improvement

Track which review suggestions developers accept or reject. Feed this data back to refine prompts over time:

# feedback_collector.py

import json
from datetime import datetime
from pathlib import Path

class ReviewFeedbackCollector:
    """Collects developer feedback on review suggestions for model improvement."""
    
    def __init__(self, storage_path: str = "./review_feedback"):
        self.storage_path = Path(storage_path)
        self.storage_path.mkdir(exist_ok=True)
    
    def record_feedback(self, report_id: str, accepted_issues: List[str], 
                        rejected_issues: List[str], developer_notes: str = ""):
        """Record which issues the developer accepted or rejected."""
        feedback = {
            "report_id": report_id,
            "timestamp": datetime.now().isoformat(),
            "accepted": accepted_issues,
            "rejected": rejected_issues,
            "notes": developer_notes,
        }
        file_path = self.storage_path / f"{report_id}.json"
        with open(file_path, "w") as f:
            json.dump(feedback, f, indent=2)
    
    def get_feedback_stats(self) -> Dict:
        """Aggregate feedback statistics to identify prompt weaknesses."""
        all_feedback = []
        for file in self.storage_path.glob("*.json"):
            with open(file) as f:
                all_feedback.append(json.load(f))
        
        total_accepted = sum(len(f["accepted"]) for f in all_feedback)
        total_rejected = sum(len(f["rejected"]) for f in all_feedback)
        
        return {
            "total_reviews": len(all_feedback),
            "acceptance_rate": total_accepted / (total_accepted + total_rejected) 
                if (total_accepted + total_rejected) > 0 else 0,
            "common_rejections": self._find_common_rejections(all_feedback),
        }
    
    def _find_common_rejections(self, feedback_list: List[Dict]) -> List[str]:
        """Identify patterns in rejected suggestions."""
        from collections import Counter
        rejection_counter = Counter()
        for fb in feedback_list:
            for rejection in fb.get("rejected", []):
                # Normalize for comparison
                normalized = rejection.lower().strip()[:100]
                rejection_counter[normalized] += 1
        return [item for item, count in rejection_counter.most_common(5)]

6. Handle Large Files with Chunking

For files exceeding the model's context window, split the file logically and review each chunk independently, then merge the reports:

# chunked_review.py

def chunk_file_by_functions(content: str, max_chunk_lines: int = 200) -> List[Dict]:
    """
    Split a source file into logical chunks (by function/class boundaries)
    for independent review.
    """
    lines = content.split('\n')
    chunks = []
    current_chunk = []
    current_start = 1
    chunk_id = 0
    
    for i, line in enumerate(lines, start=1):
        current_chunk.append(line)
        
        # Detect function/class boundaries (simplified heuristic)
        is_boundary = (
            len(current_chunk) >= max_chunk_lines or
            (line.strip().startswith('def ') and len(current_chunk) > 20) or
            (line.strip().startswith('class ') and len(current_chunk) > 20)
        )
        
        if is_boundary:
            chunks.append({
                "id": chunk_id,
                "start_line": current_start,
                "end_line": i,
                "code": '\n'.join(current_chunk),
            })
            current_chunk = []
            current_start = i + 1
            chunk_id += 1
    
    # Don't forget the last chunk
    if current_chunk:
        chunks.append({
            "id": chunk_id,
            "start_line": current_start,
            "end_line": len(lines),
            "code": '\n'.join(current_chunk),
        })
    
    return chunks

def review_large_file(file_path: str):
    """Review a large file by splitting it into manageable chunks."""
    content = Path(file_path).read_text(encoding="utf-8")
    chunks = chunk_file_by_functions(content, max_chunk_lines=200)
    
    print(f"File split into {len(chunks)} chunks for review")
    
    all_reports = []
    for chunk in chunks:
        submission = f"""**File:** {file_path} (lines {chunk['start_line']}-{chunk['end_line']})
**Chunk:** {chunk['id'] + 1} of {len(chunks)}
**Note:** This is a partial review. Consider interfaces with adjacent chunks.

python
{chunk['code']}
"""
        chat_messages = run_code_review(submission, max_rounds=2)
        report = extract_review_report(chat_messages)
        all_reports.append(report)
    
    # Merge reports into a single consolidated report
    merged = merge_chunk_reports(all_reports, file_path)
    return merged

def merge_chunk_reports(reports: List[Dict], file_path: str) -> Dict:
    """Combine multiple chunk reports into one unified report."""
    merged = {
        "file_path": file_path,
        "overall_assessment": "PASS",
        "critical_issues": [],
        "warnings": [],
        "suggestions": [],
        "summary": "",
    }
    
    # Escalate to most severe assessment found
    severity_order = {"FAIL": 3, "NEEDS WORK": 2, "PASS": 1}
    for report in reports:
        current_severity = severity_order.get(report["overall_assessment"], 0)
        merged_severity = severity_order.get(merged["overall_assessment"], 0)
        if current_severity > merged_severity:
            merged["overall_assessment"] = report["overall_assessment"]
    
    for report in reports:
        merged["critical_issues"].extend(report.get("critical_issues", []))
        merged["warnings"].extend(report.get("warnings", []))
        merged["suggestions"].extend(report.get("suggestions", []))
    
    # Deduplicate similar issues
    merged["critical_issues"] = deduplicate_issues(merged["critical_issues"])
    merged["warnings"] = deduplicate_issues(merged["warnings"])
    merged["suggestions"] = deduplicate_issues(merged["suggestions"])
    
    merged["summary"] = f"Reviewed {len(reports)} chunks. " \
                        f"Found {len(merged['critical_issues'])} critical issues, " \
                        f"{len(merged['warnings'])} warnings, " \
                        f"{len(merged['suggestions'])} suggestions."
    
    return merged

def deduplicate_issues(issues: List[Dict]) -> List[Dict]:
    """Remove duplicate issues based on normalized description similarity."""
    seen = set()
    unique = []
    for issue in issues:
        normalized = issue["description"].lower().strip()[:80]
        if normalized not in seen:
            seen.add(normalized)
            unique.append(issue)
    return unique

7. Secure Your Agent Configuration

Never expose API keys in code or version control. Use environment variables, secret management services, and restrict the agent's capabilities:

# security_config.py

import os
import hashlib
from pathlib import Path

# NEVER hardcode API keys
# Use environment variables with fallback to secure vaults
def load_api_key():
    key = os.getenv("OPENAI_API_KEY")
    if not key:
        # Fallback to a secrets file with restricted permissions
        secrets_file = Path.home() / ".autogen" / "secrets.json"
        if secrets_file.exists():
            import json
            with open(secrets_file) as f:
                secrets = json.load(f)
            key = secrets.get("openai_api_key")
    if not key:
        raise RuntimeError(
            "No API key found. Set OPENAI_API_KEY environment variable "
            "or create ~/.autogen/secrets.json with 'openai_api_key' field."
        )
    return key

# Restrict file system access for the agent
def get_safe_working_directory() -> str:
    """Return a restricted directory for agent file operations."""
    safe_dir = Path.home() / ".autogen" / "sandbox"
    safe_dir.mkdir(parents=True, exist_ok=True)
    return str(safe_dir)

# Validate that reviewed file paths are within the project
def validate_file_path(file_path: str, project_root: str) -> bool:
    """Ensure the file path doesn't escape the project directory."""
    resolved = Path(file_path).resolve()
    root = Path(project_root).resolve()
    return str(resolved).startswith(str(root))

Conclusion

Building a Code Review Agent with AutoGen transforms code review from a manual bottleneck into

— Ad —

Google AdSense will appear here after approval

← Back to all articles