Multi Agent Code Review Ollama
Multi agent code review ollama example.
Multi-Agent Code Review System using Flux agent() and Graph.
Run it
python examples/ai/multi_agent_code_review_ollama.py
"""
Multi-Agent Code Review System using Flux agent() and Graph.
This example demonstrates a multi-agent code review system where specialized
AI agents review code via a DAG-based Graph orchestration. Each agent is created
with the agent() primitive and focuses on a specific aspect (security, performance,
style, testing). Their findings are aggregated into a comprehensive review report.
Key Features:
- **Graph Orchestration**: Uses flux.tasks.Graph for DAG-based fan-out/fan-in
- **agent() Primitive**: Each reviewer is a Flux agent() task with retries and observability
- **Specialized Agents**: 4 agents with domain-specific system prompts
- **Production Ready**: Structured output, priority scoring, CI/CD integration
- **Observable**: Full execution tracing in Flux workflow events
Prerequisites:
1. Install Ollama: https://ollama.ai
2. Pull a capable model: ollama pull llama3.2
3. Start Ollama service: ollama serve
Usage:
# Review a simple function
flux workflow run multi_agent_code_review_ollama '{
"code": "def unsafe_query(user_input):\\n return db.execute(f\\"SELECT * FROM users WHERE name={user_input}\\")"
}'
# Review with context
flux workflow run multi_agent_code_review_ollama '{
"code": "...",
"file_path": "api/auth.py",
"context": "Authentication endpoint for user login"
}'
"""
from __future__ import annotations
import json
import re
from datetime import datetime
from typing import Any
from flux import ExecutionContext, task, workflow
from flux.tasks import Graph
from flux.tasks.ai import agent
REVIEW_OUTPUT_FORMAT = """
Provide your review as a JSON array of findings. Each finding should have:
- severity: "critical" | "high" | "medium" | "low"
- issue: string (description)
- line: number | null
- recommendation: string
Example: [{{"severity": "high", "issue": "SQL injection", "line": 42, "recommendation": "Use parameterized queries"}}]
Respond with ONLY the JSON array, no other text."""
TESTING_OUTPUT_FORMAT = """
Provide your suggestions as a JSON array. Each suggestion should have:
- priority: "high" | "medium" | "low"
- test_case: string (description)
- verifies: string (what it tests)
- importance: string (why it matters)
Respond with ONLY the JSON array, no other text."""
def parse_llm_json_response(content: str) -> list[dict[str, Any]]:
"""Parse JSON from LLM response with robust error handling."""
content = content.strip()
if content.startswith("```"):
lines = content.split("\n")
if len(lines) > 2:
content = "\n".join(lines[1:-1])
elif len(lines) > 1:
content = "\n".join(lines[1:])
content = content.replace("```", "").strip()
try:
result = json.loads(content)
return result if isinstance(result, list) else [result]
except json.JSONDecodeError:
pass
content = content.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
try:
result = json.loads(content)
return result if isinstance(result, list) else [result]
except json.JSONDecodeError:
pass
for match in re.findall(r"\[[\s\S]*\]|\{[\s\S]*\}", content):
try:
result = json.loads(match)
return result if isinstance(result, list) else [result]
except json.JSONDecodeError:
continue
raise json.JSONDecodeError(
"Could not parse JSON from LLM response after multiple attempts",
content,
0,
)
def _build_review_prompt(
code: str,
file_path: str | None,
context: str | None,
output_format: str,
) -> str:
parts = []
if file_path:
parts.append(f"File: {file_path}")
if context:
parts.append(f"Context: {context}")
context_str = "\n".join(parts) if parts else "No additional context provided."
return f"""{context_str}
Code to review:
```
{code}
```
{output_format}"""
def _parse_review(agent_name: str, raw_output: str, key: str = "findings") -> dict[str, Any]:
try:
items = parse_llm_json_response(raw_output)
return {"agent": agent_name, "status": "success", key: items}
except json.JSONDecodeError as e:
return {"agent": agent_name, "status": "parse_error", "error": str(e), key: []}
@task
async def run_security(input_data: dict[str, Any]) -> dict[str, Any]:
"""Graph node: run security review agent."""
security_reviewer = await agent(
"You are a security code reviewer with expertise in finding vulnerabilities. "
"Analyze code for: SQL injection, XSS, authentication issues, hardcoded secrets, "
"input validation, command injection, path traversal, insecure cryptography, "
"race conditions, and sensitive data exposure.",
model="ollama/llama3.2",
name="security_review",
)
prompt = _build_review_prompt(
input_data["code"],
input_data.get("file_path"),
input_data.get("context"),
REVIEW_OUTPUT_FORMAT,
)
raw = await security_reviewer(prompt)
return _parse_review("security", raw)
@task
async def run_performance(input_data: dict[str, Any]) -> dict[str, Any]:
"""Graph node: run performance review agent."""
performance_reviewer = await agent(
"You are a performance optimization expert. "
"Review code for: algorithm efficiency, unnecessary loops, inefficient data structures, "
"memory leaks, database query optimization, missing caching, redundant computations, "
"I/O bottlenecks, and blocking operations.",
model="ollama/llama3.2",
name="performance_review",
)
prompt = _build_review_prompt(
input_data["code"],
input_data.get("file_path"),
input_data.get("context"),
REVIEW_OUTPUT_FORMAT,
)
raw = await performance_reviewer(prompt)
return _parse_review("performance", raw)
@task
async def run_style(input_data: dict[str, Any]) -> dict[str, Any]:
"""Graph node: run style review agent."""
style_reviewer = await agent(
"You are a code quality and style expert. "
"Review code for: readability, naming conventions, organization, documentation, "
"DRY violations, function complexity, magic numbers, error handling, type hints, "
"and PEP 8 compliance.",
model="ollama/llama3.2",
name="style_review",
)
prompt = _build_review_prompt(
input_data["code"],
input_data.get("file_path"),
input_data.get("context"),
REVIEW_OUTPUT_FORMAT,
)
raw = await style_reviewer(prompt)
return _parse_review("style", raw)
@task
async def run_testing(input_data: dict[str, Any]) -> dict[str, Any]:
"""Graph node: run testing review agent."""
testing_reviewer = await agent(
"You are a testing and quality assurance expert. "
"Suggest: critical test cases, edge cases, error conditions, integration tests, "
"mock requirements, test data, missing coverage, and regression tests.",
model="ollama/llama3.2",
name="testing_review",
)
prompt = _build_review_prompt(
input_data["code"],
input_data.get("file_path"),
input_data.get("context"),
TESTING_OUTPUT_FORMAT,
)
raw = await testing_reviewer(prompt)
return _parse_review("testing", raw, key="suggestions")
@task
async def collect_reviews(*review_outputs: dict[str, Any]) -> dict[str, Any]:
"""Graph fan-in node: aggregate all review results."""
reviews = list(review_outputs)
by_agent: dict[str, Any] = {}
all_findings: list[dict[str, Any]] = []
all_suggestions: list[dict[str, Any]] = []
counts: dict[str, int] = {"critical": 0, "high": 0, "medium": 0, "low": 0}
agents_completed = 0
agents_failed = 0
for review in reviews:
agent_name = review.get("agent", "unknown")
status = review.get("status", "unknown")
by_agent[agent_name] = review
if status == "success":
agents_completed += 1
if "findings" in review:
for finding in review["findings"]:
severity = finding.get("severity", "low")
counts[severity] = counts.get(severity, 0) + 1
all_findings.append({"agent": agent_name, **finding})
if "suggestions" in review:
for suggestion in review["suggestions"]:
all_suggestions.append({"agent": agent_name, **suggestion})
else:
agents_failed += 1
return {
"by_agent": by_agent,
"all_findings": all_findings,
"all_suggestions": all_suggestions,
"counts": counts,
"agents_completed": agents_completed,
"agents_failed": agents_failed,
}
@task
async def generate_summary_report(aggregated: dict[str, Any]) -> dict[str, Any]:
"""Generate final summary report from aggregated findings."""
counts = aggregated["counts"]
total_issues = sum(counts.values())
recommendations = []
for finding in aggregated["all_findings"]:
severity = finding.get("severity", "low")
if severity in ["critical", "high"]:
issue = finding.get("issue", "Unknown issue")
line = finding.get("line")
line_info = f" (line {line})" if line else ""
recommendations.append(f"{severity.upper()}: {issue}{line_info}")
return {
"summary": {
"total_issues": total_issues,
"critical": counts.get("critical", 0),
"high": counts.get("high", 0),
"medium": counts.get("medium", 0),
"low": counts.get("low", 0),
"agents_completed": aggregated["agents_completed"],
"agents_failed": aggregated["agents_failed"],
},
"by_agent": aggregated["by_agent"],
"recommendations": recommendations[:10],
"test_suggestions_count": len(aggregated["all_suggestions"]),
}
@workflow
async def multi_agent_code_review_ollama(ctx: ExecutionContext[dict[str, Any]]):
"""
Multi-agent code review system using Flux agent() and Graph.
Four agent() tasks review code via a Graph DAG:
- security_review: Finds vulnerabilities and security issues
- performance_review: Identifies optimization opportunities
- style_review: Reviews code quality and maintainability
- testing_review: Suggests test cases and coverage improvements
Graph executes nodes sequentially following the DAG topology.
Input format:
{
"code": "Source code as string (required)",
"file_path": "Optional file path for context",
"context": "Optional additional context",
"model": "llama3.2",
"ollama_url": "http://localhost:11434"
}
Returns:
Comprehensive review report with findings from all agents
"""
start_time = datetime.now()
raw_input = ctx.input or {}
if not raw_input.get("code"):
return {"error": "No code provided", "execution_id": ctx.execution_id}
graph = (
Graph("code_review")
.add_node("security", run_security)
.add_node("performance", run_performance)
.add_node("style", run_style)
.add_node("testing", run_testing)
.add_node("aggregate", collect_reviews)
.add_node("report", generate_summary_report)
.start_with("security")
.start_with("performance")
.start_with("style")
.start_with("testing")
.add_edge("security", "aggregate")
.add_edge("performance", "aggregate")
.add_edge("style", "aggregate")
.add_edge("testing", "aggregate")
.add_edge("aggregate", "report")
.end_with("report")
)
report = await graph(raw_input)
end_time = datetime.now()
execution_time = (end_time - start_time).total_seconds()
report["metadata"] = {
"execution_id": ctx.execution_id,
"execution_time": execution_time,
"model_used": raw_input.get("model", "llama3.2"),
"code_length": len(raw_input["code"]),
"file_path": raw_input.get("file_path"),
}
return report
if __name__ == "__main__": # pragma: no cover
print("=" * 80)
print("Multi-Agent Code Review Demo - Security Focus")
print("=" * 80 + "\n")
test_code = '''
def login_user(username, password):
"""Authenticate user - INSECURE EXAMPLE"""
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
result = db.execute(query)
return result.fetchone()
def process_file(user_input):
"""Process uploaded file - INSECURE EXAMPLE"""
file_path = f"/uploads/{user_input}"
with open(file_path, 'r') as f:
return f.read()
'''
try:
print("Reviewing code with known security issues...\n")
result = multi_agent_code_review_ollama.run(
{
"code": test_code,
"file_path": "auth.py",
"context": "User authentication and file processing module",
"model": "llama3.2",
},
)
if result.has_failed:
raise Exception(f"Review failed: {result.output}")
output = result.output
summary = output.get("summary", {})
print("Review Summary:")
print(f" Total Issues: {summary.get('total_issues', 0)}")
print(f" Critical: {summary.get('critical', 0)}")
print(f" High: {summary.get('high', 0)}")
print(f" Medium: {summary.get('medium', 0)}")
print(f" Low: {summary.get('low', 0)}")
print(f" Agents Completed: {summary.get('agents_completed', 0)}/4\n")
print("Top Recommendations:")
for i, rec in enumerate(output.get("recommendations", [])[:5], 1):
print(f" {i}. {rec}")
print("\n" + "=" * 80)
print("Multi-agent code review completed!")
print("=" * 80)
except Exception as e:
print(f"Error: {e}")
print("\nMake sure:")
print("1. Ollama is running: ollama serve")
print("2. Model is available: ollama pull llama3.2")
Last verified against Flux 0.56.0.