Skip to content

Commit 0ad9754

Browse files
committed
Add RustCompilerMCP class to mcp_service.py for Rust code compilation and error fixing
1 parent 752b546 commit 0ad9754

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

app/mcp_service.py

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
"""
2+
This file contains the core business logic for the Rust Compiler MCP service.
3+
Note that the API integration and HTTP endpoints are now handled by app/mcp_tools.py.
4+
"""
5+
6+
import os
7+
import tempfile
8+
import shutil
9+
from typing import Dict, List, Optional, Tuple
10+
11+
from app.compiler import RustCompiler
12+
from app.response_parser import ResponseParser
13+
from app.vector_store import QdrantStore
14+
from app.llm_client import LlamaEdgeClient
15+
16+
class RustCompilerMCP:
17+
def __init__(self, vector_store=None, llm_client=None):
18+
"""
19+
Initialize the Rust Compiler MCP service
20+
21+
Args:
22+
vector_store: Vector store for searching similar examples
23+
llm_client: LLM client for generating fixes
24+
"""
25+
self.compiler = RustCompiler()
26+
self.parser = ResponseParser()
27+
28+
# Use provided vector store or create a new one
29+
self.vector_store = vector_store
30+
if self.vector_store is None:
31+
self.vector_store = QdrantStore()
32+
self.vector_store.create_collection("project_examples")
33+
self.vector_store.create_collection("error_examples")
34+
35+
# Use provided LLM client or create a new one
36+
self.llm_client = llm_client
37+
if self.llm_client is None:
38+
import os
39+
from dotenv import load_dotenv
40+
load_dotenv()
41+
api_key = os.getenv("LLM_API_KEY")
42+
if api_key:
43+
self.llm_client = LlamaEdgeClient(api_key=api_key)
44+
else:
45+
raise ValueError("LLM_API_KEY environment variable not set")
46+
47+
def compile_rust_code(self, code_content: str) -> Dict:
48+
"""
49+
MCP function to compile Rust code
50+
51+
Args:
52+
code_content: String containing Rust code with file markers
53+
54+
Returns:
55+
Dict with compilation status, output, and error messages
56+
"""
57+
# Create temp directory
58+
with tempfile.TemporaryDirectory() as temp_dir:
59+
# Parse the multi-file content
60+
files = self.parser.parse_response(code_content)
61+
62+
# Ensure project structure
63+
os.makedirs(os.path.join(temp_dir, "src"), exist_ok=True)
64+
65+
# Write files
66+
file_paths = self.parser.write_files(files, temp_dir)
67+
68+
# Compile the project
69+
success, output = self.compiler.build_project(temp_dir)
70+
71+
if success:
72+
# Try running if compilation succeeded
73+
run_success, run_output = self.compiler.run_project(temp_dir)
74+
75+
# Store successful project in vector store for future reference
76+
self._store_successful_project(files, code_content)
77+
78+
return {
79+
"success": True,
80+
"files": file_paths,
81+
"build_output": output or "Build successful",
82+
"run_output": run_output if run_success else "Failed to run project"
83+
}
84+
else:
85+
# Return error details
86+
error_context = self.compiler.extract_error_context(output)
87+
return {
88+
"success": False,
89+
"files": file_paths,
90+
"build_output": output,
91+
"error_details": error_context
92+
}
93+
94+
def compile_and_fix_rust_code(self,
95+
code_content: str,
96+
description: str,
97+
max_attempts: int = 3) -> Dict:
98+
"""
99+
MCP function to compile Rust code and fix errors using LLM
100+
101+
Args:
102+
code_content: String containing Rust code with file markers
103+
description: Project description for context
104+
max_attempts: Maximum number of fix attempts
105+
106+
Returns:
107+
Dict with compilation status, output, fixes applied, and final code
108+
"""
109+
# Create temp directory for this compilation
110+
with tempfile.TemporaryDirectory() as temp_dir:
111+
# Parse the multi-file content
112+
files = self.parser.parse_response(code_content)
113+
114+
# Ensure project structure
115+
os.makedirs(os.path.join(temp_dir, "src"), exist_ok=True)
116+
117+
# Write files
118+
file_paths = self.parser.write_files(files, temp_dir)
119+
120+
# Check if we have similar projects in our dataset for reference
121+
project_reference = self._find_similar_project(description)
122+
123+
attempts = []
124+
current_files = files.copy()
125+
126+
# Try compiling and fixing up to max_attempts
127+
for attempt in range(max_attempts):
128+
# Compile the project
129+
success, output = self.compiler.build_project(temp_dir)
130+
131+
# Store this attempt
132+
attempts.append({
133+
"attempt": attempt + 1,
134+
"success": success,
135+
"output": output
136+
})
137+
138+
if success:
139+
# Try running if compilation succeeded
140+
run_success, run_output = self.compiler.run_project(temp_dir)
141+
142+
# Store this successful fix in our vector database
143+
if attempt > 0: # Only store if we actually fixed something
144+
self._store_successful_fix(code_content, current_files, output)
145+
146+
return {
147+
"success": True,
148+
"attempts": attempts,
149+
"final_files": current_files,
150+
"build_output": output or "Build successful",
151+
"run_output": run_output if run_success else "Failed to run project",
152+
"similar_project_used": project_reference is not None
153+
}
154+
155+
# If we've reached max attempts without success, stop
156+
if attempt == max_attempts - 1:
157+
break
158+
159+
# Extract error context for LLM
160+
error_context = self.compiler.extract_error_context(output)
161+
162+
# Find similar errors in vector DB
163+
similar_errors = self._find_similar_errors(error_context["full_error"])
164+
165+
# Generate fix prompt
166+
fix_prompt = self._generate_fix_prompt(
167+
description,
168+
error_context,
169+
similar_errors,
170+
project_reference
171+
)
172+
173+
# Get fix from LLM
174+
fix_response = self.llm_client.generate_text(fix_prompt)
175+
176+
# Parse and apply fixes
177+
fixed_files = self.parser.parse_response(fix_response)
178+
for filename, content in fixed_files.items():
179+
file_path = os.path.join(temp_dir, filename)
180+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
181+
with open(file_path, 'w') as f:
182+
f.write(content)
183+
current_files[filename] = content
184+
185+
# If we've exhausted all attempts
186+
return {
187+
"success": False,
188+
"attempts": attempts,
189+
"final_files": current_files,
190+
"build_output": attempts[-1]["output"],
191+
"error_details": self.compiler.extract_error_context(attempts[-1]["output"]),
192+
"similar_project_used": project_reference is not None,
193+
"similar_errors_found": len(similar_errors) if 'similar_errors' in locals() else 0
194+
}
195+
196+
def _find_similar_project(self, description: str) -> Optional[Dict]:
197+
"""Find similar project from vector store"""
198+
if not self.vector_store:
199+
return None
200+
201+
try:
202+
embeddings = self.llm_client.get_embeddings([description])[0]
203+
similar_projects = self.vector_store.search("project_examples", embeddings, limit=1)
204+
205+
if similar_projects:
206+
return similar_projects[0]
207+
except Exception as e:
208+
print(f"Error finding similar project: {e}")
209+
210+
return None
211+
212+
def _find_similar_errors(self, error_text: str) -> List[Dict]:
213+
"""Find similar error examples from vector store"""
214+
if not self.vector_store:
215+
return []
216+
217+
try:
218+
embeddings = self.llm_client.get_embeddings([error_text])[0]
219+
similar_errors = self.vector_store.search("error_examples", embeddings, limit=3)
220+
return similar_errors
221+
except Exception as e:
222+
print(f"Error finding similar errors: {e}")
223+
return []
224+
225+
def _generate_fix_prompt(self,
226+
description: str,
227+
error_context: Dict,
228+
similar_errors: List[Dict],
229+
project_reference: Optional[Dict]) -> str:
230+
"""Generate prompt for fixing compilation errors"""
231+
fix_examples = ""
232+
if similar_errors:
233+
fix_examples = "Here are some examples of similar errors and their fixes:\n\n"
234+
for i, err in enumerate(similar_errors):
235+
fix_examples += f"Example {i+1}:\n{err['error']}\nFix: {err['solution']}\n\n"
236+
237+
reference_text = ""
238+
if project_reference:
239+
reference_text = f"\nHere's a similar project for reference:\n{project_reference['example']}\n"
240+
241+
return f"""
242+
Here is a Rust project that failed to compile. Help me fix the compilation errors.
243+
244+
Project description: {description}
245+
{reference_text}
246+
247+
Compilation error:
248+
{error_context["full_error"]}
249+
250+
{fix_examples}
251+
252+
Please provide the fixed code for all affected files.
253+
"""
254+
255+
def _store_successful_project(self, files: Dict[str, str], code_content: str):
256+
"""Store successful project compilation in vector store"""
257+
if not self.vector_store:
258+
return
259+
260+
try:
261+
# Create a project description
262+
if "README.md" in files:
263+
description = files["README.md"][:500] # Use first 500 chars of README
264+
else:
265+
# Default to cargo.toml content
266+
description = files.get("Cargo.toml", "Rust project")
267+
268+
# Try to get embedding or use dummy embedding if failed
269+
try:
270+
embeddings = self.llm_client.get_embeddings([description])[0]
271+
except Exception as e:
272+
print(f"Error getting embeddings: {e}")
273+
# Use a dummy embedding of correct size
274+
embeddings = [0.0] * self.vector_store.embedding_size # Use configurable size
275+
276+
# Store in vector store
277+
self.vector_store.add_item(
278+
collection_name="project_examples",
279+
vector=embeddings,
280+
item={
281+
"example": code_content[:10000], # Limit size
282+
"description": description
283+
}
284+
)
285+
except Exception as e:
286+
print(f"Failed to store project in vector DB: {e}")
287+
288+
def _store_successful_fix(self, original_code: str, fixed_files: Dict[str, str], error_output: str):
289+
"""Store successful error fix in vector store"""
290+
if not self.vector_store:
291+
return
292+
293+
try:
294+
# Create a combined fixed code representation
295+
fixed_code = "\n\n".join([
296+
f"[filename: {filename}]\n{content}"
297+
for filename, content in fixed_files.items()
298+
])
299+
300+
# Get embedding of the error
301+
error_context = self.compiler.extract_error_context(error_output)
302+
embeddings = self.llm_client.get_embeddings([error_context["full_error"]])[0]
303+
304+
# Store in vector store
305+
self.vector_store.add_item(
306+
collection_name="error_examples",
307+
vector=embeddings,
308+
item={
309+
"error": error_context["full_error"],
310+
"solution": fixed_code[:5000], # Limit size
311+
"original_code": original_code[:5000] # Limit size
312+
}
313+
)
314+
except Exception as e:
315+
print(f"Failed to store error fix in vector DB: {e}")
316+
317+
def _fix_errors_with_llm(self, error_output, current_files, description, project_reference=None):
318+
"""Use LLM to fix compilation errors"""
319+
if not self.llm_client:
320+
print("Warning: No LLM client available, returning original files")
321+
return current_files
322+
323+
try:
324+
# Create prompt for error fixing
325+
prompt = self.prompt_generator.generate_error_fix_prompt(
326+
error_output=error_output,
327+
current_files=current_files,
328+
description=description,
329+
project_reference=project_reference
330+
)
331+
332+
# Get LLM response
333+
response = self.llm_client.generate_text(
334+
prompt=prompt,
335+
system_message="You are an expert Rust developer fixing compilation errors.",
336+
max_tokens=4000,
337+
temperature=0.2 # Lower temperature for more precise fixes
338+
)
339+
340+
# Parse response into fixed files
341+
fixed_files = self.parser.parse_response(response)
342+
343+
# Return the fixed files
344+
return fixed_files
345+
except Exception as e:
346+
print(f"Error fixing with LLM: {str(e)}")
347+
# Return original files if LLM fails
348+
return current_files

0 commit comments

Comments
 (0)