tingwu_new
All checks were successful
Deploy to Server / deploy (push) Successful in 1m8s

This commit is contained in:
jeremygan2021
2026-03-11 20:46:25 +08:00
parent f41fd01367
commit 7612c09571
9 changed files with 319 additions and 11 deletions

View File

@@ -1,7 +1,16 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.admin import ModelAdmin
from unfold.admin import ModelAdmin as UnfoldModelAdmin from unfold.admin import ModelAdmin as UnfoldModelAdmin
from .models import TranscriptionTask from unfold.admin import StackedInline as UnfoldStackedInline
from .models import TranscriptionTask, AIEvaluation
class AIEvaluationInline(UnfoldStackedInline):
model = AIEvaluation
extra = 0
can_delete = False
verbose_name = "AI评估"
verbose_name_plural = "AI评估"
readonly_fields = ['created_at', 'updated_at', 'raw_response', 'reasoning']
fields = ('score', 'evaluation', 'model_selection', 'prompt', 'reasoning', 'status', 'error_message')
@admin.register(TranscriptionTask) @admin.register(TranscriptionTask)
class TranscriptionTaskAdmin(UnfoldModelAdmin): class TranscriptionTaskAdmin(UnfoldModelAdmin):
@@ -9,3 +18,24 @@ class TranscriptionTaskAdmin(UnfoldModelAdmin):
list_filter = ['status', 'created_at'] list_filter = ['status', 'created_at']
search_fields = ['id', 'task_id', 'transcription', 'summary'] search_fields = ['id', 'task_id', 'transcription', 'summary']
readonly_fields = ['id', 'created_at', 'updated_at', 'task_id'] readonly_fields = ['id', 'created_at', 'updated_at', 'task_id']
inlines = [AIEvaluationInline]
@admin.register(AIEvaluation)
class AIEvaluationAdmin(UnfoldModelAdmin):
list_display = ['id', 'task', 'score', 'status', 'model_selection', 'created_at']
list_filter = ['status', 'model_selection', 'created_at']
search_fields = ['task__id', 'evaluation', 'reasoning']
readonly_fields = ['id', 'created_at', 'updated_at', 'raw_response']
fieldsets = (
(None, {
'fields': ('task', 'status', 'score', 'evaluation')
}),
('配置', {
'fields': ('model_selection', 'prompt'),
'classes': ('collapse',),
}),
('调试信息', {
'fields': ('raw_response', 'reasoning', 'error_message'),
'classes': ('collapse',),
}),
)

View File

@@ -0,0 +1,98 @@
import logging
import json
import os
from django.conf import settings
from openai import OpenAI
from .models import AIEvaluation
logger = logging.getLogger(__name__)
class BailianService:
def __init__(self):
self.api_key = getattr(settings, 'DASHSCOPE_API_KEY', None)
if not self.api_key:
self.api_key = os.environ.get("DASHSCOPE_API_KEY")
if self.api_key:
self.client = OpenAI(
api_key=self.api_key,
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
else:
self.client = None
logger.warning("DASHSCOPE_API_KEY not configured.")
def evaluate_task(self, evaluation: AIEvaluation):
"""
执行AI评估
"""
if not self.client:
evaluation.status = AIEvaluation.Status.FAILED
evaluation.error_message = "服务未配置 (DASHSCOPE_API_KEY missing)"
evaluation.save()
return
task = evaluation.task
if not task.transcription:
evaluation.status = AIEvaluation.Status.FAILED
evaluation.error_message = "关联任务无逐字稿内容"
evaluation.save()
return
evaluation.status = AIEvaluation.Status.PROCESSING
evaluation.save()
try:
prompt = evaluation.prompt
content = task.transcription
# 截断过长的内容以防止超出Token限制 (简单处理取前10000字)
if len(content) > 10000:
content = content[:10000] + "...(内容过长已截断)"
# Construct messages
messages = [
{'role': 'system', 'content': 'You are a helpful assistant designed to output JSON.'},
{'role': 'user', 'content': f"{prompt}\n\n以下是需要评估的内容:\n{content}"}
]
completion = self.client.chat.completions.create(
model=evaluation.model_selection,
messages=messages,
response_format={"type": "json_object"}
)
response_content = completion.choices[0].message.content
# Convert to dict for storage
raw_response = completion.model_dump()
evaluation.raw_response = raw_response
# Parse JSON
try:
result = json.loads(response_content)
evaluation.score = result.get('score')
evaluation.evaluation = result.get('evaluation') or result.get('comment')
# 尝试获取推理过程(如果模型返回了)
evaluation.reasoning = result.get('reasoning') or result.get('analysis')
if not evaluation.reasoning:
# 如果JSON里没有把整个JSON作为推理参考
evaluation.reasoning = json.dumps(result, ensure_ascii=False, indent=2)
evaluation.status = AIEvaluation.Status.COMPLETED
except json.JSONDecodeError:
evaluation.status = AIEvaluation.Status.FAILED
evaluation.error_message = f"无法解析JSON响应: {response_content}"
evaluation.reasoning = response_content
evaluation.save()
return evaluation
except Exception as e:
logger.error(f"AI Evaluation failed: {e}")
evaluation.status = AIEvaluation.Status.FAILED
evaluation.error_message = str(e)
evaluation.save()
return evaluation

View File

@@ -0,0 +1,44 @@
# Generated by Django 6.0.1 on 2026-03-11 12:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ai_services', '0003_transcriptiontask_auto_chapters_data_and_more'),
]
operations = [
migrations.RemoveField(
model_name='transcriptiontask',
name='evaluation',
),
migrations.RemoveField(
model_name='transcriptiontask',
name='score',
),
migrations.CreateModel(
name='AIEvaluation',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('score', models.IntegerField(blank=True, help_text='0-100分', null=True, verbose_name='AI评分')),
('evaluation', models.TextField(blank=True, null=True, verbose_name='AI评语')),
('model_selection', models.CharField(default='qwen-plus', help_text='例如: qwen-plus, qwen-turbo, qwen-max', max_length=50, verbose_name='模型选择')),
('prompt', models.TextField(default='你是一个专业的评分助手。请根据提供的转写内容对内容质量、逻辑清晰度、语言表达等方面进行综合评分0-100分并给出详细的评语。请以JSON格式返回包含"score""evaluation"字段。', help_text='用于指导AI评分的提示词', verbose_name='评分提示词')),
('raw_response', models.JSONField(blank=True, help_text='大模型返回的完整JSON', null=True, verbose_name='原始响应')),
('reasoning', models.TextField(blank=True, help_text='AI的推理过程如果有', null=True, verbose_name='推理过程')),
('status', models.CharField(choices=[('PENDING', '等待中'), ('PROCESSING', '生成中'), ('COMPLETED', '已完成'), ('FAILED', '失败')], default='PENDING', max_length=20, verbose_name='评估状态')),
('error_message', models.TextField(blank=True, null=True, verbose_name='错误信息')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='更新时间')),
('task', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='ai_evaluation', to='ai_services.transcriptiontask', verbose_name='关联任务')),
],
options={
'verbose_name': 'AI智能评估',
'verbose_name_plural': 'AI智能评估',
'ordering': ['-created_at'],
},
),
]

View File

@@ -25,8 +25,11 @@ class TranscriptionTask(models.Model):
transcription = models.TextField(verbose_name=_('逐字稿'), blank=True, null=True) transcription = models.TextField(verbose_name=_('逐字稿'), blank=True, null=True)
summary = models.TextField(verbose_name=_('AI总结'), blank=True, null=True) summary = models.TextField(verbose_name=_('AI总结'), blank=True, null=True)
score = models.IntegerField(verbose_name=_('AI评分'), blank=True, null=True, help_text=_('基于转写内容的评分'))
evaluation = models.TextField(verbose_name=_('AI评语'), blank=True, null=True) # 已解耦到 AIEvaluation 模型
# score = models.IntegerField(verbose_name=_('AI评分'), blank=True, null=True, help_text=_('基于转写内容的评分'))
# evaluation = models.TextField(verbose_name=_('AI评语'), blank=True, null=True)
error_message = models.TextField(verbose_name=_('错误信息'), blank=True, null=True) error_message = models.TextField(verbose_name=_('错误信息'), blank=True, null=True)
created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True) created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True) updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)
@@ -38,3 +41,58 @@ class TranscriptionTask(models.Model):
def __str__(self): def __str__(self):
return f"{self.id} - {self.get_status_display()}" return f"{self.id} - {self.get_status_display()}"
class AIEvaluation(models.Model):
class Status(models.TextChoices):
PENDING = 'PENDING', _('等待中')
PROCESSING = 'PROCESSING', _('生成中')
COMPLETED = 'COMPLETED', _('已完成')
FAILED = 'FAILED', _('失败')
task = models.OneToOneField(
TranscriptionTask,
on_delete=models.CASCADE,
related_name='ai_evaluation',
verbose_name=_('关联任务')
)
# 评分与评语
score = models.IntegerField(verbose_name=_('AI评分'), blank=True, null=True, help_text=_('0-100分'))
evaluation = models.TextField(verbose_name=_('AI评语'), blank=True, null=True)
# 配置选项 (可在Admin中设置)
model_selection = models.CharField(
verbose_name=_('模型选择'),
max_length=50,
default='qwen-plus',
help_text=_('例如: qwen-plus, qwen-turbo, qwen-max')
)
prompt = models.TextField(
verbose_name=_('评分提示词'),
default='你是一个专业的评分助手。请根据提供的转写内容对内容质量、逻辑清晰度、语言表达等方面进行综合评分0-100分并给出详细的评语。请以JSON格式返回包含"score""evaluation"字段。',
help_text=_('用于指导AI评分的提示词')
)
# 原始数据与推理
raw_response = models.JSONField(verbose_name=_('原始响应'), blank=True, null=True, help_text=_('大模型返回的完整JSON'))
reasoning = models.TextField(verbose_name=_('推理过程'), blank=True, null=True, help_text=_('AI的推理过程如果有'))
status = models.CharField(
verbose_name=_('评估状态'),
max_length=20,
choices=Status.choices,
default=Status.PENDING
)
error_message = models.TextField(verbose_name=_('错误信息'), blank=True, null=True)
created_at = models.DateTimeField(verbose_name=_('创建时间'), auto_now_add=True)
updated_at = models.DateTimeField(verbose_name=_('更新时间'), auto_now=True)
class Meta:
verbose_name = _('AI智能评估')
verbose_name_plural = _('AI智能评估')
ordering = ['-created_at']
def __str__(self):
return f"Evaluation for Task {self.task.id}"

View File

@@ -1,11 +1,18 @@
from rest_framework import serializers from rest_framework import serializers
from .models import TranscriptionTask from .models import TranscriptionTask, AIEvaluation
class AIEvaluationSerializer(serializers.ModelSerializer):
class Meta:
model = AIEvaluation
fields = ['id', 'score', 'evaluation', 'model_selection', 'prompt', 'reasoning', 'status', 'error_message', 'created_at', 'updated_at']
class TranscriptionTaskSerializer(serializers.ModelSerializer): class TranscriptionTaskSerializer(serializers.ModelSerializer):
ai_evaluation = AIEvaluationSerializer(read_only=True)
class Meta: class Meta:
model = TranscriptionTask model = TranscriptionTask
fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at', 'score', 'evaluation', 'transcription_data', 'summary_data', 'auto_chapters_data'] fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at', 'transcription_data', 'summary_data', 'auto_chapters_data', 'ai_evaluation']
read_only_fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at', 'score', 'evaluation', 'transcription_data', 'summary_data', 'auto_chapters_data'] read_only_fields = ['id', 'file_url', 'task_id', 'status', 'transcription', 'summary', 'error_message', 'created_at', 'updated_at', 'transcription_data', 'summary_data', 'auto_chapters_data', 'ai_evaluation']
class TranscriptionUploadSerializer(serializers.Serializer): class TranscriptionUploadSerializer(serializers.Serializer):
file = serializers.FileField(help_text="上传的音频文件") file = serializers.FileField(help_text="上传的音频文件")

View File

@@ -291,7 +291,30 @@ class AliyunTingwuService:
# 保存原始数据 # 保存原始数据
task.auto_chapters_data = auto_chapters task.auto_chapters_data = auto_chapters
# (可选) 将章节信息追加到 summary 或 evaluation 中,或者仅保存 raw data # 将章节信息追加到 summary
# 根据用户需求,这里主要保存到 model 的 auto_chapters_data 字段 (已在 models.py 定义) if auto_chapters and isinstance(auto_chapters, list):
if summary_text:
summary_text.append("\n\n### 章节速览")
else:
summary_text.append("### 章节速览")
for chapter in auto_chapters:
headline = chapter.get('Headline', '')
summary = chapter.get('Summary', '')
start_time = chapter.get('Start', 0)
# 格式化时间戳 (毫秒 -> HH:MM:SS)
seconds = int(start_time / 1000)
m, s = divmod(seconds, 60)
h, m = divmod(m, 60)
time_str = f"{h:02d}:{m:02d}:{s:02d}"
chapter_text = f"- [{time_str}] {headline}"
if summary:
chapter_text += f"\n {summary}"
summary_text.append(chapter_text)
if summary_text:
task.summary = "\n".join(summary_text)
task.save() task.save()

View File

@@ -7,8 +7,8 @@ from rest_framework.parsers import MultiPartParser, FormParser, JSONParser
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from django.conf import settings from django.conf import settings
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
from .models import TranscriptionTask from .models import TranscriptionTask, AIEvaluation
from .serializers import TranscriptionTaskSerializer, TranscriptionUploadSerializer from .serializers import TranscriptionTaskSerializer, TranscriptionUploadSerializer, AIEvaluationSerializer
from .services import AliyunTingwuService from .services import AliyunTingwuService
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -143,6 +143,51 @@ class TranscriptionTaskViewSet(viewsets.ModelViewSet):
logger.error(f"处理上传请求失败: {e}") logger.error(f"处理上传请求失败: {e}")
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@action(detail=True, methods=['post'])
@extend_schema(
request={
'application/json': {
'type': 'object',
'properties': {
'model_selection': {'type': 'string', 'description': '模型选择'},
'prompt': {'type': 'string', 'description': '评分提示词'},
}
}
},
responses={200: AIEvaluationSerializer}
)
def evaluate(self, request, pk=None):
"""
触发AI评估
"""
task = self.get_object()
# 1. 检查或创建 Evaluation 对象
evaluation, created = AIEvaluation.objects.get_or_create(task=task)
# 2. 如果请求中有配置,更新配置
model_selection = request.data.get('model_selection')
prompt = request.data.get('prompt')
updated = False
if model_selection:
evaluation.model_selection = model_selection
updated = True
if prompt:
evaluation.prompt = prompt
updated = True
if updated:
evaluation.save()
# 3. 调用 Service 执行评估
from .bailian_service import BailianService
service = BailianService()
service.evaluate_task(evaluation)
serializer = AIEvaluationSerializer(evaluation)
return Response(serializer.data)
@action(detail=True, methods=['get']) @action(detail=True, methods=['get'])
@extend_schema( @extend_schema(
parameters=[ parameters=[

View File

@@ -414,3 +414,5 @@ ALIYUN_OSS_BUCKET_NAME = os.environ.get('ALIYUN_OSS_BUCKET_NAME', '')
ALIYUN_OSS_ENDPOINT = os.environ.get('ALIYUN_OSS_ENDPOINT', 'oss-cn-shanghai.aliyuncs.com') ALIYUN_OSS_ENDPOINT = os.environ.get('ALIYUN_OSS_ENDPOINT', 'oss-cn-shanghai.aliyuncs.com')
ALIYUN_OSS_INTERNAL_ENDPOINT = os.environ.get('ALIYUN_OSS_INTERNAL_ENDPOINT', '') ALIYUN_OSS_INTERNAL_ENDPOINT = os.environ.get('ALIYUN_OSS_INTERNAL_ENDPOINT', '')
ALIYUN_TINGWU_APP_KEY = os.environ.get('ALIYUN_TINGWU_APP_KEY', '') # 听悟AppKey ALIYUN_TINGWU_APP_KEY = os.environ.get('ALIYUN_TINGWU_APP_KEY', '') # 听悟AppKey
DASHSCOPE_API_KEY = os.environ.get('DASHSCOPE_API_KEY', '')

View File

@@ -28,3 +28,4 @@ aliyun-python-sdk-core==2.16.0
aliyun-python-sdk-tingwu==1.0.7 aliyun-python-sdk-tingwu==1.0.7
oss2==2.19.1 oss2==2.19.1
python-dotenv python-dotenv
openai