feat: 实现 AI-First 代理系统

核心功能:
- AIAgent 模型:AI 代理身份管理
- AIOperationLog: AI 操作日志记录
- AITask: AI 异步任务系统
- AIWebhook: AI webhook 订阅

API 端点:
- POST /api/agents/auth/ - AI 代理认证
- GET/POST /api/agents/ - 代理管理
- GET /api/agent-logs/ - 操作日志查询
- GET/POST /api/agent-tasks/ - 任务管理
- GET/POST /api/agent-webhooks/ - Webhook 管理
- POST /api/batch/ - 批量操作

预置 AI 代理:
- content-moderator-ai: 内容审核 AI
- content-generator-ai: 内容生成 AI
- service-curator-ai: 服务推荐 AI
- analytics-ai: 数据分析 AI
- admin-ai: 管理员 AI

文档:
- AI_AGENT.md: AI-First 设计文档
- init_agents.py: AI 代理初始化脚本

测试:
- 认证系统测试通过
- JWT token 生成正常
- 权限系统工作正常
This commit is contained in:
maoshen
2026-04-12 11:40:11 +00:00
parent a60bb6f652
commit d9e09b61ee
30 changed files with 1373 additions and 0 deletions

View File

@@ -0,0 +1 @@
default_app_config = 'agents.apps.AgentsConfig'

View File

@@ -0,0 +1,107 @@
from django.contrib import admin
from .models import AIAgent, AIOperationLog, AITask, AIWebhook
@admin.register(AIAgent)
class AIAgentAdmin(admin.ModelAdmin):
list_display = ['agent_id', 'name', 'is_active', 'rate_limit', 'last_seen', 'created_at']
list_filter = ['is_active', 'permissions', 'created_at']
search_fields = ['agent_id', 'name', 'description']
readonly_fields = ['created_at', 'updated_at', 'last_seen']
fieldsets = (
('基本信息', {
'fields': ['agent_id', 'name', 'description', 'secret_key']
}),
('权限配置', {
'fields': ['permissions'],
'description': '可用权限read, write, review, delete, batch, analytics'
}),
('速率限制', {
'fields': ['rate_limit', 'rate_limit_window'],
'description': 'rate_limit: 每小时请求数rate_limit_window: 时间窗口(秒)'
}),
('状态', {
'fields': ['is_active', 'last_seen']
}),
('元数据', {
'fields': ['created_at', 'updated_at'],
'classes': ['collapse']
}),
)
@admin.register(AIOperationLog)
class AIOperationLogAdmin(admin.ModelAdmin):
list_display = ['agent', 'action', 'resource_type', 'resource_id', 'status', 'confidence', 'created_at']
list_filter = ['status', 'action', 'resource_type', 'created_at']
search_fields = ['agent__agent_id', 'action', 'resource_type']
readonly_fields = ['created_at']
date_hierarchy = 'created_at'
fieldsets = (
('操作信息', {
'fields': ['agent', 'action', 'resource_type', 'resource_id', 'status']
}),
('AI 元数据', {
'fields': ['confidence', 'reasoning']
}),
('请求/响应', {
'fields': ['request_data', 'response_data', 'error_message'],
'classes': ['collapse']
}),
('性能', {
'fields': ['execution_time_ms', 'created_at'],
'classes': ['collapse']
}),
)
@admin.register(AITask)
class AITaskAdmin(admin.ModelAdmin):
list_display = ['task_id', 'agent', 'task_type', 'status', 'progress', 'created_at', 'completed_at']
list_filter = ['status', 'task_type', 'created_at']
search_fields = ['task_id', 'agent__agent_id', 'task_type']
readonly_fields = ['created_at', 'started_at', 'completed_at']
fieldsets = (
('任务信息', {
'fields': ['task_id', 'agent', 'task_type', 'status']
}),
('进度', {
'fields': ['progress', 'processed_items', 'total_items']
}),
('结果', {
'fields': ['result', 'error_message'],
'classes': ['collapse']
}),
('回调', {
'fields': ['callback_url', 'callback_secret'],
'classes': ['collapse']
}),
('时间', {
'fields': ['created_at', 'started_at', 'completed_at'],
'classes': ['collapse']
}),
)
@admin.register(AIWebhook)
class AIWebhookAdmin(admin.ModelAdmin):
list_display = ['agent', 'event', 'url', 'is_active', 'last_triggered', 'failure_count']
list_filter = ['event', 'is_active', 'created_at']
search_fields = ['agent__agent_id', 'url', 'event']
readonly_fields = ['created_at', 'last_triggered']
fieldsets = (
('Webhook 信息', {
'fields': ['agent', 'event', 'url', 'secret']
}),
('状态', {
'fields': ['is_active', 'last_triggered', 'failure_count']
}),
('元数据', {
'fields': ['created_at'],
'classes': ['collapse']
}),
)

View File

@@ -0,0 +1,7 @@
from django.apps import AppConfig
class AgentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'agents'
verbose_name = 'AI Agents'

View File

@@ -0,0 +1,101 @@
# Generated by Django 4.2.11 on 2026-04-12 11:37
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AIAgent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('agent_id', models.CharField(max_length=100, unique=True)),
('name', models.CharField(max_length=200)),
('description', models.TextField(blank=True)),
('secret_key', models.CharField(max_length=64)),
('permissions', models.JSONField(default=list)),
('rate_limit', models.IntegerField(default=1000)),
('rate_limit_window', models.IntegerField(default=3600)),
('is_active', models.BooleanField(default=True)),
('last_seen', models.DateTimeField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'db_table': 'ai_agents',
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='AIWebhook',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event', models.CharField(choices=[('article.created', 'Article Created'), ('article.approved', 'Article Approved'), ('article.rejected', 'Article Rejected'), ('service.created', 'Service Created'), ('review.pending', 'Review Pending')], max_length=50)),
('url', models.URLField()),
('secret', models.CharField(max_length=64)),
('is_active', models.BooleanField(default=True)),
('last_triggered', models.DateTimeField(blank=True, null=True)),
('failure_count', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webhooks', to='agents.aiagent')),
],
options={
'db_table': 'ai_webhooks',
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='AITask',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('task_id', models.CharField(max_length=64, unique=True)),
('task_type', models.CharField(max_length=50)),
('status', models.CharField(choices=[('pending', 'Pending'), ('processing', 'Processing'), ('completed', 'Completed'), ('failed', 'Failed')], max_length=20)),
('progress', models.IntegerField(default=0)),
('total_items', models.IntegerField(blank=True, null=True)),
('processed_items', models.IntegerField(default=0)),
('result', models.JSONField(blank=True, null=True)),
('error_message', models.TextField(blank=True)),
('callback_url', models.URLField(blank=True, null=True)),
('callback_secret', models.CharField(blank=True, max_length=64, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('started_at', models.DateTimeField(blank=True, null=True)),
('completed_at', models.DateTimeField(blank=True, null=True)),
('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tasks', to='agents.aiagent')),
],
options={
'db_table': 'ai_tasks',
'ordering': ['-created_at'],
},
),
migrations.CreateModel(
name='AIOperationLog',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('action', models.CharField(max_length=50)),
('resource_type', models.CharField(max_length=50)),
('resource_id', models.IntegerField(blank=True, null=True)),
('status', models.CharField(choices=[('success', 'Success'), ('failed', 'Failed'), ('partial', 'Partial Success')], max_length=20)),
('confidence', models.FloatField(blank=True, null=True)),
('reasoning', models.TextField(blank=True)),
('request_data', models.JSONField(blank=True, null=True)),
('response_data', models.JSONField(blank=True, null=True)),
('error_message', models.TextField(blank=True)),
('execution_time_ms', models.IntegerField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('agent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='operations', to='agents.aiagent')),
],
options={
'db_table': 'ai_operation_logs',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['agent', '-created_at'], name='ai_operatio_agent_i_ab1f14_idx'), models.Index(fields=['resource_type', '-created_at'], name='ai_operatio_resourc_95d5e1_idx')],
},
),
]

View File

@@ -0,0 +1,245 @@
from django.db import models
from django.utils import timezone
from datetime import timedelta
class AIAgent(models.Model):
"""AI 代理模型"""
agent_id = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
secret_key = models.CharField(max_length=64)
# 权限
permissions = models.JSONField(default=list) # ['read', 'write', 'review', 'delete', 'batch']
# 速率限制
rate_limit = models.IntegerField(default=1000) # 每小时请求数
rate_limit_window = models.IntegerField(default=3600) # 秒
# 状态
is_active = models.BooleanField(default=True)
last_seen = models.DateTimeField(null=True, blank=True)
# 元数据
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = 'ai_agents'
ordering = ['created_at']
def __str__(self):
return f"{self.name} ({self.agent_id})"
def has_permission(self, permission):
"""检查是否有权限"""
return permission in self.permissions
def can_access(self, resource_type, action):
"""检查是否可以访问资源"""
permission_map = {
('regions', 'read'): 'read',
('regions', 'write'): 'write',
('articles', 'read'): 'read',
('articles', 'write'): 'write',
('articles', 'review'): 'review',
('articles', 'delete'): 'delete',
('services', 'read'): 'read',
('services', 'write'): 'write',
('services', 'delete'): 'delete',
('batch', 'execute'): 'batch',
('analytics', 'read'): 'analytics',
}
required = permission_map.get((resource_type, action))
return required and self.has_permission(required)
class AIOperationLog(models.Model):
"""AI 操作日志"""
STATUS_CHOICES = [
('success', 'Success'),
('failed', 'Failed'),
('partial', 'Partial Success'),
]
agent = models.ForeignKey(AIAgent, on_delete=models.CASCADE, related_name='operations')
action = models.CharField(max_length=50) # create, update, delete, review, etc.
resource_type = models.CharField(max_length=50) # article, service, region, etc.
resource_id = models.IntegerField(null=True, blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
# AI 元数据
confidence = models.FloatField(null=True, blank=True) # AI 置信度 0-1
reasoning = models.TextField(blank=True) # AI 推理过程
# 请求信息
request_data = models.JSONField(null=True, blank=True)
response_data = models.JSONField(null=True, blank=True)
error_message = models.TextField(blank=True)
# 性能
execution_time_ms = models.IntegerField(null=True, blank=True)
# 时间
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'ai_operation_logs'
ordering = ['-created_at']
indexes = [
models.Index(fields=['agent', '-created_at']),
models.Index(fields=['resource_type', '-created_at']),
]
def __str__(self):
return f"{self.agent.agent_id} - {self.action} - {self.status}"
@classmethod
def log(cls, agent, action, resource_type, status, **kwargs):
"""记录操作日志"""
return cls.objects.create(
agent=agent,
action=action,
resource_type=resource_type,
status=status,
**kwargs
)
class AITask(models.Model):
"""AI 异步任务"""
STATUS_CHOICES = [
('pending', 'Pending'),
('processing', 'Processing'),
('completed', 'Completed'),
('failed', 'Failed'),
]
task_id = models.CharField(max_length=64, unique=True)
agent = models.ForeignKey(AIAgent, on_delete=models.CASCADE, related_name='tasks')
task_type = models.CharField(max_length=50) # batch, analyze, optimize, etc.
status = models.CharField(max_length=20, choices=STATUS_CHOICES)
# 进度
progress = models.IntegerField(default=0) # 0-100
total_items = models.IntegerField(null=True, blank=True)
processed_items = models.IntegerField(default=0)
# 结果
result = models.JSONField(null=True, blank=True)
error_message = models.TextField(blank=True)
# 回调
callback_url = models.URLField(null=True, blank=True)
callback_secret = models.CharField(max_length=64, null=True, blank=True)
# 时间
created_at = models.DateTimeField(auto_now_add=True)
started_at = models.DateTimeField(null=True, blank=True)
completed_at = models.DateTimeField(null=True, blank=True)
class Meta:
db_table = 'ai_tasks'
ordering = ['-created_at']
def __str__(self):
return f"{self.task_id} - {self.status}"
def update_progress(self, processed, total=None):
"""更新任务进度"""
self.processed_items = processed
if total:
self.total_items = total
if total:
self.progress = int((processed / total) * 100)
self.save()
def complete(self, result=None):
"""标记任务完成"""
self.status = 'completed'
self.completed_at = timezone.now()
if result:
self.result = result
self.progress = 100
self.save()
def fail(self, error_message):
"""标记任务失败"""
self.status = 'failed'
self.completed_at = timezone.now()
self.error_message = error_message
self.save()
class AIWebhook(models.Model):
"""AI Webhook 订阅"""
EVENT_CHOICES = [
('article.created', 'Article Created'),
('article.approved', 'Article Approved'),
('article.rejected', 'Article Rejected'),
('service.created', 'Service Created'),
('review.pending', 'Review Pending'),
]
agent = models.ForeignKey(AIAgent, on_delete=models.CASCADE, related_name='webhooks')
event = models.CharField(max_length=50, choices=EVENT_CHOICES)
url = models.URLField()
secret = models.CharField(max_length=64)
is_active = models.BooleanField(default=True)
last_triggered = models.DateTimeField(null=True, blank=True)
failure_count = models.IntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
db_table = 'ai_webhooks'
ordering = ['created_at']
def __str__(self):
return f"{self.agent.agent_id} - {self.event}"
def trigger(self, payload):
"""触发 webhook"""
import requests
import hashlib
import hmac
# 生成签名
signature = hmac.new(
self.secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
try:
response = requests.post(
self.url,
data=payload,
headers={
'Content-Type': 'application/json',
'X-Webhook-Signature': f'sha256={signature}',
'X-Webhook-Event': self.event,
},
timeout=10
)
if response.status_code == 200:
self.last_triggered = timezone.now()
self.failure_count = 0
else:
self.failure_count += 1
self.save()
return response.status_code == 200
except Exception:
self.failure_count += 1
self.save()
return False

View File

@@ -0,0 +1,131 @@
from rest_framework import serializers
from .models import AIAgent, AIOperationLog, AITask, AIWebhook
class AIAgentSerializer(serializers.ModelSerializer):
"""AI 代理序列化器"""
permissions = serializers.ListField(
child=serializers.CharField(),
required=False
)
class Meta:
model = AIAgent
fields = [
'id', 'agent_id', 'name', 'description', 'permissions',
'rate_limit', 'rate_limit_window', 'is_active', 'last_seen',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at', 'last_seen']
extra_kwargs = {
'secret_key': {'write_only': True}
}
class AIAgentAuthSerializer(serializers.Serializer):
"""AI 代理认证序列化器"""
agent_id = serializers.CharField()
agent_secret = serializers.CharField()
def validate(self, data):
try:
agent = AIAgent.objects.get(
agent_id=data['agent_id'],
secret_key=data['agent_secret'],
is_active=True
)
except AIAgent.DoesNotExist:
raise serializers.ValidationError("Invalid agent credentials")
# 更新最后活跃时间
from django.utils import timezone
agent.last_seen = timezone.now()
agent.save()
self.instance = agent
return data
class AIOperationLogSerializer(serializers.ModelSerializer):
"""AI 操作日志序列化器"""
agent_id = serializers.CharField(source='agent.agent_id', read_only=True)
agent_name = serializers.CharField(source='agent.name', read_only=True)
class Meta:
model = AIOperationLog
fields = [
'id', 'agent_id', 'agent_name', 'action', 'resource_type',
'resource_id', 'status', 'confidence', 'reasoning',
'execution_time_ms', 'created_at'
]
read_only_fields = ['id', 'created_at']
class AITaskSerializer(serializers.ModelSerializer):
"""AI 任务序列化器"""
agent_id = serializers.CharField(source='agent.agent_id', read_only=True)
class Meta:
model = AITask
fields = [
'id', 'task_id', 'agent_id', 'task_type', 'status',
'progress', 'processed_items', 'total_items', 'result',
'error_message', 'callback_url', 'created_at', 'started_at', 'completed_at'
]
read_only_fields = ['id', 'created_at', 'started_at', 'completed_at']
class AITaskCreateSerializer(serializers.Serializer):
"""AI 任务创建序列化器"""
task_type = serializers.CharField()
operations = serializers.ListField(required=False)
callback_url = serializers.URLField(required=False)
def validate(self, data):
if data['task_type'] == 'batch' and not data.get('operations'):
raise serializers.ValidationError("Batch task requires operations")
return data
class AIWebhookSerializer(serializers.ModelSerializer):
"""AI Webhook 序列化器"""
agent_id = serializers.CharField(source='agent.agent_id', read_only=True)
class Meta:
model = AIWebhook
fields = [
'id', 'agent_id', 'event', 'url', 'is_active',
'last_triggered', 'failure_count', 'created_at'
]
read_only_fields = ['id', 'created_at', 'last_triggered', 'failure_count']
extra_kwargs = {
'secret': {'write_only': True}
}
class BatchOperationSerializer(serializers.Serializer):
"""批量操作序列化器"""
operations = serializers.ListField(
child=serializers.DictField()
)
def validate_operations(self, operations):
if len(operations) > 100:
raise serializers.ValidationError("Maximum 100 operations per batch")
for i, op in enumerate(operations):
if 'method' not in op:
raise serializers.ValidationError(f"Operation {i}: missing 'method'")
if op['method'] not in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
raise serializers.ValidationError(f"Operation {i}: invalid method")
if 'path' not in op:
raise serializers.ValidationError(f"Operation {i}: missing 'path'")
return operations

View File

@@ -0,0 +1,365 @@
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)