366 lines
12 KiB
Python
366 lines
12 KiB
Python
|
|
import time
|
|||
|
|
import uuid
|
|||
|
|
from datetime import timedelta
|
|||
|
|
from django.utils import timezone
|
|||
|
|
from rest_framework import status, viewsets
|
|||
|
|
from rest_framework.decorators import action, api_view, permission_classes
|
|||
|
|
from rest_framework.response import Response
|
|||
|
|
from rest_framework.permissions import AllowAny, IsAuthenticated
|
|||
|
|
from rest_framework_simplejwt.tokens import RefreshToken
|
|||
|
|
from django.db.models import Count, Q
|
|||
|
|
from django.http import JsonResponse
|
|||
|
|
|
|||
|
|
from .models import AIAgent, AIOperationLog, AITask, AIWebhook
|
|||
|
|
from .serializers import (
|
|||
|
|
AIAgentSerializer, AIAgentAuthSerializer, AIOperationLogSerializer,
|
|||
|
|
AITaskSerializer, AITaskCreateSerializer, AIWebhookSerializer,
|
|||
|
|
BatchOperationSerializer
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AIAgentViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""AI 代理管理"""
|
|||
|
|
|
|||
|
|
queryset = AIAgent.objects.all()
|
|||
|
|
serializer_class = AIAgentSerializer
|
|||
|
|
lookup_field = 'agent_id'
|
|||
|
|
|
|||
|
|
def get_permissions(self):
|
|||
|
|
if self.action == 'auth':
|
|||
|
|
return [AllowAny()]
|
|||
|
|
return [IsAuthenticated()]
|
|||
|
|
|
|||
|
|
@action(detail=False, methods=['post'], permission_classes=[AllowAny])
|
|||
|
|
def auth(self, request):
|
|||
|
|
"""AI 代理认证"""
|
|||
|
|
serializer = AIAgentAuthSerializer(data=request.data)
|
|||
|
|
serializer.is_valid(raise_exception=True)
|
|||
|
|
|
|||
|
|
agent = serializer.instance
|
|||
|
|
|
|||
|
|
# 生成自定义 JWT token(不使用 for_user,因为 AI 不是 Django User)
|
|||
|
|
from rest_framework_simplejwt.tokens import AccessToken
|
|||
|
|
from datetime import timedelta
|
|||
|
|
from django.utils import timezone
|
|||
|
|
|
|||
|
|
# 创建 access token
|
|||
|
|
access = AccessToken()
|
|||
|
|
access['agent_id'] = agent.agent_id
|
|||
|
|
access['permissions'] = agent.permissions
|
|||
|
|
access['type'] = 'agent'
|
|||
|
|
access.set_exp(lifetime=timedelta(hours=1))
|
|||
|
|
|
|||
|
|
# 创建 refresh token
|
|||
|
|
from rest_framework_simplejwt.tokens import RefreshToken as BaseRefreshToken
|
|||
|
|
class AgentRefreshToken(BaseRefreshToken):
|
|||
|
|
token_type = 'refresh'
|
|||
|
|
|
|||
|
|
@classmethod
|
|||
|
|
def for_agent(cls, agent):
|
|||
|
|
token = cls()
|
|||
|
|
token['agent_id'] = agent.agent_id
|
|||
|
|
token['permissions'] = agent.permissions
|
|||
|
|
token['type'] = 'agent'
|
|||
|
|
return token
|
|||
|
|
|
|||
|
|
refresh = AgentRefreshToken.for_agent(agent)
|
|||
|
|
|
|||
|
|
# 记录登录
|
|||
|
|
AIOperationLog.log(
|
|||
|
|
agent=agent,
|
|||
|
|
action='auth',
|
|||
|
|
resource_type='agent',
|
|||
|
|
status='success',
|
|||
|
|
confidence=1.0,
|
|||
|
|
reasoning='Agent authentication successful'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
'access_token': str(access),
|
|||
|
|
'refresh_token': str(refresh),
|
|||
|
|
'expires_in': 3600,
|
|||
|
|
'agent_info': {
|
|||
|
|
'id': agent.agent_id,
|
|||
|
|
'name': agent.name,
|
|||
|
|
'permissions': agent.permissions,
|
|||
|
|
'rate_limit': agent.rate_limit,
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['get'])
|
|||
|
|
def stats(self, request, agent_id):
|
|||
|
|
"""获取代理统计信息"""
|
|||
|
|
agent = self.get_object()
|
|||
|
|
|
|||
|
|
# 操作统计
|
|||
|
|
operations = AIOperationLog.objects.filter(agent=agent)
|
|||
|
|
stats = {
|
|||
|
|
'total_operations': operations.count(),
|
|||
|
|
'success_operations': operations.filter(status='success').count(),
|
|||
|
|
'failed_operations': operations.filter(status='failed').count(),
|
|||
|
|
'avg_confidence': operations.filter(confidence__isnull=False).aggregate(
|
|||
|
|
avg=Count('confidence')
|
|||
|
|
)['avg'],
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 任务统计
|
|||
|
|
tasks = AITask.objects.filter(agent=agent)
|
|||
|
|
stats['tasks'] = {
|
|||
|
|
'total': tasks.count(),
|
|||
|
|
'completed': tasks.filter(status='completed').count(),
|
|||
|
|
'failed': tasks.filter(status='failed').count(),
|
|||
|
|
'processing': tasks.filter(status='processing').count(),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# 最近 7 天操作趋势
|
|||
|
|
from datetime import datetime, timedelta
|
|||
|
|
seven_days_ago = timezone.now() - timedelta(days=7)
|
|||
|
|
daily_ops = operations.filter(
|
|||
|
|
created_at__gte=seven_days_ago
|
|||
|
|
).extra(
|
|||
|
|
select={'date': 'date(created_at)'}
|
|||
|
|
).values('date').annotate(count=Count('id'))
|
|||
|
|
|
|||
|
|
stats['daily_operations'] = list(daily_ops)
|
|||
|
|
|
|||
|
|
return Response(stats)
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'])
|
|||
|
|
def rotate_secret(self, request, agent_id):
|
|||
|
|
"""轮换密钥"""
|
|||
|
|
agent = self.get_object()
|
|||
|
|
agent.secret_key = uuid.uuid4().hex
|
|||
|
|
agent.save()
|
|||
|
|
|
|||
|
|
AIOperationLog.log(
|
|||
|
|
agent=agent,
|
|||
|
|
action='rotate_secret',
|
|||
|
|
resource_type='agent',
|
|||
|
|
status='success'
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return Response({'message': 'Secret key rotated', 'new_secret': agent.secret_key})
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AIOperationLogViewSet(viewsets.ReadOnlyModelViewSet):
|
|||
|
|
"""AI 操作日志查询"""
|
|||
|
|
|
|||
|
|
queryset = AIOperationLog.objects.all()
|
|||
|
|
serializer_class = AIOperationLogSerializer
|
|||
|
|
|
|||
|
|
def get_queryset(self):
|
|||
|
|
queryset = super().get_queryset()
|
|||
|
|
|
|||
|
|
# 过滤
|
|||
|
|
agent_id = self.request.query_params.get('agent_id')
|
|||
|
|
if agent_id:
|
|||
|
|
queryset = queryset.filter(agent__agent_id=agent_id)
|
|||
|
|
|
|||
|
|
action = self.request.query_params.get('action')
|
|||
|
|
if action:
|
|||
|
|
queryset = queryset.filter(action=action)
|
|||
|
|
|
|||
|
|
resource_type = self.request.query_params.get('resource_type')
|
|||
|
|
if resource_type:
|
|||
|
|
queryset = queryset.filter(resource_type=resource_type)
|
|||
|
|
|
|||
|
|
status = self.request.query_params.get('status')
|
|||
|
|
if status:
|
|||
|
|
queryset = queryset.filter(status=status)
|
|||
|
|
|
|||
|
|
return queryset
|
|||
|
|
|
|||
|
|
@action(detail=False, methods=['get'])
|
|||
|
|
def summary(self, request):
|
|||
|
|
"""操作日志摘要"""
|
|||
|
|
# 按代理统计
|
|||
|
|
by_agent = AIOperationLog.objects.values('agent__agent_id', 'agent__name').annotate(
|
|||
|
|
total=Count('id'),
|
|||
|
|
success=Count('id', filter=Q(status='success')),
|
|||
|
|
failed=Count('id', filter=Q(status='failed'))
|
|||
|
|
).order_by('-total')
|
|||
|
|
|
|||
|
|
# 按操作类型统计
|
|||
|
|
by_action = AIOperationLog.objects.values('action').annotate(
|
|||
|
|
count=Count('id')
|
|||
|
|
).order_by('-count')[:10]
|
|||
|
|
|
|||
|
|
# 按资源类型统计
|
|||
|
|
by_resource = AIOperationLog.objects.values('resource_type').annotate(
|
|||
|
|
count=Count('id')
|
|||
|
|
).order_by('-count')[:10]
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
'by_agent': list(by_agent),
|
|||
|
|
'by_action': list(by_action),
|
|||
|
|
'by_resource': list(by_resource),
|
|||
|
|
'total': AIOperationLog.objects.count(),
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AITaskViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""AI 任务管理"""
|
|||
|
|
|
|||
|
|
queryset = AITask.objects.all()
|
|||
|
|
serializer_class = AITaskSerializer
|
|||
|
|
lookup_field = 'task_id'
|
|||
|
|
|
|||
|
|
def get_serializer_class(self):
|
|||
|
|
if self.action == 'create':
|
|||
|
|
return AITaskCreateSerializer
|
|||
|
|
return AITaskSerializer
|
|||
|
|
|
|||
|
|
def create(self, request, *args, **kwargs):
|
|||
|
|
"""创建任务"""
|
|||
|
|
serializer = self.get_serializer(data=request.data)
|
|||
|
|
serializer.is_valid(raise_exception=True)
|
|||
|
|
|
|||
|
|
# 获取 AI 代理(从 token 中)
|
|||
|
|
agent_id = request.auth.payload.get('agent_id') if hasattr(request, 'auth') else None
|
|||
|
|
if not agent_id:
|
|||
|
|
return Response({'error': 'Agent authentication required'}, status=401)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
agent = AIAgent.objects.get(agent_id=agent_id)
|
|||
|
|
except AIOperationLog.DoesNotExist:
|
|||
|
|
return Response({'error': 'Agent not found'}, status=404)
|
|||
|
|
|
|||
|
|
# 创建任务
|
|||
|
|
task = AITask.objects.create(
|
|||
|
|
task_id=uuid.uuid4().hex,
|
|||
|
|
agent=agent,
|
|||
|
|
task_type=serializer.validated_data['task_type'],
|
|||
|
|
status='pending',
|
|||
|
|
callback_url=serializer.validated_data.get('callback_url')
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# TODO: 将任务加入队列异步处理
|
|||
|
|
# 这里简化处理,如果是 batch 任务,立即处理
|
|||
|
|
|
|||
|
|
if task.task_type == 'batch':
|
|||
|
|
operations = serializer.validated_data.get('operations', [])
|
|||
|
|
# 异步处理会在这里触发
|
|||
|
|
task.status = 'processing'
|
|||
|
|
task.started_at = timezone.now()
|
|||
|
|
task.total_items = len(operations)
|
|||
|
|
task.save()
|
|||
|
|
|
|||
|
|
return Response(AITaskSerializer(task).data, status=201)
|
|||
|
|
|
|||
|
|
@action(detail=True, methods=['post'])
|
|||
|
|
def cancel(self, request, task_id):
|
|||
|
|
"""取消任务"""
|
|||
|
|
task = self.get_object()
|
|||
|
|
|
|||
|
|
if task.status in ['completed', 'failed']:
|
|||
|
|
return Response({'error': 'Task already completed'}, status=400)
|
|||
|
|
|
|||
|
|
task.status = 'cancelled'
|
|||
|
|
task.completed_at = timezone.now()
|
|||
|
|
task.save()
|
|||
|
|
|
|||
|
|
return Response({'message': 'Task cancelled'})
|
|||
|
|
|
|||
|
|
|
|||
|
|
@api_view(['POST'])
|
|||
|
|
def batch_execute(request):
|
|||
|
|
"""批量操作执行"""
|
|||
|
|
serializer = BatchOperationSerializer(data=request.data)
|
|||
|
|
serializer.is_valid(raise_exception=True)
|
|||
|
|
|
|||
|
|
operations = serializer.validated_data['operations']
|
|||
|
|
results = []
|
|||
|
|
|
|||
|
|
# 获取 AI 代理
|
|||
|
|
agent_id = request.auth.payload.get('agent_id') if hasattr(request, 'auth') else None
|
|||
|
|
if not agent_id:
|
|||
|
|
return Response({'error': 'Agent authentication required'}, status=401)
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
agent = AIAgent.objects.get(agent_id=agent_id)
|
|||
|
|
except AIAgent.DoesNotExist:
|
|||
|
|
return Response({'error': 'Agent not found'}, status=404)
|
|||
|
|
|
|||
|
|
# 检查批量操作权限
|
|||
|
|
if not agent.has_permission('batch'):
|
|||
|
|
return Response({'error': 'No batch permission'}, status=403)
|
|||
|
|
|
|||
|
|
start_time = time.time()
|
|||
|
|
|
|||
|
|
# 执行操作(简化版本,实际应该异步)
|
|||
|
|
for i, op in enumerate(operations):
|
|||
|
|
try:
|
|||
|
|
# TODO: 实际执行 HTTP 请求到对应的 API
|
|||
|
|
# 这里只记录日志
|
|||
|
|
AIOperationLog.log(
|
|||
|
|
agent=agent,
|
|||
|
|
action=f"batch_{op['method'].lower()}",
|
|||
|
|
resource_type='batch',
|
|||
|
|
status='success',
|
|||
|
|
request_data=op,
|
|||
|
|
execution_time_ms=int((time.time() - start_time) * 1000)
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
results.append({
|
|||
|
|
'index': i,
|
|||
|
|
'status': 'success',
|
|||
|
|
'method': op['method'],
|
|||
|
|
'path': op['path']
|
|||
|
|
})
|
|||
|
|
except Exception as e:
|
|||
|
|
results.append({
|
|||
|
|
'index': i,
|
|||
|
|
'status': 'failed',
|
|||
|
|
'error': str(e)
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
execution_time = int((time.time() - start_time) * 1000)
|
|||
|
|
|
|||
|
|
# 记录批量操作日志
|
|||
|
|
AIOperationLog.log(
|
|||
|
|
agent=agent,
|
|||
|
|
action='batch_execute',
|
|||
|
|
resource_type='batch',
|
|||
|
|
status='success',
|
|||
|
|
confidence=1.0,
|
|||
|
|
reasoning=f'Executed {len(operations)} operations',
|
|||
|
|
request_data={'operations_count': len(operations)},
|
|||
|
|
execution_time_ms=execution_time
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
return Response({
|
|||
|
|
'task_id': uuid.uuid4().hex,
|
|||
|
|
'status': 'completed',
|
|||
|
|
'execution_time_ms': execution_time,
|
|||
|
|
'results': results,
|
|||
|
|
'summary': {
|
|||
|
|
'total': len(operations),
|
|||
|
|
'success': sum(1 for r in results if r['status'] == 'success'),
|
|||
|
|
'failed': sum(1 for r in results if r['status'] == 'failed')
|
|||
|
|
}
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
|
|||
|
|
class AIWebhookViewSet(viewsets.ModelViewSet):
|
|||
|
|
"""AI Webhook 管理"""
|
|||
|
|
|
|||
|
|
queryset = AIWebhook.objects.all()
|
|||
|
|
serializer_class = AIWebhookSerializer
|
|||
|
|
|
|||
|
|
def get_queryset(self):
|
|||
|
|
queryset = super().get_queryset()
|
|||
|
|
|
|||
|
|
# 只返回当前代理的 webhook
|
|||
|
|
agent_id = self.request.auth.payload.get('agent_id') if hasattr(self.request, 'auth') else None
|
|||
|
|
if agent_id:
|
|||
|
|
queryset = queryset.filter(agent__agent_id=agent_id)
|
|||
|
|
|
|||
|
|
return queryset
|
|||
|
|
|
|||
|
|
def perform_create(self, serializer):
|
|||
|
|
# 自动关联当前代理
|
|||
|
|
agent_id = self.request.auth.payload.get('agent_id') if hasattr(self.request, 'auth') else None
|
|||
|
|
if agent_id:
|
|||
|
|
agent = AIAgent.objects.get(agent_id=agent_id)
|
|||
|
|
serializer.save(agent=agent)
|