feat: implement ERP AI Assistant Phase 1
Backend (FastAPI + SQLAlchemy + Claude API + RAG): - Config management with Pydantic v2 - Database engine with connection pooling and SQL injection prevention - AI engine with Claude API integration (support custom base URL) - RAG engine with ChromaDB and sentence-transformers - Requirement analysis service - Config generation service - Executor engine with SQL validation - REST API endpoints: /analyze, /generate, /execute Frontend (Vue 3 + Element Plus + Pinia): - Complete 3-step workflow: analyze → generate → execute - Step indicator with progress visualization - Analysis result display with field table - SQL preview with monospace font - Execute confirmation dialog with safety warning - Execution result display - State management with Pinia - API service integration Security: - SQL injection prevention with parameterized queries - Dangerous SQL operation blocking - Database password URL encoding - Transaction auto-rollback - Pydantic config validation Features: - Natural language requirement analysis - Automated SQL configuration generation - Safe execution with human review - LAN access support - Custom Claude API endpoint support Documentation: - README with quick start guide - Quick start guide - LAN access configuration - Dependency fixes guide - Claude API configuration - Git operation guide - Implementation report Dependencies fixed: - numpy<2.0.0 for chromadb compatibility - sentence-transformers==2.7.0 for huggingface_hub compatibility Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1
backend/app/api/__init__.py
Normal file
1
backend/app/api/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""API routes for ERP AI Assistant."""
|
||||
113
backend/app/api/analyze.py
Normal file
113
backend/app/api/analyze.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""Analyze API endpoint for requirement analysis.
|
||||
|
||||
This module provides the /analyze endpoint for analyzing user requirements.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from typing import Dict
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from loguru import logger
|
||||
|
||||
from app.models.request import AnalyzeRequest
|
||||
from app.models.response import AnalyzeResponse, ErrorResponse
|
||||
from app.services.requirement_service import RequirementService
|
||||
|
||||
# Create router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/analyze",
|
||||
response_model=AnalyzeResponse,
|
||||
responses={
|
||||
400: {"model": ErrorResponse, "description": "Invalid request"},
|
||||
500: {"model": ErrorResponse, "description": "Internal server error"}
|
||||
},
|
||||
summary="Analyze user requirement",
|
||||
description="Analyze natural language or structured requirement and return structured specification"
|
||||
)
|
||||
async def analyze_requirement(request: AnalyzeRequest) -> AnalyzeResponse:
|
||||
"""Analyze user requirement and return structured specification.
|
||||
|
||||
This endpoint accepts either natural language or structured input,
|
||||
processes it through Claude AI with RAG knowledge retrieval, and
|
||||
returns a structured requirement specification.
|
||||
|
||||
Args:
|
||||
request: AnalyzeRequest containing input_type, content, and optional session_id
|
||||
|
||||
Returns:
|
||||
AnalyzeResponse with session_id, status, and structured data
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 for invalid input, 500 for processing errors
|
||||
"""
|
||||
# Generate session ID if not provided
|
||||
session_id = request.session_id or str(uuid.uuid4())
|
||||
|
||||
try:
|
||||
# Validate input type
|
||||
if request.input_type not in ["natural_language", "structured"]:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "INVALID_INPUT_TYPE",
|
||||
"message": "input_type must be 'natural_language' or 'structured'",
|
||||
"session_id": session_id
|
||||
}
|
||||
)
|
||||
|
||||
# Validate content
|
||||
if not request.content or not request.content.strip():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "EMPTY_CONTENT",
|
||||
"message": "content cannot be empty",
|
||||
"session_id": session_id
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"[{session_id}] Processing analyze request: {request.content[:50]}...")
|
||||
|
||||
# Create service and analyze
|
||||
service = RequirementService()
|
||||
result = await service.analyze(
|
||||
user_input=request.content,
|
||||
session_id=session_id
|
||||
)
|
||||
|
||||
logger.success(f"[{session_id}] Analysis completed successfully")
|
||||
|
||||
return AnalyzeResponse(
|
||||
session_id=session_id,
|
||||
status="success",
|
||||
data=result
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"[{session_id}] Validation error: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": str(e),
|
||||
"session_id": session_id
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{session_id}] Analysis failed: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "ANALYSIS_FAILED",
|
||||
"message": f"Failed to analyze requirement: {str(e)}",
|
||||
"session_id": session_id
|
||||
}
|
||||
)
|
||||
151
backend/app/api/execute.py
Normal file
151
backend/app/api/execute.py
Normal file
@@ -0,0 +1,151 @@
|
||||
"""Execute API endpoint for SQL configuration execution.
|
||||
|
||||
This module provides the /execute endpoint for executing SQL configuration.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from typing import Dict, Any
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from loguru import logger
|
||||
|
||||
from app.models.request import ExecuteRequest
|
||||
from app.models.response import ExecuteResponse, ErrorResponse
|
||||
from app.core.executor import ConfigExecutor
|
||||
|
||||
# Create router
|
||||
router = APIRouter()
|
||||
|
||||
# In-memory storage for SQL lists (should use Redis/database in production)
|
||||
_session_sql_store: Dict[str, list] = {}
|
||||
|
||||
|
||||
def store_session_sql(session_id: str, sql_list: list) -> None:
|
||||
"""Store SQL list for a session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID
|
||||
sql_list: List of SQL statements
|
||||
"""
|
||||
_session_sql_store[session_id] = sql_list
|
||||
logger.debug(f"Stored {len(sql_list)} SQL statements for session {session_id}")
|
||||
|
||||
|
||||
def get_session_sql(session_id: str) -> list:
|
||||
"""Retrieve SQL list for a session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID
|
||||
|
||||
Returns:
|
||||
List of SQL statements (empty list if not found)
|
||||
"""
|
||||
sql_list = _session_sql_store.get(session_id, [])
|
||||
logger.debug(f"Retrieved {len(sql_list)} SQL statements for session {session_id}")
|
||||
return sql_list
|
||||
|
||||
|
||||
@router.post(
|
||||
"/execute",
|
||||
response_model=ExecuteResponse,
|
||||
responses={
|
||||
400: {"model": ErrorResponse, "description": "Invalid request"},
|
||||
500: {"model": ErrorResponse, "description": "Internal server error"}
|
||||
},
|
||||
summary="Execute SQL configuration",
|
||||
description="Execute SQL configuration after user confirmation"
|
||||
)
|
||||
async def execute_config(request: ExecuteRequest) -> ExecuteResponse:
|
||||
"""Execute SQL configuration after user confirmation.
|
||||
|
||||
This endpoint executes the SQL statements associated with the session.
|
||||
User must set confirmed=True to proceed with execution.
|
||||
|
||||
Args:
|
||||
request: ExecuteRequest with session_id, confirmed, and backup_enabled
|
||||
|
||||
Returns:
|
||||
ExecuteResponse with execution_id, status, and message
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 if not confirmed or invalid, 500 for execution errors
|
||||
"""
|
||||
# Generate execution ID
|
||||
execution_id = str(uuid.uuid4())
|
||||
|
||||
try:
|
||||
# Check user confirmation
|
||||
if not request.confirmed:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "NOT_CONFIRMED",
|
||||
"message": "User must confirm execution by setting confirmed=True",
|
||||
"session_id": request.session_id,
|
||||
"execution_id": execution_id
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"[{request.session_id}] Processing execute request")
|
||||
|
||||
# Retrieve SQL list for session
|
||||
sql_list = get_session_sql(request.session_id)
|
||||
|
||||
if not sql_list:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "NO_SQL_FOUND",
|
||||
"message": "No SQL statements found for this session",
|
||||
"session_id": request.session_id,
|
||||
"execution_id": execution_id
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"[{request.session_id}] Retrieved {len(sql_list)} SQL statements")
|
||||
|
||||
# Create executor
|
||||
executor = ConfigExecutor()
|
||||
|
||||
# Execute configuration
|
||||
result = executor.execute_config(sql_list, request.session_id)
|
||||
|
||||
if result["success"]:
|
||||
logger.success(
|
||||
f"[{request.session_id}] Execution completed: {result['message']}"
|
||||
)
|
||||
return ExecuteResponse(
|
||||
execution_id=execution_id,
|
||||
status="success",
|
||||
message=result["message"]
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"[{request.session_id}] Execution failed: {result['failed']}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "EXECUTION_FAILED",
|
||||
"message": result["message"],
|
||||
"error": result["failed"],
|
||||
"session_id": request.session_id,
|
||||
"execution_id": execution_id
|
||||
}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{request.session_id}] Execution failed: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "EXECUTION_ERROR",
|
||||
"message": f"Failed to execute config: {str(e)}",
|
||||
"session_id": request.session_id,
|
||||
"execution_id": execution_id
|
||||
}
|
||||
)
|
||||
102
backend/app/api/generate.py
Normal file
102
backend/app/api/generate.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""Generate API endpoint for configuration generation.
|
||||
|
||||
This module provides the /generate endpoint for generating SQL configuration.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, status
|
||||
from loguru import logger
|
||||
|
||||
from app.models.request import GenerateRequest
|
||||
from app.models.response import GenerateResponse, ErrorResponse
|
||||
from app.services.config_service import ConfigService
|
||||
from app.api.execute import store_session_sql # Import SQL storage function
|
||||
|
||||
# Create router
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/generate",
|
||||
response_model=GenerateResponse,
|
||||
responses={
|
||||
400: {"model": ErrorResponse, "description": "Invalid request"},
|
||||
500: {"model": ErrorResponse, "description": "Internal server error"}
|
||||
},
|
||||
summary="Generate SQL configuration",
|
||||
description="Generate SQL configuration based on structured requirements"
|
||||
)
|
||||
async def generate_config(request: GenerateRequest) -> GenerateResponse:
|
||||
"""Generate SQL configuration based on structured requirements.
|
||||
|
||||
This endpoint takes structured requirements from the analysis phase
|
||||
and generates SQL configuration statements using Claude AI.
|
||||
|
||||
Args:
|
||||
request: GenerateRequest with session_id and requirements
|
||||
|
||||
Returns:
|
||||
GenerateResponse with session_id, status, and generated config
|
||||
|
||||
Raises:
|
||||
HTTPException: 400 for invalid input, 500 for processing errors
|
||||
"""
|
||||
try:
|
||||
# Validate requirements
|
||||
if not request.requirements:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "EMPTY_REQUIREMENTS",
|
||||
"message": "requirements cannot be empty",
|
||||
"session_id": request.session_id
|
||||
}
|
||||
)
|
||||
|
||||
logger.info(f"[{request.session_id}] Processing generate request")
|
||||
|
||||
# Create service and generate config
|
||||
service = ConfigService()
|
||||
result = await service.generate(
|
||||
requirements=request.requirements,
|
||||
session_id=request.session_id
|
||||
)
|
||||
|
||||
# Store generated SQL for later execution
|
||||
if result and result.get("配置方案") and result["配置方案"].get("sql_list"):
|
||||
sql_list = result["配置方案"]["sql_list"]
|
||||
store_session_sql(request.session_id, sql_list)
|
||||
logger.info(f"[{request.session_id}] Stored {len(sql_list)} SQL statements for execution")
|
||||
|
||||
logger.success(f"[{request.session_id}] Config generation completed")
|
||||
|
||||
return GenerateResponse(
|
||||
session_id=request.session_id,
|
||||
status="success",
|
||||
data=result
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions
|
||||
raise
|
||||
|
||||
except ValueError as e:
|
||||
logger.error(f"[{request.session_id}] Validation error: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail={
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": str(e),
|
||||
"session_id": request.session_id
|
||||
}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[{request.session_id}] Config generation failed: {e}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail={
|
||||
"code": "GENERATION_FAILED",
|
||||
"message": f"Failed to generate config: {str(e)}",
|
||||
"session_id": request.session_id
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user