feat: application operate api (#3176)
Some checks failed
sync2gitee / repo-sync (push) Has been cancelled

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 application.serializers.application import ApplicationCreateSerializer, ApplicationListResponse, \
ApplicationQueryRequest
ApplicationImportRequest, ApplicationEditSerializer
from common.mixins.api_mixin import APIMixin
from common.result import ResultSerializer, ResultPageSerializer
from common.result import ResultSerializer, ResultPageSerializer, DefaultResultSerializer
class ApplicationCreateRequest(ApplicationCreateSerializer.SimplateRequest):
@ -120,3 +120,50 @@ class ApplicationCreateAPI(APIMixin):
@staticmethod
def get_response():
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_access_token import *
from .application_chat import *
from .application_api_key import *

View File

@ -117,3 +117,16 @@ class ApplicationKnowledgeMapping(AppModelMixin):
class Meta:
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
from django.contrib.postgres.fields import ArrayField
@ -6,7 +5,6 @@ from django.db import models
from application.models import Application
from common.mixins.app_model_mixin import AppModelMixin
from users.models import User
@ -23,4 +21,19 @@ class ApplicationApiKey(AppModelMixin):
, default=list)
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
@desc:
"""
import datetime
import hashlib
import os
import pickle
import re
from typing import Dict
from typing import Dict, List
import uuid_utils.compat as uuid
from django.core import validators
from django.db import models
from django.db import models, transaction
from django.db.models import QuerySet
from django.http import HttpResponse
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, \
ApplicationFolder
ApplicationFolder, WorkFlowVersion
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.db.search import native_search, native_page_search
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 maxkb.conf import PROJECT_DIR
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):
@ -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):
workspace_id = serializers.CharField(required=True, label=_('workspace 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)
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 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 rest_framework import serializers
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.models import Application
from application.models.application_api_key import ApplicationApiKey
@ -16,6 +15,7 @@ class ApplicationKeySerializerModel(serializers.ModelSerializer):
model = ApplicationApiKey
fields = "__all__"
class Edit(serializers.Serializer):
pass
@ -25,10 +25,6 @@ class ApplicationKeySerializer(serializers.Serializer):
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
application_id = serializers.UUIDField(required=True, label=_('application id'))
def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True)
application_id = self.data.get("application_id")
@ -49,21 +45,18 @@ class ApplicationKeySerializer(serializers.Serializer):
application_api_key.save()
return ApplicationKeySerializerModel(application_api_key).data
def list(self,with_valid=True):
def list(self, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)
application_id = self.data.get("application_id")
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):
user_id = serializers.UUIDField(required=True, label=_('user id'))
workspace_id = serializers.CharField(required=True, label=_('workspace id'))
application_id = serializers.UUIDField(required=True, label=_('application id'))
def edit(self, instance, with_valid=True):
if with_valid:
self.is_valid(raise_exception=True)

View File

@ -5,8 +5,12 @@ from . import views
app_name = 'application'
urlpatterns = [
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>',
views.Application.Page.as_view(), name='application_page'),
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 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.views import APIView
from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI
from application.serializers.application import ApplicationSerializer, Query
from application.api.application_api import ApplicationCreateAPI, ApplicationQueryAPI, ApplicationImportAPI, \
ApplicationExportAPI, ApplicationOperateAPI, ApplicationEditAPI
from application.serializers.application import ApplicationSerializer, Query, ApplicationOperateSerializer
from common import result
from common.auth import TokenAuth
from common.auth.authentication import has_permissions
@ -67,3 +70,92 @@ class Application(APIView):
return result.success(
Query(data={'workspace_id': workspace_id, 'user_id': request.user.id}).page(current_page, page_size,
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.step.chat_step.i_chat_step import IChatStep, PostResponseHandler
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 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):

View File

@ -17,14 +17,13 @@ from django.db.models import QuerySet
from rest_framework import serializers
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.api_key_model import ApplicationPublicAccessClient
from application.models import ApplicationPublicAccessClient
from common.constants.authentication_type import AuthenticationType
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):
@ -123,31 +122,31 @@ class NodeResult:
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(
child=serializers.CharField(required=True, error_messages=ErrMessage.char("节点字段")), required=True,
error_messages=ErrMessage.list("节点字段数组"))
child=serializers.CharField(required=True, label="节点字段"), required=True,
label="节点字段数组")
class FlowParamsSerializer(serializers.Serializer):
# 历史对答
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"))
re_chat = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean("换个答案"))
user_id = serializers.UUIDField(required=True, label="用户id")
re_chat = serializers.BooleanField(required=True, label="换个答案")
class INode:

View File

@ -11,31 +11,29 @@ from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from chat.flow.i_step_node import INode, NodeResult
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,
error_messages=ErrMessage.char(_("Role Setting")))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
label=_("Role Setting"))
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"))
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,
error_messages=ErrMessage.dict(_("Model parameter settings")))
label=_("Model parameter settings"))
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,
error_messages=ErrMessage.char(_("Context Type")))
label=_("Context Type"))
mcp_enable = serializers.BooleanField(required=False,
error_messages=ErrMessage.boolean(_("Whether to enable MCP")))
mcp_servers = serializers.JSONField(required=False, error_messages=ErrMessage.list(_("MCP Server")))
label=_("Whether to enable MCP"))
mcp_servers = serializers.JSONField(required=False, label=_("MCP Server"))
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 langgraph.prebuilt import create_react_agent
from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
from application.flow.tools import Reasoning
from setting.models import Model
from setting.models_provider import get_model_credential
from setting.models_provider.tools import get_model_instance_by_model_user_id
from chat.flow.i_step_node import NodeResult, INode
from chat.flow.step_node.ai_chat_step_node.i_chat_node import IChatNode
from chat.flow.tools import Reasoning
from models_provider.models import Model
from models_provider.tools import get_model_credential, get_model_instance_by_model_user_id
tool_message_template = """
<details>

View File

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

View File

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

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

@ -8,7 +8,7 @@
"""
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):

View File

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

View File

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

View File

@ -10,18 +10,19 @@ from typing import Type
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.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
class ReplyNodeParamsSerializer(serializers.Serializer):
reply_type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Response Type")))
fields = serializers.ListField(required=False, error_messages=ErrMessage.list(_("Reference Field")))
reply_type = serializers.CharField(required=True, label=_("Response Type"))
fields = serializers.ListField(required=False, label=_("Reference Field"))
content = serializers.CharField(required=False, allow_blank=True, allow_null=True,
error_messages=ErrMessage.char(_("Direct answer content")))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
label=_("Direct answer content"))
is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True)

View File

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

View File

@ -5,12 +5,11 @@ from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from chat.flow.i_step_node import INode, NodeResult
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):

View File

@ -5,11 +5,11 @@ import mimetypes
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.db.models import QuerySet
from application.flow.i_step_node import NodeResult
from application.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
from dataset.models import File
from dataset.serializers.document_serializers import split_handles, parse_table_handle_list, FileBufferHandle
from dataset.serializers.file_serializers import FileSerializer
from chat.flow.i_step_node import NodeResult
from chat.flow.step_node.document_extract_node.i_document_extract_node import IDocumentExtractNode
from knowledge.models import File
from knowledge.serializers.document import split_handles, parse_table_handle_list, FileBufferHandle
from knowledge.serializers.file import FileSerializer
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'
class BaseDocumentExtractNode(IDocumentExtractNode):
def save_context(self, details, workflow_manage):
self.context['content'] = details.get('content')
def execute(self, document, chat_id, **kwargs):
get_buffer = FileBufferHandle().get_buffer

View File

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

View File

@ -12,9 +12,9 @@ from typing import Dict, List
from langchain_core.prompts import PromptTemplate
from application.flow.common import Answer
from application.flow.i_step_node import NodeResult
from application.flow.step_node.form_node.i_form_node import IFormNode
from chat.flow.common import Answer
from chat.flow.i_step_node import NodeResult
from chat.flow.step_node.form_node.i_form_node import IFormNode
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 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.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 _
class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
value = ObjectField(required=True, error_messages=ErrMessage.char(_("Variable Value")), model_type_list=[str, list])
name = serializers.CharField(required=True, label=_('Variable Name'))
value = ObjectField(required=True, label=_("Variable Value"), model_type_list=[str, list])
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)
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):
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:
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.utils.translation import gettext as _
from application.flow.i_step_node import NodeResult
from application.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
from chat.flow.i_step_node import NodeResult
from chat.flow.step_node.function_lib_node.i_function_lib_node import IFunctionLibNode
from common.exception.app_exception import AppApiException
from common.util.function_code import FunctionExecutor
from common.util.rsa_util import rsa_long_decrypt
from function_lib.models.function import FunctionLib
from smartdoc.const import CONFIG
from common.utils.function_code import FunctionExecutor
from common.utils.rsa_util import rsa_long_decrypt
from maxkb.const import CONFIG
from tools.models import Tool
function_executor = FunctionExecutor(CONFIG.get('SANDBOX'))
@ -117,7 +117,7 @@ class BaseFunctionLibNodeNode(IFunctionLibNode):
self.answer_text = str(details.get('result'))
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'))
params = {field.get('name'): convert_value(field.get('name'), field.get('value'), field.get('type'),
field.get('is_required'),

View File

@ -12,26 +12,26 @@ from typing import Type
from django.core import validators
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.field.common import ObjectField
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
from rest_framework.utils.formatting import lazy_format
class InputField(serializers.Serializer):
name = serializers.CharField(required=True, error_messages=ErrMessage.char(_('Variable Name')))
is_required = serializers.BooleanField(required=True, error_messages=ErrMessage.boolean(_("Is this field required")))
type = serializers.CharField(required=True, error_messages=ErrMessage.char(_("type")), validators=[
name = serializers.CharField(required=True, label=_('Variable Name'))
is_required = serializers.BooleanField(required=True, label=_("Is this field required"))
type = serializers.CharField(required=True, label=_("type"), validators=[
validators.RegexValidator(regex=re.compile("^string|int|dict|array|float$"),
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$"),
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):
super().is_valid(raise_exception=True)
@ -43,8 +43,9 @@ class InputField(serializers.Serializer):
class FunctionNodeParamsSerializer(serializers.Serializer):
input_field_list = InputField(required=True, many=True)
code = serializers.CharField(required=True, error_messages=ErrMessage.char(_("function")))
is_result = serializers.BooleanField(required=False, error_messages=ErrMessage.boolean(_('Whether to return content')))
code = serializers.CharField(required=True, label=_("function"))
is_result = serializers.BooleanField(required=False,
label=_('Whether to return content'))
def is_valid(self, *, raise_exception=False):
super().is_valid(raise_exception=True)

View File

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

View File

@ -2,31 +2,31 @@
from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
from chat.flow.i_step_node import INode, NodeResult
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, )
# 多轮对话数量
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',
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,
error_messages=ErrMessage.json(_("Model parameter settings")))
label=_("Model parameter settings"))
class IImageGenerateNode(INode):

View File

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

View File

@ -4,27 +4,28 @@ from typing import Type
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from chat.flow.i_step_node import INode, NodeResult
from django.utils.translation import gettext_lazy as _
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,
error_messages=ErrMessage.char(_("Role Setting")))
prompt = serializers.CharField(required=True, error_messages=ErrMessage.char(_("Prompt word")))
label=_("Role Setting"))
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,
error_messages=ErrMessage.json(_("Model parameter settings")))
label=_("Model parameter settings"))
class IImageUnderstandNode(INode):

View File

@ -1,18 +1,17 @@
# coding=utf-8
import base64
import os
import time
from functools import reduce
from imghdr import what
from typing import List, Dict
from django.db.models import QuerySet
from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, AIMessage
from application.flow.i_step_node import NodeResult, INode
from application.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
from dataset.models import File
from setting.models_provider.tools import get_model_instance_by_model_user_id
from imghdr import what
from chat.flow.i_step_node import NodeResult, INode
from chat.flow.step_node.image_understand_step_node.i_image_understand_node import IImageUnderstandNode
from knowledge.models import File
from models_provider.tools import get_model_instance_by_model_user_id
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):
image = []
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)
self.context['history_message'] = history_message
@ -155,7 +155,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
return HumanMessage(
content=[
{'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]
])
return HumanMessage(content=chat_record.problem_text)
@ -173,7 +174,8 @@ class BaseImageUnderstandNode(IImageUnderstandNode):
image_bytes = file.get_byte()
base64_image = base64.b64encode(image_bytes).decode("utf-8")
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(
content=[
{'type': 'text', 'text': self.workflow_manage.generate_prompt(prompt)},

View File

@ -2,24 +2,23 @@
from typing import Type
from django.utils.translation import gettext_lazy as _
from rest_framework import serializers
from application.flow.i_step_node import INode, NodeResult
from common.util.field_message import ErrMessage
from django.utils.translation import gettext_lazy as _
from chat.flow.i_step_node import INode, NodeResult
class McpNodeSerializer(serializers.Serializer):
mcp_servers = serializers.JSONField(required=True,
error_messages=ErrMessage.char(_("Mcp servers")))
label=_("Mcp servers"))
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,
error_messages=ErrMessage.char(_("Tool parameters")))
label=_("Tool parameters"))
class IMcpNode(INode):

View File

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

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