优化项目
This commit is contained in:
parent
32d09a9ac7
commit
4567e318ef
1
apps/rlhf/__init__.py
Normal file
1
apps/rlhf/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
72
apps/rlhf/admin.py
Normal file
72
apps/rlhf/admin.py
Normal file
@ -0,0 +1,72 @@
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
Conversation, Message, Feedback, FeedbackTag, DetailedFeedback,
|
||||
ConversationSubmission, ConversationEvaluation, SystemConfig
|
||||
)
|
||||
|
||||
|
||||
@admin.register(Conversation)
|
||||
class ConversationAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'user', 'is_submitted', 'created_at')
|
||||
list_filter = ('is_submitted', 'created_at')
|
||||
search_fields = ('id', 'user__username')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
|
||||
@admin.register(Message)
|
||||
class MessageAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'conversation', 'role', 'short_content', 'timestamp')
|
||||
list_filter = ('role', 'timestamp')
|
||||
search_fields = ('id', 'conversation__id', 'content')
|
||||
date_hierarchy = 'timestamp'
|
||||
|
||||
def short_content(self, obj):
|
||||
return obj.content[:50] + '...' if len(obj.content) > 50 else obj.content
|
||||
short_content.short_description = '内容'
|
||||
|
||||
|
||||
@admin.register(Feedback)
|
||||
class FeedbackAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'message', 'conversation', 'user', 'feedback_value', 'timestamp')
|
||||
list_filter = ('feedback_value', 'timestamp')
|
||||
search_fields = ('id', 'message__id', 'conversation__id', 'user__username')
|
||||
date_hierarchy = 'timestamp'
|
||||
|
||||
|
||||
@admin.register(FeedbackTag)
|
||||
class FeedbackTagAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'tag_name', 'tag_type', 'description', 'created_at')
|
||||
list_filter = ('tag_type', 'created_at')
|
||||
search_fields = ('id', 'tag_name', 'description')
|
||||
|
||||
|
||||
@admin.register(DetailedFeedback)
|
||||
class DetailedFeedbackAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'message', 'conversation', 'user', 'feedback_type', 'is_inline', 'created_at')
|
||||
list_filter = ('feedback_type', 'is_inline', 'created_at')
|
||||
search_fields = ('id', 'message__id', 'conversation__id', 'user__username', 'custom_content')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
|
||||
@admin.register(ConversationSubmission)
|
||||
class ConversationSubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'conversation', 'user', 'title', 'status', 'quality_score', 'reviewer', 'submitted_at', 'reviewed_at')
|
||||
list_filter = ('status', 'quality_score', 'submitted_at', 'reviewed_at')
|
||||
search_fields = ('id', 'conversation__id', 'user__username', 'title', 'description', 'reviewer__username')
|
||||
date_hierarchy = 'submitted_at'
|
||||
|
||||
|
||||
@admin.register(ConversationEvaluation)
|
||||
class ConversationEvaluationAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'conversation', 'user', 'has_logical_issues', 'needs_satisfied', 'created_at')
|
||||
list_filter = ('has_logical_issues', 'needs_satisfied', 'created_at')
|
||||
search_fields = ('id', 'conversation__id', 'user__username', 'overall_feeling')
|
||||
date_hierarchy = 'created_at'
|
||||
|
||||
|
||||
@admin.register(SystemConfig)
|
||||
class SystemConfigAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'config_key', 'config_value', 'config_type', 'description', 'updated_at')
|
||||
list_filter = ('config_type', 'updated_at')
|
||||
search_fields = ('id', 'config_key', 'config_value', 'description')
|
||||
date_hierarchy = 'updated_at'
|
6
apps/rlhf/apps.py
Normal file
6
apps/rlhf/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class RlhfConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'apps.rlhf'
|
1
apps/rlhf/management/__init__.py
Normal file
1
apps/rlhf/management/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
1
apps/rlhf/management/commands/__init__.py
Normal file
1
apps/rlhf/management/commands/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
55
apps/rlhf/management/commands/init_feedback_tags.py
Normal file
55
apps/rlhf/management/commands/init_feedback_tags.py
Normal file
@ -0,0 +1,55 @@
|
||||
import uuid
|
||||
from django.core.management.base import BaseCommand
|
||||
from rlhf.models import FeedbackTag
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = '初始化反馈标签'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# 正面标签
|
||||
positive_tags = [
|
||||
('有帮助', '回答对问题有实际帮助'),
|
||||
('准确', '信息准确可靠'),
|
||||
('清晰', '表达清楚易懂'),
|
||||
('完整', '回答全面完整'),
|
||||
('友好', '语调友善亲切'),
|
||||
('创新', '提供了新颖的观点')
|
||||
]
|
||||
|
||||
# 负面标签
|
||||
negative_tags = [
|
||||
('不准确', '包含错误信息'),
|
||||
('不相关', '回答偏离主题'),
|
||||
('不完整', '回答过于简略'),
|
||||
('不清晰', '表达模糊难懂'),
|
||||
('不友好', '语调生硬冷淡'),
|
||||
('重复', '内容重复冗余')
|
||||
]
|
||||
|
||||
# 插入正面标签
|
||||
for tag_name, description in positive_tags:
|
||||
FeedbackTag.objects.get_or_create(
|
||||
tag_name=tag_name,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()),
|
||||
'tag_type': 'positive',
|
||||
'description': description,
|
||||
'created_at': timezone.now()
|
||||
}
|
||||
)
|
||||
|
||||
# 插入负面标签
|
||||
for tag_name, description in negative_tags:
|
||||
FeedbackTag.objects.get_or_create(
|
||||
tag_name=tag_name,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()),
|
||||
'tag_type': 'negative',
|
||||
'description': description,
|
||||
'created_at': timezone.now()
|
||||
}
|
||||
)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS('✅ 反馈标签已添加'))
|
152
apps/rlhf/migrations/0001_initial.py
Normal file
152
apps/rlhf/migrations/0001_initial.py
Normal file
@ -0,0 +1,152 @@
|
||||
# Generated by Django 5.1.5 on 2025-06-09 08:28
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='FeedbackTag',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('tag_name', models.CharField(max_length=50, unique=True)),
|
||||
('tag_type', models.CharField(choices=[('positive', '正面'), ('negative', '负面')], max_length=20)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '反馈标签',
|
||||
'verbose_name_plural': '反馈标签',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemConfig',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('config_key', models.CharField(max_length=50, unique=True)),
|
||||
('config_value', models.TextField(blank=True, null=True)),
|
||||
('config_type', models.CharField(choices=[('string', '字符串'), ('integer', '整数'), ('float', '浮点数'), ('boolean', '布尔值'), ('json', 'JSON')], default='string', max_length=20)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '系统配置',
|
||||
'verbose_name_plural': '系统配置',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Conversation',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('is_submitted', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='conversations', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '对话',
|
||||
'verbose_name_plural': '对话',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ConversationSubmission',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('title', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('status', models.CharField(choices=[('submitted', '已提交'), ('reviewed', '已审核'), ('accepted', '已接受'), ('rejected', '已拒绝')], default='submitted', max_length=20)),
|
||||
('quality_score', models.IntegerField(blank=True, null=True)),
|
||||
('reviewer_notes', models.TextField(blank=True, null=True)),
|
||||
('submitted_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('reviewed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='rlhf.conversation')),
|
||||
('reviewer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviewed_submissions', to=settings.AUTH_USER_MODEL)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '对话提交',
|
||||
'verbose_name_plural': '对话提交',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Message',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('role', models.CharField(choices=[('user', '用户'), ('assistant', '助手'), ('system', '系统')], max_length=20)),
|
||||
('content', models.TextField()),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='rlhf.conversation')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '消息',
|
||||
'verbose_name_plural': '消息',
|
||||
'ordering': ['timestamp'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Feedback',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('feedback_value', models.IntegerField()),
|
||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='rlhf.conversation')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to=settings.AUTH_USER_MODEL)),
|
||||
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='rlhf.message')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '反馈',
|
||||
'verbose_name_plural': '反馈',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DetailedFeedback',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('feedback_type', models.CharField(choices=[('positive', '正面'), ('negative', '负面'), ('neutral', '中性')], max_length=20)),
|
||||
('feedback_tags', models.TextField(blank=True, null=True)),
|
||||
('custom_tags', models.TextField(blank=True, null=True)),
|
||||
('custom_content', models.TextField(blank=True, null=True)),
|
||||
('is_inline', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='detailed_feedback', to='rlhf.conversation')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='detailed_feedback', to=settings.AUTH_USER_MODEL)),
|
||||
('message', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='detailed_feedback', to='rlhf.message')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '详细反馈',
|
||||
'verbose_name_plural': '详细反馈',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ConversationEvaluation',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('overall_feeling', models.TextField(blank=True, null=True)),
|
||||
('has_logical_issues', models.CharField(choices=[('yes', '是'), ('no', '否'), ('unsure', '不确定')], max_length=10)),
|
||||
('needs_satisfied', models.CharField(choices=[('yes', '是'), ('no', '否'), ('partially', '部分')], max_length=10)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='evaluations', to='rlhf.conversation')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='evaluations', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '对话评估',
|
||||
'verbose_name_plural': '对话评估',
|
||||
'unique_together': {('conversation', 'user')},
|
||||
},
|
||||
),
|
||||
]
|
1
apps/rlhf/migrations/__init__.py
Normal file
1
apps/rlhf/migrations/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
|
195
apps/rlhf/models.py
Normal file
195
apps/rlhf/models.py
Normal file
@ -0,0 +1,195 @@
|
||||
from django.db import models
|
||||
import uuid
|
||||
from django.utils import timezone
|
||||
from apps.user.models import User
|
||||
|
||||
|
||||
class Conversation(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='conversations')
|
||||
is_submitted = models.BooleanField(default=False)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '对话'
|
||||
verbose_name_plural = '对话'
|
||||
|
||||
def __str__(self):
|
||||
return f"Conversation {self.id[:8]}"
|
||||
|
||||
|
||||
class Message(models.Model):
|
||||
ROLE_CHOICES = (
|
||||
('user', '用户'),
|
||||
('assistant', '助手'),
|
||||
('system', '系统'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='messages')
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES)
|
||||
content = models.TextField()
|
||||
timestamp = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '消息'
|
||||
verbose_name_plural = '消息'
|
||||
ordering = ['timestamp']
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.role}: {self.content[:50]}..."
|
||||
|
||||
|
||||
class Feedback(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='feedback')
|
||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='feedback')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='feedback')
|
||||
feedback_value = models.IntegerField()
|
||||
timestamp = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '反馈'
|
||||
verbose_name_plural = '反馈'
|
||||
|
||||
def __str__(self):
|
||||
return f"Feedback on {self.message.id[:8]}"
|
||||
|
||||
|
||||
class FeedbackTag(models.Model):
|
||||
TAG_TYPE_CHOICES = (
|
||||
('positive', '正面'),
|
||||
('negative', '负面'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
tag_name = models.CharField(max_length=50, unique=True)
|
||||
tag_type = models.CharField(max_length=20, choices=TAG_TYPE_CHOICES)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '反馈标签'
|
||||
verbose_name_plural = '反馈标签'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.tag_name} ({self.tag_type})"
|
||||
|
||||
|
||||
class DetailedFeedback(models.Model):
|
||||
FEEDBACK_TYPE_CHOICES = (
|
||||
('positive', '正面'),
|
||||
('negative', '负面'),
|
||||
('neutral', '中性'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
message = models.ForeignKey(Message, on_delete=models.CASCADE, related_name='detailed_feedback')
|
||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='detailed_feedback')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='detailed_feedback')
|
||||
feedback_type = models.CharField(max_length=20, choices=FEEDBACK_TYPE_CHOICES)
|
||||
feedback_tags = models.TextField(blank=True, null=True) # JSON格式存储多个标签
|
||||
custom_tags = models.TextField(blank=True, null=True)
|
||||
custom_content = models.TextField(blank=True, null=True)
|
||||
is_inline = models.BooleanField(default=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '详细反馈'
|
||||
verbose_name_plural = '详细反馈'
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.feedback_type} feedback on {self.message.id[:8]}"
|
||||
|
||||
|
||||
class ConversationSubmission(models.Model):
|
||||
STATUS_CHOICES = (
|
||||
('submitted', '已提交'),
|
||||
('reviewed', '已审核'),
|
||||
('accepted', '已接受'),
|
||||
('rejected', '已拒绝'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='submissions')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='submissions')
|
||||
title = models.CharField(max_length=255, blank=True, null=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='submitted')
|
||||
quality_score = models.IntegerField(null=True, blank=True)
|
||||
reviewer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviewed_submissions')
|
||||
reviewer_notes = models.TextField(blank=True, null=True)
|
||||
submitted_at = models.DateTimeField(default=timezone.now)
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '对话提交'
|
||||
verbose_name_plural = '对话提交'
|
||||
|
||||
def __str__(self):
|
||||
return f"Submission for {self.conversation.id[:8]}"
|
||||
|
||||
|
||||
class ConversationEvaluation(models.Model):
|
||||
LOGICAL_CHOICES = (
|
||||
('yes', '是'),
|
||||
('no', '否'),
|
||||
('unsure', '不确定'),
|
||||
)
|
||||
|
||||
NEEDS_CHOICES = (
|
||||
('yes', '是'),
|
||||
('no', '否'),
|
||||
('partially', '部分'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
conversation = models.ForeignKey(Conversation, on_delete=models.CASCADE, related_name='evaluations')
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='evaluations')
|
||||
overall_feeling = models.TextField(blank=True, null=True)
|
||||
has_logical_issues = models.CharField(max_length=10, choices=LOGICAL_CHOICES)
|
||||
needs_satisfied = models.CharField(max_length=10, choices=NEEDS_CHOICES)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '对话评估'
|
||||
verbose_name_plural = '对话评估'
|
||||
unique_together = ('conversation', 'user')
|
||||
|
||||
def __str__(self):
|
||||
return f"Evaluation for {self.conversation.id[:8]}"
|
||||
|
||||
|
||||
class SystemConfig(models.Model):
|
||||
CONFIG_TYPE_CHOICES = (
|
||||
('string', '字符串'),
|
||||
('integer', '整数'),
|
||||
('float', '浮点数'),
|
||||
('boolean', '布尔值'),
|
||||
('json', 'JSON'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
config_key = models.CharField(max_length=50, unique=True)
|
||||
config_value = models.TextField(blank=True, null=True)
|
||||
config_type = models.CharField(max_length=20, choices=CONFIG_TYPE_CHOICES, default='string')
|
||||
description = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
verbose_name = '系统配置'
|
||||
verbose_name_plural = '系统配置'
|
||||
|
||||
def __str__(self):
|
||||
return self.config_key
|
84
apps/rlhf/serializers.py
Normal file
84
apps/rlhf/serializers.py
Normal file
@ -0,0 +1,84 @@
|
||||
from rest_framework import serializers
|
||||
from .models import (
|
||||
Conversation, Message, Feedback, FeedbackTag, DetailedFeedback,
|
||||
ConversationSubmission, ConversationEvaluation, SystemConfig
|
||||
)
|
||||
from apps.user.serializers import UserSerializer
|
||||
|
||||
|
||||
class ConversationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Conversation
|
||||
fields = ['id', 'user', 'is_submitted', 'created_at']
|
||||
read_only_fields = ['id', 'created_at', 'user']
|
||||
|
||||
|
||||
class MessageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['id', 'conversation', 'role', 'content', 'timestamp']
|
||||
read_only_fields = ['id', 'timestamp']
|
||||
|
||||
|
||||
class FeedbackSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Feedback
|
||||
fields = ['id', 'message', 'conversation', 'user', 'feedback_value', 'timestamp']
|
||||
read_only_fields = ['id', 'timestamp']
|
||||
|
||||
|
||||
class FeedbackTagSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FeedbackTag
|
||||
fields = ['id', 'tag_name', 'tag_type', 'description', 'created_at']
|
||||
read_only_fields = ['id', 'created_at']
|
||||
|
||||
|
||||
class DetailedFeedbackSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = DetailedFeedback
|
||||
fields = ['id', 'message', 'conversation', 'user', 'feedback_type', 'feedback_tags', 'custom_tags', 'custom_content', 'is_inline', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class ConversationSubmissionSerializer(serializers.ModelSerializer):
|
||||
user_details = UserSerializer(source='user', read_only=True)
|
||||
reviewer_details = UserSerializer(source='reviewer', read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ConversationSubmission
|
||||
fields = ['id', 'conversation', 'user', 'user_details', 'title', 'description', 'status', 'quality_score', 'reviewer', 'reviewer_details', 'reviewer_notes', 'submitted_at', 'reviewed_at', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'submitted_at', 'reviewed_at', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class ConversationEvaluationSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ConversationEvaluation
|
||||
fields = ['id', 'conversation', 'user', 'overall_feeling', 'has_logical_issues', 'needs_satisfied', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class SystemConfigSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SystemConfig
|
||||
fields = ['id', 'config_key', 'config_value', 'config_type', 'description', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
|
||||
class ConversationWithMessagesSerializer(serializers.ModelSerializer):
|
||||
messages = MessageSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Conversation
|
||||
fields = ['id', 'user', 'is_submitted', 'created_at', 'messages']
|
||||
read_only_fields = ['id', 'created_at']
|
||||
|
||||
|
||||
class MessageWithFeedbackSerializer(serializers.ModelSerializer):
|
||||
feedback = FeedbackSerializer(many=True, read_only=True)
|
||||
detailed_feedback = DetailedFeedbackSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Message
|
||||
fields = ['id', 'conversation', 'role', 'content', 'timestamp', 'feedback', 'detailed_feedback']
|
||||
read_only_fields = ['id', 'timestamp']
|
307
apps/rlhf/siliconflow_client.py
Normal file
307
apps/rlhf/siliconflow_client.py
Normal file
@ -0,0 +1,307 @@
|
||||
import requests
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class SiliconFlowClient:
|
||||
def __init__(self, api_key="sk-xqbujijjqqmlmlvkhvxeogqjtzslnhdtqxqgiyuhwpoqcjvf", model="Qwen/QwQ-32B"):
|
||||
"""
|
||||
初始化SiliconFlow客户端
|
||||
"""
|
||||
self.api_key = api_key
|
||||
self.model = model
|
||||
self.base_url = "https://api.siliconflow.cn/v1"
|
||||
self.messages = []
|
||||
self.system_message = None
|
||||
|
||||
logger.info(f"初始化SiliconFlow客户端 - 模型: {model}")
|
||||
|
||||
def set_model(self, model):
|
||||
"""设置使用的模型"""
|
||||
self.model = model
|
||||
logger.info(f"SiliconFlow切换模型: {model}")
|
||||
|
||||
def set_system_message(self, message):
|
||||
"""设置系统消息"""
|
||||
self.system_message = message
|
||||
self.messages = []
|
||||
if message:
|
||||
self.messages.append({"role": "system", "content": message})
|
||||
logger.debug(f"SiliconFlow设置系统消息 - 长度: {len(message) if message else 0}")
|
||||
|
||||
def add_message(self, role, content):
|
||||
"""添加消息到对话历史"""
|
||||
self.messages.append({"role": role, "content": content})
|
||||
|
||||
def clear_history(self):
|
||||
"""清空对话历史"""
|
||||
self.messages = []
|
||||
if self.system_message:
|
||||
self.messages.append({"role": "system", "content": self.system_message})
|
||||
logger.debug("SiliconFlow清空对话历史")
|
||||
|
||||
def chat(self, message):
|
||||
"""
|
||||
非流式对话
|
||||
"""
|
||||
try:
|
||||
# 添加用户消息
|
||||
self.add_message("user", message)
|
||||
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": self.messages,
|
||||
"stream": False,
|
||||
"max_tokens": 2048,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.7,
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{self.base_url}/chat/completions",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
assistant_message = data['choices'][0]['message']['content']
|
||||
self.add_message("assistant", assistant_message)
|
||||
return assistant_message
|
||||
else:
|
||||
error_msg = f"SiliconFlow API错误: {response.status_code} - {response.text}"
|
||||
logger.error(error_msg)
|
||||
return f"API调用失败: {error_msg}"
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"SiliconFlow对话出错: {str(e)}"
|
||||
logger.exception("SiliconFlow对话异常")
|
||||
return error_msg
|
||||
|
||||
def chat_stream(self, message):
|
||||
"""
|
||||
流式对话
|
||||
"""
|
||||
try:
|
||||
# 添加用户消息
|
||||
self.add_message("user", message)
|
||||
|
||||
payload = {
|
||||
"model": self.model,
|
||||
"messages": self.messages,
|
||||
"stream": True,
|
||||
"max_tokens": 2048,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.7,
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.api_key}",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{self.base_url}/chat/completions",
|
||||
json=payload,
|
||||
headers=headers,
|
||||
stream=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_msg = f"SiliconFlow API错误: {response.status_code} - {response.text}"
|
||||
logger.error(error_msg)
|
||||
yield f"API调用失败: {error_msg}"
|
||||
return
|
||||
|
||||
assistant_message = ""
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
line = line.decode('utf-8')
|
||||
if line.startswith('data: '):
|
||||
data_str = line[6:] # 移除 'data: ' 前缀
|
||||
|
||||
if data_str.strip() == '[DONE]':
|
||||
break
|
||||
|
||||
try:
|
||||
data = json.loads(data_str)
|
||||
if 'choices' in data and len(data['choices']) > 0:
|
||||
delta = data['choices'][0].get('delta', {})
|
||||
content = delta.get('content', '')
|
||||
|
||||
if content:
|
||||
assistant_message += content
|
||||
yield content
|
||||
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
# 添加完整的助手回复到历史
|
||||
if assistant_message:
|
||||
self.add_message("assistant", assistant_message)
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
error_msg = "SiliconFlow请求超时"
|
||||
logger.error(error_msg)
|
||||
yield error_msg
|
||||
except Exception as e:
|
||||
error_msg = f"SiliconFlow流式对话出错: {str(e)}"
|
||||
logger.exception("SiliconFlow流式对话异常")
|
||||
yield error_msg
|
||||
|
||||
@classmethod
|
||||
def get_available_models(cls, api_key="sk-xqbujijjqqmlmlvkhvxeogqjtzslnhdtqxqgiyuhwpoqcjvf"):
|
||||
"""
|
||||
获取可用的模型列表
|
||||
"""
|
||||
import os
|
||||
|
||||
# 尝试多种网络配置
|
||||
proxy_configs = [
|
||||
# 不使用代理
|
||||
{'proxies': None, 'verify': True},
|
||||
# 使用系统代理但禁用SSL验证
|
||||
{'proxies': None, 'verify': False},
|
||||
# 明确禁用代理
|
||||
{'proxies': {'http': None, 'https': None}, 'verify': True},
|
||||
{'proxies': {'http': None, 'https': None}, 'verify': False},
|
||||
]
|
||||
|
||||
for i, config in enumerate(proxy_configs):
|
||||
try:
|
||||
logger.info(f"尝试网络配置 {i+1}/{len(proxy_configs)}")
|
||||
|
||||
headers = {"Authorization": f"Bearer {api_key}"}
|
||||
|
||||
# 创建会话以便更好地控制连接
|
||||
session = requests.Session()
|
||||
if config['proxies'] is not None:
|
||||
session.proxies.update(config['proxies'])
|
||||
|
||||
response = session.get(
|
||||
"https://api.siliconflow.cn/v1/models",
|
||||
headers=headers,
|
||||
timeout=30,
|
||||
verify=config['verify']
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
all_models = data.get('data', [])
|
||||
logger.info(f"SiliconFlow API返回 {len(all_models)} 个模型")
|
||||
|
||||
models = []
|
||||
excluded_keywords = ['embedding', 'stable-diffusion', 'bge-', 'rerank', 'whisper']
|
||||
|
||||
for model in all_models:
|
||||
model_id = model.get('id', '')
|
||||
if not model_id:
|
||||
continue
|
||||
|
||||
# 排除明显的非聊天模型
|
||||
if any(keyword in model_id.lower() for keyword in excluded_keywords):
|
||||
continue
|
||||
|
||||
# 包含常见的聊天模型关键词或者包含chat、instruct等
|
||||
chat_keywords = ['chat', 'instruct', 'qwen', 'glm', 'internlm', 'baichuan', 'llama', 'mistral', 'claude', 'gpt', 'yi']
|
||||
if any(keyword in model_id.lower() for keyword in chat_keywords):
|
||||
models.append({
|
||||
'id': model_id,
|
||||
'name': model_id,
|
||||
'description': model.get('description', ''),
|
||||
})
|
||||
|
||||
logger.info(f"获取SiliconFlow模型列表成功 - 总数: {len(all_models)}, 聊天模型: {len(models)}")
|
||||
|
||||
# 如果过滤后的模型太少,返回所有模型(除了明确的排除项)
|
||||
if len(models) < 10:
|
||||
logger.warning("过滤后的聊天模型数量过少,返回所有非排除模型")
|
||||
models = []
|
||||
for model in all_models:
|
||||
model_id = model.get('id', '')
|
||||
if model_id and not any(keyword in model_id.lower() for keyword in excluded_keywords):
|
||||
models.append({
|
||||
'id': model_id,
|
||||
'name': model_id,
|
||||
'description': model.get('description', ''),
|
||||
})
|
||||
|
||||
return models
|
||||
else:
|
||||
logger.warning(f"网络配置 {i+1} 失败: {response.status_code} - {response.text}")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"网络配置 {i+1} 异常: {str(e)}")
|
||||
continue
|
||||
|
||||
# 所有配置都失败了
|
||||
logger.error("所有网络配置都失败,无法获取模型列表")
|
||||
raise Exception("无法连接到SiliconFlow API服务器")
|
||||
|
||||
@classmethod
|
||||
def _get_fallback_models(cls):
|
||||
"""
|
||||
当API调用失败时,返回预定义的常用模型列表
|
||||
"""
|
||||
logger.info("使用预定义的模型列表作为备选方案")
|
||||
return [
|
||||
{
|
||||
'id': 'Qwen/Qwen2.5-7B-Instruct',
|
||||
'name': 'Qwen2.5-7B-Instruct',
|
||||
'description': '通义千问2.5 7B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'Qwen/Qwen2.5-14B-Instruct',
|
||||
'name': 'Qwen2.5-14B-Instruct',
|
||||
'description': '通义千问2.5 14B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'Qwen/Qwen2.5-32B-Instruct',
|
||||
'name': 'Qwen2.5-32B-Instruct',
|
||||
'description': '通义千问2.5 32B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'Qwen/Qwen2.5-72B-Instruct',
|
||||
'name': 'Qwen2.5-72B-Instruct',
|
||||
'description': '通义千问2.5 72B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'Qwen/QwQ-32B-Preview',
|
||||
'name': 'QwQ-32B-Preview',
|
||||
'description': '通义千问推理模型预览版'
|
||||
},
|
||||
{
|
||||
'id': 'deepseek-ai/DeepSeek-V2.5',
|
||||
'name': 'DeepSeek-V2.5',
|
||||
'description': 'DeepSeek V2.5 模型'
|
||||
},
|
||||
{
|
||||
'id': 'meta-llama/Llama-3.1-8B-Instruct',
|
||||
'name': 'Llama-3.1-8B-Instruct',
|
||||
'description': 'Meta Llama 3.1 8B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'meta-llama/Llama-3.1-70B-Instruct',
|
||||
'name': 'Llama-3.1-70B-Instruct',
|
||||
'description': 'Meta Llama 3.1 70B指令模型'
|
||||
},
|
||||
{
|
||||
'id': 'THUDM/glm-4-9b-chat',
|
||||
'name': 'GLM-4-9B-Chat',
|
||||
'description': '智谱GLM-4 9B对话模型'
|
||||
},
|
||||
{
|
||||
'id': 'internlm/internlm2_5-7b-chat',
|
||||
'name': 'InternLM2.5-7B-Chat',
|
||||
'description': '书生浦语2.5 7B对话模型'
|
||||
}
|
||||
]
|
29
apps/rlhf/urls.py
Normal file
29
apps/rlhf/urls.py
Normal file
@ -0,0 +1,29 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import (
|
||||
ConversationViewSet, MessageViewSet, FeedbackViewSet,
|
||||
FeedbackTagViewSet, DetailedFeedbackViewSet, ConversationSubmissionViewSet,
|
||||
ConversationEvaluationViewSet, SystemConfigViewSet
|
||||
)
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'conversations', ConversationViewSet)
|
||||
router.register(r'messages', MessageViewSet)
|
||||
router.register(r'feedback', FeedbackViewSet)
|
||||
router.register(r'feedback-tags', FeedbackTagViewSet)
|
||||
router.register(r'detailed-feedback', DetailedFeedbackViewSet)
|
||||
router.register(r'submissions', ConversationSubmissionViewSet)
|
||||
router.register(r'evaluations', ConversationEvaluationViewSet)
|
||||
router.register(r'system-config', SystemConfigViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
# 额外的RLHF相关API端点
|
||||
path('conversation/<str:pk>/messages/', ConversationViewSet.as_view({'get': 'messages'}), name='conversation-messages'),
|
||||
path('conversation/<str:pk>/message/', ConversationViewSet.as_view({'post': 'message'}), name='send-message'),
|
||||
path('conversation/<str:pk>/submit/', ConversationViewSet.as_view({'post': 'submit'}), name='submit-conversation'),
|
||||
path('conversation/<str:pk>/resume/', ConversationViewSet.as_view({'post': 'resume'}), name='resume-conversation'),
|
||||
path('submission/<str:pk>/review/', ConversationSubmissionViewSet.as_view({'post': 'review'}), name='review-submission'),
|
||||
path('models/', SystemConfigViewSet.as_view({'get': 'models'}), name='models-list'),
|
||||
path('model/', SystemConfigViewSet.as_view({'get': 'model', 'post': 'model'}), name='current-model'),
|
||||
]
|
559
apps/rlhf/views.py
Normal file
559
apps/rlhf/views.py
Normal file
@ -0,0 +1,559 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from rest_framework import viewsets, status
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from .models import (
|
||||
Conversation, Message, Feedback, FeedbackTag, DetailedFeedback,
|
||||
ConversationSubmission, ConversationEvaluation, SystemConfig
|
||||
)
|
||||
from .serializers import (
|
||||
ConversationSerializer, MessageSerializer, FeedbackSerializer,
|
||||
FeedbackTagSerializer, DetailedFeedbackSerializer, ConversationSubmissionSerializer,
|
||||
ConversationEvaluationSerializer, SystemConfigSerializer
|
||||
)
|
||||
from apps.user.models import User, UserActivityLog, AnnotationStats
|
||||
from django.utils import timezone
|
||||
import uuid
|
||||
import json
|
||||
from django.db.models import Count, Avg, Sum, Q, F
|
||||
from datetime import datetime, timedelta
|
||||
from django.db import transaction
|
||||
from django.db.models.functions import TruncDate
|
||||
from apps.user.authentication import CustomTokenAuthentication
|
||||
|
||||
class ConversationViewSet(viewsets.ModelViewSet):
|
||||
queryset = Conversation.objects.all()
|
||||
serializer_class = ConversationSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return Conversation.objects.filter(user=user).order_by('-created_at')
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
@action(detail=True, methods=['get'])
|
||||
def messages(self, request, pk=None):
|
||||
conversation = self.get_object()
|
||||
messages = Message.objects.filter(conversation=conversation).order_by('timestamp')
|
||||
serializer = MessageSerializer(messages, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def message(self, request, pk=None):
|
||||
conversation = self.get_object()
|
||||
content = request.data.get('content')
|
||||
|
||||
if not content:
|
||||
return Response({'error': '消息内容不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 创建用户消息
|
||||
user_message = Message.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
conversation=conversation,
|
||||
role='user',
|
||||
content=content
|
||||
)
|
||||
|
||||
# 这里需要调用AI服务获取回复
|
||||
# 示例:调用SiliconFlow或其他AI服务
|
||||
ai_response = self._generate_ai_response(user_message.content, conversation)
|
||||
|
||||
# 创建AI回复消息
|
||||
ai_message = Message.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
conversation=conversation,
|
||||
role='assistant',
|
||||
content=ai_response
|
||||
)
|
||||
|
||||
# 更新用户的标注统计
|
||||
self._update_annotation_stats(request.user.id)
|
||||
|
||||
messages = [
|
||||
MessageSerializer(user_message).data,
|
||||
MessageSerializer(ai_message).data
|
||||
]
|
||||
|
||||
return Response(messages)
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def submit(self, request, pk=None):
|
||||
conversation = self.get_object()
|
||||
title = request.data.get('title', '')
|
||||
description = request.data.get('description', '')
|
||||
|
||||
if conversation.is_submitted:
|
||||
return Response({'error': '该对话已提交'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 更新对话为已提交状态
|
||||
conversation.is_submitted = True
|
||||
conversation.save()
|
||||
|
||||
# 创建提交记录
|
||||
submission = ConversationSubmission.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
conversation=conversation,
|
||||
user=request.user,
|
||||
title=title,
|
||||
description=description,
|
||||
status='submitted',
|
||||
submitted_at=timezone.now()
|
||||
)
|
||||
|
||||
# 记录活动日志
|
||||
UserActivityLog.objects.create(
|
||||
user=request.user,
|
||||
action_type='conversation_submit',
|
||||
target_type='conversation',
|
||||
target_id=str(conversation.id),
|
||||
details={'title': title}
|
||||
)
|
||||
|
||||
return Response({
|
||||
'message': '对话提交成功',
|
||||
'submission_id': submission.id
|
||||
})
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def resume(self, request, pk=None):
|
||||
conversation = self.get_object()
|
||||
|
||||
if not conversation.is_submitted:
|
||||
return Response({'error': '该对话未提交,无需恢复'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 更新对话为未提交状态
|
||||
conversation.is_submitted = False
|
||||
conversation.save()
|
||||
|
||||
# 获取最新的提交记录
|
||||
submission = ConversationSubmission.objects.filter(
|
||||
conversation=conversation
|
||||
).order_by('-submitted_at').first()
|
||||
|
||||
if submission and submission.status == 'submitted':
|
||||
submission.status = 'rejected'
|
||||
submission.save()
|
||||
|
||||
# 记录活动日志
|
||||
UserActivityLog.objects.create(
|
||||
user=request.user,
|
||||
action_type='conversation_resume',
|
||||
target_type='conversation',
|
||||
target_id=str(conversation.id)
|
||||
)
|
||||
|
||||
return Response({'message': '对话已恢复为未提交状态'})
|
||||
|
||||
def _generate_ai_response(self, user_message, conversation):
|
||||
"""
|
||||
生成AI回复
|
||||
这里只是一个示例,实际应用中需要对接真实的AI服务
|
||||
"""
|
||||
# 从系统配置获取当前使用的模型
|
||||
model_config = SystemConfig.objects.filter(config_key='current_model').first()
|
||||
model_name = model_config.config_value if model_config else "默认模型"
|
||||
|
||||
# 获取历史消息作为上下文
|
||||
history_messages = Message.objects.filter(conversation=conversation).order_by('timestamp')
|
||||
history = []
|
||||
for msg in history_messages:
|
||||
history.append({"role": msg.role, "content": msg.content})
|
||||
|
||||
# 在这里调用实际的AI API
|
||||
# 例如,如果使用SiliconFlow API
|
||||
# response = sf_client.chat(user_message, history)
|
||||
|
||||
# 这里仅作为示例,返回一个固定的回复
|
||||
return f"这是AI({model_name})的回复:我已收到您的消息「{user_message}」。根据您的问题,我的建议是..."
|
||||
|
||||
def _update_annotation_stats(self, user_id):
|
||||
"""更新用户的标注统计信息"""
|
||||
today = timezone.now().date()
|
||||
|
||||
# 获取或创建今天的统计记录
|
||||
stats, created = AnnotationStats.objects.get_or_create(
|
||||
user_id=user_id,
|
||||
date=today,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()),
|
||||
'total_annotations': 0,
|
||||
'positive_annotations': 0,
|
||||
'negative_annotations': 0,
|
||||
'conversations_count': 0,
|
||||
'messages_count': 0
|
||||
}
|
||||
)
|
||||
|
||||
# 更新消息计数
|
||||
stats.messages_count += 1
|
||||
stats.save()
|
||||
|
||||
|
||||
class MessageViewSet(viewsets.ModelViewSet):
|
||||
queryset = Message.objects.all()
|
||||
serializer_class = MessageSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return Message.objects.filter(conversation__user=user).order_by('timestamp')
|
||||
|
||||
|
||||
class FeedbackViewSet(viewsets.ModelViewSet):
|
||||
queryset = Feedback.objects.all()
|
||||
serializer_class = FeedbackSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return Feedback.objects.filter(user=user).order_by('-timestamp')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
message_id = request.data.get('message_id')
|
||||
conversation_id = request.data.get('conversation_id')
|
||||
feedback_value = request.data.get('feedback_value')
|
||||
|
||||
if not message_id or not conversation_id:
|
||||
return Response({'error': '消息ID和对话ID不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
message = Message.objects.get(id=message_id)
|
||||
conversation = Conversation.objects.get(id=conversation_id)
|
||||
except (Message.DoesNotExist, Conversation.DoesNotExist):
|
||||
return Response({'error': '消息或对话不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 创建或更新反馈
|
||||
feedback, created = Feedback.objects.update_or_create(
|
||||
message_id=message_id,
|
||||
conversation_id=conversation_id,
|
||||
user=request.user,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()) if created else F('id'),
|
||||
'feedback_value': feedback_value,
|
||||
'timestamp': timezone.now()
|
||||
}
|
||||
)
|
||||
|
||||
# 更新用户的标注统计
|
||||
self._update_annotation_stats(request.user.id, feedback_value)
|
||||
|
||||
return Response(FeedbackSerializer(feedback).data)
|
||||
|
||||
def _update_annotation_stats(self, user_id, feedback_value):
|
||||
"""更新用户的标注统计信息"""
|
||||
today = timezone.now().date()
|
||||
|
||||
# 获取或创建今天的统计记录
|
||||
stats, created = AnnotationStats.objects.get_or_create(
|
||||
user_id=user_id,
|
||||
date=today,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()),
|
||||
'total_annotations': 0,
|
||||
'positive_annotations': 0,
|
||||
'negative_annotations': 0,
|
||||
'conversations_count': 0,
|
||||
'messages_count': 0
|
||||
}
|
||||
)
|
||||
|
||||
# 更新统计
|
||||
stats.total_annotations += 1
|
||||
if feedback_value > 0:
|
||||
stats.positive_annotations += 1
|
||||
elif feedback_value < 0:
|
||||
stats.negative_annotations += 1
|
||||
|
||||
stats.save()
|
||||
|
||||
|
||||
class FeedbackTagViewSet(viewsets.ModelViewSet):
|
||||
queryset = FeedbackTag.objects.all()
|
||||
serializer_class = FeedbackTagSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
tag_type = self.request.query_params.get('type')
|
||||
if tag_type and tag_type in ['positive', 'negative']:
|
||||
return FeedbackTag.objects.filter(tag_type=tag_type)
|
||||
return FeedbackTag.objects.all()
|
||||
|
||||
|
||||
class DetailedFeedbackViewSet(viewsets.ModelViewSet):
|
||||
queryset = DetailedFeedback.objects.all()
|
||||
serializer_class = DetailedFeedbackSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return DetailedFeedback.objects.filter(user=user).order_by('-created_at')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
message_id = request.data.get('message_id')
|
||||
conversation_id = request.data.get('conversation_id')
|
||||
feedback_type = request.data.get('feedback_type')
|
||||
feedback_tags = request.data.get('feedback_tags', [])
|
||||
custom_tags = request.data.get('custom_tags', '')
|
||||
custom_content = request.data.get('custom_content', '')
|
||||
is_inline = request.data.get('is_inline', True)
|
||||
|
||||
if not message_id or not conversation_id or not feedback_type:
|
||||
return Response({
|
||||
'error': '消息ID、对话ID和反馈类型不能为空'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
message = Message.objects.get(id=message_id)
|
||||
conversation = Conversation.objects.get(id=conversation_id)
|
||||
except (Message.DoesNotExist, Conversation.DoesNotExist):
|
||||
return Response({'error': '消息或对话不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 将标签列表转换为JSON字符串
|
||||
if isinstance(feedback_tags, list):
|
||||
feedback_tags = json.dumps(feedback_tags)
|
||||
|
||||
# 创建详细反馈
|
||||
detailed_feedback = DetailedFeedback.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
message=message,
|
||||
conversation=conversation,
|
||||
user=request.user,
|
||||
feedback_type=feedback_type,
|
||||
feedback_tags=feedback_tags,
|
||||
custom_tags=custom_tags,
|
||||
custom_content=custom_content,
|
||||
is_inline=is_inline,
|
||||
created_at=timezone.now(),
|
||||
updated_at=timezone.now()
|
||||
)
|
||||
|
||||
# 记录活动日志
|
||||
UserActivityLog.objects.create(
|
||||
user=request.user,
|
||||
action_type='detailed_feedback_submit',
|
||||
target_type='message',
|
||||
target_id=message_id,
|
||||
details={
|
||||
'feedback_type': feedback_type,
|
||||
'is_inline': is_inline
|
||||
}
|
||||
)
|
||||
|
||||
# 更新用户的标注统计
|
||||
self._update_annotation_stats(request.user.id, feedback_type)
|
||||
|
||||
return Response(DetailedFeedbackSerializer(detailed_feedback).data)
|
||||
|
||||
def _update_annotation_stats(self, user_id, feedback_type):
|
||||
"""更新用户的标注统计信息"""
|
||||
today = timezone.now().date()
|
||||
|
||||
# 获取或创建今天的统计记录
|
||||
stats, created = AnnotationStats.objects.get_or_create(
|
||||
user_id=user_id,
|
||||
date=today,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()),
|
||||
'total_annotations': 0,
|
||||
'positive_annotations': 0,
|
||||
'negative_annotations': 0,
|
||||
'conversations_count': 0,
|
||||
'messages_count': 0
|
||||
}
|
||||
)
|
||||
|
||||
# 更新统计
|
||||
stats.total_annotations += 1
|
||||
if feedback_type == 'positive':
|
||||
stats.positive_annotations += 1
|
||||
elif feedback_type == 'negative':
|
||||
stats.negative_annotations += 1
|
||||
|
||||
stats.save()
|
||||
|
||||
|
||||
class ConversationSubmissionViewSet(viewsets.ModelViewSet):
|
||||
queryset = ConversationSubmission.objects.all()
|
||||
serializer_class = ConversationSubmissionSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
|
||||
# 管理员可以查看所有提交
|
||||
if user.role == 'admin':
|
||||
queryset = ConversationSubmission.objects.all()
|
||||
else:
|
||||
# 普通用户只能查看自己的提交
|
||||
queryset = ConversationSubmission.objects.filter(user=user)
|
||||
|
||||
# 过滤状态
|
||||
status_filter = self.request.query_params.get('status')
|
||||
if status_filter:
|
||||
queryset = queryset.filter(status=status_filter)
|
||||
|
||||
return queryset.order_by('-submitted_at')
|
||||
|
||||
@action(detail=True, methods=['post'])
|
||||
def review(self, request, pk=None):
|
||||
if request.user.role != 'admin':
|
||||
return Response({'error': '只有管理员可以进行审核'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
submission = self.get_object()
|
||||
status_value = request.data.get('status')
|
||||
quality_score = request.data.get('quality_score')
|
||||
reviewer_notes = request.data.get('reviewer_notes', '')
|
||||
|
||||
if not status_value or status_value not in ['accepted', 'rejected']:
|
||||
return Response({'error': '状态值无效'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
if quality_score is not None and (quality_score < 1 or quality_score > 5):
|
||||
return Response({'error': '质量分数必须在1-5之间'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 更新提交状态
|
||||
submission.status = status_value
|
||||
submission.quality_score = quality_score
|
||||
submission.reviewer = request.user
|
||||
submission.reviewer_notes = reviewer_notes
|
||||
submission.reviewed_at = timezone.now()
|
||||
submission.save()
|
||||
|
||||
# 记录活动日志
|
||||
UserActivityLog.objects.create(
|
||||
user=request.user,
|
||||
action_type='submission_review',
|
||||
target_type='submission',
|
||||
target_id=submission.id,
|
||||
details={
|
||||
'status': status_value,
|
||||
'quality_score': quality_score
|
||||
}
|
||||
)
|
||||
|
||||
return Response({
|
||||
'message': '审核完成',
|
||||
'submission': ConversationSubmissionSerializer(submission).data
|
||||
})
|
||||
|
||||
|
||||
class ConversationEvaluationViewSet(viewsets.ModelViewSet):
|
||||
queryset = ConversationEvaluation.objects.all()
|
||||
serializer_class = ConversationEvaluationSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
return ConversationEvaluation.objects.filter(user=user).order_by('-created_at')
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
conversation_id = request.data.get('conversation_id')
|
||||
overall_feeling = request.data.get('overall_feeling', '')
|
||||
has_logical_issues = request.data.get('has_logical_issues')
|
||||
needs_satisfied = request.data.get('needs_satisfied')
|
||||
|
||||
if not conversation_id or not has_logical_issues or not needs_satisfied:
|
||||
return Response({
|
||||
'error': '对话ID、逻辑问题和需求满足度不能为空'
|
||||
}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
conversation = Conversation.objects.get(id=conversation_id)
|
||||
except Conversation.DoesNotExist:
|
||||
return Response({'error': '对话不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 创建或更新评估
|
||||
evaluation, created = ConversationEvaluation.objects.update_or_create(
|
||||
conversation_id=conversation_id,
|
||||
user=request.user,
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()) if created else F('id'),
|
||||
'overall_feeling': overall_feeling,
|
||||
'has_logical_issues': has_logical_issues,
|
||||
'needs_satisfied': needs_satisfied,
|
||||
'updated_at': timezone.now()
|
||||
}
|
||||
)
|
||||
|
||||
# 记录活动日志
|
||||
UserActivityLog.objects.create(
|
||||
user=request.user,
|
||||
action_type='conversation_evaluation',
|
||||
target_type='conversation',
|
||||
target_id=conversation_id,
|
||||
details={
|
||||
'has_logical_issues': has_logical_issues,
|
||||
'needs_satisfied': needs_satisfied
|
||||
}
|
||||
)
|
||||
|
||||
return Response(ConversationEvaluationSerializer(evaluation).data)
|
||||
|
||||
|
||||
class SystemConfigViewSet(viewsets.ModelViewSet):
|
||||
queryset = SystemConfig.objects.all()
|
||||
serializer_class = SystemConfigSerializer
|
||||
authentication_classes = [CustomTokenAuthentication]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
# 只有管理员可以查看所有配置
|
||||
if self.request.user.role == 'admin':
|
||||
return SystemConfig.objects.all()
|
||||
# 其他用户只能查看公开配置
|
||||
return SystemConfig.objects.filter(config_key__startswith='public_')
|
||||
|
||||
@action(detail=False, methods=['get', 'post'])
|
||||
def model(self, request):
|
||||
if request.method == 'GET':
|
||||
# 获取当前模型
|
||||
model_config = SystemConfig.objects.filter(config_key='current_model').first()
|
||||
if model_config:
|
||||
return Response({'model': model_config.config_value})
|
||||
return Response({'model': '默认模型'})
|
||||
|
||||
elif request.method == 'POST':
|
||||
# 设置当前模型
|
||||
if request.user.role != 'admin':
|
||||
return Response({'error': '只有管理员可以更改模型'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
model_name = request.data.get('model')
|
||||
if not model_name:
|
||||
return Response({'error': '模型名称不能为空'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 更新或创建配置
|
||||
config, created = SystemConfig.objects.update_or_create(
|
||||
config_key='current_model',
|
||||
defaults={
|
||||
'id': str(uuid.uuid4()) if created else F('id'),
|
||||
'config_value': model_name,
|
||||
'config_type': 'string',
|
||||
'description': '当前使用的AI模型',
|
||||
'updated_at': timezone.now()
|
||||
}
|
||||
)
|
||||
|
||||
return Response({'model': model_name})
|
||||
|
||||
@action(detail=False, methods=['get'])
|
||||
def models(self, request):
|
||||
# 返回可用的模型列表
|
||||
return Response({
|
||||
'models': [
|
||||
{'id': 'model1', 'name': 'GPT-3.5'},
|
||||
{'id': 'model2', 'name': 'GPT-4'},
|
||||
{'id': 'model3', 'name': 'Claude'},
|
||||
{'id': 'model4', 'name': 'LLaMA'},
|
||||
{'id': 'model5', 'name': 'Qwen'}
|
||||
]
|
||||
})
|
@ -0,0 +1,155 @@
|
||||
# Generated by Django 5.1.5 on 2025-06-09 08:28
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
import uuid
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
('user', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AnnotationQuality',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('message_id', models.CharField(max_length=36)),
|
||||
('quality_score', models.FloatField(default=0.0)),
|
||||
('consistency_score', models.FloatField(default=0.0)),
|
||||
('review_status', models.CharField(choices=[('pending', '待审核'), ('approved', '已通过'), ('rejected', '已拒绝')], default='pending', max_length=20)),
|
||||
('review_notes', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('reviewed_at', models.DateTimeField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '标注质量',
|
||||
'verbose_name_plural': '标注质量',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AnnotationStats',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('date', models.DateField()),
|
||||
('total_annotations', models.IntegerField(default=0)),
|
||||
('positive_annotations', models.IntegerField(default=0)),
|
||||
('negative_annotations', models.IntegerField(default=0)),
|
||||
('conversations_count', models.IntegerField(default=0)),
|
||||
('messages_count', models.IntegerField(default=0)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('updated_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '标注统计',
|
||||
'verbose_name_plural': '标注统计',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserActivityLog',
|
||||
fields=[
|
||||
('id', models.CharField(default=uuid.uuid4, editable=False, max_length=36, primary_key=True, serialize=False)),
|
||||
('action_type', models.CharField(max_length=50)),
|
||||
('target_type', models.CharField(blank=True, max_length=50, null=True)),
|
||||
('target_id', models.CharField(blank=True, max_length=36, null=True)),
|
||||
('details', models.TextField(blank=True, null=True)),
|
||||
('ip_address', models.CharField(blank=True, max_length=45, null=True)),
|
||||
('user_agent', models.TextField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': '用户活动日志',
|
||||
'verbose_name_plural': '用户活动日志',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='full_name',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='groups',
|
||||
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='profile_image',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='role',
|
||||
field=models.CharField(choices=[('user', '普通用户'), ('annotator', '标注员'), ('admin', '管理员')], default='annotator', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='user_permissions',
|
||||
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='username',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, unique=True, verbose_name='用户名'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usertoken',
|
||||
name='ip_address',
|
||||
field=models.CharField(blank=True, max_length=100, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usertoken',
|
||||
name='last_accessed',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usertoken',
|
||||
name='user_agent',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['username'], name='users_usernam_baeb4b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['email'], name='users_email_4b85f2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='usertoken',
|
||||
index=models.Index(fields=['token'], name='user_token_token_deff65_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='usertoken',
|
||||
index=models.Index(fields=['user'], name='user_token_user_id_209416_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='annotationquality',
|
||||
name='reviewer',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='reviews', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='annotationquality',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotation_quality', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='annotationstats',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='annotation_stats', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='useractivitylog',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity_logs', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='annotationstats',
|
||||
unique_together={('user', 'date')},
|
||||
),
|
||||
]
|
@ -1,7 +1,8 @@
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
|
||||
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
||||
import uuid
|
||||
|
||||
class UserManager(BaseUserManager):
|
||||
def create_user(self, email, password=None, **extra_fields):
|
||||
@ -17,6 +18,7 @@ class UserManager(BaseUserManager):
|
||||
def create_superuser(self, email, password=None, **extra_fields):
|
||||
extra_fields.setdefault('is_staff', True)
|
||||
extra_fields.setdefault('is_superuser', True)
|
||||
extra_fields.setdefault('role', 'admin')
|
||||
|
||||
if extra_fields.get('is_staff') is not True:
|
||||
raise ValueError('超级用户必须设置is_staff=True')
|
||||
@ -25,12 +27,22 @@ class UserManager(BaseUserManager):
|
||||
|
||||
return self.create_user(email, password, **extra_fields)
|
||||
|
||||
class User(AbstractBaseUser):
|
||||
class User(AbstractBaseUser, PermissionsMixin):
|
||||
"""用户模型,用于登录和账户管理"""
|
||||
ROLE_CHOICES = (
|
||||
('user', '普通用户'),
|
||||
('annotator', '标注员'),
|
||||
('admin', '管理员'),
|
||||
)
|
||||
|
||||
email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱")
|
||||
password = models.CharField(max_length=255, verbose_name="密码")
|
||||
username = models.CharField(max_length=255, unique=True, null=True, blank=True, verbose_name="用户名")
|
||||
company = models.CharField(max_length=255, blank=True, null=True, verbose_name="公司名称")
|
||||
name = models.CharField(max_length=255, blank=True, null=True, verbose_name="用户姓名")
|
||||
full_name = models.CharField(max_length=255, null=True, blank=True)
|
||||
role = models.CharField(max_length=50, choices=ROLE_CHOICES, default='annotator')
|
||||
profile_image = models.CharField(max_length=255, null=True, blank=True)
|
||||
is_first_login = models.BooleanField(default=True, verbose_name="是否首次登录")
|
||||
last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间")
|
||||
is_active = models.BooleanField(default=True)
|
||||
@ -44,12 +56,17 @@ class User(AbstractBaseUser):
|
||||
objects = UserManager()
|
||||
|
||||
USERNAME_FIELD = 'email'
|
||||
EMAIL_FIELD = 'email'
|
||||
REQUIRED_FIELDS = []
|
||||
|
||||
class Meta:
|
||||
verbose_name = "用户"
|
||||
verbose_name_plural = verbose_name
|
||||
db_table = "users"
|
||||
indexes = [
|
||||
models.Index(fields=['username']),
|
||||
models.Index(fields=['email']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.email
|
||||
@ -71,6 +88,9 @@ class UserToken(models.Model):
|
||||
token = models.CharField(max_length=40, unique=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
expired_at = models.DateTimeField()
|
||||
last_accessed = models.DateTimeField(default=timezone.now)
|
||||
ip_address = models.CharField(max_length=100, null=True, blank=True)
|
||||
user_agent = models.CharField(max_length=255, null=True, blank=True)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.expired_at:
|
||||
@ -83,3 +103,66 @@ class UserToken(models.Model):
|
||||
|
||||
class Meta:
|
||||
db_table = 'user_token'
|
||||
indexes = [
|
||||
models.Index(fields=['token']),
|
||||
models.Index(fields=['user']),
|
||||
]
|
||||
|
||||
class UserActivityLog(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='activity_logs')
|
||||
action_type = models.CharField(max_length=50)
|
||||
target_type = models.CharField(max_length=50, blank=True, null=True)
|
||||
target_id = models.CharField(max_length=36, blank=True, null=True)
|
||||
details = models.TextField(blank=True, null=True)
|
||||
ip_address = models.CharField(max_length=45, blank=True, null=True)
|
||||
user_agent = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '用户活动日志'
|
||||
verbose_name_plural = '用户活动日志'
|
||||
|
||||
|
||||
class AnnotationStats(models.Model):
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='annotation_stats')
|
||||
date = models.DateField()
|
||||
total_annotations = models.IntegerField(default=0)
|
||||
positive_annotations = models.IntegerField(default=0)
|
||||
negative_annotations = models.IntegerField(default=0)
|
||||
conversations_count = models.IntegerField(default=0)
|
||||
messages_count = models.IntegerField(default=0)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
updated_at = models.DateTimeField(default=timezone.now)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '标注统计'
|
||||
verbose_name_plural = '标注统计'
|
||||
unique_together = ('user', 'date')
|
||||
|
||||
|
||||
class AnnotationQuality(models.Model):
|
||||
REVIEW_STATUS_CHOICES = (
|
||||
('pending', '待审核'),
|
||||
('approved', '已通过'),
|
||||
('rejected', '已拒绝'),
|
||||
)
|
||||
|
||||
id = models.CharField(primary_key=True, max_length=36, default=uuid.uuid4, editable=False)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='annotation_quality')
|
||||
message_id = models.CharField(max_length=36)
|
||||
quality_score = models.FloatField(default=0.0)
|
||||
consistency_score = models.FloatField(default=0.0)
|
||||
review_status = models.CharField(max_length=20, choices=REVIEW_STATUS_CHOICES, default='pending')
|
||||
reviewer = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='reviews')
|
||||
review_notes = models.TextField(blank=True, null=True)
|
||||
created_at = models.DateTimeField(default=timezone.now)
|
||||
reviewed_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
|
||||
verbose_name = '标注质量'
|
||||
verbose_name_plural = '标注质量'
|
81
apps/user/serializers.py
Normal file
81
apps/user/serializers.py
Normal file
@ -0,0 +1,81 @@
|
||||
from rest_framework import serializers
|
||||
from .models import User, UserToken, UserActivityLog, AnnotationStats, AnnotationQuality
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
"""用户序列化器"""
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['id', 'email', 'username', 'company', 'name', 'full_name',
|
||||
'role', 'profile_image', 'is_first_login', 'last_login',
|
||||
'is_active', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at', 'last_login']
|
||||
|
||||
class UserCreateSerializer(serializers.ModelSerializer):
|
||||
"""用户创建序列化器"""
|
||||
password = serializers.CharField(write_only=True, required=True, style={'input_type': 'password'})
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['email', 'password', 'username', 'company', 'name', 'full_name', 'role']
|
||||
|
||||
def create(self, validated_data):
|
||||
return User.objects.create_user(**validated_data)
|
||||
|
||||
class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
"""用户更新序列化器"""
|
||||
password = serializers.CharField(write_only=True, required=False, style={'input_type': 'password'})
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['email', 'password', 'username', 'company', 'name', 'full_name', 'profile_image']
|
||||
read_only_fields = ['email']
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
password = validated_data.pop('password', None)
|
||||
user = super().update(instance, validated_data)
|
||||
|
||||
if password:
|
||||
user.set_password(password)
|
||||
user.save()
|
||||
|
||||
return user
|
||||
|
||||
class UserTokenSerializer(serializers.ModelSerializer):
|
||||
"""用户令牌序列化器"""
|
||||
user = UserSerializer(read_only=True)
|
||||
is_expired = serializers.BooleanField(read_only=True, source='is_expired')
|
||||
|
||||
class Meta:
|
||||
model = UserToken
|
||||
fields = ['id', 'user', 'token', 'created_at', 'expired_at', 'last_accessed', 'ip_address', 'user_agent', 'is_expired']
|
||||
read_only_fields = ['id', 'user', 'token', 'created_at', 'expired_at', 'last_accessed']
|
||||
|
||||
class UserActivityLogSerializer(serializers.ModelSerializer):
|
||||
"""用户活动日志序列化器"""
|
||||
user = UserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = UserActivityLog
|
||||
fields = ['id', 'user', 'action_type', 'target_type', 'target_id', 'details', 'ip_address', 'user_agent', 'created_at']
|
||||
read_only_fields = ['id', 'created_at']
|
||||
|
||||
class AnnotationStatsSerializer(serializers.ModelSerializer):
|
||||
"""标注统计序列化器"""
|
||||
user = UserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AnnotationStats
|
||||
fields = ['id', 'user', 'date', 'total_annotations', 'positive_annotations', 'negative_annotations',
|
||||
'conversations_count', 'messages_count', 'created_at', 'updated_at']
|
||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||
|
||||
class AnnotationQualitySerializer(serializers.ModelSerializer):
|
||||
"""标注质量序列化器"""
|
||||
user = UserSerializer(read_only=True)
|
||||
reviewer = UserSerializer(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = AnnotationQuality
|
||||
fields = ['id', 'user', 'message_id', 'quality_score', 'consistency_score', 'review_status',
|
||||
'reviewer', 'review_notes', 'created_at', 'reviewed_at']
|
||||
read_only_fields = ['id', 'created_at', 'reviewed_at']
|
@ -56,6 +56,7 @@ INSTALLED_APPS = [
|
||||
"apps.feishu.apps.FeishuConfig",
|
||||
'django_celery_beat', # Celery定时任务
|
||||
'django_celery_results', # Celery结果存储
|
||||
"apps.rlhf.apps.RlhfConfig",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
|
@ -21,6 +21,7 @@ urlpatterns = [
|
||||
path('api/gmail/', include('apps.gmail.urls')),
|
||||
path('api/feishu/', include('apps.feishu.urls')),
|
||||
path('api/knowledge-bases/', include('apps.knowledge_base.urls')),
|
||||
path('api/rlhf/', include('apps.rlhf.urls')),
|
||||
]
|
||||
|
||||
# 在开发环境中提供媒体文件服务
|
||||
|
Loading…
Reference in New Issue
Block a user