Upload 4 files
Browse files- Dockerfile +33 -0
- app.py +218 -0
- inference.py +94 -0
- requirements.txt +6 -0
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use lightweight Python image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Prevent Python from writing .pyc files
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
|
| 7 |
+
# Ensure logs appear immediately
|
| 8 |
+
ENV PYTHONUNBUFFERED=1
|
| 9 |
+
|
| 10 |
+
# Set working directory
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Install system dependencies (minimal but safe)
|
| 14 |
+
RUN apt-get update && \
|
| 15 |
+
apt-get install -y --no-install-recommends \
|
| 16 |
+
build-essential \
|
| 17 |
+
curl && \
|
| 18 |
+
rm -rf /var/lib/apt/lists/*
|
| 19 |
+
|
| 20 |
+
# Copy requirements first (for caching)
|
| 21 |
+
COPY requirements.txt .
|
| 22 |
+
|
| 23 |
+
# Install Python dependencies
|
| 24 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 25 |
+
|
| 26 |
+
# Copy application files
|
| 27 |
+
COPY . .
|
| 28 |
+
|
| 29 |
+
# Expose port used by Flask / Gunicorn
|
| 30 |
+
EXPOSE 7860
|
| 31 |
+
|
| 32 |
+
# Start server (production-safe)
|
| 33 |
+
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:7860", "--workers", "1", "--timeout", "120"]
|
app.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import re
|
| 4 |
+
from time import time
|
| 5 |
+
from flask import Flask, request, jsonify
|
| 6 |
+
from flask_cors import CORS
|
| 7 |
+
from dotenv import load_dotenv
|
| 8 |
+
from openai import OpenAI
|
| 9 |
+
|
| 10 |
+
# api load
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
app = Flask(__name__)
|
| 14 |
+
|
| 15 |
+
# frontend lock so other canot use
|
| 16 |
+
allowed_origins = os.getenv("ALLOWED_ORIGINS", "https://sumit989bishnoi-crypto.github.io")
|
| 17 |
+
CORS(app, origins=allowed_origins.split(","))
|
| 18 |
+
|
| 19 |
+
# models
|
| 20 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 21 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct")
|
| 22 |
+
|
| 23 |
+
# api and hftoken both can pull as user request
|
| 24 |
+
API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN")
|
| 25 |
+
|
| 26 |
+
# when no api key found
|
| 27 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY) if API_KEY else None
|
| 28 |
+
|
| 29 |
+
# language lmt
|
| 30 |
+
SUPPORTED_LANGUAGES = [
|
| 31 |
+
"python", "javascript", "typescript", "java", "c", "cpp",
|
| 32 |
+
"csharp", "go", "rust", "php", "ruby", "swift", "kotlin", "bash"
|
| 33 |
+
]
|
| 34 |
+
|
| 35 |
+
# map ip
|
| 36 |
+
_last_request: dict[str, float] = {}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def is_rate_limited(ip: str) -> bool:
|
| 40 |
+
"""Block the same IP if it hits us again within 2 seconds."""
|
| 41 |
+
now = time()
|
| 42 |
+
if ip in _last_request and now - _last_request[ip] < 2:
|
| 43 |
+
return True
|
| 44 |
+
_last_request[ip] = now
|
| 45 |
+
return False
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def extract_json(raw: str) -> dict | None:
|
| 49 |
+
"""
|
| 50 |
+
Try to parse JSON out of the AI's raw response.
|
| 51 |
+
The model sometimes wraps output in markdown fences or adds extra prose,
|
| 52 |
+
so we strip that first, then fall back to a boundary-aware parser.
|
| 53 |
+
"""
|
| 54 |
+
cleaned = re.sub(r"^```(?:json)?\s*", "", raw.strip())
|
| 55 |
+
cleaned = re.sub(r"\s*```$", "", cleaned).strip()
|
| 56 |
+
|
| 57 |
+
# fast path
|
| 58 |
+
try:
|
| 59 |
+
return json.loads(cleaned)
|
| 60 |
+
except json.JSONDecodeError:
|
| 61 |
+
pass
|
| 62 |
+
|
| 63 |
+
# slow path
|
| 64 |
+
# raw decode stops exactly where the JSON ends
|
| 65 |
+
try:
|
| 66 |
+
idx = cleaned.index("{")
|
| 67 |
+
decoded, _ = json.JSONDecoder().raw_decode(cleaned, idx)
|
| 68 |
+
return decoded
|
| 69 |
+
except (ValueError, json.JSONDecodeError):
|
| 70 |
+
return None
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# routes start
|
| 74 |
+
|
| 75 |
+
@app.route("/")
|
| 76 |
+
def index():
|
| 77 |
+
# basic info of endpoint
|
| 78 |
+
return jsonify({"name": "CodeRescue API", "version": "1.0.0", "status": "running"})
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@app.route("/health")
|
| 82 |
+
def health():
|
| 83 |
+
# health check code
|
| 84 |
+
return jsonify({"status": "ok"})
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# languages support
|
| 88 |
+
@app.route("/languages")
|
| 89 |
+
def languages():
|
| 90 |
+
return jsonify({"languages": SUPPORTED_LANGUAGES})
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@app.route("/analyze", methods=["POST"])
|
| 94 |
+
def analyze_code():
|
| 95 |
+
# rate limit
|
| 96 |
+
|
| 97 |
+
# x forwarded for is set by proxies
|
| 98 |
+
forwarded = request.headers.get("X-Forwarded-For", "")
|
| 99 |
+
ip = forwarded.split(",")[0].strip() if forwarded else (request.remote_addr or "unknown")
|
| 100 |
+
|
| 101 |
+
if is_rate_limited(ip):
|
| 102 |
+
return jsonify({
|
| 103 |
+
"error": "Too many requests. Please wait a moment.",
|
| 104 |
+
"fixed_code": "",
|
| 105 |
+
"language": ""
|
| 106 |
+
}), 429
|
| 107 |
+
|
| 108 |
+
# pre checks
|
| 109 |
+
|
| 110 |
+
if not client:
|
| 111 |
+
# to stop request here
|
| 112 |
+
return jsonify({"error": "API key not configured", "fixed_code": "", "language": ""}), 500
|
| 113 |
+
|
| 114 |
+
data = request.get_json(silent=True)
|
| 115 |
+
if not data or not data.get("code", "").strip():
|
| 116 |
+
return jsonify({"error": "No code provided"}), 400
|
| 117 |
+
|
| 118 |
+
raw_code = data["code"].strip()
|
| 119 |
+
language = data.get("language", "python").lower()
|
| 120 |
+
|
| 121 |
+
if language not in SUPPORTED_LANGUAGES:
|
| 122 |
+
return jsonify({
|
| 123 |
+
"error": f"Unsupported language '{language}'. Supported: {SUPPORTED_LANGUAGES}"
|
| 124 |
+
}), 400
|
| 125 |
+
|
| 126 |
+
# input prep
|
| 127 |
+
|
| 128 |
+
# input limit
|
| 129 |
+
warning = ""
|
| 130 |
+
if len(raw_code) > 800:
|
| 131 |
+
warning = "Code was truncated to 800 characters for processing."
|
| 132 |
+
user_code = raw_code[:800]
|
| 133 |
+
|
| 134 |
+
# call model
|
| 135 |
+
try:
|
| 136 |
+
response = client.chat.completions.create(
|
| 137 |
+
model=MODEL_NAME,
|
| 138 |
+
max_tokens=800, # max token
|
| 139 |
+
messages=[
|
| 140 |
+
{
|
| 141 |
+
"role": "system",
|
| 142 |
+
"content": (
|
| 143 |
+
"You are an expert developer and code debugger.\n"
|
| 144 |
+
"Return ONLY valid JSON β no markdown, no extra text.\n"
|
| 145 |
+
"Explain briefly WHY the error happened (1-2 lines).\n"
|
| 146 |
+
"Fixed code must be SHORT and COMPLETE.\n"
|
| 147 |
+
"Preserve all newlines and indentation in fixed_code.\n"
|
| 148 |
+
"Do NOT cut output mid-way.\n"
|
| 149 |
+
"Set confidence to 'high', 'medium', or 'low' based on how certain you are.\n"
|
| 150 |
+
"Format strictly:\n"
|
| 151 |
+
"{\"explanation\":\"...\",\"fixed_code\":\"...\","
|
| 152 |
+
"\"language\":\"...\",\"confidence\":\"high|medium|low\"}"
|
| 153 |
+
),
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"role": "user",
|
| 157 |
+
"content": f"Language: {language}\n\nCode:\n{user_code}",
|
| 158 |
+
},
|
| 159 |
+
],
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
raw = response.choices[0].message.content.strip()
|
| 163 |
+
|
| 164 |
+
# model returned nothing
|
| 165 |
+
if not raw:
|
| 166 |
+
return jsonify({
|
| 167 |
+
"explanation": "Empty response from AI.",
|
| 168 |
+
"fixed_code": "",
|
| 169 |
+
"language": language,
|
| 170 |
+
"confidence": "low",
|
| 171 |
+
"warning": warning
|
| 172 |
+
}), 200
|
| 173 |
+
|
| 174 |
+
# parse response
|
| 175 |
+
parsed = extract_json(raw)
|
| 176 |
+
|
| 177 |
+
# return the raw text so the user sees something
|
| 178 |
+
if not parsed:
|
| 179 |
+
return jsonify({
|
| 180 |
+
"explanation": "AI response was not clean JSON β raw output returned.",
|
| 181 |
+
"fixed_code": raw[:800],
|
| 182 |
+
"language": language,
|
| 183 |
+
"confidence": "low",
|
| 184 |
+
"warning": warning
|
| 185 |
+
}), 200
|
| 186 |
+
|
| 187 |
+
# unescape literal \n and \t that the model sometimes emits inside strings
|
| 188 |
+
fixed_code = parsed.get("fixed_code", "")
|
| 189 |
+
fixed_code = fixed_code.replace("\\n", "\n").replace("\\t", "\t")
|
| 190 |
+
|
| 191 |
+
return jsonify({
|
| 192 |
+
"explanation": parsed.get("explanation", "No explanation provided."),
|
| 193 |
+
"fixed_code": fixed_code,
|
| 194 |
+
"language": parsed.get("language", language),
|
| 195 |
+
"confidence": parsed.get("confidence", "medium"),
|
| 196 |
+
"warning": warning,
|
| 197 |
+
})
|
| 198 |
+
|
| 199 |
+
except Exception as e:
|
| 200 |
+
return jsonify({
|
| 201 |
+
"explanation": f"Server error: {str(e)}",
|
| 202 |
+
"fixed_code": "",
|
| 203 |
+
"language": language,
|
| 204 |
+
"confidence": "low",
|
| 205 |
+
"warning": warning
|
| 206 |
+
}), 500
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
#donot havev to change this
|
| 210 |
+
@app.route("/openenv/reset", methods=["POST"])
|
| 211 |
+
@app.route("/reset", methods=["POST"])
|
| 212 |
+
def openenv_reset():
|
| 213 |
+
return jsonify({"status": "success"})
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
if __name__ == "__main__":
|
| 217 |
+
port = int(os.environ.get("PORT", 7860))
|
| 218 |
+
app.run(host="0.0.0.0", port=port)
|
inference.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
from openai import OpenAI
|
| 4 |
+
|
| 5 |
+
# ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 6 |
+
API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
|
| 7 |
+
MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct")
|
| 8 |
+
API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN")
|
| 9 |
+
|
| 10 |
+
client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY) if API_KEY else None
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def solve(task_name: str, task_input: str) -> str:
|
| 14 |
+
print(f"[START] task={task_name}", flush=True)
|
| 15 |
+
print(f"[STEP] step=1 reward=0.5", flush=True)
|
| 16 |
+
|
| 17 |
+
if not client:
|
| 18 |
+
print(f"[END] task={task_name} score=0.1 steps=1", flush=True)
|
| 19 |
+
return "Error: API key not configured"
|
| 20 |
+
|
| 21 |
+
response = client.chat.completions.create(
|
| 22 |
+
model=MODEL_NAME,
|
| 23 |
+
max_tokens=2048,
|
| 24 |
+
messages=[
|
| 25 |
+
{
|
| 26 |
+
"role": "system",
|
| 27 |
+
"content": (
|
| 28 |
+
"You are an expert developer.\n"
|
| 29 |
+
"Return ONLY valid JSON.\n"
|
| 30 |
+
"Explanation must be MAX 2 lines.\n"
|
| 31 |
+
"Fixed code must be SHORT and COMPLETE.\n"
|
| 32 |
+
"Preserve all newlines and indentation in fixed_code.\n"
|
| 33 |
+
"Do NOT cut output.\n"
|
| 34 |
+
"Format strictly:\n"
|
| 35 |
+
"{\"explanation\":\"...\",\"fixed_code\":\"...\",\"language\":\"...\"}"
|
| 36 |
+
),
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"role": "user",
|
| 40 |
+
"content": f"Fix this code and explain the errors:\n{task_input}",
|
| 41 |
+
},
|
| 42 |
+
],
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
output = response.choices[0].message.content
|
| 46 |
+
score = grade(task_input, output)
|
| 47 |
+
|
| 48 |
+
print(f"[STEP] step=2 reward={score}", flush=True)
|
| 49 |
+
print(f"[END] task={task_name} score={score} steps=2", flush=True)
|
| 50 |
+
|
| 51 |
+
return output
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def grade(task_input: str, output: str) -> float:
|
| 55 |
+
if not output or len(output.strip()) < 5:
|
| 56 |
+
return 0.1
|
| 57 |
+
|
| 58 |
+
score = 0.5
|
| 59 |
+
try:
|
| 60 |
+
raw = output.strip().replace("```json", "").replace("```", "").strip()
|
| 61 |
+
parsed = json.loads(raw)
|
| 62 |
+
if parsed.get("explanation"):
|
| 63 |
+
score += 0.15
|
| 64 |
+
if parsed.get("fixed_code"):
|
| 65 |
+
score += 0.15
|
| 66 |
+
if parsed.get("language"):
|
| 67 |
+
score += 0.1
|
| 68 |
+
except Exception:
|
| 69 |
+
score = 0.3
|
| 70 |
+
|
| 71 |
+
return round(min(max(score, 0.1), 0.9), 2)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# ββ Tasks βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 75 |
+
TASKS = [
|
| 76 |
+
{
|
| 77 |
+
"id": "task_1",
|
| 78 |
+
"description": "Fix syntax error in Python",
|
| 79 |
+
"input": "def hello(\n print('hello world')",
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"id": "task_2",
|
| 83 |
+
"description": "Fix logic bug in JavaScript",
|
| 84 |
+
"input": "function add(a, b) { return a - b; }",
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"id": "task_3",
|
| 88 |
+
"description": "Fix type error and missing await in async function",
|
| 89 |
+
"input": "async function fetchData() { let data = fetchFromAPI(); return data.json; }",
|
| 90 |
+
},
|
| 91 |
+
]
|
| 92 |
+
|
| 93 |
+
for task in TASKS:
|
| 94 |
+
output = solve(task["id"], task["input"])
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
flask-cors
|
| 3 |
+
python-dotenv
|
| 4 |
+
openai
|
| 5 |
+
gunicorn
|
| 6 |
+
requests
|