Spaces:
Sleeping
Sleeping
Update backend.py
Browse files- backend.py +49 -60
backend.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
# backend.py — FINAL,
|
| 2 |
import sqlite3
|
| 3 |
import os
|
| 4 |
import json
|
|
@@ -58,12 +58,9 @@ def get_project(project_id): return _db_execute("SELECT * FROM projects WHERE id
|
|
| 58 |
|
| 59 |
# ------------------------------ MODEL LOADING & CACHING ------------------------------
|
| 60 |
MODEL_REGISTRY = {
|
| 61 |
-
"
|
| 62 |
-
# --- FINAL, CORRECTED MODEL NAME AS PER YOUR INSTRUCTION ---
|
| 63 |
-
"architect": "Qwen/Qwen2.5-Coder-0.5B-Instruct",
|
| 64 |
-
"coder": "Qwen/Qwen2.5-Coder-0.5B-Instruct",
|
| 65 |
"reviewer": "microsoft/Phi-3-mini-4k-instruct",
|
| 66 |
-
"tester": "Qwen/Qwen2
|
| 67 |
"publisher": "microsoft/Phi-3-mini-4k-instruct",
|
| 68 |
}
|
| 69 |
_MODEL_CACHE = {}
|
|
@@ -72,25 +69,14 @@ def load_model(model_name):
|
|
| 72 |
if model_name in _MODEL_CACHE:
|
| 73 |
return _MODEL_CACHE[model_name]
|
| 74 |
|
| 75 |
-
|
| 76 |
-
model_kwargs = {
|
| 77 |
-
"device_map": "auto",
|
| 78 |
-
"trust_remote_code": True,
|
| 79 |
-
"attn_implementation": "eager",
|
| 80 |
-
}
|
| 81 |
|
| 82 |
if torch.cuda.is_available():
|
| 83 |
print(f"CUDA is available. Loading model '{model_name}' in 4-bit for GPU acceleration.")
|
| 84 |
-
bnb_config = BitsAndBytesConfig(
|
| 85 |
-
load_in_4bit=True,
|
| 86 |
-
bnb_4bit_use_double_quant=True,
|
| 87 |
-
bnb_4bit_quant_type="nf4",
|
| 88 |
-
bnb_4bit_compute_dtype=torch.bfloat16
|
| 89 |
-
)
|
| 90 |
model_kwargs["quantization_config"] = bnb_config
|
| 91 |
else:
|
| 92 |
print(f"CUDA not available. Loading model '{model_name}' on CPU in default precision.")
|
| 93 |
-
# No quantization on CPU
|
| 94 |
|
| 95 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 96 |
model = AutoModelForCausalLM.from_pretrained(model_name, **model_kwargs)
|
|
@@ -101,12 +87,14 @@ def load_model(model_name):
|
|
| 101 |
|
| 102 |
# ------------------------------ AGENT PROMPTS ------------------------------
|
| 103 |
ROLE_PROMPTS = {
|
| 104 |
-
"planner": """You are an expert file planner. Based on the user's request, determine the necessary file structure. Output ONLY a single JSON object with a single key: "files". The "files" key MUST be an array of strings representing complete file paths (e.g., ["src/main.py", "tests/test_main.py", "requirements.txt"]).""",
|
| 105 |
-
"architect": """You are a software architect. Create initial placeholder content for a list of files. Output ONLY a single JSON object where keys are file paths and values are the initial content (e.g., a comment like '# Main application logic here').""",
|
| 106 |
"coder": "You are a professional programmer. Your ONLY job is to write the complete, clean, and functional code for the single file requested. Do NOT add any explanations, introductions, or markdown formatting. Output ONLY the raw source code.",
|
| 107 |
"reviewer": """You are a meticulous code reviewer. Analyze the given code for bugs, style issues, and security vulnerabilities. Output ONLY a single JSON object with two keys: "has_issues" (boolean) and "suggestions" (a string containing a bulleted list of required changes).""",
|
| 108 |
"tester": "You are a QA engineer. Write a complete pytest test file for the given source code. Cover main functionality and edge cases. Output ONLY the raw source code for the test file.",
|
| 109 |
-
"publisher": """You are a release manager.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
}
|
| 111 |
|
| 112 |
# ------------------------------ FILE SYSTEM & AI TOOLS ------------------------------
|
|
@@ -156,9 +144,7 @@ def generate_with_model(role: str, prompt: str) -> str:
|
|
| 156 |
messages = [{"role": "system", "content": ROLE_PROMPTS[role]}, {"role": "user", "content": prompt}]
|
| 157 |
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 158 |
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
|
| 159 |
-
|
| 160 |
-
outputs = model.generate(**inputs, max_new_tokens=2048, pad_token_id=tokenizer.eos_token_id, use_cache=True)
|
| 161 |
-
|
| 162 |
return tokenizer.decode(outputs[0][len(inputs.input_ids[0]):], skip_special_tokens=True).strip()
|
| 163 |
except Exception as e:
|
| 164 |
print(f"Error during model generation for role {role}: {e}")
|
|
@@ -171,59 +157,63 @@ def run_agent_chain(project_id, user_id, initial_prompt):
|
|
| 171 |
|
| 172 |
def log_step(agent, action, output=""):
|
| 173 |
log_entry = f"**[{agent.upper()}]**: {action}\n"
|
| 174 |
-
if output:
|
|
|
|
| 175 |
log_entries.append(log_entry)
|
| 176 |
update_project_status(project_id, "running", logs="".join(log_entries))
|
| 177 |
|
| 178 |
try:
|
| 179 |
-
log_step("SYSTEM", "Initializing project...")
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
for file_path in source_files:
|
| 197 |
-
log_step("CODER", f"Writing complete code for `{file_path}`...")
|
| 198 |
-
coder_prompt = f"Based on the user's overall request: '{initial_prompt}'.\n\nWrite the full Python code for the file: `{file_path}`."
|
| 199 |
code = generate_with_model("coder", coder_prompt)
|
| 200 |
create_file(project_dir, file_path, code)
|
| 201 |
log_step("CODER", f"Finished writing `{file_path}`.", code)
|
| 202 |
|
| 203 |
-
|
|
|
|
| 204 |
code_content = read_file(project_dir, file_path)
|
| 205 |
if not code_content: continue
|
| 206 |
log_step("REVIEWER", f"Reviewing `{file_path}`...")
|
| 207 |
review_response = generate_with_model("reviewer", f"Review this code from `{file_path}`:\n\n{code_content}")
|
| 208 |
log_step("REVIEWER", f"Review of `{file_path}` complete.", review_response)
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
|
|
|
| 216 |
create_file(project_dir, test_file_path, test_code)
|
| 217 |
-
log_step("TESTER", f"Generated
|
| 218 |
|
| 219 |
-
|
| 220 |
-
log_step("PUBLISHER", "Generating final documentation and
|
| 221 |
-
|
|
|
|
|
|
|
| 222 |
pub_data = _extract_json(pub_response)
|
| 223 |
if not pub_data: raise ValueError("Publisher failed to create valid final assets.")
|
| 224 |
-
for path, content in pub_data.items():
|
|
|
|
| 225 |
log_step("PUBLISHER", "Final assets created.", json.dumps(pub_data, indent=2))
|
| 226 |
|
|
|
|
| 227 |
log_step("SYSTEM", "Packaging project into a ZIP file...")
|
| 228 |
zip_path = zip_project(project_dir, project_id)
|
| 229 |
update_project_status(project_id, "completed", logs="".join(log_entries), zip_path=zip_path)
|
|
@@ -231,13 +221,12 @@ def run_agent_chain(project_id, user_id, initial_prompt):
|
|
| 231 |
|
| 232 |
except Exception as e:
|
| 233 |
tb_str = traceback.format_exc()
|
| 234 |
-
print(f"--- AGENT CHAIN FAILED for project {project_id} ---\n{tb_str}\n
|
| 235 |
error_log = "".join(log_entries) + f"\n\n❌ **CRITICAL ERROR:**\nAn unexpected error occurred.\n\n**Details:**\n```{str(e)}```"
|
| 236 |
update_project_status(project_id, "failed", logs=error_log)
|
| 237 |
|
| 238 |
# ------------------------------ JOB QUEUE ------------------------------
|
| 239 |
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
| 240 |
-
|
| 241 |
def queue_job(project_id, user_id, prompt):
|
| 242 |
print(f"Queuing job for project: {project_id}")
|
| 243 |
executor.submit(run_agent_chain, project_id, user_id, prompt)
|
|
|
|
| 1 |
+
# backend.py — FINAL, TEMPLATE-DRIVEN VERSION v2.0
|
| 2 |
import sqlite3
|
| 3 |
import os
|
| 4 |
import json
|
|
|
|
| 58 |
|
| 59 |
# ------------------------------ MODEL LOADING & CACHING ------------------------------
|
| 60 |
MODEL_REGISTRY = {
|
| 61 |
+
"coder": "Qwen/Qwen2-0.5B-Instruct",
|
|
|
|
|
|
|
|
|
|
| 62 |
"reviewer": "microsoft/Phi-3-mini-4k-instruct",
|
| 63 |
+
"tester": "Qwen/Qwen2-0.5B-Instruct",
|
| 64 |
"publisher": "microsoft/Phi-3-mini-4k-instruct",
|
| 65 |
}
|
| 66 |
_MODEL_CACHE = {}
|
|
|
|
| 69 |
if model_name in _MODEL_CACHE:
|
| 70 |
return _MODEL_CACHE[model_name]
|
| 71 |
|
| 72 |
+
model_kwargs = {"device_map": "auto", "trust_remote_code": True, "attn_implementation": "eager"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
if torch.cuda.is_available():
|
| 75 |
print(f"CUDA is available. Loading model '{model_name}' in 4-bit for GPU acceleration.")
|
| 76 |
+
bnb_config = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
model_kwargs["quantization_config"] = bnb_config
|
| 78 |
else:
|
| 79 |
print(f"CUDA not available. Loading model '{model_name}' on CPU in default precision.")
|
|
|
|
| 80 |
|
| 81 |
tokenizer = AutoTokenizer.from_pretrained(model_name)
|
| 82 |
model = AutoModelForCausalLM.from_pretrained(model_name, **model_kwargs)
|
|
|
|
| 87 |
|
| 88 |
# ------------------------------ AGENT PROMPTS ------------------------------
|
| 89 |
ROLE_PROMPTS = {
|
|
|
|
|
|
|
| 90 |
"coder": "You are a professional programmer. Your ONLY job is to write the complete, clean, and functional code for the single file requested. Do NOT add any explanations, introductions, or markdown formatting. Output ONLY the raw source code.",
|
| 91 |
"reviewer": """You are a meticulous code reviewer. Analyze the given code for bugs, style issues, and security vulnerabilities. Output ONLY a single JSON object with two keys: "has_issues" (boolean) and "suggestions" (a string containing a bulleted list of required changes).""",
|
| 92 |
"tester": "You are a QA engineer. Write a complete pytest test file for the given source code. Cover main functionality and edge cases. Output ONLY the raw source code for the test file.",
|
| 93 |
+
"publisher": """You are a release manager. Your job is to create the final project documentation and dependencies based on all the generated code.
|
| 94 |
+
- Analyze the code to determine the correct Python libraries for requirements.txt.
|
| 95 |
+
- Write a helpful README.md that explains the project's purpose and how to run it.
|
| 96 |
+
- Create a standard .gitignore file.
|
| 97 |
+
Output ONLY a single JSON object where keys are the filenames ("requirements.txt", "README.md", ".gitignore") and values are their complete string content."""
|
| 98 |
}
|
| 99 |
|
| 100 |
# ------------------------------ FILE SYSTEM & AI TOOLS ------------------------------
|
|
|
|
| 144 |
messages = [{"role": "system", "content": ROLE_PROMPTS[role]}, {"role": "user", "content": prompt}]
|
| 145 |
input_text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 146 |
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
|
| 147 |
+
outputs = model.generate(**inputs, max_new_tokens=3072, pad_token_id=tokenizer.eos_token_id, use_cache=True)
|
|
|
|
|
|
|
| 148 |
return tokenizer.decode(outputs[0][len(inputs.input_ids[0]):], skip_special_tokens=True).strip()
|
| 149 |
except Exception as e:
|
| 150 |
print(f"Error during model generation for role {role}: {e}")
|
|
|
|
| 157 |
|
| 158 |
def log_step(agent, action, output=""):
|
| 159 |
log_entry = f"**[{agent.upper()}]**: {action}\n"
|
| 160 |
+
if output:
|
| 161 |
+
log_entry += f"```\n{output[:1000]}{'...' if len(output) > 1000 else ''}\n```\n---\n"
|
| 162 |
log_entries.append(log_entry)
|
| 163 |
update_project_status(project_id, "running", logs="".join(log_entries))
|
| 164 |
|
| 165 |
try:
|
| 166 |
+
log_step("SYSTEM", "Initializing project with pre-built architecture...")
|
| 167 |
+
|
| 168 |
+
# 1. CREATE SKELETON FILES
|
| 169 |
+
ARCHITECTURE_TEMPLATE = {
|
| 170 |
+
"app.py": "# Gradio UI will be generated here.",
|
| 171 |
+
"backend.py": "# Backend logic and database functions will be generated here.",
|
| 172 |
+
"requirements.txt": "# Dependencies will be generated by the Publisher agent."
|
| 173 |
+
}
|
| 174 |
+
for file_path, skeleton_content in ARCHITECTURE_TEMPLATE.items():
|
| 175 |
+
create_file(project_dir, file_path, skeleton_content)
|
| 176 |
+
log_step("SYSTEM", "Template files created.", "\n".join(ARCHITECTURE_TEMPLATE.keys()))
|
| 177 |
+
|
| 178 |
+
# 2. CODER AGENT FILLS IN THE TEMPLATE
|
| 179 |
+
files_to_code = ["backend.py", "app.py"]
|
| 180 |
+
for file_path in files_to_code:
|
| 181 |
+
log_step("CODER", f"Generating full code for `{file_path}`...")
|
| 182 |
+
coder_prompt = f"Based on the user's high-level request: '{initial_prompt}'\n\nYour task is to write the complete, final Python code for the file named `{file_path}`. The file should be fully functional and ready to run."
|
|
|
|
|
|
|
|
|
|
| 183 |
code = generate_with_model("coder", coder_prompt)
|
| 184 |
create_file(project_dir, file_path, code)
|
| 185 |
log_step("CODER", f"Finished writing `{file_path}`.", code)
|
| 186 |
|
| 187 |
+
# 3. REVIEWER AGENT CHECKS THE CODE
|
| 188 |
+
for file_path in files_to_code:
|
| 189 |
code_content = read_file(project_dir, file_path)
|
| 190 |
if not code_content: continue
|
| 191 |
log_step("REVIEWER", f"Reviewing `{file_path}`...")
|
| 192 |
review_response = generate_with_model("reviewer", f"Review this code from `{file_path}`:\n\n{code_content}")
|
| 193 |
log_step("REVIEWER", f"Review of `{file_path}` complete.", review_response)
|
| 194 |
|
| 195 |
+
# 4. TESTER AGENT TESTS THE BACKEND
|
| 196 |
+
log_step("TESTER", "Writing unit tests for `backend.py`...")
|
| 197 |
+
backend_code = read_file(project_dir, "backend.py")
|
| 198 |
+
if backend_code:
|
| 199 |
+
test_file_path = "tests/test_backend.py"
|
| 200 |
+
tester_prompt = f"Write a complete pytest test file (`{test_file_path}`) for the following backend code:\n\n{backend_code}"
|
| 201 |
+
test_code = generate_with_model("tester", tester_prompt)
|
| 202 |
create_file(project_dir, test_file_path, test_code)
|
| 203 |
+
log_step("TESTER", f"Generated `{test_file_path}`.", test_code)
|
| 204 |
|
| 205 |
+
# 5. PUBLISHER AGENT CREATES FINAL ASSETS
|
| 206 |
+
log_step("PUBLISHER", "Generating final documentation and dependencies...")
|
| 207 |
+
all_code = f"--- app.py ---\n{read_file(project_dir, 'app.py')}\n\n--- backend.py ---\n{read_file(project_dir, 'backend.py')}"
|
| 208 |
+
pub_prompt = f"Based on the following Python code, generate the project's final assets.\n\n{all_code}"
|
| 209 |
+
pub_response = generate_with_model("publisher", pub_prompt)
|
| 210 |
pub_data = _extract_json(pub_response)
|
| 211 |
if not pub_data: raise ValueError("Publisher failed to create valid final assets.")
|
| 212 |
+
for path, content in pub_data.items():
|
| 213 |
+
create_file(project_dir, path, content)
|
| 214 |
log_step("PUBLISHER", "Final assets created.", json.dumps(pub_data, indent=2))
|
| 215 |
|
| 216 |
+
# 6. FINALIZATION
|
| 217 |
log_step("SYSTEM", "Packaging project into a ZIP file...")
|
| 218 |
zip_path = zip_project(project_dir, project_id)
|
| 219 |
update_project_status(project_id, "completed", logs="".join(log_entries), zip_path=zip_path)
|
|
|
|
| 221 |
|
| 222 |
except Exception as e:
|
| 223 |
tb_str = traceback.format_exc()
|
| 224 |
+
print(f"--- AGENT CHAIN FAILED for project {project_id} ---\n{tb_str}\n---")
|
| 225 |
error_log = "".join(log_entries) + f"\n\n❌ **CRITICAL ERROR:**\nAn unexpected error occurred.\n\n**Details:**\n```{str(e)}```"
|
| 226 |
update_project_status(project_id, "failed", logs=error_log)
|
| 227 |
|
| 228 |
# ------------------------------ JOB QUEUE ------------------------------
|
| 229 |
executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
|
|
|
| 230 |
def queue_job(project_id, user_id, prompt):
|
| 231 |
print(f"Queuing job for project: {project_id}")
|
| 232 |
executor.submit(run_agent_chain, project_id, user_id, prompt)
|