feat: Support openai dialogue (#3551)
This commit is contained in:
parent
691d7ceaa5
commit
c370e99304
@ -11,10 +11,17 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
from drf_spectacular.types import OpenApiTypes
|
from drf_spectacular.types import OpenApiTypes
|
||||||
from drf_spectacular.utils import OpenApiParameter
|
from drf_spectacular.utils import OpenApiParameter
|
||||||
|
|
||||||
|
from chat.serializers.chat import OpenAIInstanceSerializer
|
||||||
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer
|
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer
|
||||||
from common.mixins.api_mixin import APIMixin
|
from common.mixins.api_mixin import APIMixin
|
||||||
|
|
||||||
|
|
||||||
|
class OpenAIAPI(APIMixin):
|
||||||
|
@staticmethod
|
||||||
|
def get_request():
|
||||||
|
return OpenAIInstanceSerializer
|
||||||
|
|
||||||
|
|
||||||
class ChatAuthenticationAPI(APIMixin):
|
class ChatAuthenticationAPI(APIMixin):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_request():
|
def get_request():
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from gettext import gettext
|
from gettext import gettext
|
||||||
from typing import List
|
from typing import List, Dict
|
||||||
|
|
||||||
import uuid_utils.compat as uuid
|
import uuid_utils.compat as uuid
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
@ -31,6 +31,7 @@ from application.serializers.application import ApplicationOperateSerializer
|
|||||||
from application.serializers.common import ChatInfo
|
from application.serializers.common import ChatInfo
|
||||||
from common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException
|
from common.exception.app_exception import AppApiException, AppChatNumOutOfBoundsFailed, ChatException
|
||||||
from common.handle.base_to_response import BaseToResponse
|
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.handle.impl.response.system_to_response import SystemToResponse
|
||||||
from common.utils.common import flat_map
|
from common.utils.common import flat_map
|
||||||
from knowledge.models import Document, Paragraph
|
from knowledge.models import Document, Paragraph
|
||||||
@ -111,6 +112,66 @@ class DebugChatSerializers(serializers.Serializer):
|
|||||||
}).chat(instance, base_to_response)
|
}).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):
|
class ChatSerializers(serializers.Serializer):
|
||||||
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
chat_id = serializers.UUIDField(required=True, label=_("Conversation ID"))
|
||||||
chat_user_id = serializers.CharField(required=True, label=_("Client id"))
|
chat_user_id = serializers.CharField(required=True, label=_("Client id"))
|
||||||
|
|||||||
@ -14,6 +14,8 @@ urlpatterns = [
|
|||||||
path('text_to_speech', views.TextToSpeech.as_view()),
|
path('text_to_speech', views.TextToSpeech.as_view()),
|
||||||
path('speech_to_text', views.SpeechToText.as_view()),
|
path('speech_to_text', views.SpeechToText.as_view()),
|
||||||
path('captcha', views.CaptchaView.as_view(), name='captcha'),
|
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('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', 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'),
|
path('historical_conversation/<str:chat_id>/record/<str:chat_record_id>',views.ChatRecordView.as_view(),name='conversation_details'),
|
||||||
|
|||||||
@ -14,14 +14,13 @@ from rest_framework.request import Request
|
|||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
from application.api.application_api import SpeechToTextAPI, TextToSpeechAPI
|
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_api import ChatAPI
|
||||||
from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI
|
from chat.api.chat_authentication_api import ChatAuthenticationAPI, ChatAuthenticationProfileAPI, ChatOpenAPI, OpenAIAPI
|
||||||
from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, TextToSpeechSerializers
|
from chat.serializers.chat import OpenChatSerializers, ChatSerializers, SpeechToTextSerializers, \
|
||||||
|
TextToSpeechSerializers, OpenAIChatSerializer
|
||||||
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \
|
from chat.serializers.chat_authentication import AnonymousAuthenticationSerializer, ApplicationProfileSerializer, \
|
||||||
AuthProfileSerializer
|
AuthProfileSerializer
|
||||||
from common.auth import TokenAuth
|
from common.auth import TokenAuth
|
||||||
from common.auth.authentication import has_permissions
|
|
||||||
from common.constants.permission_constants import ChatAuth
|
from common.constants.permission_constants import ChatAuth
|
||||||
from common.exception.app_exception import AppAuthenticationFailed
|
from common.exception.app_exception import AppAuthenticationFailed
|
||||||
from common.result import result
|
from common.result import result
|
||||||
@ -31,6 +30,23 @@ from users.api import CaptchaAPI
|
|||||||
from users.serializers.login import CaptchaSerializer
|
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):
|
class AnonymousAuthentication(APIView):
|
||||||
def options(self, request, *args, **kwargs):
|
def options(self, request, *args, **kwargs):
|
||||||
return HttpResponse(
|
return HttpResponse(
|
||||||
|
|||||||
43
apps/common/auth/handle/impl/application_key.py
Normal file
43
apps/common/auth/handle/impl/application_key.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# coding=utf-8
|
||||||
|
"""
|
||||||
|
@project: MaxKB
|
||||||
|
@Author:虎虎
|
||||||
|
@file: application_key.py
|
||||||
|
@date:2025/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-")
|
||||||
@ -8,8 +8,9 @@
|
|||||||
"""
|
"""
|
||||||
USER_TOKEN_AUTH = 'common.auth.handle.impl.user_token.UserToken'
|
USER_TOKEN_AUTH = 'common.auth.handle.impl.user_token.UserToken'
|
||||||
CHAT_ANONYMOUS_USER_AURH = 'common.auth.handle.impl.chat_anonymous_user_token.ChatAnonymousUserToken'
|
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 = [
|
AUTH_HANDLES = [
|
||||||
USER_TOKEN_AUTH,
|
USER_TOKEN_AUTH,
|
||||||
CHAT_ANONYMOUS_USER_AURH
|
CHAT_ANONYMOUS_USER_AURH,
|
||||||
|
APPLICATION_KEY_AUTH
|
||||||
]
|
]
|
||||||
|
|||||||
@ -9,9 +9,7 @@
|
|||||||
</h4>
|
</h4>
|
||||||
<el-card shadow="never" class="overview-card" v-loading="loading">
|
<el-card shadow="never" class="overview-card" v-loading="loading">
|
||||||
<div class="title flex align-center">
|
<div class="title flex align-center">
|
||||||
<div
|
<div class="edit-avatar mr-12">
|
||||||
class="edit-avatar mr-12"
|
|
||||||
>
|
|
||||||
<el-avatar shape="square" :size="32" style="background: none">
|
<el-avatar shape="square" :size="32" style="background: none">
|
||||||
<img :src="resetUrl(detail?.icon, resetUrl('./favicon.ico'))" alt="" />
|
<img :src="resetUrl(detail?.icon, resetUrl('./favicon.ico'))" alt="" />
|
||||||
</el-avatar>
|
</el-avatar>
|
||||||
@ -23,9 +21,8 @@
|
|||||||
<el-row :gutter="12">
|
<el-row :gutter="12">
|
||||||
<el-col :span="12" class="mt-16">
|
<el-col :span="12" class="mt-16">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<el-text type="info">{{
|
<el-text type="info"
|
||||||
$t('views.applicationOverview.appInfo.publicAccessLink')
|
>{{ $t('views.applicationOverview.appInfo.publicAccessLink') }}
|
||||||
}}
|
|
||||||
</el-text>
|
</el-text>
|
||||||
<el-switch
|
<el-switch
|
||||||
v-model="accessToken.is_active"
|
v-model="accessToken.is_active"
|
||||||
@ -110,9 +107,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4 mb-16 url-height">
|
<div class="mt-4 mb-16 url-height">
|
||||||
<div>
|
<div>
|
||||||
<el-text>API {{ $t('common.fileUpload.document') }}:
|
<el-text>API {{ $t('common.fileUpload.document') }}: </el-text>
|
||||||
</el-text
|
|
||||||
>
|
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
@ -146,8 +141,7 @@
|
|||||||
<Key />
|
<Key />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
{{ $t('views.applicationOverview.appInfo.apiKey') }}
|
{{ $t('views.applicationOverview.appInfo.apiKey') }}
|
||||||
</el-button
|
</el-button>
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
@ -240,7 +234,7 @@ const {
|
|||||||
|
|
||||||
const apiUrl = window.location.origin + '/doc/chat/'
|
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 APIKeyDialogRef = ref()
|
||||||
const EmbedDialogRef = ref()
|
const EmbedDialogRef = ref()
|
||||||
@ -379,8 +373,7 @@ function refreshAccessToken() {
|
|||||||
const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')
|
const str = t('views.applicationOverview.appInfo.refreshToken.refreshSuccess')
|
||||||
updateAccessToken(obj, str)
|
updateAccessToken(obj, str)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeState(bool: boolean) {
|
function changeState(bool: boolean) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user