Spaces:
Sleeping
Sleeping
wip
Browse files- CLAUDE.md +14 -2
- app.py +9 -5
- src/agents/ada_converter.py +64 -0
- src/agents/code_analyzer.py +55 -65
- src/ui.py +30 -28
- tests/test_ada_converter_agent.py +63 -0
- tests/test_code_analyzer_agent.py +42 -21
CLAUDE.md
CHANGED
|
@@ -31,12 +31,18 @@
|
|
| 31 |
```
|
| 32 |
src/
|
| 33 |
├── agents/
|
| 34 |
-
│
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
└── main.py # CLI interface for development/testing
|
| 36 |
tests/
|
| 37 |
├── test_code_analyzer_agent.py # Unit tests for CodeAnalyzerAgent
|
|
|
|
|
|
|
| 38 |
└── tictactoe.ads # Sample Ada file for testing
|
| 39 |
-
app.py #
|
| 40 |
requirements.txt # Generated from uv dependencies
|
| 41 |
pyproject.toml # Project configuration and dependencies
|
| 42 |
.env # Environment configuration
|
|
@@ -48,6 +54,12 @@ pyproject.toml # Project configuration and dependencies
|
|
| 48 |
* extract_business_logic() method analyzes Ada code via LLM
|
| 49 |
* Returns structured JSON with core algorithms, data structures, business rules
|
| 50 |
* Handles JSON parsing errors with fallback responses
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
## Hosting Platform
|
| 53 |
* Hugging Face Spaces with Gradio SDK
|
|
|
|
| 31 |
```
|
| 32 |
src/
|
| 33 |
├── agents/
|
| 34 |
+
│ ├── code_analyzer.py # CodeAnalyzerAgent for business logic extraction
|
| 35 |
+
│ └── ada_converter.py # AdaConverterAgent for Ada to Python conversion
|
| 36 |
+
├── tools/
|
| 37 |
+
│ └── project_handler.py # Zip extraction and file handling utilities
|
| 38 |
+
├── ui.py # Gradio UI components and logic
|
| 39 |
└── main.py # CLI interface for development/testing
|
| 40 |
tests/
|
| 41 |
├── test_code_analyzer_agent.py # Unit tests for CodeAnalyzerAgent
|
| 42 |
+
├── test_ada_converter_agent.py # Unit tests for AdaConverterAgent
|
| 43 |
+
├── test_project_handler.py # Unit tests for project handler
|
| 44 |
└── tictactoe.ads # Sample Ada file for testing
|
| 45 |
+
app.py # Application entry point and agent initialization
|
| 46 |
requirements.txt # Generated from uv dependencies
|
| 47 |
pyproject.toml # Project configuration and dependencies
|
| 48 |
.env # Environment configuration
|
|
|
|
| 54 |
* extract_business_logic() method analyzes Ada code via LLM
|
| 55 |
* Returns structured JSON with core algorithms, data structures, business rules
|
| 56 |
* Handles JSON parsing errors with fallback responses
|
| 57 |
+
|
| 58 |
+
* **AdaConverterAgent**: Converts Ada code to Python code
|
| 59 |
+
* Uses dependency injection pattern (model passed as parameter)
|
| 60 |
+
* convert_to_python() method converts Ada files to equivalent Python code
|
| 61 |
+
* Follows Ada to Python conversion principles (procedures→functions, packages→modules, etc.)
|
| 62 |
+
* Generates clean, PEP 8 compliant Python code with type hints
|
| 63 |
|
| 64 |
## Hosting Platform
|
| 65 |
* Hugging Face Spaces with Gradio SDK
|
app.py
CHANGED
|
@@ -2,6 +2,7 @@ import os
|
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
from smolagents import LiteLLMModel, OpenAIServerModel
|
| 4 |
from src.agents.code_analyzer import CodeAnalyzerAgent
|
|
|
|
| 5 |
from src.ui import create_interface
|
| 6 |
from langfuse import get_client
|
| 7 |
from openinference.instrumentation.smolagents import SmolagentsInstrumentor
|
|
@@ -19,8 +20,8 @@ else:
|
|
| 19 |
|
| 20 |
SmolagentsInstrumentor().instrument()
|
| 21 |
|
| 22 |
-
def
|
| 23 |
-
"""Initialize the
|
| 24 |
environment = os.getenv("ENVIRONMENT", "development")
|
| 25 |
temperature = 0.1
|
| 26 |
|
|
@@ -38,10 +39,13 @@ def initialize_analyzer():
|
|
| 38 |
temperature=temperature
|
| 39 |
)
|
| 40 |
|
| 41 |
-
|
|
|
|
|
|
|
|
|
|
| 42 |
|
| 43 |
# Create Gradio interface
|
| 44 |
if __name__ == "__main__":
|
| 45 |
-
analyzer =
|
| 46 |
-
app = create_interface(analyzer)
|
| 47 |
app.launch()
|
|
|
|
| 2 |
from dotenv import load_dotenv
|
| 3 |
from smolagents import LiteLLMModel, OpenAIServerModel
|
| 4 |
from src.agents.code_analyzer import CodeAnalyzerAgent
|
| 5 |
+
from src.agents.ada_converter import AdaConverterAgent
|
| 6 |
from src.ui import create_interface
|
| 7 |
from langfuse import get_client
|
| 8 |
from openinference.instrumentation.smolagents import SmolagentsInstrumentor
|
|
|
|
| 20 |
|
| 21 |
SmolagentsInstrumentor().instrument()
|
| 22 |
|
| 23 |
+
def initialize_agents():
|
| 24 |
+
"""Initialize the agents with appropriate model"""
|
| 25 |
environment = os.getenv("ENVIRONMENT", "development")
|
| 26 |
temperature = 0.1
|
| 27 |
|
|
|
|
| 39 |
temperature=temperature
|
| 40 |
)
|
| 41 |
|
| 42 |
+
analyzer = CodeAnalyzerAgent(model)
|
| 43 |
+
converter = AdaConverterAgent(model)
|
| 44 |
+
|
| 45 |
+
return analyzer, converter
|
| 46 |
|
| 47 |
# Create Gradio interface
|
| 48 |
if __name__ == "__main__":
|
| 49 |
+
analyzer, converter = initialize_agents()
|
| 50 |
+
app = create_interface(analyzer, converter)
|
| 51 |
app.launch()
|
src/agents/ada_converter.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ada to Python converter agent."""
|
| 2 |
+
|
| 3 |
+
from smolagents import ToolCallingAgent, FinalAnswerTool
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class AdaConverterAgent:
|
| 7 |
+
"""Agent that converts Ada code to Python code."""
|
| 8 |
+
|
| 9 |
+
def __init__(self, model):
|
| 10 |
+
"""Initialize the converter agent with a model."""
|
| 11 |
+
self.model = model
|
| 12 |
+
self.converter_agent = ToolCallingAgent(
|
| 13 |
+
model=self.model,
|
| 14 |
+
tools=[FinalAnswerTool()],
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
def convert_to_python(self, ada_file_path):
|
| 18 |
+
"""Convert Ada file to Python code."""
|
| 19 |
+
try:
|
| 20 |
+
# Read the Ada file
|
| 21 |
+
with open(ada_file_path, 'r', encoding='utf-8') as f:
|
| 22 |
+
ada_code = f.read()
|
| 23 |
+
|
| 24 |
+
# Create the conversion prompt
|
| 25 |
+
prompt = f"""You are an expert Ada and Python programmer. Your task is to convert Ada code to equivalent Python code.
|
| 26 |
+
|
| 27 |
+
Key conversion principles:
|
| 28 |
+
1. Convert Ada procedures to Python functions
|
| 29 |
+
2. Convert Ada packages to Python modules/classes
|
| 30 |
+
3. Convert Ada types to appropriate Python types
|
| 31 |
+
4. Convert Ada control structures to Python equivalents
|
| 32 |
+
5. Convert Ada exception handling to Python try/except
|
| 33 |
+
6. Maintain the original logic and functionality
|
| 34 |
+
7. Use Pythonic idioms and conventions
|
| 35 |
+
8. Add appropriate type hints where beneficial
|
| 36 |
+
|
| 37 |
+
Always provide clean, readable Python code that follows PEP 8 standards.
|
| 38 |
+
|
| 39 |
+
Convert the following Ada code to Python:
|
| 40 |
+
|
| 41 |
+
```ada
|
| 42 |
+
{ada_code}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Please provide only the Python code without any explanations or markdown formatting."""
|
| 46 |
+
|
| 47 |
+
# Run the conversion
|
| 48 |
+
result = self.converter_agent.run(prompt)
|
| 49 |
+
|
| 50 |
+
# Extract response text like CodeAnalyzerAgent does
|
| 51 |
+
return self._extract_response_text(result)
|
| 52 |
+
|
| 53 |
+
except Exception as e:
|
| 54 |
+
return f"Error converting Ada code: {str(e)}"
|
| 55 |
+
|
| 56 |
+
def _extract_response_text(self, result) -> str:
|
| 57 |
+
"""Extract text response from ToolCallingAgent result"""
|
| 58 |
+
|
| 59 |
+
print(f"_extract_response_text result = {result}")
|
| 60 |
+
|
| 61 |
+
if hasattr(result, 'messages') and result.messages:
|
| 62 |
+
return str(result.messages[-1].content)
|
| 63 |
+
else:
|
| 64 |
+
return str(result)
|
src/agents/code_analyzer.py
CHANGED
|
@@ -8,10 +8,9 @@ This agent is responsible for:
|
|
| 8 |
4. Generating analysis reports for conversion
|
| 9 |
"""
|
| 10 |
|
| 11 |
-
from typing import
|
| 12 |
from pathlib import Path
|
| 13 |
from smolagents import ToolCallingAgent, FinalAnswerTool
|
| 14 |
-
import json
|
| 15 |
|
| 16 |
class CodeAnalyzerAgent:
|
| 17 |
"""Agent specialized in analyzing Ada codebases"""
|
|
@@ -28,21 +27,14 @@ class CodeAnalyzerAgent:
|
|
| 28 |
self.supported_extensions = ['.ads', '.adb', '.ada']
|
| 29 |
self.analysis_results = {}
|
| 30 |
|
| 31 |
-
def _parse_llm_response(self, response: str) -> Dict[str, Any]:
|
| 32 |
-
"""Parse LLM response and extract JSON analysis"""
|
| 33 |
-
import json
|
| 34 |
-
import re
|
| 35 |
-
|
| 36 |
-
return json.loads(response);
|
| 37 |
-
|
| 38 |
def _extract_response_text(self, result) -> str:
|
| 39 |
"""Extract text response from CodeAgent result"""
|
| 40 |
if hasattr(result, 'messages') and result.messages:
|
| 41 |
return str(result.messages[-1].content)
|
| 42 |
else:
|
| 43 |
-
return str(result)
|
| 44 |
|
| 45 |
-
def extract_business_logic(self, ada_file_path: Union[Path, str]) ->
|
| 46 |
"""
|
| 47 |
Extract business logic patterns from Ada file using LLM
|
| 48 |
|
|
@@ -70,10 +62,10 @@ class CodeAnalyzerAgent:
|
|
| 70 |
# Use the code agent to analyze business logic
|
| 71 |
result = self.code_agent.run(analysis_prompt)
|
| 72 |
|
| 73 |
-
# Extract response text from RunResult
|
| 74 |
response_text = self._extract_response_text(result)
|
| 75 |
|
| 76 |
-
return
|
| 77 |
|
| 78 |
except Exception as e:
|
| 79 |
print(f"LLM business logic analysis failed: {e}")
|
|
@@ -82,64 +74,62 @@ class CodeAnalyzerAgent:
|
|
| 82 |
def _create_business_logic_prompt(self, file_path: Path, file_content: str) -> str:
|
| 83 |
"""Create structured business logic analysis prompt for LLM"""
|
| 84 |
return f"""
|
| 85 |
-
Analyze the business logic of this Ada code and return
|
| 86 |
-
Do not write code or execute anything - just analyze and respond with
|
| 87 |
|
| 88 |
Ada File: {file_path}
|
| 89 |
Ada Code Content:
|
| 90 |
{file_content}
|
| 91 |
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
-
|
| 106 |
-
-
|
| 107 |
-
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
"""
|
| 114 |
|
| 115 |
-
def _ensure_business_logic_structure(self, business_logic: Dict[str, Any]) -> Dict[str, Any]:
|
| 116 |
-
"""Ensure business logic response has required structure with defaults"""
|
| 117 |
-
if not isinstance(business_logic, dict):
|
| 118 |
-
business_logic = {}
|
| 119 |
-
|
| 120 |
-
defaults = {
|
| 121 |
-
'core_algorithms': [],
|
| 122 |
-
'data_structures': [],
|
| 123 |
-
'business_rules': [],
|
| 124 |
-
'domain_concepts': [],
|
| 125 |
-
'conversion_complexity': 'medium',
|
| 126 |
-
'recommended_approach': 'Standard modernization approach recommended'
|
| 127 |
-
}
|
| 128 |
-
|
| 129 |
-
for key, default_value in defaults.items():
|
| 130 |
-
if key not in business_logic:
|
| 131 |
-
business_logic[key] = default_value
|
| 132 |
-
|
| 133 |
-
return business_logic
|
| 134 |
|
| 135 |
-
def _create_fallback_business_logic_with_error(self, error_msg: str) ->
|
| 136 |
"""Create fallback business logic response when analysis fails"""
|
| 137 |
-
return
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
4. Generating analysis reports for conversion
|
| 9 |
"""
|
| 10 |
|
| 11 |
+
from typing import Any, Union
|
| 12 |
from pathlib import Path
|
| 13 |
from smolagents import ToolCallingAgent, FinalAnswerTool
|
|
|
|
| 14 |
|
| 15 |
class CodeAnalyzerAgent:
|
| 16 |
"""Agent specialized in analyzing Ada codebases"""
|
|
|
|
| 27 |
self.supported_extensions = ['.ads', '.adb', '.ada']
|
| 28 |
self.analysis_results = {}
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
def _extract_response_text(self, result) -> str:
|
| 31 |
"""Extract text response from CodeAgent result"""
|
| 32 |
if hasattr(result, 'messages') and result.messages:
|
| 33 |
return str(result.messages[-1].content)
|
| 34 |
else:
|
| 35 |
+
return str(result)
|
| 36 |
|
| 37 |
+
def extract_business_logic(self, ada_file_path: Union[Path, str]) -> str:
|
| 38 |
"""
|
| 39 |
Extract business logic patterns from Ada file using LLM
|
| 40 |
|
|
|
|
| 62 |
# Use the code agent to analyze business logic
|
| 63 |
result = self.code_agent.run(analysis_prompt)
|
| 64 |
|
| 65 |
+
# Extract response text from RunResult (now markdown)
|
| 66 |
response_text = self._extract_response_text(result)
|
| 67 |
|
| 68 |
+
return response_text
|
| 69 |
|
| 70 |
except Exception as e:
|
| 71 |
print(f"LLM business logic analysis failed: {e}")
|
|
|
|
| 74 |
def _create_business_logic_prompt(self, file_path: Path, file_content: str) -> str:
|
| 75 |
"""Create structured business logic analysis prompt for LLM"""
|
| 76 |
return f"""
|
| 77 |
+
Analyze the business logic of this Ada code and return a well-formatted markdown report.
|
| 78 |
+
Do not write code or execute anything - just analyze and respond with markdown.
|
| 79 |
|
| 80 |
Ada File: {file_path}
|
| 81 |
Ada Code Content:
|
| 82 |
{file_content}
|
| 83 |
|
| 84 |
+
Structure your analysis as follows in markdown format:
|
| 85 |
+
|
| 86 |
+
# Business Logic Analysis
|
| 87 |
+
|
| 88 |
+
## Core Algorithms
|
| 89 |
+
- List the main algorithms and computational logic found in the code
|
| 90 |
+
- Describe the key processing steps and workflows
|
| 91 |
+
|
| 92 |
+
## Data Structures & Types
|
| 93 |
+
- Identify important data structures, types, and their relationships
|
| 94 |
+
- Note any custom types or complex data arrangements
|
| 95 |
+
|
| 96 |
+
## Business Rules & Constraints
|
| 97 |
+
- List business rules, validation logic, and constraints
|
| 98 |
+
- Pay special attention to SPARK contracts and formal specifications
|
| 99 |
+
- Note any domain-specific rules or policies
|
| 100 |
+
|
| 101 |
+
## Domain Concepts
|
| 102 |
+
- Identify domain-specific concepts and terminology
|
| 103 |
+
- Explain the business context and purpose of the code
|
| 104 |
+
|
| 105 |
+
## Conversion Complexity
|
| 106 |
+
**Level**: Low/Medium/High
|
| 107 |
+
|
| 108 |
+
**Reasoning**: Explain why this complexity level was assigned
|
| 109 |
+
|
| 110 |
+
## Recommended Approach
|
| 111 |
+
Describe the recommended strategy for converting this code to modern languages like Python/TypeScript, including:
|
| 112 |
+
- Key architectural considerations
|
| 113 |
+
- Potential challenges and solutions
|
| 114 |
+
- Suggested modernization patterns
|
| 115 |
+
|
| 116 |
+
Focus on providing clear, actionable insights for code modernization and conversion.
|
| 117 |
+
Return ONLY the markdown report, nothing else.
|
| 118 |
"""
|
| 119 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
+
def _create_fallback_business_logic_with_error(self, error_msg: str) -> str:
|
| 122 |
"""Create fallback business logic response when analysis fails"""
|
| 123 |
+
return f"""# Business Logic Analysis
|
| 124 |
+
|
| 125 |
+
## Error
|
| 126 |
+
Analysis failed with error: {error_msg}
|
| 127 |
+
|
| 128 |
+
## Recommended Approach
|
| 129 |
+
Manual analysis recommended due to analysis failure. Please review the Ada code manually or try again.
|
| 130 |
+
|
| 131 |
+
## Next Steps
|
| 132 |
+
- Check if the file is valid Ada code
|
| 133 |
+
- Verify network connectivity for LLM access
|
| 134 |
+
- Consider using a different analysis approach
|
| 135 |
+
"""
|
src/ui.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
"""UI components and logic for the Ada Assistant Gradio interface."""
|
| 2 |
|
| 3 |
import gradio as gr
|
| 4 |
-
import json
|
| 5 |
from src.tools.project_handler import unzip_project
|
| 6 |
|
| 7 |
|
|
@@ -23,14 +22,14 @@ def extract_project(file):
|
|
| 23 |
def analyze_ada_file(file, analyzer):
|
| 24 |
"""Analyze uploaded Ada file and extract business logic"""
|
| 25 |
try:
|
| 26 |
-
# Analyze the uploaded file
|
| 27 |
result = analyzer.extract_business_logic(file.name)
|
| 28 |
|
| 29 |
-
#
|
| 30 |
-
return
|
| 31 |
|
| 32 |
except Exception as e:
|
| 33 |
-
return f"Error analyzing file: {str(e)}"
|
| 34 |
|
| 35 |
|
| 36 |
def handle_zip_upload(zip_file):
|
|
@@ -47,23 +46,19 @@ def handle_zip_upload(zip_file):
|
|
| 47 |
return None, f"Error processing zip file: {str(e)}"
|
| 48 |
|
| 49 |
|
| 50 |
-
def create_file_selection_handler(analyzer):
|
| 51 |
-
"""Create file selection handler with analyzer
|
| 52 |
def handle_file_selection(selected_file):
|
| 53 |
"""Handle file selection from FileExplorer"""
|
| 54 |
|
| 55 |
if not selected_file:
|
| 56 |
-
return "", "", gr.
|
| 57 |
|
| 58 |
# Check if it's an Ada file
|
| 59 |
if not (selected_file.endswith('.ads') or selected_file.endswith('.adb')):
|
| 60 |
-
return "", "", gr.
|
| 61 |
|
| 62 |
try:
|
| 63 |
-
# Read the Ada source code
|
| 64 |
-
with open(selected_file, 'r', encoding='utf-8') as f:
|
| 65 |
-
source_code = f.read()
|
| 66 |
-
|
| 67 |
# Create a mock file object for analyze_ada_file
|
| 68 |
class MockFile:
|
| 69 |
def __init__(self, name):
|
|
@@ -71,20 +66,18 @@ def create_file_selection_handler(analyzer):
|
|
| 71 |
|
| 72 |
mock_file = MockFile(selected_file)
|
| 73 |
|
| 74 |
-
# Analyze the Ada file
|
| 75 |
analysis_result = analyze_ada_file(mock_file, analyzer)
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
-
#
|
| 78 |
-
|
| 79 |
-
parsed_result = json.loads(analysis_result)
|
| 80 |
-
|
| 81 |
-
return parsed_result
|
| 82 |
-
except:
|
| 83 |
-
# If not valid JSON, display as text
|
| 84 |
-
return {"analysis": analysis_result}
|
| 85 |
|
| 86 |
except Exception as e:
|
| 87 |
-
|
|
|
|
| 88 |
|
| 89 |
return handle_file_selection
|
| 90 |
|
|
@@ -96,7 +89,7 @@ def update_explorer(path):
|
|
| 96 |
return gr.FileExplorer(visible=False)
|
| 97 |
|
| 98 |
|
| 99 |
-
def create_interface(analyzer):
|
| 100 |
"""Create the main Gradio interface"""
|
| 101 |
with gr.Blocks(title="Ada Assistant - Project Analyzer") as app:
|
| 102 |
gr.Markdown("# Ada Assistant - Project Analyzer")
|
|
@@ -129,9 +122,18 @@ def create_interface(analyzer):
|
|
| 129 |
with gr.Row():
|
| 130 |
with gr.Column(scale=1):
|
| 131 |
# Analysis results display
|
| 132 |
-
analysis_results = gr.
|
| 133 |
label="Business Logic Analysis",
|
| 134 |
-
visible=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
)
|
| 136 |
|
| 137 |
# Hidden state to store extracted path
|
|
@@ -152,11 +154,11 @@ def create_interface(analyzer):
|
|
| 152 |
)
|
| 153 |
|
| 154 |
# Handle file selection
|
| 155 |
-
handle_file_selection = create_file_selection_handler(analyzer)
|
| 156 |
file_explorer.change(
|
| 157 |
fn=handle_file_selection,
|
| 158 |
inputs=[file_explorer],
|
| 159 |
-
outputs=[analysis_results]
|
| 160 |
)
|
| 161 |
|
| 162 |
return app
|
|
|
|
| 1 |
"""UI components and logic for the Ada Assistant Gradio interface."""
|
| 2 |
|
| 3 |
import gradio as gr
|
|
|
|
| 4 |
from src.tools.project_handler import unzip_project
|
| 5 |
|
| 6 |
|
|
|
|
| 22 |
def analyze_ada_file(file, analyzer):
|
| 23 |
"""Analyze uploaded Ada file and extract business logic"""
|
| 24 |
try:
|
| 25 |
+
# Analyze the uploaded file (now returns markdown)
|
| 26 |
result = analyzer.extract_business_logic(file.name)
|
| 27 |
|
| 28 |
+
# Return markdown result directly
|
| 29 |
+
return result
|
| 30 |
|
| 31 |
except Exception as e:
|
| 32 |
+
return f"# Error\n\nError analyzing file: {str(e)}"
|
| 33 |
|
| 34 |
|
| 35 |
def handle_zip_upload(zip_file):
|
|
|
|
| 46 |
return None, f"Error processing zip file: {str(e)}"
|
| 47 |
|
| 48 |
|
| 49 |
+
def create_file_selection_handler(analyzer, converter):
|
| 50 |
+
"""Create file selection handler with analyzer and converter dependencies"""
|
| 51 |
def handle_file_selection(selected_file):
|
| 52 |
"""Handle file selection from FileExplorer"""
|
| 53 |
|
| 54 |
if not selected_file:
|
| 55 |
+
return "", "", gr.Markdown(visible=False), gr.Code(visible=False)
|
| 56 |
|
| 57 |
# Check if it's an Ada file
|
| 58 |
if not (selected_file.endswith('.ads') or selected_file.endswith('.adb')):
|
| 59 |
+
return "", "", gr.Markdown(visible=False), gr.Code(visible=False)
|
| 60 |
|
| 61 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
# Create a mock file object for analyze_ada_file
|
| 63 |
class MockFile:
|
| 64 |
def __init__(self, name):
|
|
|
|
| 66 |
|
| 67 |
mock_file = MockFile(selected_file)
|
| 68 |
|
| 69 |
+
# Analyze the Ada file (now returns markdown)
|
| 70 |
analysis_result = analyze_ada_file(mock_file, analyzer)
|
| 71 |
+
|
| 72 |
+
# Convert Ada file to Python
|
| 73 |
+
python_code = converter.convert_to_python(selected_file)
|
| 74 |
|
| 75 |
+
# Return markdown analysis and python code
|
| 76 |
+
return analysis_result, python_code, gr.Markdown(visible=True), gr.Code(visible=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
except Exception as e:
|
| 79 |
+
error_markdown = f"# Error\n\nError processing file: {str(e)}"
|
| 80 |
+
return error_markdown, "", gr.Markdown(visible=True), gr.Code(visible=False)
|
| 81 |
|
| 82 |
return handle_file_selection
|
| 83 |
|
|
|
|
| 89 |
return gr.FileExplorer(visible=False)
|
| 90 |
|
| 91 |
|
| 92 |
+
def create_interface(analyzer, converter):
|
| 93 |
"""Create the main Gradio interface"""
|
| 94 |
with gr.Blocks(title="Ada Assistant - Project Analyzer") as app:
|
| 95 |
gr.Markdown("# Ada Assistant - Project Analyzer")
|
|
|
|
| 122 |
with gr.Row():
|
| 123 |
with gr.Column(scale=1):
|
| 124 |
# Analysis results display
|
| 125 |
+
analysis_results = gr.Markdown(
|
| 126 |
label="Business Logic Analysis",
|
| 127 |
+
visible=False
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
with gr.Column(scale=1):
|
| 131 |
+
# Python code display
|
| 132 |
+
python_code_display = gr.Code(
|
| 133 |
+
label="Converted Python Code",
|
| 134 |
+
language="python",
|
| 135 |
+
lines=20,
|
| 136 |
+
visible=False
|
| 137 |
)
|
| 138 |
|
| 139 |
# Hidden state to store extracted path
|
|
|
|
| 154 |
)
|
| 155 |
|
| 156 |
# Handle file selection
|
| 157 |
+
handle_file_selection = create_file_selection_handler(analyzer, converter)
|
| 158 |
file_explorer.change(
|
| 159 |
fn=handle_file_selection,
|
| 160 |
inputs=[file_explorer],
|
| 161 |
+
outputs=[analysis_results, python_code_display, analysis_results, python_code_display]
|
| 162 |
)
|
| 163 |
|
| 164 |
return app
|
tests/test_ada_converter_agent.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for AdaConverterAgent."""
|
| 2 |
+
|
| 3 |
+
import unittest
|
| 4 |
+
from unittest.mock import Mock, patch
|
| 5 |
+
from src.agents.ada_converter import AdaConverterAgent
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestAdaConverterAgent(unittest.TestCase):
|
| 9 |
+
"""Test cases for AdaConverterAgent."""
|
| 10 |
+
|
| 11 |
+
def setUp(self):
|
| 12 |
+
"""Set up test fixtures."""
|
| 13 |
+
self.mock_model = Mock()
|
| 14 |
+
self.agent = AdaConverterAgent(self.mock_model)
|
| 15 |
+
|
| 16 |
+
def test_initialization_with_model(self):
|
| 17 |
+
"""Test that agent initializes correctly with a model."""
|
| 18 |
+
self.assertIsNotNone(self.agent)
|
| 19 |
+
self.assertEqual(self.agent.model, self.mock_model)
|
| 20 |
+
|
| 21 |
+
@patch('builtins.open', create=True)
|
| 22 |
+
@patch('src.agents.ada_converter.ToolCallingAgent')
|
| 23 |
+
def test_convert_to_python_with_ada_file(self, mock_tool_calling_agent, mock_open):
|
| 24 |
+
"""Test converting Ada code to Python."""
|
| 25 |
+
# Setup
|
| 26 |
+
ada_code = """
|
| 27 |
+
procedure Hello is
|
| 28 |
+
begin
|
| 29 |
+
Put_Line("Hello, World!");
|
| 30 |
+
end Hello;
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
mock_open.return_value.__enter__.return_value.read.return_value = ada_code
|
| 34 |
+
|
| 35 |
+
# Mock the LLM response
|
| 36 |
+
expected_python = """
|
| 37 |
+
def hello():
|
| 38 |
+
print("Hello, World!")
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
hello()
|
| 42 |
+
"""
|
| 43 |
+
|
| 44 |
+
# Mock the ToolCallingAgent response
|
| 45 |
+
mock_result = Mock()
|
| 46 |
+
mock_result.messages = [Mock(content=expected_python.strip())]
|
| 47 |
+
mock_tool_calling_agent.return_value.run.return_value = mock_result
|
| 48 |
+
|
| 49 |
+
# Create agent
|
| 50 |
+
agent = AdaConverterAgent(self.mock_model)
|
| 51 |
+
|
| 52 |
+
# Test
|
| 53 |
+
result = agent.convert_to_python("test_file.adb")
|
| 54 |
+
|
| 55 |
+
# Verify
|
| 56 |
+
self.assertIsInstance(result, str)
|
| 57 |
+
self.assertIn("def hello()", result)
|
| 58 |
+
self.assertIn('print("Hello, World!")', result)
|
| 59 |
+
mock_tool_calling_agent.return_value.run.assert_called_once()
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
unittest.main()
|
tests/test_code_analyzer_agent.py
CHANGED
|
@@ -35,19 +35,39 @@ class TestCodeAnalyzerAgent:
|
|
| 35 |
"""Test extract_business_logic with Ada file path and mocked LLM response"""
|
| 36 |
from unittest.mock import MagicMock
|
| 37 |
|
| 38 |
-
# Mock the CodeAgent run method to return business logic analysis
|
| 39 |
mock_result = MagicMock()
|
| 40 |
mock_result.messages = []
|
| 41 |
mock_message = MagicMock()
|
| 42 |
-
mock_message.content = '''
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
'''
|
| 52 |
mock_result.messages.append(mock_message)
|
| 53 |
|
|
@@ -81,19 +101,20 @@ end Tictactoe;
|
|
| 81 |
|
| 82 |
result = analyzer_agent.extract_business_logic(ada_file)
|
| 83 |
|
| 84 |
-
# Verify result
|
| 85 |
-
assert isinstance(result,
|
| 86 |
-
assert
|
| 87 |
-
assert
|
| 88 |
-
assert
|
| 89 |
-
assert
|
| 90 |
-
assert
|
| 91 |
-
assert
|
|
|
|
| 92 |
|
| 93 |
# Verify LLM found business logic concepts
|
| 94 |
-
assert "TicTacToe game logic" in result
|
| 95 |
-
assert "Board array structure" in result
|
| 96 |
-
assert
|
| 97 |
|
| 98 |
# Verify LLM analysis was called
|
| 99 |
analyzer_agent.code_agent.run.assert_called_once()
|
|
|
|
| 35 |
"""Test extract_business_logic with Ada file path and mocked LLM response"""
|
| 36 |
from unittest.mock import MagicMock
|
| 37 |
|
| 38 |
+
# Mock the CodeAgent run method to return markdown business logic analysis
|
| 39 |
mock_result = MagicMock()
|
| 40 |
mock_result.messages = []
|
| 41 |
mock_message = MagicMock()
|
| 42 |
+
mock_message.content = '''# Business Logic Analysis
|
| 43 |
+
|
| 44 |
+
## Core Algorithms
|
| 45 |
+
- TicTacToe game logic
|
| 46 |
+
- Board state evaluation
|
| 47 |
+
- Win condition checking
|
| 48 |
+
|
| 49 |
+
## Data Structures & Types
|
| 50 |
+
- Board array structure
|
| 51 |
+
- Position type system
|
| 52 |
+
- Slot enumeration
|
| 53 |
+
|
| 54 |
+
## Business Rules & Constraints
|
| 55 |
+
- SPARK verification contracts
|
| 56 |
+
- Pre/post conditions
|
| 57 |
+
- Game state constraints
|
| 58 |
+
|
| 59 |
+
## Domain Concepts
|
| 60 |
+
- Game board management
|
| 61 |
+
- Player moves
|
| 62 |
+
- Win detection
|
| 63 |
+
|
| 64 |
+
## Conversion Complexity
|
| 65 |
+
**Level**: Medium
|
| 66 |
+
|
| 67 |
+
**Reasoning**: Moderate complexity due to formal verification contracts
|
| 68 |
+
|
| 69 |
+
## Recommended Approach
|
| 70 |
+
Translate to Python classes with similar structure
|
| 71 |
'''
|
| 72 |
mock_result.messages.append(mock_message)
|
| 73 |
|
|
|
|
| 101 |
|
| 102 |
result = analyzer_agent.extract_business_logic(ada_file)
|
| 103 |
|
| 104 |
+
# Verify result is markdown string
|
| 105 |
+
assert isinstance(result, str)
|
| 106 |
+
assert "# Business Logic Analysis" in result
|
| 107 |
+
assert "## Core Algorithms" in result
|
| 108 |
+
assert "## Data Structures & Types" in result
|
| 109 |
+
assert "## Business Rules & Constraints" in result
|
| 110 |
+
assert "## Domain Concepts" in result
|
| 111 |
+
assert "## Conversion Complexity" in result
|
| 112 |
+
assert "## Recommended Approach" in result
|
| 113 |
|
| 114 |
# Verify LLM found business logic concepts
|
| 115 |
+
assert "TicTacToe game logic" in result
|
| 116 |
+
assert "Board array structure" in result
|
| 117 |
+
assert "Medium" in result
|
| 118 |
|
| 119 |
# Verify LLM analysis was called
|
| 120 |
analyzer_agent.code_agent.run.assert_called_once()
|