优化项目

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.db import models
from django.utils import timezone from django.utils import timezone
from datetime import timedelta 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): class UserManager(BaseUserManager):
def create_user(self, email, password=None, **extra_fields): 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): def create_superuser(self, email, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True) extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True) extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('role', 'admin')
if extra_fields.get('is_staff') is not True: if extra_fields.get('is_staff') is not True:
raise ValueError('超级用户必须设置is_staff=True') raise ValueError('超级用户必须设置is_staff=True')
@ -25,12 +27,22 @@ class UserManager(BaseUserManager):
return self.create_user(email, password, **extra_fields) 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="电子邮箱") email = models.EmailField(max_length=255, unique=True, verbose_name="电子邮箱")
password = models.CharField(max_length=255, 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="公司名称") company = models.CharField(max_length=255, blank=True, null=True, verbose_name="公司名称")
name = 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="是否首次登录") is_first_login = models.BooleanField(default=True, verbose_name="是否首次登录")
last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间") last_login = models.DateTimeField(blank=True, null=True, verbose_name="最近登录时间")
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
@ -44,12 +56,17 @@ class User(AbstractBaseUser):
objects = UserManager() objects = UserManager()
USERNAME_FIELD = 'email' USERNAME_FIELD = 'email'
EMAIL_FIELD = 'email'
REQUIRED_FIELDS = [] REQUIRED_FIELDS = []
class Meta: class Meta:
verbose_name = "用户" verbose_name = "用户"
verbose_name_plural = verbose_name verbose_name_plural = verbose_name
db_table = "users" db_table = "users"
indexes = [
models.Index(fields=['username']),
models.Index(fields=['email']),
]
def __str__(self): def __str__(self):
return self.email return self.email
@ -71,6 +88,9 @@ class UserToken(models.Model):
token = models.CharField(max_length=40, unique=True) token = models.CharField(max_length=40, unique=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
expired_at = models.DateTimeField() 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): def save(self, *args, **kwargs):
if not self.expired_at: if not self.expired_at:
@ -83,3 +103,66 @@ class UserToken(models.Model):
class Meta: class Meta:
db_table = 'user_token' 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", "apps.feishu.apps.FeishuConfig",
'django_celery_beat', # Celery定时任务 'django_celery_beat', # Celery定时任务
'django_celery_results', # Celery结果存储 'django_celery_results', # Celery结果存储
"apps.rlhf.apps.RlhfConfig",
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -21,6 +21,7 @@ urlpatterns = [
path('api/gmail/', include('apps.gmail.urls')), path('api/gmail/', include('apps.gmail.urls')),
path('api/feishu/', include('apps.feishu.urls')), path('api/feishu/', include('apps.feishu.urls')),
path('api/knowledge-bases/', include('apps.knowledge_base.urls')), path('api/knowledge-bases/', include('apps.knowledge_base.urls')),
path('api/rlhf/', include('apps.rlhf.urls')),
] ]
# 在开发环境中提供媒体文件服务 # 在开发环境中提供媒体文件服务