Keeby-smilyai commited on
Commit
469e3da
·
verified ·
1 Parent(s): 1c29bb4

Update backend.py

Browse files
Files changed (1) hide show
  1. backend.py +49 -60
backend.py CHANGED
@@ -1,4 +1,4 @@
1
- # backend.py — FINAL, STABLE & OPTIMIZED VERSION v1.6
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
- "planner": "microsoft/Phi-3-mini-4k-instruct",
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.5-Coder-0.5B-Instruct",
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
- # --- THIS IS THE FIX: Conditional Quantization for CPU/GPU compatibility ---
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. Create final documentation and configuration files. Output ONLY a single JSON object where keys are the filenames ("README.md", ".gitignore", "Dockerfile") and values are their complete string content."""
 
 
 
 
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: log_entry += f"```\n{output[:1000]}{'...' if len(output) > 1000 else ''}\n```\n---\n"
 
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
- log_step("PLANNER", "Determining file structure...")
182
- plan_response = generate_with_model("planner", initial_prompt)
183
- plan_data = _extract_json(plan_response)
184
- if not plan_data or "files" not in plan_data: raise ValueError("Planner failed to create a valid JSON plan.")
185
- log_step("PLANNER", "File structure plan created.", json.dumps(plan_data, indent=2))
186
-
187
- log_step("ARCHITECT", "Creating initial file skeletons...")
188
- arch_prompt = f"Create initial content for these files:\n{json.dumps(plan_data['files'])}"
189
- arch_response = generate_with_model("architect", arch_prompt)
190
- arch_data = _extract_json(arch_response)
191
- if not arch_data: raise ValueError("Architect failed to create valid JSON skeletons.")
192
- for path, content in arch_data.items(): create_file(project_dir, path, content)
193
- log_step("ARCHITECT", "File skeletons created.", "\n".join(arch_data.keys()))
194
-
195
- source_files = [f for f in plan_data['files'] if f.startswith('src/') and f.endswith('.py')]
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
- for file_path in source_files:
 
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
- for file_path in source_files:
211
- code_content = read_file(project_dir, file_path)
212
- if not code_content: continue
213
- test_file_path = os.path.join("tests", f"test_{os.path.basename(file_path)}")
214
- log_step("TESTER", f"Writing test for `{file_path}`...")
215
- test_code = generate_with_model("tester", f"Write a pytest test file (`{test_file_path}`) for this code from `{file_path}`:\n\n{code_content}")
 
216
  create_file(project_dir, test_file_path, test_code)
217
- log_step("TESTER", f"Generated test `{test_file_path}`.", test_code)
218
 
219
- all_files = [os.path.join(r, f).replace(project_dir, '', 1) for r, _, fs in os.walk(project_dir) for f in fs]
220
- log_step("PUBLISHER", "Generating final documentation and configuration...")
221
- pub_response = generate_with_model("publisher", f"Project file structure: {json.dumps(all_files)}. Generate README.md, .gitignore, and Dockerfile.")
 
 
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(): create_file(project_dir, path, content)
 
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)