feat: Support openai dialogue (#3551)

This commit is contained in:
shaohuzhang1 2025-07-10 19:16:47 +08:00 committed by GitHub
parent 691d7ceaa5
commit c370e99304
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 180 additions and 57 deletions

View File

@ -11,10 +11,17 @@ from django.utils.translation import gettext_lazy as _
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
from chat.serializers.chat import OpenAIInstanceSerializer
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer
from common.mixins.api_mixin import APIMixin
class OpenAIAPI(APIMixin):
@staticmethod
def get_request():
return OpenAIInstanceSerializer
class ChatAuthenticationAPI(APIMixin):
@staticmethod
def get_request():

View File

@ -8,7 +8,7 @@
"""
from gettext import gettext
from typing import List
from typing import List, Dict
import uuid_utils.compat as uuid
from django.db.models import QuerySet
@ -31,6 +31,7 @@ from application.serializers.application import ApplicationOperateSerializer
from application.serializers.common import ChatInfo
from common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException
from common.handle.base_to_response import BaseToResponse
from common.handle.impl.response.openai_to_response import OpenaiToResponse
from common.handle.impl.response.system_to_response import SystemToResponse
from common.utils.common import flat_map
from knowledge.models import Document, Paragraph
@ -111,6 +112,66 @@ class DebugChatSerializers(serializers.Serializer):
}).chat(instance, base_to_response)
class OpenAIMessage(serializers.Serializer):
content = serializers.CharField(required=True, label=_('content'))
role = serializers.CharField(required=True, label=_('Role'))
class OpenAIInstanceSerializer(serializers.Serializer):
messages = serializers.ListField(child=OpenAIMessage())
chat_id = serializers.UUIDField(required=False, label=_("Conversation ID"))
re_chat = serializers.BooleanField(required=False, label=_("Regenerate"))
stream = serializers.BooleanField(required=False, label=_("Streaming Output"))
class OpenAIChatSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
chat_user_id = serializers.CharField(required=True, label=_("Client id"))
chat_user_type = serializers.CharField(required=True, label=_("Client Type"))
@staticmethod
def get_message(instance):
return instance.get('messages')[-1].get('content')
@staticmethod
def generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type):
if chat_id is None:
chat_id = str(uuid.uuid1())
chat_info = ChatInfo(chat_id, chat_user_id, chat_user_type, [], [],
application_id)
chat_info.set_cache()
return chat_id
def chat(self, instance: Dict, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
OpenAIInstanceSerializer(data=instance).is_valid(raise_exception=True)
chat_id = instance.get('chat_id')
message = self.get_message(instance)
re_chat = instance.get('re_chat', False)
stream = instance.get('stream', False)
application_id = self.data.get('application_id')
chat_user_id = self.data.get('chat_user_id')
chat_user_type = self.data.get('chat_user_type')
chat_id = self.generate_chat(chat_id, application_id, message, chat_user_id, chat_user_type)
return ChatSerializers(
data={
'chat_id': chat_id,
'chat_user_id': chat_user_id,
'chat_user_type': chat_user_type,
'application_id': application_id
}
).chat({'message': message,
're_chat': re_chat,
'stream': stream,
'form_data': instance.get('form_data', {}),
'image_list': instance.get('image_list', []),
'document_list': instance.get('document_list', []),
'audio_list': instance.get('audio_list', []),
'other_list': instance.get('other_list', [])},
base_to_response=OpenaiToResponse())
class ChatSerializers(serializers.Serializer):
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
chat_user_id = serializers.CharField(required=True, label=_("Client id"))

View File

@ -14,6 +14,8 @@ urlpatterns = [
path('text_to_speech', views.TextToSpeech.as_view()),
path('speech_to_text', views.SpeechToText.as_view()),
path('captcha', views.CaptchaView.as_view(), name='captcha'),
path('<str:application_id>/chat/completions', views.OpenAIView.as_view(),
name='application/chat_completions'),
path('vote/chat/<str:chat_id>/chat_record/<str:chat_record_id>', views.VoteView.as_view(), name='vote'),
path('historical_conversation', views.HistoricalConversationView.as_view(), name='historical_conversation'),
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),

View File

@ -14,14 +14,13 @@ from rest_framework.request import Request
from rest_framework.views import APIView
from application.api.application_api import SpeechToTextAPI, TextToSpeechAPI
from application.serializers.application import ApplicationOperateSerializer
from chat.api.chat_api import ChatAPI
from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI
from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, TextToSpeechSerializers
from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI
from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \
TextToSpeechSerializers, OpenAIChatSerializer
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \
AuthProfileSerializer
from common.auth import TokenAuth
from common.auth.authentication import has_permissions
from common.constants.permission_constants import ChatAuth
from common.exception.app_exception import AppAuthenticationFailed
from common.result import result
@ -31,6 +30,23 @@ from users.api import CaptchaAPI
from users.serializers.login import CaptchaSerializer
class OpenAIView(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['POST'],
description=_('OpenAI Interface Dialogue'),
summary=_('OpenAI Interface Dialogue'),
operation_id=_('OpenAI Interface Dialogue'), # type: ignore
request=OpenAIAPI.get_request(),
responses=None,
tags=[_('Chat')] # type: ignore
)
def post(self, request: Request, application_id: str):
return OpenAIChatSerializer(data={'application_id': application_id, 'chat_user_id': request.auth.chat_user_id,
'chat_user_type': request.auth.chat_user_type}).chat(request.data)
class AnonymousAuthentication(APIView):
def options(self, request, *args, **kwargs):
return HttpResponse(

View File

@ -0,0 +1,43 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file application_key.py
@date2025/7/10 03:02
@desc: 应用api key认证
"""
from django.db.models import QuerySet
from django.utils.translation import gettext_lazy as _
from application.models import ApplicationApiKey, ChatUserType
from common.auth.handle.auth_base_handle import AuthBaseHandle
from common.constants.permission_constants import Permission, Group, Operate, RoleConstants, ChatAuth
from common.database_model_manage.database_model_manage import DatabaseModelManage
from common.exception.app_exception import AppAuthenticationFailed
class ApplicationKey(AuthBaseHandle):
def handle(self, request, token: str, get_token_details):
application_api_key = QuerySet(ApplicationApiKey).filter(secret_key=token).first()
if application_api_key is None:
raise AppAuthenticationFailed(500, _('Secret key is invalid'))
if not application_api_key.is_active:
raise AppAuthenticationFailed(500, _('Secret key is invalid'))
application_setting_model = DatabaseModelManage.get_model("application_setting")
if application_setting_model is not None:
application_setting = QuerySet(application_setting_model).filter(
application_id=application_api_key.application_id).first()
if application_setting.authentication:
if application_setting.authentication != 'password':
raise AppAuthenticationFailed(1002, _('Authentication information is incorrect'))
return None, ChatAuth(
current_role_list=[RoleConstants.CHAT_ANONYMOUS_USER],
permission_list=[
Permission(group=Group.APPLICATION,
operate=Operate.READ)],
application_id=application_api_key.application_id,
chat_user_id=str(application_api_key.id),
chat_user_type=ChatUserType.ANONYMOUS_USER.value)
def support(self, request, token: str, get_token_details):
return str(token).startswith("application-")

View File

@ -8,8 +8,9 @@
"""
USER_TOKEN_AUTH = 'common.auth.handle.impl.user_token.UserToken'
CHAT_ANONYMOUS_USER_AURH = 'common.auth.handle.impl.chat_anonymous_user_token.ChatAnonymousUserToken'
APPLICATION_KEY_AUTH = 'common.auth.handle.impl.application_key.ApplicationKey'
AUTH_HANDLES = [
USER_TOKEN_AUTH,
CHAT_ANONYMOUS_USER_AURH
CHAT_ANONYMOUS_USER_AURH,
APPLICATION_KEY_AUTH
]

View File

@ -9,9 +9,7 @@
</h4>
<el-card shadow="never" class="overview-card" v-loading="loading">
<div class="title flex align-center">
<div
class="edit-avatar mr-12"
>
<div class="edit-avatar mr-12">
<el-avatar shape="square" :size="32" style="background: none">
<img :src="resetUrl(detail?.icon, resetUrl('./favicon.ico'))" alt="" />
</el-avatar>
@ -23,9 +21,8 @@
<el-row :gutter="12">
<el-col :span="12" class="mt-16">
<div class="flex">
<el-text type="info">{{
$t('views.applicationOverview.appInfo.publicAccessLink')
}}
<el-text type="info"
>{{ $t('views.applicationOverview.appInfo.publicAccessLink') }}
</el-text>
<el-switch
v-model="accessToken.is_active"
@ -110,9 +107,7 @@
</div>
<div class="mt-4 mb-16 url-height">
<div>
<el-text>API {{ $t('common.fileUpload.document') }}
</el-text
>
<el-text>API {{ $t('common.fileUpload.document') }} </el-text>
<el-button
type="primary"
link
@ -146,8 +141,7 @@
<Key />
</el-icon>
{{ $t('views.applicationOverview.appInfo.apiKey') }}
</el-button
>
</el-button>
</div>
</el-col>
</el-row>
@ -240,7 +234,7 @@ const {
const apiUrl = window.location.origin + '/doc/chat/'
const baseUrl = window.location.origin + '/api/application/'
const baseUrl = window.location.origin + `${window.MaxKB.chatPrefix}/api/`
const APIKeyDialogRef = ref()
const EmbedDialogRef = ref()
@ -379,8 +373,7 @@ function refreshAccessToken() {
const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')
updateAccessToken(obj, str)
})
.catch(() => {
})
.catch(() => {})
}
function changeState(bool: boolean) {