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', 'regions',
'content', 'content',
'services', 'services',
'agents', # AI 代理系统
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@@ -9,6 +9,10 @@ from regions.views import RegionViewSet, ModeratorApplicationViewSet
from users.views import UserViewSet, UserRegistrationView from users.views import UserViewSet, UserRegistrationView
from content.views import ArticleViewSet, CommentViewSet, RatingViewSet from content.views import ArticleViewSet, CommentViewSet, RatingViewSet
from services.views import FeaturedServiceViewSet from services.views import FeaturedServiceViewSet
from agents.views import (
AIAgentViewSet, AIOperationLogViewSet, AITaskViewSet,
AIWebhookViewSet, batch_execute
)
router = DefaultRouter() router = DefaultRouter()
router.register(r'regions', RegionViewSet) router.register(r'regions', RegionViewSet)
@@ -18,12 +22,19 @@ router.register(r'articles', ArticleViewSet)
router.register(r'comments', CommentViewSet) router.register(r'comments', CommentViewSet)
router.register(r'ratings', RatingViewSet) router.register(r'ratings', RatingViewSet)
router.register(r'services', FeaturedServiceViewSet) 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 = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('api/register/', UserRegistrationView.as_view(), name='user_register'), 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)), 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)