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:
310
city-manual/AI_AGENT.md
Normal file
310
city-manual/AI_AGENT.md
Normal 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 如何自动化操作。
|
||||
1
city-manual/backend/agents/__init__.py
Normal file
1
city-manual/backend/agents/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
default_app_config = 'agents.apps.AgentsConfig'
|
||||
107
city-manual/backend/agents/admin.py
Normal file
107
city-manual/backend/agents/admin.py
Normal 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']
|
||||
}),
|
||||
)
|
||||
7
city-manual/backend/agents/apps.py
Normal file
7
city-manual/backend/agents/apps.py
Normal 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'
|
||||
101
city-manual/backend/agents/migrations/0001_initial.py
Normal file
101
city-manual/backend/agents/migrations/0001_initial.py
Normal 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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
city-manual/backend/agents/migrations/__init__.py
Normal file
0
city-manual/backend/agents/migrations/__init__.py
Normal file
245
city-manual/backend/agents/models.py
Normal file
245
city-manual/backend/agents/models.py
Normal 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
|
||||
131
city-manual/backend/agents/serializers.py
Normal file
131
city-manual/backend/agents/serializers.py
Normal 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
|
||||
365
city-manual/backend/agents/views.py
Normal file
365
city-manual/backend/agents/views.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
@@ -45,6 +45,7 @@ INSTALLED_APPS = [
|
||||
'regions',
|
||||
'content',
|
||||
'services',
|
||||
'agents', # AI 代理系统
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
||||
@@ -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)),
|
||||
]
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
94
city-manual/backend/init_agents.py
Normal file
94
city-manual/backend/init_agents.py
Normal 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)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user