优化项目

This commit is contained in:
jlj 2025-06-09 16:29:14 +08:00
parent 32d09a9ac7
commit 4567e318ef
18 changed files with 1786 additions and 2 deletions

1
apps/rlhf/__init__.py Normal file
View File

@ -0,0 +1 @@

72
apps/rlhf/admin.py Normal file
View 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
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class RlhfConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.rlhf'

View File

@ -0,0 +1 @@

View File

@ -0,0 +1 @@

View 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('✅ 反馈标签已添加'))

View 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')},
},
),
]

View File

@ -0,0 +1 @@

195
apps/rlhf/models.py Normal file
View 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
View 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']

View 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
View 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
View 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'}
]
})

View File

@ -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')},
),
]

View File

@ -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
View 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']

View File

@ -56,6 +56,7 @@ INSTALLED_APPS = [
"apps.feishu.apps.FeishuConfig",
'django_celery_beat', # Celery定时任务
'django_celery_results', # Celery结果存储
"apps.rlhf.apps.RlhfConfig",
]
MIDDLEWARE = [

View File

@ -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')),
]
# 在开发环境中提供媒体文件服务