We are going to build a Codebase Refactoring Agent that reads tangled legacy functions, identifies hidden dependencies, and outputs clean, testable modules. This is for engineers who are tired of refactoring by hand and want to automate the analysis phase without losing architectural rigor. We will wire it directly to Oxlo.ai so every reasoning step costs one flat request, even when we feed it thousand-line files.
What You'll Need
- Python 3.10+
- The OpenAI SDK:
pip install openai - An Oxlo.ai API key from https://portal.oxlo.ai
1. Setup and Configuration
I start by importing the OpenAI SDK and pointing it at Oxlo.ai. Because Oxlo.ai is fully OpenAI-compatible, this is a drop-in replacement. No extra adapters.
from openai import OpenAI
import json
client = OpenAI(
base_url="https://api.oxlo.ai/v1",
api_key="YOUR_OXLO_API_KEY"
)
MODEL = "deepseek-v3.2" # strong on coding and reasoning, available on the free tier
2. Define the Agent's System Prompt
The system prompt is the only logic we hardcode. It forces the model to return structured JSON and think in three phases: analyze, plan, implement. I keep it strict so the output is machine-parseable.
SYSTEM_PROMPT = """You are a senior staff engineer who specializes in untangling complex code.
When given code, you must respond with a single JSON object containing exactly these keys:
- "analysis": a string describing architectural flaws, hidden coupling, and violation of single responsibility
- "plan": a list of 3 to 5 concrete refactoring steps
- "refactored_code": a string containing the full rewritten code
- "tests": a string containing unit tests for the new code
Rules:
1. Do not change external behavior.
2. Split large functions into single-responsibility helpers.
3. Use type hints.
4. Prefer dependency injection over global state.
5. Return ONLY the JSON object, no markdown fences.
"""
3. Build the Analysis and Refactoring Pipeline
I wrap the API call in a function that sends messy code and parses the JSON response. Because Oxlo.ai uses request-based pricing, you pay one flat cost per call even when the prompt contains a full module. See https://oxlo.ai/pricing for details.
def refactor_code(messy_code: str) -> dict:
user_message = f"Refactor the following Python module:\n\n
```python\n{messy_code}\n```
"
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
],
temperature=0.2,
max_tokens=4096,
)
raw = response.choices[0].message.content
# some reasoning models wrap thinking in tags; strip them if present
if "<think>" in raw:
raw = raw.split("</think>")[-1].strip()
return json.loads(raw)
4. Create a Validation Harness
Before I trust the agent, I need a quick way to verify the refactored code runs and the tests pass. This helper writes the files to disk and runs them with pytest.
import subprocess
import tempfile
import os
def validate(refactor_result: dict) -> bool:
with tempfile.TemporaryDirectory() as tmpdir:
refactored_path = os.path.join(tmpdir, "refactored.py")
with open(refactored_path, "w") as f:
f.write(refactor_result["refactored_code"])
test_path = os.path.join(tmpdir, "test_refactored.py")
with open(test_path, "w") as f:
f.write(refactor_result["tests"])
result = subprocess.run(
["python", "-m", "pytest", test_path, "-v"],
capture_output=True,
text=True,
cwd=tmpdir
)
print(result.stdout)
if result.returncode != 0:
print(result.stderr)
return False
return True
5. Add a Retry Loop for Stubborn Code
Complex code sometimes breaks the first test run. Instead of giving up, I feed the stderr back to the model and ask for a fix. Because Oxlo.ai charges per request, not per token, retrying with long error traces does not punish the budget.
def refactor_with_retry(messy_code: str, max_retries: int = 2) -> dict:
result = refactor_code(messy_code)
for attempt in range(max_retries):
try:
if validate(result):
print(f"Validation passed on attempt {attempt + 1}")
return result
except Exception as e:
print(f"Attempt {attempt + 1} failed with: {e}")
feedback = f"""The previous refactor failed validation.
Please fix the code and tests. Here is the current output:
{json.dumps(result, indent=2)}
Fix any syntax or logic errors and return the corrected JSON."""
response = client.chat.completions.create(
model=MODEL,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": feedback},
],
temperature=0.2,
max_tokens=4096,
)
raw = response.choices[0].message.content
if "<think>" in raw:
raw = raw.split("</think>")[-1].strip()
result = json.loads(raw)
return result
Run It
Here is a real example: a function that handles user authentication, database writes, and email notifications all at once. I pass it to the agent and print the structured output.
MESSY_CODE = '''
def handle_user(data):
import sqlite3, smtplib
conn = sqlite3.connect("app.db")
c = conn.cursor()
if data.get("age") > 18:
c.execute("INSERT INTO users VALUES (?,?)", (data["name"], data["email"]))
conn.commit()
server = smtplib.SMTP("smtp.example.com")
server.sendmail("admin@example.com", data["email"], "Welcome")
server.quit()
return True
else:
return False
'''
if __name__ == "__main__":
result = refactor_with_retry(MESSY_CODE)
print("\n=== ANALYSIS ===")
print(result["analysis"])
print("\n=== PLAN ===")
for step in result["plan"]:
print(f"- {step}")
print("\n=== REFACTORED CODE ===")
print(result["refactored_code"])
When I ran this against Oxlo.ai using deepseek-v3.2, the agent returned the following on the second attempt after pytest caught a missing import:
Validation passed on attempt 2
=== ANALYSIS ===
The function violates single responsibility by mixing persistence, business logic, and infrastructure concerns. It uses a hardcoded database path and SMTP server, making testing impossible. No input validation or error handling is present.
=== PLAN ===
- Extract database operations into a UserRepository class
- Extract email delivery into a Mailer service with injectable configuration
- Add Pydantic model for input validation
- Wrap external calls in try/except blocks
- Add type hints to all public methods
=== REFACTORED CODE ===
from pydantic import BaseModel, EmailStr
from typing import Protocol
class UserData(BaseModel):
name: str
email: EmailStr
age: int
class UserRepository(Protocol):
def save(self, name: str, email: str) -> None: ...
class Mailer(Protocol):
def send_welcome(self, to: str) -> None: ...
class SQLiteUserRepository:
def __init__(self, db_path: str):
self.db_path = db_path
def save(self, name: str, email: str) -> None:
import sqlite3
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute("INSERT INTO users VALUES (?,?)", (name, email))
conn.commit()
conn.close()
class SMTPMailer:
def __init__(self, host: str, from_addr: str):
self.host = host
self.from_addr = from_addr
def send_welcome(self, to: str) -> None:
import smtplib
server = smtplib.SMTP(self.host)
server.sendmail(self.from_addr, to, "Welcome")
server.quit()
def register_user(data: dict, repo: UserRepository, mailer: Mailer) -> bool:
user = UserData(**data)
if user.age <= 18:
return False
repo.save(user.name, user.email)
mailer.send_welcome(user.email)
return True
Wrap-Up
You now have a working refactoring agent that turns spaghetti code into testable modules. Two concrete next steps: wire this into a pre-commit hook so every pull request gets an automatic architectural review, or swap the model to kimi-k2.6 when you need vision capabilities to refactor code from screenshots of legacy documentation.
Top comments (0)