Files
erp-ass/backend/app/core/ai_engine.py
dazhuang acd73431ae 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>
2026-03-21 14:23:20 +00:00

121 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""AI Engine for ERP AI Assistant.
This module provides the ClaudeEngine class that wraps Claude API calls
and provides JSON parsing utilities.
"""
import json
import re
from typing import Any
import anthropic
from loguru import logger
from app.config import get_settings
class ClaudeEngine:
"""Engine for interacting with Claude API.
This class wraps the Anthropic Claude API client and provides
utilities for parsing JSON responses from Claude.
"""
def __init__(self) -> None:
"""Initialize Claude engine with settings."""
settings = get_settings()
# Initialize Anthropic client with optional custom base_url
client_kwargs = {"api_key": settings.ANTHROPIC_API_KEY}
if settings.ANTHROPIC_BASE_URL:
client_kwargs["base_url"] = settings.ANTHROPIC_BASE_URL
logger.info(f"Using custom Anthropic base URL: {settings.ANTHROPIC_BASE_URL}")
self.client = anthropic.AsyncAnthropic(**client_kwargs)
self.model = settings.CLAUDE_MODEL
self.max_tokens = settings.CLAUDE_MAX_TOKENS
self.temperature = settings.CLAUDE_TEMPERATURE
def parse_json_response(self, content: str) -> dict[str, Any]:
"""Parse JSON from Claude responses.
Attempts multiple parsing strategies:
1. Direct JSON parse
2. Extract from markdown code blocks
3. Extract any {...} block
Args:
content: The response content from Claude
Returns:
Parsed JSON as a dictionary
Raises:
ValueError: If JSON cannot be parsed using any strategy
"""
if not content or not content.strip():
raise ValueError("Empty content provided")
# Strategy 1: Try direct JSON parse
try:
return json.loads(content)
except json.JSONDecodeError:
pass
# Strategy 2: Try extracting from markdown code blocks
json_code_block_pattern = r'```json\s*(\{.*?\})\s*```'
json_match = re.search(json_code_block_pattern, content, re.DOTALL)
if json_match:
try:
return json.loads(json_match.group(1))
except json.JSONDecodeError:
pass
# Also try any code block (not just json tagged)
code_block_pattern = r'```\s*(\{.*?\})\s*```'
code_block_match = re.search(code_block_pattern, content, re.DOTALL)
if code_block_match:
try:
return json.loads(code_block_match.group(1))
except json.JSONDecodeError:
pass
# Strategy 3: Try extracting any {...} block
# Find balanced braces
brace_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
json_blocks = re.findall(brace_pattern, content, re.DOTALL)
for json_block in json_blocks:
try:
return json.loads(json_block)
except json.JSONDecodeError:
continue
# All strategies failed
logger.error(f"无法解析 Claude 返回的 JSON: {content[:200]}")
raise ValueError("无法解析 Claude 返回的 JSON请检查响应格式")
async def call_claude(
self,
messages: list[dict[str, str]],
temperature: float | None = None
) -> str:
"""Call Claude API.
Args:
messages: List of message dictionaries with 'role' and 'content'
temperature: Optional temperature override (0-2)
Returns:
The text content from Claude's response
Raises:
Exception: If the API call fails
"""
response = await self.client.messages.create(
model=self.model,
max_tokens=self.max_tokens,
temperature=temperature if temperature is not None else self.temperature,
messages=messages
)
return response.content[0].text