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

310
city-manual/AI_AGENT.md Normal file
View File

@@ -0,0 +1,310 @@
# AI Agent 设计文档
> 城市手册是一个 **AI-First** 的应用,所有功能都可以由 AI 机器人自动操作
## 设计原则
### 1. 机器可读的 API
- ✅ RESTful 设计,资源导向
- ✅ 统一的 JSON 响应格式
- ✅ 标准化的错误码和错误信息
- ✅ 完整的 OpenAPI/Swagger 文档
- ✅ HATEOAS 链接(可选)
### 2. 自动化的认证流程
- ✅ JWT Token无状态认证
- ✅ Token 自动刷新机制
- ✅ Service Account 支持AI 专用账号)
- ✅ API Key 支持(长期有效)
### 3. 结构化的日志系统
- ✅ 所有操作记录到数据库
- ✅ 区分人类用户和 AI 代理
- ✅ 操作审计追踪
- ✅ 机器可读的日志格式
### 4. AI 友好的错误处理
- ✅ 明确的错误码
- ✅ 详细的错误描述
- ✅ 建议的修复方案
- ✅ 多语言支持(可选)
### 5. 批量操作支持
- ✅ 批量创建/更新/删除
- ✅ 异步任务支持
- ✅ 任务状态查询
- ✅ 操作结果回调
## AI 代理类型
### 🤖 内容审核 AI
```python
# AI 审核员自动审核用户提交的内容
POST /api/articles/{id}/review/
{
"agent_id": "content-moderator-ai",
"action": "approve" | "reject",
"reason": "内容符合社区规范",
"confidence": 0.95
}
```
### 📝 内容生成 AI
```python
# AI 作者自动生成城市介绍文章
POST /api/articles/
{
"agent_id": "content-generator-ai",
"title": "北京市旅游指南",
"region": 1,
"content": "...",
"auto_generated": true
}
```
### 🏪 服务推荐 AI
```python
# AI 推荐官自动添加特色服务
POST /api/services/
{
"agent_id": "service-curator-ai",
"name": "故宫博物院",
"region": 1,
"category": "旅游",
"description": "...",
"auto_generated": true
}
```
### 📊 数据分析 AI
```python
# AI 分析师生成统计报告
GET /api/analytics/summary?agent=analytics-ai
```
### 🔍 搜索优化 AI
```python
# AI 优化搜索索引
POST /api/search/optimize/
{
"agent_id": "search-optimizer-ai",
"scope": "all" | "regions" | "articles" | "services"
}
```
## AI 专用 API 端点
### AI 身份认证
```http
POST /api/agents/auth/
Content-Type: application/json
{
"agent_id": "content-moderator-ai",
"agent_secret": "xxx",
"capabilities": ["review", "approve", "reject"]
}
Response:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_in": 3600,
"agent_info": {
"id": "content-moderator-ai",
"name": " AI",
"permissions": ["review", "approve", "reject"]
}
}
```
### AI 批量操作
```http
POST /api/batch/
Content-Type: application/json
Authorization: Bearer {agent_token}
{
"operations": [
{
"method": "POST",
"path": "/api/articles/",
"body": {"title": "...", "content": "..."}
},
{
"method": "PUT",
"path": "/api/articles/1/",
"body": {"title": "..."}
},
{
"method": "DELETE",
"path": "/api/articles/2/"
}
]
}
Response:
{
"task_id": "batch-123",
"status": "processing",
"results": [...]
}
```
### AI 任务状态查询
```http
GET /api/tasks/{task_id}/
Authorization: Bearer {agent_token}
Response:
{
"id": "batch-123",
"type": "batch_operation",
"status": "completed" | "processing" | "failed",
"progress": 100,
"created_at": "2026-04-12T11:00:00Z",
"completed_at": "2026-04-12T11:05:00Z",
"result": {...},
"error": null
}
```
### AI Webhook 回调
```http
POST /api/webhooks/
Content-Type: application/json
Authorization: Bearer {agent_token}
{
"event": "article.created",
"url": "https://ai-agent.example.com/webhook",
"secret": "xxx"
}
```
## AI 操作日志
```python
# 数据库模型
class AIOperationLog(models.Model):
agent_id = models.CharField(max_length=100) # AI 代理 ID
action = models.CharField(max_length=50) # 操作类型
resource_type = models.CharField(max_length=50) # 资源类型
resource_id = models.IntegerField() # 资源 ID
status = models.CharField(max_length=20) # success/failed
confidence = models.FloatField() # AI 置信度
reasoning = models.TextField() # AI 推理过程
created_at = models.DateTimeField(auto_now_add=True)
```
## AI 权限系统
| 权限 | 说明 | 适用 AI |
|------|------|--------|
| `ai:read` | 读取数据 | 所有 AI |
| `ai:write` | 创建/更新数据 | 内容生成 AI、服务推荐 AI |
| `ai:review` | 审核内容 | 内容审核 AI |
| `ai:delete` | 删除数据 | 管理员 AI |
| `ai:batch` | 批量操作 | 所有 AI |
| `ai:analytics` | 访问分析数据 | 数据分析 AI |
## 最佳实践
### 1. AI 应该
- ✅ 使用专用的 AI 账号Service Account
- ✅ 记录所有操作的 `agent_id`
- ✅ 提供操作的置信度
- ✅ 提供操作的推理过程
- ✅ 支持人工复核
- ✅ 遵守速率限制
### 2. AI 不应该
- ❌ 使用人类用户的账号
- ❌ 隐藏 AI 身份
- ❌ 绕过审核流程
- ❌ 无限制批量操作
- ❌ 忽略错误处理
## 示例AI 自动运营流程
```python
# 1. AI 内容生成器创建文章
POST /api/articles/
{
"agent_id": "content-generator-ai",
"title": "成都美食攻略",
"region": 11,
"content": "...",
"auto_generated": true
}
# 2. AI 审核器自动审核
POST /api/articles/{id}/review/
{
"agent_id": "content-moderator-ai",
"action": "approve",
"confidence": 0.98,
"reasoning": "内容质量高,无明显问题"
}
# 3. AI 推荐器添加到首页
POST /api/featured/
{
"agent_id": "recommendation-ai",
"article_id": 123,
"reason": "热门文章,用户关注度高"
}
# 4. AI 分析器生成报告
GET /api/analytics/daily?agent=analytics-ai
```
## 配置示例
```python
# settings.py
AI_AGENTS = {
'content-moderator-ai': {
'name': '内容审核 AI',
'secret': 'xxx',
'permissions': ['review', 'approve', 'reject'],
'rate_limit': 1000, # 每小时请求数
},
'content-generator-ai': {
'name': '内容生成 AI',
'secret': 'xxx',
'permissions': ['write'],
'rate_limit': 100,
},
# ...
}
```
## 未来扩展
- [ ] 自然语言查询接口
- [ ] AI 之间的协作协议
- [ ] 多 AI 投票决策机制
- [ ] AI 操作可视化面板
- [ ] AI 性能评估系统
- [ ] AI 训练数据导出
---
**设计理念:** 城市手册不仅是给人用的,更是给 AI 用的。每一个 API、每一个功能、每一个流程都要考虑 AI 如何自动化操作。

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)

View File

@@ -45,6 +45,7 @@ INSTALLED_APPS = [
'regions',
'content',
'services',
'agents', # AI 代理系统
]
MIDDLEWARE = [

View File

@@ -9,6 +9,10 @@ from regions.views import RegionViewSet, ModeratorApplicationViewSet
from users.views import UserViewSet, UserRegistrationView
from content.views import ArticleViewSet, CommentViewSet, RatingViewSet
from services.views import FeaturedServiceViewSet
from agents.views import (
AIAgentViewSet, AIOperationLogViewSet, AITaskViewSet,
AIWebhookViewSet, batch_execute
)
router = DefaultRouter()
router.register(r'regions', RegionViewSet)
@@ -18,12 +22,19 @@ router.register(r'articles', ArticleViewSet)
router.register(r'comments', CommentViewSet)
router.register(r'ratings', RatingViewSet)
router.register(r'services', FeaturedServiceViewSet)
# AI 代理系统
router.register(r'agents', AIAgentViewSet, basename='agent')
router.register(r'agent-logs', AIOperationLogViewSet, basename='agent-log')
router.register(r'agent-tasks', AITaskViewSet, basename='agent-task')
router.register(r'agent-webhooks', AIWebhookViewSet, basename='agent-webhook')
urlpatterns = [
path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/register/', UserRegistrationView.as_view(), name='user_register'),
path('api/agents/auth/', AIAgentViewSet.as_view({'post': 'auth'}), name='agent-auth'),
path('api/batch/', batch_execute, name='batch-execute'),
path('api/', include(router.urls)),
]

View File

@@ -0,0 +1,94 @@
#!/usr/bin/env python3
"""
初始化 AI 代理 - 创建默认的 AI 代理账号
"""
import os
import sys
import django
# 设置 Django 环境
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'city_manual.settings')
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
django.setup()
from agents.models import AIAgent
def create_default_agents():
"""创建默认的 AI 代理"""
agents = [
{
'agent_id': 'content-moderator-ai',
'name': '内容审核 AI',
'description': '负责审核用户提交的文章和服务内容',
'permissions': ['read', 'review', 'approve', 'write'],
'rate_limit': 1000,
},
{
'agent_id': 'content-generator-ai',
'name': '内容生成 AI',
'description': '自动生成城市介绍、旅游攻略等内容',
'permissions': ['read', 'write'],
'rate_limit': 100,
},
{
'agent_id': 'service-curator-ai',
'name': '服务推荐 AI',
'description': '自动发现和推荐本地特色服务',
'permissions': ['read', 'write'],
'rate_limit': 100,
},
{
'agent_id': 'analytics-ai',
'name': '数据分析 AI',
'description': '分析用户行为和平台数据',
'permissions': ['read', 'analytics'],
'rate_limit': 500,
},
{
'agent_id': 'admin-ai',
'name': '管理员 AI',
'description': '全自动管理员,拥有所有权限',
'permissions': ['read', 'write', 'review', 'delete', 'batch', 'analytics'],
'rate_limit': 10000,
},
]
import uuid
for agent_data in agents:
agent, created = AIAgent.objects.get_or_create(
agent_id=agent_data['agent_id'],
defaults={
'name': agent_data['name'],
'description': agent_data['description'],
'secret_key': uuid.uuid4().hex,
'permissions': agent_data['permissions'],
'rate_limit': agent_data['rate_limit'],
}
)
if created:
print(f"✅ 创建 AI 代理:{agent.name} ({agent.agent_id})")
print(f" 密钥:{agent.secret_key}")
print(f" 权限:{', '.join(agent.permissions)}")
print()
else:
print(f"⚠️ AI 代理已存在:{agent.name}")
print(f" 密钥:{agent.secret_key}")
print()
if __name__ == '__main__':
print("=" * 60)
print("🤖 初始化 AI 代理系统")
print("=" * 60)
print()
create_default_agents()
print("=" * 60)
print("✅ AI 代理初始化完成!")
print()
print("⚠️ 请妥善保管密钥,用于 AI 代理认证")
print("=" * 60)