auto-analyst-previous / src /routes /code_routes.py
Ashad001's picture
code enhancements
636cc4d
Raw
History Blame Contribute Delete
11.5 kB
import io
import logging
from fastapi import APIRouter, Depends, HTTPException, Request
from typing import Dict, Optional
from pydantic import BaseModel
from scripts.format_response import execute_code_from_markdown, format_code_block
from src.utils.logger import Logger
from src.routes.session_routes import get_session_id_dependency
from src.agents.agents import code_edit, code_fix
import dspy
import os
# Initialize router
router = APIRouter(
prefix="/code",
tags=["code"],
responses={404: {"description": "Not found"}},
)
# Initialize logger
logger = Logger("code_routes", see_time=True, console_log=False)
# Request body model
class CodeExecuteRequest(BaseModel):
code: str
class CodeEditRequest(BaseModel):
original_code: str
user_prompt: str
class CodeFixRequest(BaseModel):
code: str
error: str
class CodeCleanRequest(BaseModel):
code: str
def get_dataset_context(df):
"""
Generate context information about the dataset
Args:
df: The pandas dataframe
Returns:
String with dataset information (columns, types, null values)
"""
if df is None:
return "No dataset is currently loaded."
try:
# Get basic dataframe info
col_types = df.dtypes.to_dict()
null_counts = df.isnull().sum().to_dict()
# Format the context string
context = "Dataset context:\n"
context += f"- Shape: {df.shape[0]} rows, {df.shape[1]} columns\n"
context += "- Columns and types:\n"
for col, dtype in col_types.items():
null_count = null_counts.get(col, 0)
null_percent = (null_count / len(df)) * 100 if len(df) > 0 else 0
context += f" * {col} ({dtype}): {null_count} null values"
# Add sample values for each column (first 2 non-null values)
context += "- Sample values:\n"
for col in df.columns:
sample_values = df[col].dropna().head(2).tolist()
# if float, round to 2 decimal places
if df[col].dtype == "float64":
sample_values = [round(v, 1) for v in sample_values]
sample_str = ", ".join(str(v) for v in sample_values)
context += f" * {col}: {sample_str}\n"
return context
except Exception as e:
logger.log_message(f"Error generating dataset context: {str(e)}", level=logging.ERROR)
return "Could not generate dataset context information."
def edit_code_with_dspy(original_code: str, user_prompt: str, dataset_context: str = ""):
gemini = dspy.LM("gemini/gemini-2.5-pro-preview-03-25", api_key = os.environ['GEMINI_API_KEY'], max_tokens=2000)
with dspy.context(lm=gemini):
code_editor = dspy.ChainOfThought(code_edit)
logger.log_message(f"Dataset context: {dataset_context}", level=logging.INFO)
logger.log_message(f"Original code: {original_code}", level=logging.INFO)
logger.log_message(f"User prompt: {user_prompt}", level=logging.INFO)
result = code_editor(
dataset_context=dataset_context,
original_code=original_code,
user_prompt=user_prompt,
)
return result.edited_code
def fix_code_with_dspy(code: str, error: str, dataset_context: str = ""):
gemini = dspy.LM("gemini/gemini-2.5-pro-preview-03-25", api_key = os.environ['GEMINI_API_KEY'], max_tokens=2000)
with dspy.context(lm=gemini):
code_fixer = dspy.ChainOfThought(code_fix)
logger.log_message(f"FIX Dataset context: {dataset_context}", level=logging.INFO)
logger.log_message(f"FIX Original code: {code}", level=logging.INFO)
logger.log_message(f"FIX Error: {error}", level=logging.INFO)
# Add dataset context information to help the agent understand the data
result = code_fixer(
dataset_context=dataset_context,
faulty_code=code,
error=error,
)
return result.fixed_code
import re
def move_imports_to_top(code: str) -> str:
"""
Moves all import statements to the top of the Python code.
Args:
code (str): The raw Python code as a string.
Returns:
str: The cleaned code with import statements at the top.
"""
# Extract import statements
import_statements = re.findall(
r'^\s*(import\s+[^\n]+|from\s+[^\n]+import\s+[^\n]+)', code, flags=re.MULTILINE
)
# Remove import statements from original code
code_without_imports = re.sub(
r'^\s*(import\s+[^\n]+|from\s+[^\n]+import\s+[^\n]+)\n?', '', code, flags=re.MULTILINE
)
# Deduplicate and sort imports
sorted_imports = sorted(set(import_statements))
# Combine cleaned imports and remaining code
cleaned_code = '\n'.join(sorted_imports) + '\n\n' + code_without_imports.strip()
return cleaned_code
@router.post("/execute")
async def execute_code(
request_data: CodeExecuteRequest,
request: Request,
session_id: str = Depends(get_session_id_dependency)
):
"""
Execute code provided in the request against the session's dataframe
Args:
request_data: Body containing code to execute
request: FastAPI Request object
session_id: Session identifier
Returns:
Dictionary containing execution output and any plot outputs
"""
# Access app state via request
app_state = request.app.state
session_state = app_state.get_session_state(session_id)
if session_state["current_df"] is None:
raise HTTPException(
status_code=400,
detail="No dataset is currently loaded. Please link a dataset before executing code."
)
try:
code = request_data.code
if not code:
raise HTTPException(status_code=400, detail="No code provided")
# Execute the code with the dataframe from session state
output, json_outputs = execute_code_from_markdown(code, session_state["current_df"])
# Format plotly outputs for frontend
plotly_outputs = [f"```plotly\n{json_output}\n```\n" for json_output in json_outputs]
return {
"output": output,
"plotly_outputs": plotly_outputs if json_outputs else None
}
except Exception as e:
logger.log_message(f"Error executing code: {str(e)}", level=logging.ERROR)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/edit")
async def edit_code(
request_data: CodeEditRequest,
request: Request,
session_id: str = Depends(get_session_id_dependency)
):
"""
Edit code provided in the request using AI
Args:
request_data: Body containing original code and user prompt
request: FastAPI Request object
session_id: Session identifier
Returns:
Dictionary containing the edited code
"""
try:
# Check if code and prompt are provided
if not request_data.original_code or not request_data.user_prompt:
raise HTTPException(status_code=400, detail="Both original code and editing instructions are required")
# Access app state via request
app_state = request.app.state
session_state = app_state.get_session_state(session_id)
# Get dataset context
dataset_context = get_dataset_context(session_state["current_df"])
try:
# Use the configured language model with dataset context
edited_code = edit_code_with_dspy(
request_data.original_code,
request_data.user_prompt,
dataset_context
)
edited_code = format_code_block(edited_code)
return {
"edited_code": edited_code,
}
except Exception as e:
# Fallback if DSPy models are not initialized or there's an error
logger.log_message(f"Error with DSPy models: {str(e)}", level=logging.ERROR)
# Return a helpful error message that doesn't expose implementation details
return {
"edited_code": request_data.original_code,
"error": "Could not process edit request. Please try again later."
}
except Exception as e:
logger.log_message(f"Error editing code: {str(e)}", level=logging.ERROR)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/fix")
async def fix_code(
request_data: CodeFixRequest,
request: Request,
session_id: str = Depends(get_session_id_dependency)
):
"""
Fix code with errors using the code_fix agent
Args:
request_data: Body containing code and error message
request: FastAPI Request object
session_id: Session identifier
Returns:
Dictionary containing the fixed code
"""
try:
# Check if code and error are provided
if not request_data.code or not request_data.error:
raise HTTPException(status_code=400, detail="Both code and error message are required")
# Access app state via request
app_state = request.app.state
session_state = app_state.get_session_state(session_id)
# Get dataset context
dataset_context = get_dataset_context(session_state["current_df"])
try:
# Use the code_fix agent to fix the code, with dataset context
fixed_code = fix_code_with_dspy(
request_data.code,
request_data.error,
dataset_context
)
fixed_code = format_code_block(fixed_code)
return {
"fixed_code": fixed_code,
}
except Exception as e:
# Fallback if DSPy models are not initialized or there's an error
logger.log_message(f"Error with DSPy models: {str(e)}", level=logging.ERROR)
# Return a helpful error message that doesn't expose implementation details
return {
"fixed_code": request_data.code,
"error": "Could not process fix request. Please try again later."
}
except Exception as e:
logger.log_message(f"Error fixing code: {str(e)}", level=logging.ERROR)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/clean-code")
async def clean_code(
request_data: CodeCleanRequest,
request: Request,
session_id: str = Depends(get_session_id_dependency)
):
"""
Clean code provided in the request
Args:
request_data: Body containing code to clean
request: FastAPI Request object
session_id: Session identifier
Returns:
Dictionary containing the cleaned code
"""
try:
# Check if code is provided
if not request_data.code:
raise HTTPException(status_code=400, detail="Code is required")
# Clean the code
cleaned_code = move_imports_to_top(request_data.code)
return {
"cleaned_code": cleaned_code,
}
except Exception as e:
logger.log_message(f"Error cleaning code: {str(e)}", level=logging.ERROR)
raise HTTPException(status_code=500, detail=str(e))