feat: application operate api (#3176)

This commit is contained in:
shaohuzhang1 2025-05-30 20:02:39 +08:00 committed by GitHub
parent b16353fbe8
commit 43654bddaf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
144 changed files with 1628 additions and 290 deletions

View File

@ -12,9 +12,9 @@ from drf_spectacular.utils import OpenApiParameter
from rest_framework import serializers from rest_framework import serializers
from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \ from application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \
ApplicationQueryRequest ApplicationImportRequest, ApplicationEditSerializer
from common.mixins.api_mixin import APIMixin from common.mixins.api_mixin import APIMixin
from common.result import ResultSerializer, ResultPageSerializer from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer
class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest): class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest):
@ -120,3 +120,50 @@ class ApplicationCreateAPI(APIMixin):
@staticmethod @staticmethod
def get_response(): def get_response():
return ApplicationCreateResponse return ApplicationCreateResponse
class ApplicationImportAPI(APIMixin):
@staticmethod
def get_parameters():
ApplicationCreateAPI.get_parameters()
@staticmethod
def get_request():
return ApplicationImportRequest
class ApplicationOperateAPI(APIMixin):
@staticmethod
def get_parameters():
return [
OpenApiParameter(
name="workspace_id",
description="工作空间id",
type=OpenApiTypes.STR,
location='path',
required=True,
),
OpenApiParameter(
name="application_id",
description="应用id",
type=OpenApiTypes.STR,
location='path',
required=True,
)
]
class ApplicationExportAPI(APIMixin):
@staticmethod
def get_parameters():
return ApplicationOperateAPI.get_parameters()
@staticmethod
def get_response():
return DefaultResultSerializer
class ApplicationEditAPI(APIMixin):
@staticmethod
def get_request():
return ApplicationEditSerializer

View File

@ -8,3 +8,5 @@
""" """
from .application import * from .application import *
from .application_access_token import * from .application_access_token import *
from .application_chat import *
from .application_api_key import *

View File

@ -117,3 +117,16 @@ class ApplicationKnowledgeMapping(AppModelMixin):
class Meta: class Meta:
db_table = "application_knowledge_mapping" db_table = "application_knowledge_mapping"
class WorkFlowVersion(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
application = models.ForeignKey(Application, on_delete=models.CASCADE)
workspace_id = models.CharField(max_length=64, verbose_name="工作空间id", default="default", db_index=True)
name = models.CharField(verbose_name="版本名称", max_length=128, default="")
publish_user_id = models.UUIDField(verbose_name="发布者id", max_length=128, default=None, null=True)
publish_user_name = models.CharField(verbose_name="发布者名称", max_length=128, default="")
work_flow = models.JSONField(verbose_name="工作流数据", default=dict)
class Meta:
db_table = "application_work_flow_version"

View File

@ -1,4 +1,3 @@
import uuid import uuid
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
@ -6,7 +5,6 @@ from django.db import models
from application.models import Application from application.models import Application
from common.mixins.app_model_mixin import AppModelMixin from common.mixins.app_model_mixin import AppModelMixin
from users.models import User from users.models import User
@ -23,4 +21,19 @@ class ApplicationApiKey(AppModelMixin):
, default=list) , default=list)
class Meta: class Meta:
db_table = "application_api_key" db_table = "application_api_key"
class ApplicationPublicAccessClient(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
client_id = models.UUIDField(max_length=128, default=uuid.uuid1, verbose_name="公共访问链接客户端id")
client_type = models.CharField(max_length=64, verbose_name="客户端类型")
application = models.ForeignKey(Application, on_delete=models.CASCADE, verbose_name="应用id")
access_num = models.IntegerField(default=0, verbose_name="访问总次数次数")
intraday_access_num = models.IntegerField(default=0, verbose_name="当日访问次数")
class Meta:
db_table = "application_public_access_client"
indexes = [
models.Index(fields=['application_id', 'client_id']),
]

View File

@ -0,0 +1,83 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file application_chat_log.py
@date2025/5/29 17:12
@desc:
"""
import uuid_utils.compat as uuid
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _
from langchain_core.messages import HumanMessage, AIMessage
from application.models import Application
from common.encoder.encoder import SystemEncoder
from common.mixins.app_model_mixin import AppModelMixin
def default_asker():
return {'user_name': '游客'}
class Chat(AppModelMixin):
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid7(), editable=False, verbose_name="主键id")
application = models.ForeignKey(Application, on_delete=models.CASCADE)
abstract = models.CharField(max_length=1024, verbose_name="摘要")
asker = models.JSONField(verbose_name="访问者", default=default_asker, encoder=SystemEncoder)
client_id = models.UUIDField(verbose_name="客户端id", default=None, null=True)
is_deleted = models.BooleanField(verbose_name="", default=False)
class Meta:
db_table = "application_chat"
class VoteChoices(models.TextChoices):
"""订单类型"""
UN_VOTE = "-1", '未投票'
STAR = "0", '赞同'
TRAMPLE = "1", '反对'
class ChatRecord(AppModelMixin):
"""
对话日志 详情
"""
id = models.UUIDField(primary_key=True, max_length=128, default=uuid.uuid1, editable=False, verbose_name="主键id")
chat = models.ForeignKey(Chat, on_delete=models.CASCADE)
vote_status = models.CharField(verbose_name='投票', max_length=10, choices=VoteChoices.choices,
default=VoteChoices.UN_VOTE)
problem_text = models.CharField(max_length=10240, verbose_name="问题")
answer_text = models.CharField(max_length=40960, verbose_name="答案")
answer_text_list = ArrayField(verbose_name="改进标注列表",
base_field=models.JSONField()
, default=list)
message_tokens = models.IntegerField(verbose_name="请求token数量", default=0)
answer_tokens = models.IntegerField(verbose_name="响应token数量", default=0)
const = models.IntegerField(verbose_name="总费用", default=0)
details = models.JSONField(verbose_name="对话详情", default=dict, encoder=SystemEncoder)
improve_paragraph_id_list = ArrayField(verbose_name="改进标注列表",
base_field=models.UUIDField(max_length=128, blank=True)
, default=list)
run_time = models.FloatField(verbose_name="运行时长", default=0)
index = models.IntegerField(verbose_name="对话下标")
def get_human_message(self):
if 'problem_padding' in self.details:
return HumanMessage(content=self.details.get('problem_padding').get('padding_problem_text'))
return HumanMessage(content=self.problem_text)
def get_ai_message(self):
answer_text = self.answer_text
if answer_text is None or len(str(answer_text).strip()) == 0:
answer_text = _(
'Sorry, no relevant content was found. Please re-describe your problem or provide more information. ')
return AIMessage(content=answer_text)
def get_node_details_runtime_node_id(self, runtime_node_id):
return self.details.get(runtime_node_id, None)
class Meta:
db_table = "application_chat_record"

View File

@ -6,28 +6,64 @@
@date2025/5/26 17:03 @date2025/5/26 17:03
@desc: @desc:
""" """
import datetime
import hashlib import hashlib
import os import os
import pickle
import re import re
from typing import Dict from typing import Dict, List
import uuid_utils.compat as uuid import uuid_utils.compat as uuid
from django.core import validators from django.core import validators
from django.db import models from django.db import models, transaction
from django.db.models import QuerySet from django.db.models import QuerySet
from django.http import HttpResponse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers, status
from rest_framework.utils.formatting import lazy_format
from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \ from application.models.application import Application, ApplicationTypeChoices, ApplicationKnowledgeMapping, \
ApplicationFolder ApplicationFolder, WorkFlowVersion
from application.models.application_access_token import ApplicationAccessToken from application.models.application_access_token import ApplicationAccessToken
from chat.flow.workflow_manage import Flow
from common import result
from common.database_model_manage.database_model_manage import DatabaseModelManage from common.database_model_manage.database_model_manage import DatabaseModelManage
from common.db.search import native_search, native_page_search from common.db.search import native_search, native_page_search
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.utils.common import get_file_content from common.field.common import UploadedFileField
from common.utils.common import get_file_content, valid_license, restricted_loads
from knowledge.models import Knowledge from knowledge.models import Knowledge
from maxkb.conf import PROJECT_DIR from maxkb.conf import PROJECT_DIR
from models_provider.models import Model from models_provider.models import Model
from tools.models import Tool, ToolScope
from tools.serializers.tool import ToolModelSerializer
from users.models import User
def get_base_node_work_flow(work_flow):
node_list = work_flow.get('nodes')
base_node_list = [node for node in node_list if node.get('id') == 'base-node']
if len(base_node_list) > 0:
return base_node_list[-1]
return None
class MKInstance:
def __init__(self, application: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):
self.application = application
self.function_lib_list = function_lib_list
self.version = version
self.tool_list = tool_list
def get_tool_list(self):
return [*(self.tool_list or []), *(self.function_lib_list or [])]
class ApplicationSerializerModel(serializers.ModelSerializer):
class Meta:
model = Application
fields = "__all__"
class NoReferencesChoices(models.TextChoices): class NoReferencesChoices(models.TextChoices):
@ -307,6 +343,55 @@ class Query(serializers.Serializer):
) )
class ApplicationImportRequest(serializers.Serializer):
file = UploadedFileField(required=True, label=_("file"))
class ApplicationEditSerializer(serializers.Serializer):
name = serializers.CharField(required=False, max_length=64, min_length=1,
label=_("Application Name"))
desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True,
label=_("Application Description"))
model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True,
label=_("Model"))
dialogue_number = serializers.IntegerField(required=False,
min_value=0,
max_value=1024,
label=_("Historical chat records"))
prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
label=_("Opening remarks"))
dataset_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
label=_("Related Knowledge Base")
)
# 数据集相关设置
knowledge_setting = KnowledgeSettingSerializer(required=False, allow_null=True,
label=_("Dataset settings"))
# 模型相关设置
model_setting = ModelSettingSerializer(required=False, allow_null=True,
label=_("Model setup"))
# 问题补全
problem_optimization = serializers.BooleanField(required=False, allow_null=True,
label=_("Question completion"))
icon = serializers.CharField(required=False, allow_null=True, label=_("Icon"))
model_params_setting = serializers.DictField(required=False,
label=_('Model parameters'))
tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled'))
tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID"))
tts_type = serializers.CharField(required=False, label=_('Voice playback type'))
tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay'))
stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled'))
stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID'))
stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission'))
class ApplicationSerializer(serializers.Serializer): class ApplicationSerializer(serializers.Serializer):
workspace_id = serializers.CharField(required=True, label=_('workspace id')) workspace_id = serializers.CharField(required=True, label=_('workspace id'))
user_id = serializers.UUIDField(required=True, label=_("User ID")) user_id = serializers.UUIDField(required=True, label=_("User ID"))
@ -352,3 +437,281 @@ class ApplicationSerializer(serializers.Serializer):
# 插入关联数据 # 插入关联数据
QuerySet(ApplicationKnowledgeMapping).bulk_create(application_knowledge_mapping_model_list) QuerySet(ApplicationKnowledgeMapping).bulk_create(application_knowledge_mapping_model_list)
return ApplicationCreateSerializer.ApplicationResponse(application_model).data return ApplicationCreateSerializer.ApplicationResponse(application_model).data
@valid_license(model=Application, count=5,
message=_(
'The community version supports up to 5 applications. If you need more applications, please contact us (https://fit2cloud.com/).'))
@transaction.atomic
def import_(self, instance: dict, with_valid=True):
if with_valid:
self.is_valid()
ApplicationImportRequest(data=instance).is_valid(raise_exception=True)
user_id = self.data.get('user_id')
workspace_id = self.data.get("workspace_id")
mk_instance_bytes = instance.get('file').read()
try:
mk_instance = restricted_loads(mk_instance_bytes)
except Exception as e:
raise AppApiException(1001, _("Unsupported file format"))
application = mk_instance.application
tool_list = mk_instance.get_tool_list()
if len(tool_list) > 0:
tool_id_list = [tool.get('id') for tool in tool_list]
exits_tool_id_list = [str(tool.id) for tool in
QuerySet(Tool).filter(id__in=tool_id_list)]
# 获取到需要插入的函数
tool_list = [tool for tool in tool_id_list if
not exits_tool_id_list.__contains__(tool.get('id'))]
application_model = self.to_application(application, workspace_id, user_id)
tool_model_list = [self.to_tool(f, workspace_id, user_id) for f in tool_list]
application_model.save()
# 插入认证信息
ApplicationAccessToken(application_id=application_model.id,
access_token=hashlib.md5(str(uuid.uuid1()).encode()).hexdigest()[8:24]).save()
QuerySet(Tool).bulk_create(tool_model_list) if len(tool_model_list) > 0 else None
return True
@staticmethod
def to_tool(tool, workspace_id, user_id):
"""
@param workspace_id:
@param user_id: 用户id
@param tool: 工具
@return:
"""
return Tool(id=tool.get('id'),
user_id=user_id,
name=tool.get('name'),
code=tool.get('code'),
input_field_list=tool.get('input_field_list'),
is_active=tool.get('is_active'),
scope=ToolScope.WORKSPACE,
workspace_id=workspace_id)
@staticmethod
def to_application(application, workspace_id, user_id):
work_flow = application.get('work_flow')
for node in work_flow.get('nodes', []):
if node.get('type') == 'search-dataset-node':
node.get('properties', {}).get('node_data', {})['dataset_id_list'] = []
return Application(id=uuid.uuid1(),
user_id=user_id,
name=application.get('name'),
workspace_id=workspace_id,
desc=application.get('desc'),
prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number'),
dataset_setting=application.get('dataset_setting'),
model_setting=application.get('model_setting'),
model_params_setting=application.get('model_params_setting'),
tts_model_params_setting=application.get('tts_model_params_setting'),
problem_optimization=application.get('problem_optimization'),
icon="/ui/favicon.ico",
work_flow=work_flow,
type=application.get('type'),
problem_optimization_prompt=application.get('problem_optimization_prompt'),
tts_model_enable=application.get('tts_model_enable'),
stt_model_enable=application.get('stt_model_enable'),
tts_type=application.get('tts_type'),
clean_time=application.get('clean_time'),
file_upload_enable=application.get('file_upload_enable'),
file_upload_setting=application.get('file_upload_setting'),
)
class ApplicationOperateSerializer(serializers.Serializer):
application_id = serializers.UUIDField(required=True, label=_("Application ID"))
user_id = serializers.UUIDField(required=True, label=_("User ID"))
def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True)
if not QuerySet(Application).filter(id=self.data.get('application_id')).exists():
raise AppApiException(500, _('Application id does not exist'))
def delete(self, with_valid=True):
if with_valid:
self.is_valid()
QuerySet(Application).filter(id=self.data.get('application_id')).delete()
return True
def export(self, with_valid=True):
try:
if with_valid:
self.is_valid()
application_id = self.data.get('application_id')
application = QuerySet(Application).filter(id=application_id).first()
tool_id_list = [node.get('properties', {}).get('node_data', {}).get('tool_id') for node
in
application.work_flow.get('nodes', []) if
node.get('type') == 'tool-node']
tool_list = []
if len(tool_id_list) > 0:
tool_list = QuerySet(Tool).filter(id__in=tool_id_list)
application_dict = ApplicationSerializerModel(application).data
mk_instance = MKInstance(application_dict,
[],
'v2',
[ToolModelSerializer(tool).data for tool in
tool_list])
application_pickle = pickle.dumps(mk_instance)
response = HttpResponse(content_type='text/plain', content=application_pickle)
response['Content-Disposition'] = f'attachment; filename="{application.name}.mk"'
return response
except Exception as e:
return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@transaction.atomic
def publish(self, instance, with_valid=True):
if with_valid:
self.is_valid()
user_id = self.data.get('user_id')
workspace_id = self.data.get("workspace_id")
user = QuerySet(User).filter(id=user_id).first()
application = QuerySet(Application).filter(id=self.data.get("application_id"),
workspace_id=workspace_id).first()
work_flow = instance.get('work_flow')
if work_flow is None:
raise AppApiException(500, _("work_flow is a required field"))
Flow.new_instance(work_flow).is_valid()
base_node = get_base_node_work_flow(work_flow)
if base_node is not None:
node_data = base_node.get('properties').get('node_data')
if node_data is not None:
application.name = node_data.get('name')
application.desc = node_data.get('desc')
application.prologue = node_data.get('prologue')
application.work_flow = work_flow
application.save()
work_flow_version = WorkFlowVersion(work_flow=work_flow, application=application,
name=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
publish_user_id=user_id,
publish_user_name=user.username,
workspace_id=workspace_id)
work_flow_version.save()
return True
@staticmethod
def update_work_flow_model(instance):
if 'nodes' not in instance.get('work_flow'):
return
nodes = instance.get('work_flow')['nodes']
for node in nodes:
if node['id'] == 'base-node':
node_data = node['properties']['node_data']
if 'stt_model_id' in node_data:
instance['stt_model_id'] = node_data['stt_model_id']
if 'tts_model_id' in node_data:
instance['tts_model_id'] = node_data['tts_model_id']
if 'stt_model_enable' in node_data:
instance['stt_model_enable'] = node_data['stt_model_enable']
if 'tts_model_enable' in node_data:
instance['tts_model_enable'] = node_data['tts_model_enable']
if 'tts_type' in node_data:
instance['tts_type'] = node_data['tts_type']
if 'tts_autoplay' in node_data:
instance['tts_autoplay'] = node_data['tts_autoplay']
if 'stt_autosend' in node_data:
instance['stt_autosend'] = node_data['stt_autosend']
if 'tts_model_params_setting' in node_data:
instance['tts_model_params_setting'] = node_data['tts_model_params_setting']
if 'file_upload_enable' in node_data:
instance['file_upload_enable'] = node_data['file_upload_enable']
if 'file_upload_setting' in node_data:
instance['file_upload_setting'] = node_data['file_upload_setting']
if 'name' in node_data:
instance['name'] = node_data['name']
break
@transaction.atomic
def edit(self, instance: Dict, with_valid=True):
if with_valid:
self.is_valid()
ApplicationEditSerializer(data=instance).is_valid(
raise_exception=True)
application_id = self.data.get("application_id")
application = QuerySet(Application).get(id=application_id)
if instance.get('model_id') is None or len(instance.get('model_id')) == 0:
application.model_id = None
else:
model = QuerySet(Model).filter(
id=instance.get('model_id')).first()
if model is None:
raise AppApiException(500, _("Model does not exist"))
if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0:
application.stt_model_id = None
else:
model = QuerySet(Model).filter(
id=instance.get('stt_model_id')).first()
if model is None:
raise AppApiException(500, _("Model does not exist"))
if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0:
application.tts_model_id = None
else:
model = QuerySet(Model).filter(
id=instance.get('tts_model_id')).first()
if model is None:
raise AppApiException(500, _("Model does not exist"))
if 'work_flow' in instance:
# 修改语音配置相关
self.update_work_flow_model(instance)
update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
'dataset_setting', 'model_setting', 'problem_optimization', 'dialogue_number',
'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type',
'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting',
'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting',
'problem_optimization_prompt', 'clean_time']
for update_key in update_keys:
if update_key in instance and instance.get(update_key) is not None:
application.__setattr__(update_key, instance.get(update_key))
print(application.name)
application.save()
if 'knowledge_id_list' in instance:
knowledge_id_list = instance.get('knowledge_id_list')
# 当前用户可修改关联的知识库列表
application_knowledge_id_list = [str(knowledge.id) for knowledge in
self.list_knowledge(with_valid=False)]
for dataset_id in knowledge_id_list:
if not application_knowledge_id_list.__contains__(dataset_id):
message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'),
dataset_id=dataset_id)
raise AppApiException(500, message)
self.save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id)
return self.one(with_valid=False)
def one(self, with_valid=True):
if with_valid:
self.is_valid()
application_id = self.data.get("application_id")
application = QuerySet(Application).get(id=application_id)
dataset_list = self.list_knowledge(with_valid=False)
mapping_knowledge_id_list = [akm.knowledge_id for akm in
QuerySet(ApplicationKnowledgeMapping).filter(application_id=application_id)]
knowledge_id_list = [d.get('id') for d in
list(filter(lambda row: mapping_knowledge_id_list.__contains__(row.get('id')),
dataset_list))]
return {**ApplicationSerializerModel(application).data,
'dataset_id_list': knowledge_id_list}
def list_knowledge(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
workspace_id = self.data.get("workspace_id")
knowledge_list = QuerySet(Knowledge).filter(workspace_id=workspace_id)
return knowledge_list
@staticmethod
def save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id):
# 需要排除已删除的数据集
knowledge_id_list = [knowledge.id for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list)]
# 删除已经关联的id
QuerySet(ApplicationKnowledgeMapping).filter(knowledge_id__in=application_knowledge_id_list,
application_id=application_id).delete()
# 插入
QuerySet(ApplicationKnowledgeMapping).bulk_create(
[ApplicationKnowledgeMapping(application_id=application_id, dataset_id=dataset_id) for dataset_id in
knowledge_id_list]) if len(knowledge_id_list) > 0 else None

View File

@ -1,10 +1,9 @@
import hashlib import hashlib
import uuid_utils.compat as uuid
from baidubce.services.bmr.bmr_client import application
import uuid_utils.compat as uuid
from django.db.models import QuerySet from django.db.models import QuerySet
from rest_framework import serializers
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.models import Application from application.models import Application
from application.models.application_api_key import ApplicationApiKey from application.models.application_api_key import ApplicationApiKey
@ -16,6 +15,7 @@ class ApplicationKeySerializerModel(serializers.ModelSerializer):
model = ApplicationApiKey model = ApplicationApiKey
fields = "__all__" fields = "__all__"
class Edit(serializers.Serializer): class Edit(serializers.Serializer):
pass pass
@ -25,10 +25,6 @@ class ApplicationKeySerializer(serializers.Serializer):
workspace_id = serializers.CharField(required=True, label=_('workspace id')) workspace_id = serializers.CharField(required=True, label=_('workspace id'))
application_id = serializers.UUIDField(required=True, label=_('application id')) application_id = serializers.UUIDField(required=True, label=_('application id'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
application_id = self.data.get("application_id") application_id = self.data.get("application_id")
@ -49,21 +45,18 @@ class ApplicationKeySerializer(serializers.Serializer):
application_api_key.save() application_api_key.save()
return ApplicationKeySerializerModel(application_api_key).data return ApplicationKeySerializerModel(application_api_key).data
def list(self,with_valid=True): def list(self, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)
application_id = self.data.get("application_id") application_id = self.data.get("application_id")
return [ApplicationKeySerializerModel(application_api_key).data for application_api_key in return [ApplicationKeySerializerModel(application_api_key).data for application_api_key in
QuerySet(ApplicationApiKey).filter(application_id = application_id)] QuerySet(ApplicationApiKey).filter(application_id=application_id)]
class Operate(serializers.Serializer): class Operate(serializers.Serializer):
user_id = serializers.UUIDField(required=True, label=_('user id')) user_id = serializers.UUIDField(required=True, label=_('user id'))
workspace_id = serializers.CharField(required=True, label=_('workspace id')) workspace_id = serializers.CharField(required=True, label=_('workspace id'))
application_id = serializers.UUIDField(required=True, label=_('application id')) application_id = serializers.UUIDField(required=True, label=_('application id'))
def edit(self, instance, with_valid=True): def edit(self, instance, with_valid=True):
if with_valid: if with_valid:
self.is_valid(raise_exception=True) self.is_valid(raise_exception=True)

View File

@ -5,8 +5,12 @@ from . import views
app_name = 'application' app_name = 'application'
urlpatterns = [ urlpatterns = [
path('workspace/<str:workspace_id>/application', views.Application.as_view(), name='application'), path('workspace/<str:workspace_id>/application', views.Application.as_view(), name='application'),
path('workspace/<str:workspace_id>/application/import', views.Application.Import.as_view()),
path('workspace/<str:workspace_id>/application/<int:current_page>/<int:page_size>', path('workspace/<str:workspace_id>/application/<int:current_page>/<int:page_size>',
views.Application.Page.as_view(), name='application_page'), views.Application.Page.as_view(), name='application_page'),
path('workspace/<str:workspace_id>/application/<str:application_id>/application_key', path('workspace/<str:workspace_id>/application/<str:application_id>/application_key',
views.ApplicationKey.as_view())] views.ApplicationKey.as_view()),
path('workspace/<str:workspace_id>/application/<str:application_id>/export', views.Application.Export.as_view()),
]

View File

@ -8,11 +8,14 @@
""" """
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.views import APIView from rest_framework.views import APIView
from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI, ApplicationImportAPI, \
from application.serializers.application import ApplicationSerializer, Query ApplicationExportAPI, ApplicationOperateAPI, ApplicationEditAPI
from application.serializers.application import ApplicationSerializer, Query, ApplicationOperateSerializer
from common import result from common import result
from common.auth import TokenAuth from common.auth import TokenAuth
from common.auth.authentication import has_permissions from common.auth.authentication import has_permissions
@ -67,3 +70,92 @@ class Application(APIView):
return result.success( return result.success(
Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size, Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size,
request.query_params)) request.query_params))
class Import(APIView):
authentication_classes = [TokenAuth]
parser_classes = [MultiPartParser]
@extend_schema(
methods=['POST'],
description=_('Import Application'),
summary=_('Import Application'),
operation_id=_('Import Application'), # type: ignore
parameters=ApplicationImportAPI.get_parameters(),
request=ApplicationImportAPI.get_request(),
responses=result.DefaultResultSerializer,
tags=[_('Application')] # type: ignore
)
@has_permissions(PermissionConstants.APPLICATION_READ)
def post(self, request: Request, workspace_id: str):
return result.success(ApplicationSerializer(
data={'user_id': request.user.id, 'workspace_id': workspace_id,
}).import_({'file': request.FILES.get('file')}))
class Export(APIView):
authentication_classes = [TokenAuth]
@action(methods=['POST'], detail=False)
@extend_schema(
methods=['POST'],
description=_('Export conversation'),
summary=_('Export conversation'),
operation_id=_('Export conversation'), # type: ignore
parameters=ApplicationExportAPI.get_parameters(),
responses=ApplicationExportAPI.get_response(),
tags=[_('Application')] # type: ignore
)
@has_permissions(PermissionConstants.APPLICATION_EXPORT.get_workspace_application_permission())
def post(self, request: Request, workspace_id: str, application_id: str):
return ApplicationOperateSerializer(
data={'application_id': application_id,
'user_id': request.user.id}).export(request.data)
class Operate(APIView):
authentication_classes = [TokenAuth]
@extend_schema(
methods=['DELETE'],
description=_('Deleting application'),
summary=_('Deleting application'),
operation_id=_('Deleting application'), # type: ignore
parameters=ApplicationOperateAPI.get_parameters(),
responses=result.DefaultResultSerializer,
tags=[_('Application')] # type: ignore
)
@has_permissions(PermissionConstants.APPLICATION_DELETE.get_workspace_application_permission())
def delete(self, request: Request, application_id: str):
return result.success(ApplicationOperateSerializer(
data={'application_id': application_id, 'user_id': request.user.id}).delete(
with_valid=True))
@extend_schema(
methods=['PUT'],
description=_('Modify the application'),
summary=_('Modify the application'),
operation_id=_('Modify the application'), # type: ignore
parameters=ApplicationOperateAPI.get_parameters(),
request=ApplicationEditAPI.get_request(),
responses=ApplicationCreateAPI.get_response(),
tags=[_('Application')] # type: ignore
)
@has_permissions(PermissionConstants.APPLICATION_EDIT.get_workspace_application_permission())
def put(self, request: Request, application_id: str):
return result.success(
ApplicationOperateSerializer(
data={'application_id': application_id, 'user_id': request.user.id}).edit(
request.data))
@extend_schema(
methods=['PUT'],
description=_('Get application details'),
summary=_('Get application details'),
operation_id=_('Get application details'), # type: ignore
parameters=ApplicationOperateAPI.get_parameters(),
request=ApplicationEditAPI.get_request(),
responses=result.DefaultResultSerializer,
tags=[_('Application')] # type: ignore
)
@has_permissions(PermissionConstants.WORKSPACE_READ.get_workspace_application_permission())
def get(self, request: Request, application_id: str):
return result.success(ApplicationOperateSerializer(
data={'application_id': application_id, 'user_id': request.user.id}).one())

0
apps/chat/__init__.py Normal file
View File

3
apps/chat/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,47 @@
# coding=utf-8
"""
@project: MaxKB
@Author虎虎
@file chat_embed_api.py
@date2025/5/30 15:25
@desc:
"""
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter
from common.mixins.api_mixin import APIMixin
from django.utils.translation import gettext_lazy as _
from common.result import DefaultResultSerializer
class ChatEmbedAPI(APIMixin):
@staticmethod
def get_parameters():
return [
OpenApiParameter(
name="host",
description=_("host"),
type=OpenApiTypes.STR,
location='query',
required=False,
),
OpenApiParameter(
name="protocol",
description=_("protocol"),
type=OpenApiTypes.STR,
location='query',
required=False,
),
OpenApiParameter(
name="token",
description=_("token"),
type=OpenApiTypes.STR,
location='query',
required=False,
)
]
@staticmethod
def get_response():
return DefaultResultSerializer

6
apps/chat/apps.py Normal file
View File

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

View File

@ -25,9 +25,9 @@ from application.chat_pipeline.I_base_chat_pipeline import ParagraphPipelineMode
from application.chat_pipeline.pipeline_manage import PipelineManage from application.chat_pipeline.pipeline_manage import PipelineManage
from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler from application.chat_pipeline.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
from application.flow.tools import Reasoning from application.flow.tools import Reasoning
from application.models.api_key_model import ApplicationPublicAccessClient from application.models.application_api_key import ApplicationPublicAccessClient
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from setting.models_provider.tools import get_model_instance_by_model_user_id from models_provider.tools import get_model_instance_by_model_user_id
def add_access_num(client_id=None, client_type=None, application_id=None): def add_access_num(client_id=None, client_type=None, application_id=None):

View File

@ -17,14 +17,13 @@ from django.db.models import QuerySet
from rest_framework import serializers from rest_framework import serializers
from rest_framework.exceptions import ValidationError, ErrorDetail from rest_framework.exceptions import ValidationError, ErrorDetail
from application.flow.common import Answer, NodeChunk from chat.flow.common import Answer, NodeChunk
from application.models import ChatRecord from application.models import ChatRecord
from application.models.api_key_model import ApplicationPublicAccessClient from application.models import ApplicationPublicAccessClient
from common.constants.authentication_type import AuthenticationType from common.constants.authentication_type import AuthenticationType
from common.field.common import InstanceField from common.field.common import InstanceField
from common.util.field_message import ErrMessage
chat_cache = cache.caches['chat_cache'] chat_cache = cache
def write_context(step_variable: Dict, global_variable: Dict, node, workflow): def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
@ -123,31 +122,31 @@ class NodeResult:
class ReferenceAddressSerializer(serializers.Serializer): class ReferenceAddressSerializer(serializers.Serializer):
node_id = serializers.CharField(required=True, error_messages=ErrMessage.char("节点id")) node_id = serializers.CharField(required=True, label="节点id")
fields = serializers.ListField( fields = serializers.ListField(
child=serializers.CharField(required=True, error_messages=ErrMessage.char("节点字段")), required=True, child=serializers.CharField(required=True, label="节点字段"), required=True,
error_messages=ErrMessage.list("节点字段数组")) label="节点字段数组")
class FlowParamsSerializer(serializers.Serializer): class FlowParamsSerializer(serializers.Serializer):
# 历史对答 # 历史对答
history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True), history_chat_record = serializers.ListField(child=InstanceField(model_type=ChatRecord, required=True),
error_messages=ErrMessage.list("历史对答")) label="历史对答")
question = serializers.CharField(required=True, error_messages=ErrMessage.list("用户问题")) question = serializers.CharField(required=True, label="用户问题")
chat_id = serializers.CharField(required=True, error_messages=ErrMessage.list("对话id")) chat_id = serializers.CharField(required=True, label="对话id")
chat_record_id = serializers.CharField(required=True, error_messages=ErrMessage.char("对话记录id")) chat_record_id = serializers.CharField(required=True, label="对话记录id")
stream = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("流式输出")) stream = serializers.BooleanField(required=True, label="流式输出")
client_id = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端id")) client_id = serializers.CharField(required=False, label="客户端id")
client_type = serializers.CharField(required=False, error_messages=ErrMessage.char("客户端类型")) client_type = serializers.CharField(required=False, label="客户端类型")
user_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid("用户id")) user_id = serializers.UUIDField(required=True, label="用户id")
re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("换个答案")) re_chat = serializers.BooleanField(required=True, label="换个答案")
class INode: class INode:

View File

@ -11,31 +11,29 @@ from typing import Type
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
class ChatNodeSerializer(serializers.Serializer): class ChatNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, label=_("Model id"))
system = serializers.CharField(required=False, allow_blank=True, allow_null=True, system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Role Setting"))) label=_("Role Setting"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, label=_("Prompt word"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer( dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
_("Number of multi-round conversations")))
is_result = serializers.BooleanField(required=False, is_result = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_('Whether to return content'))) label=_('Whether to return content'))
model_params_setting = serializers.DictField(required=False, model_params_setting = serializers.DictField(required=False,
error_messages=ErrMessage.dict(_("Model parameter settings"))) label=_("Model parameter settings"))
model_setting = serializers.DictField(required=False, model_setting = serializers.DictField(required=False,
error_messages=ErrMessage.dict('Model settings')) label='Model settings')
dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True, dialogue_type = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Context Type"))) label=_("Context Type"))
mcp_enable = serializers.BooleanField(required=False, mcp_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_("Whether to enable MCP"))) label=_("Whether to enable MCP"))
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server"))) mcp_servers = serializers.JSONField(required=False, label=_("MCP Server"))
class IChatNode(INode): class IChatNode(INode):

View File

@ -20,12 +20,11 @@ from langchain_core.messages import BaseMessage, AIMessage, AIMessageChunk, Tool
from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent from langgraph.prebuilt import create_react_agent
from application.flow.i_step_node import NodeResult, INode from chat.flow.i_step_node import NodeResult, INode
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode from chat.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
from application.flow.tools import Reasoning from chat.flow.tools import Reasoning
from setting.models import Model from models_provider.models import Model
from setting.models_provider import get_model_credential from models_provider.tools import get_model_credential, get_model_instance_by_model_user_id
from setting.models_provider.tools import get_model_instance_by_model_user_id
tool_message_template = """ tool_message_template = """
<details> <details>

View File

@ -3,25 +3,24 @@ from typing import Type
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class ApplicationNodeSerializer(serializers.Serializer): class ApplicationNodeSerializer(serializers.Serializer):
application_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Application ID"))) application_id = serializers.CharField(required=True, label=_("Application ID"))
question_reference_address = serializers.ListField(required=True, question_reference_address = serializers.ListField(required=True,
error_messages=ErrMessage.list(_("User Questions"))) label=_("User Questions"))
api_input_field_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("API Input Fields"))) api_input_field_list = serializers.ListField(required=False, label=_("API Input Fields"))
user_input_field_list = serializers.ListField(required=False, user_input_field_list = serializers.ListField(required=False,
error_messages=ErrMessage.uuid(_("User Input Fields"))) label=_("User Input Fields"))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) image_list = serializers.ListField(required=False, label=_("picture"))
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) document_list = serializers.ListField(required=False, label=_("document"))
audio_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Audio"))) audio_list = serializers.ListField(required=False, label=_("Audio"))
child_node = serializers.DictField(required=False, allow_null=True, child_node = serializers.DictField(required=False, allow_null=True,
error_messages=ErrMessage.dict(_("Child Nodes"))) label=_("Child Nodes"))
node_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data"))) node_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
class IApplicationNode(INode): class IApplicationNode(INode):

View File

@ -5,9 +5,9 @@ import time
import uuid import uuid
from typing import Dict, List from typing import Dict, List
from application.flow.common import Answer from chat.flow.common import Answer
from application.flow.i_step_node import NodeResult, INode from chat.flow.i_step_node import NodeResult, INode
from application.flow.step_node.application_node.i_application_node import IApplicationNode from chat.flow.step_node.application_node.i_application_node import IApplicationNode
from application.models import Chat from application.models import Chat

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class ContainCompare(Compare): class ContainCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class EqualCompare(Compare): class EqualCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class GECompare(Compare): class GECompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class GTCompare(Compare): class GTCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare import Compare from chat.flow.step_node.condition_node.compare import Compare
class IsNotNullCompare(Compare): class IsNotNullCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare import Compare from chat.flow.step_node.condition_node.compare import Compare
class IsNotTrueCompare(Compare): class IsNotTrueCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare import Compare from chat.flow.step_node.condition_node.compare import Compare
class IsNullCompare(Compare): class IsNullCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare import Compare from chat.flow.step_node.condition_node.compare import Compare
class IsTrueCompare(Compare): class IsTrueCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LECompare(Compare): class LECompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LenEqualCompare(Compare): class LenEqualCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LenGECompare(Compare): class LenGECompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LenGTCompare(Compare): class LenGTCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LenLECompare(Compare): class LenLECompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LenLTCompare(Compare): class LenLTCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class LTCompare(Compare): class LTCompare(Compare):

View File

@ -8,7 +8,7 @@
""" """
from typing import List from typing import List
from application.flow.step_node.condition_node.compare.compare import Compare from chat.flow.step_node.condition_node.compare.compare import Compare
class NotContainCompare(Compare): class NotContainCompare(Compare):

View File

@ -11,20 +11,19 @@ from typing import Type
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode from chat.flow.i_step_node import INode
from common.util.field_message import ErrMessage
class ConditionSerializer(serializers.Serializer): class ConditionSerializer(serializers.Serializer):
compare = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Comparator"))) compare = serializers.CharField(required=True, label=_("Comparator"))
value = serializers.CharField(required=True, error_messages=ErrMessage.char(_("value"))) value = serializers.CharField(required=True, label=_("value"))
field = serializers.ListField(required=True, error_messages=ErrMessage.char(_("Fields"))) field = serializers.ListField(required=True, label=_("Fields"))
class ConditionBranchSerializer(serializers.Serializer): class ConditionBranchSerializer(serializers.Serializer):
id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch id"))) id = serializers.CharField(required=True, label=_("Branch id"))
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Branch Type"))) type = serializers.CharField(required=True, label=_("Branch Type"))
condition = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Condition or|and"))) condition = serializers.CharField(required=True, label=_("Condition or|and"))
conditions = ConditionSerializer(many=True) conditions = ConditionSerializer(many=True)

View File

@ -8,9 +8,9 @@
""" """
from typing import List from typing import List
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.condition_node.compare import compare_handle_list from chat.flow.step_node.condition_node.compare import compare_handle_list
from application.flow.step_node.condition_node.i_condition_node import IConditionNode from chat.flow.step_node.condition_node.i_condition_node import IConditionNode
class BaseConditionNode(IConditionNode): class BaseConditionNode(IConditionNode):

View File

@ -10,18 +10,19 @@ from typing import Type
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class ReplyNodeParamsSerializer(serializers.Serializer): class ReplyNodeParamsSerializer(serializers.Serializer):
reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type"))) reply_type = serializers.CharField(required=True, label=_("Response Type"))
fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field"))) fields = serializers.ListField(required=False, label=_("Reference Field"))
content = serializers.CharField(required=False, allow_blank=True, allow_null=True, content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Direct answer content"))) label=_("Direct answer content"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)

View File

@ -8,8 +8,8 @@
""" """
from typing import List from typing import List
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.direct_reply_node.i_reply_node import IReplyNode from chat.flow.step_node.direct_reply_node.i_reply_node import IReplyNode
class BaseReplyNode(IReplyNode): class BaseReplyNode(IReplyNode):

View File

@ -5,12 +5,11 @@ from typing import Type
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
class DocumentExtractNodeSerializer(serializers.Serializer): class DocumentExtractNodeSerializer(serializers.Serializer):
document_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("document"))) document_list = serializers.ListField(required=False, label=_("document"))
class IDocumentExtractNode(INode): class IDocumentExtractNode(INode):

View File

@ -5,11 +5,11 @@ import mimetypes
from django.core.files.uploadedfile import InMemoryUploadedFile from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db.models import QuerySet from django.db.models import QuerySet
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode from chat.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
from dataset.models import File from knowledge.models import File
from dataset.serializers.document_serializers import split_handles, parse_table_handle_list, FileBufferHandle from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle
from dataset.serializers.file_serializers import FileSerializer from knowledge.serializers.file import FileSerializer
def bytes_to_uploaded_file(file_bytes, file_name="file.txt"): def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
@ -37,11 +37,11 @@ def bytes_to_uploaded_file(file_bytes, file_name="file.txt"):
splitter = '\n`-----------------------------------`\n' splitter = '\n`-----------------------------------`\n'
class BaseDocumentExtractNode(IDocumentExtractNode): class BaseDocumentExtractNode(IDocumentExtractNode):
def save_context(self, details, workflow_manage): def save_context(self, details, workflow_manage):
self.context['content'] = details.get('content') self.context['content'] = details.get('content')
def execute(self, document, chat_id, **kwargs): def execute(self, document, chat_id, **kwargs):
get_buffer = FileBufferHandle().get_buffer get_buffer = FileBufferHandle().get_buffer

View File

@ -10,15 +10,15 @@ from typing import Type
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class FormNodeParamsSerializer(serializers.Serializer): class FormNodeParamsSerializer(serializers.Serializer):
form_field_list = serializers.ListField(required=True, error_messages=ErrMessage.list(_("Form Configuration"))) form_field_list = serializers.ListField(required=True, label=_("Form Configuration"))
form_content_format = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Form output content'))) form_content_format = serializers.CharField(required=True, label=_('Form output content'))
form_data = serializers.DictField(required=False, allow_null=True, error_messages=ErrMessage.dict(_("Form Data"))) form_data = serializers.DictField(required=False, allow_null=True, label=_("Form Data"))
class IFormNode(INode): class IFormNode(INode):

View File

@ -12,9 +12,9 @@ from typing import Dict, List
from langchain_core.prompts import PromptTemplate from langchain_core.prompts import PromptTemplate
from application.flow.common import Answer from chat.flow.common import Answer
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.form_node.i_form_node import IFormNode from chat.flow.step_node.form_node.i_form_node import IFormNode
def write_context(step_variable: Dict, global_variable: Dict, node, workflow): def write_context(step_variable: Dict, global_variable: Dict, node, workflow):

View File

@ -11,26 +11,27 @@ from typing import Type
from django.db.models import QuerySet from django.db.models import QuerySet
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.field.common import ObjectField from common.field.common import ObjectField
from common.util.field_message import ErrMessage
from function_lib.models.function import FunctionLib from tools.models.tool import Tool
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class InputField(serializers.Serializer): class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) name = serializers.CharField(required=True, label=_('Variable Name'))
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
class FunctionLibNodeParamsSerializer(serializers.Serializer): class FunctionLibNodeParamsSerializer(serializers.Serializer):
function_lib_id = serializers.UUIDField(required=True, error_messages=ErrMessage.uuid(_('Library ID'))) tool_id = serializers.UUIDField(required=True, label=_('Library ID'))
input_field_list = InputField(required=True, many=True) input_field_list = InputField(required=True, many=True)
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
f_lib = QuerySet(FunctionLib).filter(id=self.data.get('function_lib_id')).first() f_lib = QuerySet(Tool).filter(id=self.data.get('tool_id')).first()
if f_lib is None: if f_lib is None:
raise Exception(_('The function has been deleted')) raise Exception(_('The function has been deleted'))

View File

@ -13,13 +13,13 @@ from typing import Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode from chat.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.util.function_code import FunctionExecutor from common.utils.function_code import FunctionExecutor
from common.util.rsa_util import rsa_long_decrypt from common.utils.rsa_util import rsa_long_decrypt
from function_lib.models.function import FunctionLib from maxkb.const import CONFIG
from smartdoc.const import CONFIG from tools.models import Tool
function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
@ -117,7 +117,7 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
self.answer_text = str(details.get('result')) self.answer_text = str(details.get('result'))
def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult: def execute(self, function_lib_id, input_field_list, **kwargs) -> NodeResult:
function_lib = QuerySet(FunctionLib).filter(id=function_lib_id).first() function_lib = QuerySet(Tool).filter(id=function_lib_id).first()
valid_function(function_lib, self.flow_params_serializer.data.get('user_id')) valid_function(function_lib, self.flow_params_serializer.data.get('user_id'))
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'), params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
field.get('is_required'), field.get('is_required'),

View File

@ -12,26 +12,26 @@ from typing import Type
from django.core import validators from django.core import validators
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.exception.app_exception import AppApiException from common.exception.app_exception import AppApiException
from common.field.common import ObjectField from common.field.common import ObjectField
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.utils.formatting import lazy_format from rest_framework.utils.formatting import lazy_format
class InputField(serializers.Serializer): class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name'))) name = serializers.CharField(required=True, label=_('Variable Name'))
is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required"))) is_required = serializers.BooleanField(required=True, label=_("Is this field required"))
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), validators=[ type = serializers.CharField(required=True, label=_("type"), validators=[
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"), validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
message=_("The field only supports string|int|dict|array|float"), code=500) message=_("The field only supports string|int|dict|array|float"), code=500)
]) ])
source = serializers.CharField(required=True, error_messages=ErrMessage.char(_("source")), validators=[ source = serializers.CharField(required=True, label=_("source"), validators=[
validators.RegexValidator(regex=re.compile("^custom|reference$"), validators.RegexValidator(regex=re.compile("^custom|reference$"),
message=_("The field only supports custom|reference"), code=500) message=_("The field only supports custom|reference"), code=500)
]) ])
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list]) value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)
@ -43,8 +43,9 @@ class InputField(serializers.Serializer):
class FunctionNodeParamsSerializer(serializers.Serializer): class FunctionNodeParamsSerializer(serializers.Serializer):
input_field_list = InputField(required=True, many=True) input_field_list = InputField(required=True, many=True)
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function"))) code = serializers.CharField(required=True, label=_("function"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
def is_valid(self, *, raise_exception=False): def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True) super().is_valid(raise_exception=True)

View File

@ -8,14 +8,12 @@
""" """
import json import json
import time import time
from typing import Dict from typing import Dict
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.function_node.i_function_node import IFunctionNode from chat.flow.step_node.function_node.i_function_node import IFunctionNode
from common.exception.app_exception import AppApiException from common.utils.function_code import FunctionExecutor
from common.util.function_code import FunctionExecutor from maxkb.const import CONFIG
from smartdoc.const import CONFIG
function_executor = FunctionExecutor(CONFIG.get('SANDBOX')) function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))

View File

@ -2,31 +2,31 @@
from typing import Type from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ImageGenerateNodeSerializer(serializers.Serializer): class ImageGenerateNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, label=_("Model id"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word (positive)"))) prompt = serializers.CharField(required=True, label=_("Prompt word (positive)"))
negative_prompt = serializers.CharField(required=False, error_messages=ErrMessage.char(_("Prompt word (negative)")), negative_prompt = serializers.CharField(required=False, label=_("Prompt word (negative)"),
allow_null=True, allow_blank=True, ) allow_null=True, allow_blank=True, )
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=False, default=0, dialogue_number = serializers.IntegerField(required=False, default=0,
error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) label=_("Number of multi-round conversations"))
dialogue_type = serializers.CharField(required=False, default='NODE', dialogue_type = serializers.CharField(required=False, default='NODE',
error_messages=ErrMessage.char(_("Conversation storage type"))) label=_("Conversation storage type"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
model_params_setting = serializers.JSONField(required=False, default=dict, model_params_setting = serializers.JSONField(required=False, default=dict,
error_messages=ErrMessage.json(_("Model parameter settings"))) label=_("Model parameter settings"))
class IImageGenerateNode(INode): class IImageGenerateNode(INode):

View File

@ -5,11 +5,11 @@ from typing import List
import requests import requests
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage from langchain_core.messages import BaseMessage, HumanMessage, AIMessage
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode from chat.flow.step_node.image_generate_step_node.i_image_generate_node import IImageGenerateNode
from common.util.common import bytes_to_uploaded_file from common.utils.common import bytes_to_uploaded_file
from dataset.serializers.file_serializers import FileSerializer from knowledge.serializers.file import FileSerializer
from setting.models_provider.tools import get_model_instance_by_model_user_id from models_provider.tools import get_model_instance_by_model_user_id
class BaseImageGenerateNode(IImageGenerateNode): class BaseImageGenerateNode(IImageGenerateNode):

View File

@ -4,27 +4,28 @@ from typing import Type
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class ImageUnderstandNodeSerializer(serializers.Serializer): class ImageUnderstandNodeSerializer(serializers.Serializer):
model_id = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Model id"))) model_id = serializers.CharField(required=True, label=_("Model id"))
system = serializers.CharField(required=False, allow_blank=True, allow_null=True, system = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Role Setting"))) label=_("Role Setting"))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word"))) prompt = serializers.CharField(required=True, label=_("Prompt word"))
# 多轮对话数量 # 多轮对话数量
dialogue_number = serializers.IntegerField(required=True, error_messages=ErrMessage.integer(_("Number of multi-round conversations"))) dialogue_number = serializers.IntegerField(required=True, label=_("Number of multi-round conversations"))
dialogue_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Conversation storage type"))) dialogue_type = serializers.CharField(required=True, label=_("Conversation storage type"))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content'))) is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
image_list = serializers.ListField(required=False, error_messages=ErrMessage.list(_("picture"))) image_list = serializers.ListField(required=False, label=_("picture"))
model_params_setting = serializers.JSONField(required=False, default=dict, model_params_setting = serializers.JSONField(required=False, default=dict,
error_messages=ErrMessage.json(_("Model parameter settings"))) label=_("Model parameter settings"))
class IImageUnderstandNode(INode): class IImageUnderstandNode(INode):

View File

@ -1,18 +1,17 @@
# coding=utf-8 # coding=utf-8
import base64 import base64
import os
import time import time
from functools import reduce from functools import reduce
from imghdr import what
from typing import List, Dict from typing import List, Dict
from django.db.models import QuerySet from django.db.models import QuerySet
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage
from application.flow.i_step_node import NodeResult, INode from chat.flow.i_step_node import NodeResult, INode
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode from chat.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
from dataset.models import File from knowledge.models import File
from setting.models_provider.tools import get_model_instance_by_model_user_id from models_provider.tools import get_model_instance_by_model_user_id
from imghdr import what
def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str): def _write_context(node_variable: Dict, workflow_variable: Dict, node: INode, workflow, answer: str):
@ -81,7 +80,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
if image is None or not isinstance(image, list): if image is None or not isinstance(image, list):
image = [] image = []
print(model_params_setting) print(model_params_setting)
image_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'), **model_params_setting) image_model = get_model_instance_by_model_user_id(model_id, self.flow_params_serializer.data.get('user_id'),
**model_params_setting)
# 执行详情中的历史消息不需要图片内容 # 执行详情中的历史消息不需要图片内容
history_message = self.get_history_message_for_details(history_chat_record, dialogue_number) history_message = self.get_history_message_for_details(history_chat_record, dialogue_number)
self.context['history_message'] = history_message self.context['history_message'] = history_message
@ -155,7 +155,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
return HumanMessage( return HumanMessage(
content=[ content=[
{'type': 'text', 'text': data['question']}, {'type': 'text', 'text': data['question']},
*[{'type': 'image_url', 'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for *[{'type': 'image_url',
'image_url': {'url': f'data:image/{base64_image[1]};base64,{base64_image[0]}'}} for
base64_image in image_base64_list] base64_image in image_base64_list]
]) ])
return HumanMessage(content=chat_record.problem_text) return HumanMessage(content=chat_record.problem_text)
@ -173,7 +174,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
image_bytes = file.get_byte() image_bytes = file.get_byte()
base64_image = base64.b64encode(image_bytes).decode("utf-8") base64_image = base64.b64encode(image_bytes).decode("utf-8")
image_format = what(None, image_bytes.tobytes()) image_format = what(None, image_bytes.tobytes())
images.append({'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}}) images.append(
{'type': 'image_url', 'image_url': {'url': f'data:image/{image_format};base64,{base64_image}'}})
messages = [HumanMessage( messages = [HumanMessage(
content=[ content=[
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)}, {'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},

View File

@ -2,24 +2,23 @@
from typing import Type from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult from chat.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class McpNodeSerializer(serializers.Serializer): class McpNodeSerializer(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True, mcp_servers = serializers.JSONField(required=True,
error_messages=ErrMessage.char(_("Mcp servers"))) label=_("Mcp servers"))
mcp_server = serializers.CharField(required=True, mcp_server = serializers.CharField(required=True,
error_messages=ErrMessage.char(_("Mcp server"))) label=_("Mcp server"))
mcp_tool = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Mcp tool"))) mcp_tool = serializers.CharField(required=True, label=_("Mcp tool"))
tool_params = serializers.DictField(required=True, tool_params = serializers.DictField(required=True,
error_messages=ErrMessage.char(_("Tool parameters"))) label=_("Tool parameters"))
class IMcpNode(INode): class IMcpNode(INode):

View File

@ -5,8 +5,8 @@ from typing import List
from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.client import MultiServerMCPClient
from application.flow.i_step_node import NodeResult from chat.flow.i_step_node import NodeResult
from application.flow.step_node.mcp_node.i_mcp_node import IMcpNode from chat.flow.step_node.mcp_node.i_mcp_node import IMcpNode
class BaseMcpNode(IMcpNode): class BaseMcpNode(IMcpNode):

Some files were not shown because too many files have changed in this diff Show More