Introduction
No software project is perfect. Bugs, memory leaks, infinite loops - debugging and resolving issues can often feel like a never-ending process filled with repetitive tasks and countless hours spent wondering why the code misbehaved. Manual debugging is inherently slow, error prone, and exhausting.
An automated, AI-powered solution presents an opportunity to streamline and enhance this debugging workflow. By leveraging intelligent automation, developers can spend less time manually identifying and fixing repetitive issues, allowing more focus on innovation and meaningful development tasks.
In this blog, we walk through building an AI-powered GitHub issue solving assistant covering:
- Repository mapping: identifying relevant files by analyzing repository structures and issue descriptions
- AI-based fix generation: leveraging AI models to suggest precise, context-aware code modifications
- Evaluation pipeline: applying rigorous tests to validate the correctness of AI-generated patches
Project Overview
This project introduces an AI-driven software engineering tool built to automatically resolve issues within a GitHub repository. The system operates through a structured approach comprising three main stages:
- Repository Mapping: Identifying relevant files by analyzing repository structures and issue descriptions
- AI-Based Fix Generation: Leveraging sophisticated AI models to suggest precise and context-aware code modifications
- Evaluation Pipeline: Applying rigorous tests to validate the correctness and effectiveness of AI-generated patches
Existing Tools: Agentless vs Aider
Agentless is a fully automated debugging pipeline that operates without interactive agent loops. It takes an issue description along with a repository and autonomously identifies relevant files, pinpoints the faulty code, and generates candidate patches in a structured manner. The workflow follows a deterministic sequence: first localizing the error by analyzing repository structure, then prompting the LLM to generate minimal precise fixes formatted as unified diffs, and finally validating patches through syntax checks and test execution. Agentless is particularly effective in repositories with strong test coverage but may struggle with ambiguous issues requiring human intuition.
Aider is an interactive AI debugging assistant designed for command line use. Unlike Agentless, Aider requires an active developer in the loop to guide the process. It allows users to chat with an AI model while making real-time modifications to a local codebase and seamlessly integrates with Git. Aider can run test suites and repeatedly refine code until tests pass. It supports multiple LLMs including GPT-4 and locally hosted models, but presents a steeper learning curve for CLI-unfamiliar developers.
The AI debugging system described in this blog shares similarities with Agentless in its structured, automated pipeline approach but introduces enhancements including a more sophisticated prompt engineering strategy and repository mapping for improved file identification accuracy. It is designed to function as an autonomous debugging agent that can be integrated into development pipelines while still allowing human oversight where necessary.
Motivation
- Increased Efficiency: Reducing manual effort by automating repetitive debugging tasks
- Improved Accuracy: Leveraging AI to suggest precise, effective solutions to code issues
- Higher Quality Software: Systematic validation processes ensuring code reliability and stability
- Enhanced Productivity: Allowing developers to redirect focus towards strategic, higher-value tasks
Data Mapping
Before an AI-powered debugging tool can efficiently resolve issues, it must understand the structure of the repository. Repository mapping plays a crucial role by scanning, structuring, and indexing project files. This ensures the AI can quickly locate relevant sections when diagnosing and fixing issues.
Implementing the RepoMap Class
Class Initialization
import os
import fnmatch
class RepoMap:
def __init__(self, repo_path, max_file_size=100000000, ignore_patterns=None):
self.repo_path = os.path.abspath(repo_path)
self.root_dir_name = os.path.basename(self.repo_path)
self.max_file_size = max_file_size
self.ignore_patterns = ignore_patterns or [
".github", ".git", "__pycache__", "*.pyc"
]
self.file_map = {}
self.directory_structure = {"dirs": {}, "files": []}
self.build_repo_map()This initialization ensures the repository path is stored in absolute form and prevents processing of large files that could slow down the debugging process. The file_map stores indexed files along with their content, while directory_structure represents the hierarchical organization of the repository.
Building the Data Map
def build_repo_map(self):
for root, dirs, files in os.walk(self.repo_path):
dirs[:] = [
d for d in dirs if not any(fnmatch.fnmatch(d, pat) for pat in self.ignore_patterns)
]
rel_path = self.normalize_path(os.path.relpath(root, self.repo_path))
if rel_path == ".":
rel_path = ""
current_level = self.directory_structure
path_parts = rel_path.split("/") if rel_path else []
for part in path_parts:
if part not in current_level["dirs"]:
current_level["dirs"][part] = {"dirs": {}, "files": []}
current_level = current_level["dirs"][part]
for file in files:
if not file.endswith(".py") or any(
fnmatch.fnmatch(file, pat) for pat in self.ignore_patterns
):
continue
file_path = os.path.join(root, file)
abs_file_path = os.path.abspath(file_path)
unix_style_path = self.normalize_path(abs_file_path)
current_level["files"].append(file)
if os.path.getsize(file_path) <= self.max_file_size:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
self.file_map[unix_style_path] = content
else:
self.file_map[unix_style_path] = (
f"File too large. Size: {os.path.getsize(file_path)} bytes"
)Searching for Relevant Files
def search_files(self, query):
relevant_files = []
for file_path, content in self.file_map.items():
if isinstance(content, str) and query.lower() in content.lower():
rel_path = os.path.relpath(file_path, self.normalize_path(self.repo_path))
relevant_files.append(self.normalize_path(rel_path))
return relevant_filesGenerating a Repository Overview
def get_repo_overview(self):
overview = ["Repository Structure:\n"]
self._add_directory_to_overview(self.directory_structure, "", overview, is_root=True)
return "".join(overview)
# Example output:
# Repository Structure:
# └─ project/
# ├─ src/
# │ ├─ main.py
# │ ├─ utils.py
# ├─ tests/
# │ ├─ test_main.pyAI Agent System
The AI Agent System serves as the core component of the automated debugging tool. It integrates multiple AI models, manages repository access, and orchestrates issue resolution efficiently.
Class Initialization
import os
from urllib.parse import urlparse
import git
import google.generativeai as genai
import anthropic
from dotenv import load_dotenv
from agent.prompts import PromptGenerator
from agent.repomap import RepoMap
from agent.utils import *
load_dotenv()
genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
openai_key = os.getenv("OPENAI_API_KEY2")
anthropic_key = os.getenv("ANTHROPIC_API_KEY")
class AIAgentSystem:
def __init__(self, model_name="gpt-4o-2024-08-06"):
self.model_name = model_name
self.repo = None
self.repo_path = None
self.repo_map = None
self.prompt_generator = PromptGenerator()
self.gpt4o_client = CustomOpenAI(api_key=openai_key)
self.json_model = genai.GenerativeModel(model_name="gemini-1.5-pro-002")
self.normal_model = genai.GenerativeModel(model_name="gemini-1.5-pro-002")
print(f"Initialized AIAgentSystem with model: {model_name}")Cloning and Accessing Repositories
def get_or_clone_repo(self, repo_url, commit_hash=None):
parsed_url = urlparse(repo_url)
repo_name = os.path.basename(parsed_url.path).rstrip(".git")
base_dir = os.path.join(os.getcwd(), "cloned_repos")
os.makedirs(base_dir, exist_ok=True)
self.repo_path = os.path.abspath(os.path.join(base_dir, repo_name))
if os.path.exists(self.repo_path):
self.repo = git.Repo(self.repo_path)
if commit_hash:
self.repo.git.checkout(commit_hash)
else:
self.repo = git.Repo.clone_from(repo_url, self.repo_path)
if commit_hash:
self.repo.git.checkout(commit_hash)
self.repo_map = RepoMap(self.repo_path)
return self.repo_pathGenerating AI Responses
def generate_response(self, prompt):
messages = [{"role": "user", "content": prompt}]
response = self.gpt4o_client.chat_completions_create(
model=self.model_name, messages=messages, temperature=0.3
)
return response.choices[0].message.contentIdentifying Relevant Files
def find_relevant_files(self, root, issue_description):
repo_overview = self.repo_map.get_repo_overview()
prompt = self.prompt_generator.get_find_relevant_files_prompt(root, repo_overview, issue_description)
response = self.generate_response(prompt)
return extract_file_paths(response)Processing an Issue
def process_issue(self, repo_url, issue_description, commit_hash=None):
print(f"\n{'='*50}\nProcessing issue: {issue_description}")
repo_path = self.get_or_clone_repo(repo_url, commit_hash)
if not repo_path:
return "Failed to clone or access the repository."
relevant_files = self.find_relevant_files(self.repo_path, issue_description)
most_relevant_file = self.get_most_relevant_file(relevant_files, issue_description)
self.og_content = self.repo_map.get_file_content(most_relevant_file)
original_file_content = self.repo_map.get_file_content(most_relevant_file)
instructions = self.generate_instructions(issue_description)
print(f"\nInstructions:\n{instructions}\n")
patch = self.generate_repair(most_relevant_file, original_file_content, instructions)
if patch:
print(f"\nGenerated patch for {most_relevant_file}:")
print(patch)
return patch
else:
print(f"\nNo changes were necessary for {most_relevant_file}")
return NoneGenerating Fix Instructions
def generate_instructions(self, issue_description):
prompt = self.prompt_generator.get_fix_instructions_prompt(issue_description, self.og_content)
response = self.generate_response(prompt)
return responseApplying the Patch
def generate_repair(self, file_path, og_content, instructions):
ne_content = copy.deepcopy(og_content)
prompt = self.prompt_generator.get_repair_prompt(instructions, ne_content)
correction = self.generate_response(prompt)
extraction_prompt = self.prompt_generator.extract_repair_code_prompt(correction)
extracted_resp = self.generate_json_response(extraction_prompt)
extracted_block = ast.literal_eval(extracted_resp)
for fix in extracted_block["fixes"]:
search_term = fix["search"]
replace_term = fix["replace"]
line_interval = find_search_term_interval(ne_content, search_term)
if line_interval:
ne_content = replace_search_term(ne_content, search_term, replace_term, line_interval)
relative_path = os.path.relpath(file_path, self.repo_path)
diff = fake_git_repo("fake_repos", relative_path, og_content, ne_content)
return diffExample git diff output:
--- src/data_handler.py
+++ src/data_handler.py
@@ -34,6 +34,7 @@
processed_data = compute_results(data)
store_data(processed_data)
+ del data # Prevent memory leak
return processed_dataPrompt Engineering
Finding Relevant Files
@staticmethod
def get_find_relevant_files_prompt(root, repo_overview, issue_description):
return f"""
Root directory: ### {root} ###
Repository Structure:
{repo_overview}
Issue Description:
---BEGIN ISSUE---
{issue_description}
---END ISSUE---
Instructions:
Identify the most relevant files related to the issue.
List at most 10 files in order of relevance.
Output should be a newline separated list wrapped in triple backticks.
"""Generating Fix Instructions
@staticmethod
def get_fix_instructions_prompt(issue, file_content):
return f"""
File content:
---BEGIN CONTENT---
{file_content}
---END CONTENT---
Issue description:
---BEGIN ISSUE---
{issue}
---END ISSUE---
Provide step-by-step modification instructions to resolve the issue.
"""Generating Repair Prompts
@staticmethod
def get_repair_prompt(instruction, file_content):
return f"""
File content:
---BEGIN CODE---
{file_content}
---END CODE---
Instruction:
---BEGIN INSTRUCTION---
{instruction}
---END INSTRUCTION---
Implement the changes using SEARCH and REPLACE blocks with proper indentation.
"""Utility Functions
Token Management
def calculate_tokens(text):
encoding = tiktoken.get_encoding("cl100k_base")
return len(encoding.encode(text))Syntax Validation
def check_file_syntax(file_content):
try:
ast.parse(file_content)
return True
except SyntaxError:
return FalseSearch and Replace
def find_search_term_interval(file_content, search_term):
content_lines = file_content.splitlines()
for i, line in enumerate(content_lines):
if line.strip() == search_term.strip():
return [i + 1, i + len(search_term.splitlines())]
return None
def replace_search_term(file_content, search_term, replace_term, line_interval):
content_lines = file_content.splitlines()
content_lines[line_interval[0] - 1: line_interval[1]] = replace_term.splitlines()
return "\n".join(content_lines)Git Patch Generation
def fake_git_repo(repo_playground, file_path, old_content, new_content):
with tempfile.TemporaryDirectory(dir=repo_playground) as temp_dir:
full_file_path = os.path.join(temp_dir, file_path)
os.makedirs(os.path.dirname(full_file_path), exist_ok=True)
with open(full_file_path, "w") as f:
f.write(old_content)
subprocess.run(["git", "init"], cwd=temp_dir)
subprocess.run(["git", "add", file_path], cwd=temp_dir)
subprocess.run(["git", "commit", "-m", "initial commit"], cwd=temp_dir)
with open(full_file_path, "w") as f:
f.write(new_content)
git_diff = subprocess.run(
["git", "diff", file_path],
cwd=temp_dir, capture_output=True, text=True
)
return git_diff.stdoutRunning the System
# Step 1: Install dependencies
pip install -r requirements.txt
# Step 2: Initialize the AI agent
agent = AIAgentSystem(model_name="gpt-4o-2024-08-06")
# Step 3: Process an issue
repo_url = "https://github.com/example/repo.git"
issue_description = "Fix the function causing memory leaks."
commit_hash = None # Optional
patch = agent.process_issue(repo_url, issue_description, commit_hash)
print(patch)
# Step 4: Validate the patch
if check_file_syntax(patch):
print("Patch is syntactically correct!")Evaluation on SWE Bench Lite
To assess the effectiveness of AI-generated patches, we ran the system through the SWE bench lite dataset, which provides real-world software engineering issues from GitHub including instance_id, patch, repo, base_commit, problem_statement, and FAIL_TO_PASS/PASS_TO_PASS test case lists.
We processed 20 randomly sampled issues and evaluated them using the Moatless-Evaltool, which requires submitting a JSONL file:
{"instance_id": "example_repo__12345", "model_patch": "diff --git a/file.py b/file.py\n--- a/file.py\n+++ b/file.py\n@@ -10,7 +10,7 @@\n- old_code()\n+ new_code()\n", "model_name_or_path": "swe_agent"}
{"instance_id": "another_repo__67890", "model_patch": "diff --git a/module.py b/module.py\n--- a/module.py\n+++ b/module.py\n@@ -20,3 +20,3 @@\n- buggy_line()\n+ fixed_line()\n", "model_name_or_path": "swe_agent"}Results
- Overall Accuracy: 30% (6/20 problems solved correctly)
- File Identification: 7 out of 14 failures attributed to incorrect file selection
- Fix Generation Accuracy given correct file: 40% (2/7 additional problems resolved)
- Future focus: Enhancing file selection strategies to improve Stage 1 accuracy
The system performs well when the issue is localized within a single, clearly defined file. Errors primarily stem from selecting incorrect files in large repositories. Refining the repository scanning process and incorporating better heuristics for identifying relevant files is a promising next step.
Conclusion and Future Improvements
The AI-powered software engineering tool provides a structured, automated approach to identifying, analyzing, and resolving issues. By integrating AI models with repository mapping, structured prompt generation, and robust evaluation pipelines, this system streamlines debugging and accelerates issue resolution.
Future improvements include:
- Enhancing file identification accuracy through refined AI prompts and semantic analysis beyond keyword matching
- Improving prompt engineering with adaptive refinement using reinforcement learning techniques
- Multi-agent collaboration combining outputs from multiple AI models with voting mechanisms to rank suggested fixes
- Expanding support for Java, JavaScript, and C++ beyond Python
- Automated functional testing and CI/CD integration for real-time patch validation
At Cogninest AI, we specialize in helping companies build cutting edge AI solutions. To explore how we can assist your business, feel free to reach out to us at team@cogninest.ai.
- LLM
- GitHub
- automated debugging
- SWE-bench
- GPT-4
- Agentless
- Aider
- AI engineering
- code repair
- prompt engineering
.png&w=3840&q=75)

.png&w=3840&q=75)
