MCP Code Expert System
by tomsiwik
Verified
"""Service for interacting with Ollama AI models.
This module provides a simple client for generating code reviews
using Ollama's REST API.
"""
import json
import os
import random
import requests
from typing import Dict, Any, Optional, List, Union
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Default configuration
DEFAULT_OLLAMA_HOST = "http://localhost:11434"
DEFAULT_OLLAMA_MODEL = "llama3:8b"
# Get configuration from environment
OLLAMA_HOST = os.environ.get("OLLAMA_HOST", DEFAULT_OLLAMA_HOST)
OLLAMA_MODEL = os.environ.get("OLLAMA_MODEL", DEFAULT_OLLAMA_MODEL)
class OllamaService:
"""Client for interacting with Ollama API."""
def __init__(self, host: str = OLLAMA_HOST, model: str = OLLAMA_MODEL):
"""Initialize the Ollama service.
Args:
host: Ollama API host
model: Model to use for generation
"""
self.host = host
self.model = model
self.api_url = f"{host}/api/generate"
self.is_available = self._check_availability()
def _check_availability(self) -> bool:
"""Check if Ollama is available.
Returns:
True if Ollama is available, False otherwise
"""
try:
response = requests.get(f"{self.host}/api/tags")
return response.status_code == 200
except requests.RequestException:
print(f"Ollama service not available at {self.host}")
return False
def get_martin_fowler_review(
self,
code: str,
language: Optional[str] = None,
description: Optional[str] = None
) -> Dict[str, Any]:
"""Get a code review from Martin Fowler's perspective.
Args:
code: Code to review
language: Programming language
description: Description of the code
Returns:
Dictionary with review, suggestions, and rating
"""
if not self.is_available:
return self._mock_martin_fowler_review(code, language)
# Prepare prompt
prompt = self._prepare_martin_fowler_prompt(code, language, description)
try:
# Call Ollama API
response = self._call_ollama(prompt)
# Parse response
return self._parse_review_response(response)
except Exception as e:
print(f"Error getting review from Ollama: {e}")
return self._mock_martin_fowler_review(code, language)
def get_robert_c_martin_review(
self,
code: str,
language: Optional[str] = None,
description: Optional[str] = None
) -> Dict[str, Any]:
"""Get a code review from Robert C. Martin's perspective.
Args:
code: Code to review
language: Programming language
description: Description of the code
Returns:
Dictionary with review, suggestions, and rating
"""
if not self.is_available:
return self._mock_robert_c_martin_review(code, language)
# Prepare prompt
prompt = self._prepare_robert_c_martin_prompt(code, language, description)
try:
# Call Ollama API
response = self._call_ollama(prompt)
# Parse response
return self._parse_review_response(response)
except Exception as e:
print(f"Error getting review from Ollama: {e}")
return self._mock_robert_c_martin_review(code, language)
def _call_ollama(self, prompt: str) -> str:
"""Call Ollama API to generate text.
Args:
prompt: Prompt for the model
Returns:
Generated text
"""
data = {
"model": self.model,
"prompt": prompt,
"stream": False,
"temperature": 0.7,
"max_tokens": 1024
}
response = requests.post(self.api_url, json=data)
response.raise_for_status()
result = response.json()
return result.get("response", "")
def _prepare_martin_fowler_prompt(
self,
code: str,
language: Optional[str] = None,
description: Optional[str] = None
) -> str:
"""Prepare a prompt for Martin Fowler's review.
Args:
code: Code to review
language: Programming language
description: Description of the code
Returns:
Formatted prompt
"""
lang_info = f" in {language}" if language else ""
desc_info = f"\nDescription: {description}" if description else ""
return f"""You are Martin Fowler, a renowned software architect and author who specializes in refactoring, patterns, and software design.
Review the following code{lang_info}{desc_info}. Provide a detailed review focusing on:
1. Code structure and organization
2. Potential code smells
3. Refactoring opportunities
4. Design pattern usage (or potential for it)
Format your response as JSON with these fields:
- "review": Your detailed analysis
- "suggestions": An array of specific refactoring suggestions
- "rating": A numerical score from 1-5 (1=poor, 5=excellent)
CODE TO REVIEW:
```
{code}
```
JSON RESPONSE:
"""
def _prepare_robert_c_martin_prompt(
self,
code: str,
language: Optional[str] = None,
description: Optional[str] = None
) -> str:
"""Prepare a prompt for Robert C. Martin's review.
Args:
code: Code to review
language: Programming language
description: Description of the code
Returns:
Formatted prompt
"""
lang_info = f" in {language}" if language else ""
desc_info = f"\nDescription: {description}" if description else ""
return f"""You are Robert C. Martin (Uncle Bob), a renowned software engineer and advocate for clean code principles.
Review the following code{lang_info}{desc_info}. Provide a detailed review focusing on:
1. Clean code principles
2. SOLID principles
3. Function naming, size, and responsibility
4. Overall code clarity and maintainability
Format your response as JSON with these fields:
- "review": Your detailed analysis
- "suggestions": An array of specific clean code improvements
- "rating": A numerical score from 1-5 (1=poor, 5=excellent)
CODE TO REVIEW:
```
{code}
```
JSON RESPONSE:
"""
def _parse_review_response(self, response: str) -> Dict[str, Any]:
"""Parse the review response from Ollama.
Args:
response: Raw text response from Ollama
Returns:
Parsed review dictionary
"""
try:
# Try to extract JSON from the response
json_start = response.find('{')
json_end = response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
json_str = response[json_start:json_end]
review_data = json.loads(json_str)
# Ensure all required fields are present
if not all(key in review_data for key in ['review', 'suggestions', 'rating']):
raise ValueError("Missing required fields in review data")
# Ensure suggestions is a list
if not isinstance(review_data['suggestions'], list):
review_data['suggestions'] = [review_data['suggestions']]
# Ensure rating is a number between 1 and 5
if not isinstance(review_data['rating'], (int, float)) or review_data['rating'] < 1 or review_data['rating'] > 5:
review_data['rating'] = random.randint(2, 5)
return review_data
except Exception as e:
print(f"Error parsing review response: {e}")
# Fallback to extracting parts of the review manually
lines = response.split('\n')
review_lines = []
suggestions = []
rating = random.randint(2, 5)
in_review = False
in_suggestions = False
for line in lines:
# Look for review section
if 'review' in line.lower() and ':' in line:
in_review = True
in_suggestions = False
continue
# Look for suggestions section
if 'suggestion' in line.lower() and ':' in line:
in_review = False
in_suggestions = True
continue
# Look for rating
if 'rating' in line.lower() and ':' in line:
try:
# Extract rating value
rating_str = line.split(':')[1].strip()
# Remove any non-numeric characters
rating_str = ''.join(c for c in rating_str if c.isdigit() or c == '.')
rating = float(rating_str)
# Ensure rating is between 1 and 5
rating = max(1, min(5, rating))
except:
pass
continue
# Collect review text
if in_review:
review_lines.append(line.strip())
# Collect suggestions
if in_suggestions and line.strip():
# Check if line starts with number or dash
if (line.strip().startswith('-') or
any(line.strip().startswith(str(i)) for i in range(10))):
suggestions.append(line.strip())
# Create review dictionary
review = {
'review': ' '.join(review_lines) if review_lines else response,
'suggestions': suggestions if suggestions else ["Improve code readability", "Consider adding comments"],
'rating': rating
}
return review
def _mock_martin_fowler_review(
self,
code: str,
language: Optional[str] = None
) -> Dict[str, Any]:
"""Generate a mock review from Martin Fowler's perspective.
Args:
code: Code to review
language: Programming language
Returns:
Mock review dictionary
"""
lang_text = language if language else "this code"
code_length = len(code.strip().split('\n'))
# Determine complexity heuristic
complexity = "simple" if code_length < 10 else "moderately complex" if code_length < 30 else "complex"
rating = 4 if code_length < 10 else 3 if code_length < 30 else 2
review = f"""I've reviewed the {complexity} {lang_text} snippet. The code appears functional but has room for improvement in terms of structure and design. There are several refactoring opportunities that could make it more maintainable and aligned with good software design principles."""
suggestions = [
"Extract smaller, focused methods with clear responsibilities",
"Consider introducing appropriate design patterns",
"Improve variable and method naming for clarity",
"Reduce duplication and increase code reuse"
]
if complexity == "complex":
suggestions.append("Break down complex logic into smaller, testable units")
suggestions.append("Consider separating concerns with appropriate abstractions")
return {
"review": review,
"suggestions": suggestions,
"rating": rating
}
def _mock_robert_c_martin_review(
self,
code: str,
language: Optional[str] = None
) -> Dict[str, Any]:
"""Generate a mock review from Robert C. Martin's perspective.
Args:
code: Code to review
language: Programming language
Returns:
Mock review dictionary
"""
lang_text = language if language else "this code"
code_length = len(code.strip().split('\n'))
# Determine complexity heuristic
complexity = "simple" if code_length < 10 else "moderately complex" if code_length < 30 else "complex"
rating = 4 if code_length < 10 else 3 if code_length < 30 else 2
review = f"""I've examined the {complexity} {lang_text} snippet through the lens of Clean Code principles. The code has several areas where it could better adhere to SOLID principles and clean code practices. Function naming and responsibility could be improved to enhance readability and maintainability."""
suggestions = [
"Use more descriptive names for variables and functions",
"Ensure each function does one thing well",
"Keep functions small and focused on a single responsibility",
"Add meaningful comments explaining 'why' not 'what'"
]
if complexity == "complex":
suggestions.append("Apply the Single Responsibility Principle more rigorously")
suggestions.append("Reduce function parameter counts for simpler interfaces")
return {
"review": review,
"suggestions": suggestions,
"rating": rating
}