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:
2026-03-21 14:23:20 +00:00
commit acd73431ae
60 changed files with 11284 additions and 0 deletions

View File

@@ -0,0 +1 @@
"""API routes for ERP AI Assistant."""

113
backend/app/api/analyze.py Normal file
View 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
View 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
View 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
}
)