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:
- Consistency: The agent applies the same rules and patterns every time, eliminating reviewer fatigue and subjective variability.
- Speed: Reviews are completed in seconds or minutes, not hours or days, unblocking continuous delivery pipelines.
- Scalability: Every commit can be reviewed, not just the ones that make it to a pull request with available reviewers.
- Knowledge Capture: Senior developers' review patterns and coding standards can be encoded into agent prompts, preserving institutional knowledge.
- Developer Experience: Authors receive immediate, private feedback before sharing code publicly, reducing friction and embarrassment.
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