application.py 74 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400
  1. # coding=utf-8
  2. """
  3. @project: MaxKB
  4. @Author:虎虎
  5. @file: application.py
  6. @date:2025/5/26 17:03
  7. @desc:
  8. """
  9. import asyncio
  10. import base64
  11. import hashlib
  12. import json
  13. import os
  14. import pickle
  15. import re
  16. import tempfile
  17. import zipfile
  18. from functools import reduce
  19. from typing import Dict, List
  20. import requests
  21. import uuid_utils.compat as uuid
  22. from django.core import validators
  23. from django.db import models, transaction
  24. from django.db.models import QuerySet, Q
  25. from django.http import HttpResponse
  26. from django.utils import timezone
  27. from django.utils.translation import gettext_lazy as _
  28. from langchain_mcp_adapters.client import MultiServerMCPClient
  29. from rest_framework import serializers, status
  30. from rest_framework.utils.formatting import lazy_format
  31. from application.flow.common import Workflow
  32. from application.models.application import Application, ApplicationTypeChoices, \
  33. ApplicationFolder, ApplicationVersion
  34. from application.models.application_access_token import ApplicationAccessToken
  35. from application.serializers.common import update_resource_mapping_by_application
  36. from common import result
  37. from common.cache_data.application_access_token_cache import del_application_access_token
  38. from common.database_model_manage.database_model_manage import DatabaseModelManage
  39. from common.db.search import native_search, native_page_search
  40. from common.exception.app_exception import AppApiException
  41. from common.field.common import UploadedFileField
  42. from common.utils.common import get_file_content, restricted_loads, generate_uuid, _remove_empty_lines, \
  43. bytes_to_uploaded_file
  44. from common.utils.logger import maxkb_logger
  45. from common.utils.tool_code import ToolExecutor
  46. from knowledge.models import Knowledge, KnowledgeScope, File, FileSourceType
  47. from knowledge.serializers.common import BatchSerializer, BatchMoveSerializer
  48. from knowledge.serializers.knowledge import KnowledgeSerializer, KnowledgeModelSerializer
  49. from maxkb.conf import PROJECT_DIR
  50. from maxkb.const import CONFIG
  51. from models_provider.models import Model
  52. from models_provider.tools import get_model_instance_by_model_workspace_id
  53. from system_manage.models import WorkspaceUserResourcePermission, AuthTargetType
  54. from system_manage.models.resource_mapping import ResourceMapping
  55. from system_manage.serializers.resource_mapping_serializers import ResourceMappingSerializer
  56. from system_manage.serializers.user_resource_permission import UserResourcePermissionSerializer
  57. from tools.models import Tool, ToolScope, ToolType, ToolWorkflow
  58. from tools.serializers.tool import ToolExportModelSerializer
  59. from trigger.models import TriggerTask, Trigger
  60. from users.models import User
  61. from users.serializers.user import is_workspace_manage
  62. def get_base_node_work_flow(work_flow):
  63. node_list = work_flow.get('nodes')
  64. base_node_list = [node for node in node_list if node.get('id') == 'base-node']
  65. if len(base_node_list) > 0:
  66. return base_node_list[-1]
  67. return None
  68. def hand_node(node, update_tool_map):
  69. if node.get('type') == 'tool-lib-node':
  70. tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '')
  71. node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id,
  72. tool_lib_id)
  73. if node.get('type') == 'search-knowledge-node':
  74. node.get('properties', {}).get('node_data', {})['knowledge_id_list'] = []
  75. if node.get('type') == 'ai-chat-node':
  76. node_data = node.get('properties', {}).get('node_data', {})
  77. mcp_tool_ids = node_data.get('mcp_tool_ids') or []
  78. node_data['mcp_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in mcp_tool_ids]
  79. skill_tool_ids = node_data.get('skill_tool_ids') or []
  80. node_data['skill_tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in skill_tool_ids]
  81. tool_ids = node_data.get('tool_ids') or []
  82. node_data['tool_ids'] = [update_tool_map.get(tool_id, tool_id) for tool_id in tool_ids]
  83. if node.get('type') == 'mcp-node':
  84. mcp_tool_id = (node.get('properties', {}).get('node_data', {}).get('mcp_tool_id') or '')
  85. node.get('properties', {}).get('node_data', {})['mcp_tool_id'] = update_tool_map.get(mcp_tool_id,
  86. mcp_tool_id)
  87. if node.get('type') == 'tool-workflow-lib-node':
  88. tool_lib_id = (node.get('properties', {}).get('node_data', {}).get('tool_lib_id') or '')
  89. node.get('properties', {}).get('node_data', {})['tool_lib_id'] = update_tool_map.get(tool_lib_id,
  90. tool_lib_id)
  91. class MKInstance:
  92. def __init__(self, application: dict, function_lib_list: List[dict], version: str, tool_list: List[dict]):
  93. self.application = application
  94. self.function_lib_list = function_lib_list
  95. self.version = version
  96. self.tool_list = tool_list
  97. def get_tool_list(self):
  98. return [*(self.tool_list or []), *(self.function_lib_list or [])]
  99. class ApplicationSerializerModel(serializers.ModelSerializer):
  100. class Meta:
  101. model = Application
  102. fields = "__all__"
  103. class NoReferencesChoices(models.TextChoices):
  104. """订单类型"""
  105. ai_questioning = 'ai_questioning', 'ai回答'
  106. designated_answer = 'designated_answer', '指定回答'
  107. class NoReferencesSetting(serializers.Serializer):
  108. status = serializers.ChoiceField(required=True, choices=NoReferencesChoices.choices,
  109. label=_("No reference status"))
  110. value = serializers.CharField(required=True, label=_("Prompt word"))
  111. class KnowledgeSettingSerializer(serializers.Serializer):
  112. top_n = serializers.FloatField(required=True, max_value=10000, min_value=1,
  113. label=_("Reference segment number"))
  114. similarity = serializers.FloatField(required=True, max_value=1, min_value=0,
  115. label=_("Acquaintance"))
  116. max_paragraph_char_number = serializers.IntegerField(required=True, min_value=500, max_value=100000,
  117. label=_("Maximum number of quoted characters"))
  118. search_mode = serializers.CharField(required=True, validators=[
  119. validators.RegexValidator(regex=re.compile("^embedding|keywords|blend$"),
  120. message=_("The type only supports embedding|keywords|blend"), code=500)
  121. ], label=_("Retrieval Mode"))
  122. no_references_setting = NoReferencesSetting(required=True,
  123. label=_("Segment settings not referenced"))
  124. class ModelKnowledgeAssociation(serializers.Serializer):
  125. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  126. model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
  127. label=_("Model id"))
  128. knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True,
  129. label=_(
  130. "Knowledge base id")),
  131. label=_("Knowledge Base List"))
  132. def is_valid(self, *, raise_exception=True):
  133. super().is_valid(raise_exception=True)
  134. model_id = self.data.get('model_id')
  135. user_id = self.data.get('user_id')
  136. if model_id is not None and len(model_id) > 0:
  137. if not QuerySet(Model).filter(id=model_id).exists():
  138. raise AppApiException(500, f'{_("Model does not exist")}【{model_id}】')
  139. knowledge_id_list = list(set(self.data.get('knowledge_id_list', [])))
  140. exist_knowledge_id_list = [str(knowledge.id) for knowledge in
  141. QuerySet(Knowledge).filter(id__in=knowledge_id_list, user_id=user_id)]
  142. for knowledge_id in knowledge_id_list:
  143. if not exist_knowledge_id_list.__contains__(knowledge_id):
  144. raise AppApiException(500, f'{_("The knowledge base id does not exist")}【{knowledge_id}】')
  145. class ModelSettingSerializer(serializers.Serializer):
  146. prompt = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
  147. label=_("Prompt word"))
  148. system = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
  149. label=_("Role prompts"))
  150. no_references_prompt = serializers.CharField(required=True, max_length=102400, allow_null=True, allow_blank=True,
  151. label=_("No citation segmentation prompt"))
  152. reasoning_content_enable = serializers.BooleanField(required=False,
  153. label=_("Thinking process switch"))
  154. reasoning_content_start = serializers.CharField(required=False, allow_null=True, default="<think>",
  155. allow_blank=True, max_length=256,
  156. trim_whitespace=False,
  157. label=_("The thinking process begins to mark"))
  158. reasoning_content_end = serializers.CharField(required=False, allow_null=True, allow_blank=True, default="</think>",
  159. max_length=256,
  160. trim_whitespace=False,
  161. label=_("End of thinking process marker"))
  162. class ApplicationCreateSerializer(serializers.Serializer):
  163. class ApplicationResponse(serializers.ModelSerializer):
  164. class Meta:
  165. model = Application
  166. fields = "__all__"
  167. class WorkflowRequest(serializers.Serializer):
  168. name = serializers.CharField(required=True, max_length=64, min_length=1,
  169. label=_("Application Name"))
  170. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
  171. max_length=256, min_length=1,
  172. label=_("Application Description"))
  173. work_flow = serializers.DictField(required=True, label=_("Workflow Objects"))
  174. prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
  175. label=_("Opening remarks"))
  176. folder_id = serializers.CharField(required=True, label=_('folder id'))
  177. @staticmethod
  178. def to_application_model(user_id: str, workspace_id: str, application: Dict):
  179. default_workflow = application.get('work_flow')
  180. for node in default_workflow.get('nodes'):
  181. if node.get('id') == 'base-node':
  182. node.get('properties')['node_data']['desc'] = application.get('desc')
  183. node.get('properties')['node_data']['name'] = application.get('name')
  184. node.get('properties')['node_data']['prologue'] = application.get('prologue')
  185. return Application(
  186. id=uuid.uuid7(),
  187. name=application.get('name'),
  188. desc=application.get('desc'),
  189. workspace_id=workspace_id,
  190. folder_id=application.get('folder_id', application.get('workspace_id')),
  191. prologue="",
  192. dialogue_number=0,
  193. user_id=user_id, model_id=None,
  194. knowledge_setting={},
  195. model_setting={},
  196. problem_optimization=False,
  197. type=ApplicationTypeChoices.WORK_FLOW,
  198. stt_model_enable=application.get('stt_model_enable', False),
  199. stt_model_id=application.get('stt_model', None),
  200. tts_model_id=application.get('tts_model', None),
  201. tts_model_enable=application.get('tts_model_enable', False),
  202. tts_model_params_setting=application.get('tts_model_params_setting', {}),
  203. tts_type=application.get('tts_type', 'BROWSER'),
  204. file_upload_enable=application.get('file_upload_enable', False),
  205. file_upload_setting=application.get('file_upload_setting', {}),
  206. work_flow=default_workflow
  207. )
  208. class SimplateRequest(serializers.Serializer):
  209. name = serializers.CharField(required=True, max_length=64, min_length=1,
  210. label=_("application name"))
  211. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True,
  212. max_length=256, min_length=1,
  213. label=_("application describe"))
  214. folder_id = serializers.CharField(required=True, label=_('folder id'))
  215. model_id = serializers.CharField(required=False, allow_null=True, allow_blank=True,
  216. label=_("Model"))
  217. dialogue_number = serializers.IntegerField(required=True,
  218. min_value=0,
  219. max_value=1024,
  220. label=_("Historical chat records"))
  221. prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=40960,
  222. label=_("Opening remarks"))
  223. knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
  224. allow_null=True,
  225. label=_("Related Knowledge Base"))
  226. # 数据集相关设置
  227. knowledge_setting = KnowledgeSettingSerializer(required=True)
  228. # 模型相关设置
  229. model_setting = ModelSettingSerializer(required=True)
  230. # 问题补全
  231. problem_optimization = serializers.BooleanField(required=True,
  232. label=_("Question completion"))
  233. problem_optimization_prompt = serializers.CharField(required=False, max_length=102400,
  234. label=_("Question completion prompt"))
  235. # 应用类型
  236. type = serializers.CharField(required=True, label=_("Application Type"),
  237. validators=[
  238. validators.RegexValidator(regex=re.compile("^SIMPLE|WORK_FLOW$"),
  239. message=_(
  240. "Application type only supports SIMPLE|WORK_FLOW"),
  241. code=500)
  242. ]
  243. )
  244. model_params_setting = serializers.DictField(required=False,
  245. label=_('Model parameters'))
  246. tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled'))
  247. tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID"))
  248. tts_type = serializers.CharField(required=False, label=_('Voice playback type'))
  249. tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay'))
  250. stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled'))
  251. stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID'))
  252. stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission'))
  253. def is_valid(self, *, user_id=None, raise_exception=False):
  254. super().is_valid(raise_exception=True)
  255. ModelKnowledgeAssociation(data={'user_id': user_id, 'model_id': self.data.get('model_id'),
  256. 'knowledge_id_list': self.data.get('knowledge_id_list')}).is_valid()
  257. @staticmethod
  258. def to_application_model(user_id: str, workspace_id: str, application: Dict):
  259. return Application(
  260. id=uuid.uuid7(),
  261. name=application.get('name'),
  262. desc=application.get('desc'),
  263. workspace_id=workspace_id,
  264. prologue=application.get('prologue'),
  265. dialogue_number=application.get('dialogue_number', 0),
  266. user_id=user_id, model_id=application.get('model_id'),
  267. folder_id=application.get('folder_id', application.get('workspace_id')),
  268. knowledge_setting=application.get('knowledge_setting'),
  269. model_setting=application.get('model_setting'),
  270. problem_optimization=application.get('problem_optimization'),
  271. type=ApplicationTypeChoices.SIMPLE,
  272. model_params_setting=application.get('model_params_setting', {}),
  273. problem_optimization_prompt=application.get('problem_optimization_prompt', None),
  274. stt_model_enable=application.get('stt_model_enable', False),
  275. stt_model_id=application.get('stt_model', None),
  276. stt_autosend=application.get('stt_autosend', False),
  277. tts_model_id=application.get('tts_model', None),
  278. tts_model_enable=application.get('tts_model_enable', False),
  279. tts_model_params_setting=application.get('tts_model_params_setting', {}),
  280. tts_type=application.get('tts_type', 'BROWSER'),
  281. file_upload_enable=application.get('file_upload_enable', False),
  282. file_upload_setting=application.get('file_upload_setting', {}),
  283. work_flow={},
  284. mcp_enable=application.get('mcp_enable', False),
  285. mcp_tool_ids=application.get('mcp_tool_ids', []),
  286. mcp_servers=application.get('mcp_servers', {}),
  287. mcp_source=application.get('mcp_source', 'referencing'),
  288. tool_enable=application.get('tool_enable', False),
  289. tool_ids=application.get('tool_ids', []),
  290. mcp_output_enable=application.get('mcp_output_enable', False),
  291. )
  292. class ApplicationQueryRequest(serializers.Serializer):
  293. folder_id = serializers.CharField(required=False, label=_("folder id"))
  294. name = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_('Application Name'))
  295. desc = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Application Description"))
  296. publish_status = serializers.ChoiceField(required=False, label=_("Publish status"),
  297. choices=[('published', _("Published")),
  298. ('unpublished', _("Unpublished"))])
  299. user_id = serializers.UUIDField(required=False, label=_("User ID"))
  300. class ApplicationListResponse(serializers.Serializer):
  301. id = serializers.CharField(required=True, label=_("Primary key id"), help_text=_("Primary key id"))
  302. name = serializers.CharField(required=True, label=_("Application Name"), help_text=_("Application Name"))
  303. desc = serializers.CharField(required=True, label=_("Application Description"),
  304. help_text=_("Application Description"))
  305. is_publish = serializers.BooleanField(required=True, label=_("Model id"), help_text=_("Model id"))
  306. type = serializers.CharField(required=True, label=_("Application type"), help_text=_("Application type"))
  307. resource_type = serializers.CharField(required=True, label=_("Resource type"), help_text=_("Resource type"))
  308. user_id = serializers.CharField(required=True, label=_('Affiliation user'), help_text=_("Affiliation user"))
  309. create_time = serializers.CharField(required=True, label=_('Creation time'), help_text=_("Creation time"))
  310. update_time = serializers.CharField(required=True, label=_('Modification time'), help_text=_("Modification time"))
  311. class Query(serializers.Serializer):
  312. workspace_id = serializers.CharField(required=False, label=_('Workspace ID'))
  313. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  314. def get_query_set(self, instance: Dict, workspace_manage: bool, is_x_pack_ee: bool):
  315. folder_query_set = QuerySet(ApplicationFolder)
  316. application_query_set = QuerySet(Application)
  317. workspace_id = self.data.get('workspace_id')
  318. user_id = self.data.get('user_id')
  319. desc = instance.get('desc')
  320. name = instance.get('name')
  321. publish_status = instance.get("publish_status")
  322. create_user = instance.get('create_user')
  323. if publish_status is not None:
  324. is_publish = True if publish_status == "published" else False
  325. application_query_set = application_query_set.filter(is_publish=is_publish)
  326. if workspace_id is not None:
  327. folder_query_set = folder_query_set.filter(workspace_id=workspace_id)
  328. application_query_set = application_query_set.filter(workspace_id=workspace_id)
  329. folder_id = instance.get('folder_id')
  330. if folder_id is not None and folder_id != workspace_id:
  331. folder_query_set = folder_query_set.filter(parent=folder_id)
  332. application_query_set = application_query_set.filter(folder_id=folder_id)
  333. if name is not None:
  334. folder_query_set = folder_query_set.filter(name__contains=name)
  335. application_query_set = application_query_set.filter(name__contains=name)
  336. if desc is not None:
  337. folder_query_set = folder_query_set.filter(desc__contains=desc)
  338. application_query_set = application_query_set.filter(desc__contains=desc)
  339. if create_user is not None:
  340. application_query_set = application_query_set.filter(user_id=create_user)
  341. application_custom_sql_query_set = application_query_set
  342. application_query_set = application_query_set.order_by("-create_time")
  343. resource_and_folder_query_set = QuerySet(WorkspaceUserResourcePermission).filter(
  344. auth_target_type="APPLICATION",
  345. workspace_id=workspace_id,
  346. user_id=user_id)
  347. return {'application_query_set': application_query_set,
  348. 'workspace_user_resource_permission_query_set': resource_and_folder_query_set,
  349. } if (
  350. not workspace_manage) else {
  351. 'application_query_set': application_query_set,
  352. 'application_custom_sql': application_custom_sql_query_set
  353. }
  354. @staticmethod
  355. def is_x_pack_ee():
  356. workspace_user_role_mapping_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
  357. role_permission_mapping_model = DatabaseModelManage.get_model("role_permission_mapping_model")
  358. return workspace_user_role_mapping_model is not None and role_permission_mapping_model is not None
  359. def list(self, instance: Dict):
  360. self.is_valid(raise_exception=True)
  361. workspace_id = self.data.get('workspace_id')
  362. user_id = self.data.get("user_id")
  363. req_dict = ApplicationQueryRequest(data=instance)
  364. req_dict.is_valid(raise_exception=True)
  365. workspace_manage = is_workspace_manage(user_id, workspace_id)
  366. is_x_pack_ee = self.is_x_pack_ee()
  367. return native_search(self.get_query_set(req_dict.data, workspace_manage, is_x_pack_ee),
  368. select_string=get_file_content(
  369. os.path.join(PROJECT_DIR, "apps", "application", 'sql',
  370. 'list_application.sql' if workspace_manage else (
  371. 'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql')
  372. )))
  373. def page(self, current_page: int, page_size: int, instance: Dict):
  374. self.is_valid(raise_exception=True)
  375. req_dict = ApplicationQueryRequest(data=instance)
  376. req_dict.is_valid(raise_exception=True)
  377. workspace_id = self.data.get('workspace_id')
  378. user_id = self.data.get("user_id")
  379. workspace_manage = is_workspace_manage(user_id, workspace_id)
  380. is_x_pack_ee = self.is_x_pack_ee()
  381. result = native_page_search(current_page, page_size,
  382. self.get_query_set(req_dict.data, workspace_manage, is_x_pack_ee),
  383. get_file_content(
  384. os.path.join(PROJECT_DIR, "apps", "application", 'sql',
  385. 'list_application.sql' if workspace_manage else (
  386. 'list_application_user_ee.sql' if is_x_pack_ee else 'list_application_user.sql'))),
  387. )
  388. return ResourceMappingSerializer().get_resource_count(result)
  389. class ApplicationImportRequest(serializers.Serializer):
  390. file = UploadedFileField(required=True, label=_("file"))
  391. folder_id = serializers.CharField(required=True, label=_("Folder ID"))
  392. class ApplicationEditSerializer(serializers.Serializer):
  393. name = serializers.CharField(required=False, max_length=64, min_length=1,
  394. label=_("Application Name"))
  395. desc = serializers.CharField(required=False, max_length=256, min_length=1, allow_null=True, allow_blank=True,
  396. label=_("Application Description"))
  397. model_id = serializers.CharField(required=False, allow_blank=True, allow_null=True,
  398. label=_("Model"))
  399. dialogue_number = serializers.IntegerField(required=False,
  400. min_value=0,
  401. max_value=1024,
  402. label=_("Historical chat records"))
  403. prologue = serializers.CharField(required=False, allow_null=True, allow_blank=True, max_length=102400,
  404. label=_("Opening remarks"))
  405. knowledge_id_list = serializers.ListSerializer(required=False, child=serializers.UUIDField(required=True),
  406. label=_("Related Knowledge Base")
  407. )
  408. # 数据集相关设置
  409. knowledge_setting = KnowledgeSettingSerializer(required=False, allow_null=True,
  410. label=_("Dataset settings"))
  411. # 模型相关设置
  412. model_setting = ModelSettingSerializer(required=False, allow_null=True,
  413. label=_("Model setup"))
  414. # 问题补全
  415. problem_optimization = serializers.BooleanField(required=False, allow_null=True,
  416. label=_("Question completion"))
  417. icon = serializers.CharField(required=False, allow_null=True, label=_("Icon"))
  418. model_params_setting = serializers.DictField(required=False,
  419. label=_('Model parameters'))
  420. tts_model_enable = serializers.BooleanField(required=False, label=_('Voice playback enabled'))
  421. tts_model_id = serializers.UUIDField(required=False, allow_null=True, label=_("Voice playback model ID"))
  422. tts_type = serializers.CharField(required=False, label=_('Voice playback type'))
  423. tts_autoplay = serializers.BooleanField(required=False, label=_('Voice playback autoplay'))
  424. stt_model_enable = serializers.BooleanField(required=False, label=_('Voice recognition enabled'))
  425. stt_model_id = serializers.UUIDField(required=False, allow_null=True, label=_('Speech recognition model ID'))
  426. stt_autosend = serializers.BooleanField(required=False, label=_('Voice recognition automatic transmission'))
  427. class ApplicationSerializer(serializers.Serializer):
  428. workspace_id = serializers.CharField(required=True, label=_('workspace id'))
  429. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  430. @transaction.atomic
  431. def insert(self, instance: Dict):
  432. work_flow_template = instance.get('work_flow_template')
  433. application_type = instance.get('type')
  434. # 处理工作流模板安装逻辑
  435. if work_flow_template:
  436. return self.insert_template_workflow(instance)
  437. if 'WORK_FLOW' == application_type:
  438. r = self.insert_workflow(instance)
  439. else:
  440. r = self.insert_simple(instance)
  441. UserResourcePermissionSerializer(data={
  442. 'workspace_id': self.data.get('workspace_id'),
  443. 'user_id': self.data.get('user_id'),
  444. 'auth_target_type': AuthTargetType.APPLICATION.value
  445. }).auth_resource(str(r.get('id')))
  446. return r
  447. def insert_template_workflow(self, instance: Dict):
  448. self.is_valid(raise_exception=True)
  449. work_flow_template = instance.get('work_flow_template')
  450. download_url = work_flow_template.get('downloadUrl')
  451. # 查找匹配的版本名称
  452. res = requests.get(download_url, timeout=5)
  453. app = ApplicationSerializer(
  454. data={'user_id': self.data.get('user_id'), 'workspace_id': self.data.get('workspace_id')}
  455. ).import_({
  456. 'file': bytes_to_uploaded_file(res.content, 'file.mk'),
  457. 'folder_id': instance.get('folder_id', instance.get('workspace_id'))
  458. }, True)
  459. work_flow = app.get('work_flow')
  460. for node in work_flow.get('nodes', []):
  461. if node.get('type') == 'base-node':
  462. node_data = node.get('properties').get('node_data')
  463. node_data['name'] = instance.get('name')
  464. node_data['desc'] = instance.get('desc')
  465. QuerySet(Application).filter(id=app.get('id')).update(
  466. name=instance.get('name'),
  467. desc=instance.get('desc'),
  468. work_flow=work_flow
  469. )
  470. try:
  471. requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5)
  472. except Exception as e:
  473. maxkb_logger.error(f"callback appstore tool download error: {e}")
  474. return app
  475. def insert_workflow(self, instance: Dict):
  476. self.is_valid(raise_exception=True)
  477. user_id = self.data.get('user_id')
  478. workspace_id = self.data.get('workspace_id')
  479. wq = ApplicationCreateSerializer.WorkflowRequest(data=instance)
  480. wq.is_valid(raise_exception=True)
  481. application_model = wq.to_application_model(user_id, workspace_id, instance)
  482. application_model.save()
  483. # 插入认证信息
  484. ApplicationAccessToken(application_id=application_model.id,
  485. access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()
  486. return ApplicationCreateSerializer.ApplicationResponse(application_model).data
  487. @staticmethod
  488. def to_application_knowledge_mapping(application_id: str, knowledge_id: str):
  489. return ResourceMapping(id=uuid.uuid7(), source_id=application_id, target_id=knowledge_id,
  490. source_type="APPLICATION",
  491. target_type="KNOWLEDGE")
  492. def insert_simple(self, instance: Dict):
  493. self.is_valid(raise_exception=True)
  494. user_id = self.data.get('user_id')
  495. workspace_id = self.data.get("workspace_id")
  496. ApplicationCreateSerializer.SimplateRequest(data=instance).is_valid(user_id=user_id, raise_exception=True)
  497. application_model = ApplicationCreateSerializer.SimplateRequest.to_application_model(user_id, workspace_id,
  498. instance)
  499. knowledge_id_list = instance.get('knowledge_id_list', [])
  500. application_knowledge_mapping_model_list = [
  501. self.to_application_knowledge_mapping(application_model.id, knowledge_id) for
  502. knowledge_id in knowledge_id_list]
  503. # 插入应用
  504. application_model.save()
  505. # 插入认证信息
  506. ApplicationAccessToken(application_id=application_model.id,
  507. access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()
  508. # 插入关联数据
  509. QuerySet(ResourceMapping).bulk_create(application_knowledge_mapping_model_list)
  510. return ApplicationCreateSerializer.ApplicationResponse(application_model).data
  511. @transaction.atomic
  512. def import_(self, instance: dict, is_import_tool, with_valid=True):
  513. if with_valid:
  514. self.is_valid()
  515. ApplicationImportRequest(data=instance).is_valid(raise_exception=True)
  516. user_id = self.data.get('user_id')
  517. workspace_id = self.data.get("workspace_id")
  518. folder_id = instance.get('folder_id')
  519. mk_instance_bytes = instance.get('file').read()
  520. try:
  521. mk_instance = restricted_loads(mk_instance_bytes)
  522. except Exception as e:
  523. raise AppApiException(1001, _("Unsupported file format"))
  524. application = mk_instance.application
  525. tool_list = mk_instance.get_tool_list()
  526. update_tool_map = {}
  527. if len(tool_list) > 0:
  528. tool_id_list = reduce(lambda x, y: [*x, *y],
  529. [[tool.get('id'), generate_uuid((tool.get('id') + workspace_id or ''))]
  530. for tool
  531. in
  532. tool_list], [])
  533. # 存在的工具列表
  534. exits_tool_id_list = [str(tool.id) for tool in
  535. QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=workspace_id)]
  536. # 需要更新的工具集合
  537. update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + workspace_id or '')) for tool
  538. in
  539. tool_list if
  540. not exits_tool_id_list.__contains__(
  541. tool.get('id'))}
  542. tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if
  543. not exits_tool_id_list.__contains__(
  544. tool.get('id')) and not exits_tool_id_list.__contains__(
  545. generate_uuid((tool.get('id') + workspace_id or '')))]
  546. application_model = self.to_application(application, workspace_id, user_id, update_tool_map, folder_id)
  547. tool_model_list = [self.to_tool(f, workspace_id, user_id) for f in tool_list]
  548. application_model.save()
  549. # 插入授权数据
  550. UserResourcePermissionSerializer(data={
  551. 'workspace_id': self.data.get('workspace_id'),
  552. 'user_id': self.data.get('user_id'),
  553. 'auth_target_type': AuthTargetType.APPLICATION.value
  554. }).auth_resource(str(application_model.id))
  555. # 插入认证信息
  556. ApplicationAccessToken(application_id=application_model.id,
  557. access_token=hashlib.md5(str(uuid.uuid7()).encode()).hexdigest()[8:24]).save()
  558. if is_import_tool:
  559. if len(tool_model_list) > 0:
  560. QuerySet(Tool).bulk_create(tool_model_list)
  561. QuerySet(ToolWorkflow).bulk_create(
  562. [ToolWorkflow(workspace_id=workspace_id,
  563. work_flow=self.reset_workflow(tool.get('work_flow'), update_tool_map),
  564. tool_id=tool.get('id'))
  565. for
  566. tool in tool_list if tool.get('tool_type') == ToolType.WORKFLOW])
  567. UserResourcePermissionSerializer(data={
  568. 'workspace_id': self.data.get('workspace_id'),
  569. 'user_id': self.data.get('user_id'),
  570. 'auth_target_type': AuthTargetType.TOOL.value
  571. }).auth_resource_batch([t.id for t in tool_model_list])
  572. return ApplicationCreateSerializer.ApplicationResponse(application_model).data
  573. @staticmethod
  574. def to_tool(tool, workspace_id, user_id):
  575. """
  576. @param workspace_id:
  577. @param user_id: 用户id
  578. @param tool: 工具
  579. @return:
  580. """
  581. # 如果是技能类型的工具,需要将code保存为文件
  582. code = tool.get('code')
  583. if tool.get('tool_type') == ToolType.SKILL:
  584. skill_file_id = uuid.uuid7()
  585. skill_file = File(
  586. id=skill_file_id,
  587. file_name=f"{tool.get('name')}.zip",
  588. source_type=FileSourceType.TOOL,
  589. source_id=tool.get('id'),
  590. meta={}
  591. )
  592. skill_file.save(base64.b64decode(code))
  593. tool['code'] = skill_file_id
  594. return Tool(id=tool.get('id'),
  595. user_id=user_id,
  596. name=tool.get('name'),
  597. code=tool.get('code'),
  598. template_id=tool.get('template_id'),
  599. input_field_list=tool.get('input_field_list'),
  600. init_field_list=tool.get('init_field_list'),
  601. is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),
  602. tool_type=tool.get('tool_type', 'CUSTOM') or 'CUSTOM',
  603. scope=ToolScope.WORKSPACE,
  604. folder_id=workspace_id,
  605. workspace_id=workspace_id)
  606. @staticmethod
  607. def reset_workflow(work_flow, update_tool_map):
  608. for node in work_flow.get('nodes', []):
  609. hand_node(node, update_tool_map)
  610. if node.get('type') == 'loop-node':
  611. for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):
  612. hand_node(n, update_tool_map)
  613. return work_flow
  614. @staticmethod
  615. def to_application(application, workspace_id, user_id, update_tool_map, folder_id):
  616. work_flow = application.get('work_flow')
  617. for node in work_flow.get('nodes', []):
  618. hand_node(node, update_tool_map)
  619. if node.get('type') == 'loop-node':
  620. for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):
  621. hand_node(n, update_tool_map)
  622. return Application(id=uuid.uuid7(),
  623. user_id=user_id,
  624. name=application.get('name'),
  625. workspace_id=workspace_id,
  626. folder_id=folder_id,
  627. desc=application.get('desc'),
  628. prologue=application.get('prologue'), dialogue_number=application.get('dialogue_number'),
  629. knowledge_setting=application.get('knowledge_setting'),
  630. model_setting=application.get('model_setting'),
  631. model_params_setting=application.get('model_params_setting'),
  632. tts_model_params_setting=application.get('tts_model_params_setting'),
  633. problem_optimization=application.get('problem_optimization'),
  634. icon="./favicon.ico",
  635. work_flow=work_flow,
  636. type=application.get('type'),
  637. problem_optimization_prompt=application.get('problem_optimization_prompt'),
  638. tts_model_enable=application.get('tts_model_enable'),
  639. stt_model_enable=application.get('stt_model_enable'),
  640. tts_type=application.get('tts_type'),
  641. clean_time=application.get('clean_time'),
  642. file_clean_time=application.get('file_clean_time') or 180,
  643. file_upload_enable=application.get('file_upload_enable'),
  644. file_upload_setting=application.get('file_upload_setting'),
  645. tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in
  646. application.get('tool_ids', [])],
  647. skill_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in
  648. application.get('skill_tool_ids', [])],
  649. mcp_tool_ids=[update_tool_map.get(tool_id, tool_id) for tool_id in
  650. application.get('mcp_tool_ids', [])],
  651. )
  652. class StoreApplication(serializers.Serializer):
  653. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  654. name = serializers.CharField(required=False, label=_("tool name"), allow_null=True, allow_blank=True)
  655. def get_appstore_templates(self):
  656. self.is_valid(raise_exception=True)
  657. # 下载zip文件
  658. try:
  659. appstore_url = CONFIG.get('APPSTORE_URL', 'https://apps-assets.fit2cloud.com/stable/maxkb.json.zip')
  660. res = requests.get(appstore_url, timeout=5)
  661. res.raise_for_status()
  662. # 创建临时文件保存zip
  663. with tempfile.NamedTemporaryFile(delete=False, suffix='.zip') as temp_zip:
  664. temp_zip.write(res.content)
  665. temp_zip_path = temp_zip.name
  666. try:
  667. # 解压zip文件
  668. with zipfile.ZipFile(temp_zip_path, 'r') as zip_ref:
  669. # 获取zip中的第一个文件(假设只有一个json文件)
  670. json_filename = zip_ref.namelist()[0]
  671. json_content = zip_ref.read(json_filename)
  672. # 将json转换为字典
  673. tool_store = json.loads(json_content.decode('utf-8'))
  674. tag_dict = {tag['name']: tag['key'] for tag in tool_store['additionalProperties']['tags']}
  675. filter_apps = []
  676. for tool in tool_store['apps']:
  677. if self.data.get('name', '') != '':
  678. if self.data.get('name').lower() not in tool.get('name', '').lower():
  679. continue
  680. if not tool['downloadUrl'].endswith('.mk'):
  681. continue
  682. versions = tool.get('versions', [])
  683. tool['label'] = tag_dict[tool.get('tags')[0]] if tool.get('tags') else ''
  684. tool['version'] = next(
  685. (version.get('name') for version in versions if
  686. version.get('downloadUrl') == tool['downloadUrl']),
  687. )
  688. filter_apps.append(tool)
  689. tool_store['apps'] = filter_apps
  690. return tool_store
  691. finally:
  692. # 清理临时文件
  693. os.unlink(temp_zip_path)
  694. except Exception as e:
  695. maxkb_logger.error(f"fetch appstore tools error: {e}")
  696. return {'apps': [], 'additionalProperties': {'tags': []}}
  697. class TextToSpeechRequest(serializers.Serializer):
  698. text = serializers.CharField(required=True, label=_('Text'))
  699. class SpeechToTextRequest(serializers.Serializer):
  700. file = UploadedFileField(required=True, label=_("file"))
  701. class PlayDemoTextRequest(serializers.Serializer):
  702. tts_model_id = serializers.UUIDField(required=True, label=_('Text to speech model ID'))
  703. async def get_mcp_tools(servers):
  704. client = MultiServerMCPClient(servers)
  705. return await client.get_tools()
  706. class McpServersSerializer(serializers.Serializer):
  707. mcp_servers = serializers.JSONField(required=True)
  708. class ApplicationOperateSerializer(serializers.Serializer):
  709. application_id = serializers.UUIDField(required=True, label=_("Application ID"))
  710. user_id = serializers.UUIDField(required=True, label=_("User ID"))
  711. workspace_id = serializers.CharField(required=False, allow_null=True, allow_blank=True, label=_("Workspace ID"))
  712. def is_valid(self, *, raise_exception=False):
  713. super().is_valid(raise_exception=True)
  714. workspace_id = self.data.get('workspace_id')
  715. query_set = QuerySet(Application).filter(id=self.data.get('application_id'))
  716. if workspace_id:
  717. query_set = query_set.filter(workspace_id=workspace_id)
  718. if not query_set.exists():
  719. raise AppApiException(500, _('Application id does not exist'))
  720. def get_mcp_servers(self, instance, with_valid=True):
  721. if with_valid:
  722. self.is_valid(raise_exception=True)
  723. McpServersSerializer(data=instance).is_valid(raise_exception=True)
  724. servers = json.loads(instance.get('mcp_servers'))
  725. for server, config in servers.items():
  726. if config.get('transport') not in ['sse', 'streamable_http']:
  727. raise AppApiException(500, _('Only support transport=sse or transport=streamable_http'))
  728. tools = []
  729. for server in servers:
  730. tools += [
  731. {
  732. 'server': server,
  733. 'name': tool.name,
  734. 'description': tool.description,
  735. 'args_schema': tool.args_schema,
  736. }
  737. for tool in asyncio.run(get_mcp_tools({server: servers[server]}))]
  738. return tools
  739. def delete(self, with_valid=True):
  740. from trigger.handler.simple_tools import deploy
  741. from trigger.serializers.trigger import TriggerModelSerializer
  742. if with_valid:
  743. self.is_valid()
  744. application_id = self.data.get('application_id')
  745. QuerySet(ApplicationVersion).filter(application_id=application_id).delete()
  746. QuerySet(ResourceMapping).filter(
  747. Q(target_id=application_id) | Q(source_id=application_id)
  748. ).delete()
  749. QuerySet(WorkspaceUserResourcePermission).filter(target=application_id).delete()
  750. QuerySet(Application).filter(id=application_id).delete()
  751. trigger_ids = list(
  752. QuerySet(TriggerTask).filter(
  753. source_type="APPLICATION", source_id=application_id
  754. ).values('trigger_id').distinct()
  755. )
  756. QuerySet(TriggerTask).filter(source_type="APPLICATION", source_id=application_id).delete()
  757. for trigger_id in trigger_ids:
  758. trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first()
  759. if trigger and trigger.is_active:
  760. deploy(TriggerModelSerializer(trigger).data, **{})
  761. return True
  762. def export(self, with_valid=True):
  763. try:
  764. if with_valid:
  765. self.is_valid()
  766. application_id = self.data.get('application_id')
  767. application = QuerySet(Application).filter(id=application_id).first()
  768. from application.flow.tools import get_tool_id_list
  769. tool_id_list = get_tool_id_list(application.work_flow, True)
  770. if len(tool_id_list) > 0:
  771. tool_list = QuerySet(Tool).filter(id__in=tool_id_list).exclude(scope=ToolScope.SHARED)
  772. else:
  773. tool_list = QuerySet(Tool).filter(
  774. id__in=application.tool_ids + application.mcp_tool_ids + application.skill_tool_ids
  775. ).exclude(scope=ToolScope.SHARED)
  776. tw_dict = {tw.tool_id: tw
  777. for tw in QuerySet(ToolWorkflow).filter(
  778. tool_id__in=[tool.id for tool in tool_list if tool.tool_type == ToolType.WORKFLOW])}
  779. # 如果是技能工具,则需要将code字段转换为文件内容的base64字符串
  780. for tool in tool_list:
  781. if tool.tool_type == ToolType.SKILL:
  782. skill_file = QuerySet(File).filter(id=tool.code).first()
  783. if skill_file:
  784. tool.code = base64.b64encode(skill_file.get_bytes()).decode('utf-8')
  785. application_dict = ApplicationSerializerModel(application).data
  786. mk_instance = MKInstance(application_dict,
  787. [],
  788. 'v2',
  789. [self.to_tool_dict(tool, tw_dict) for tool in
  790. tool_list])
  791. application_pickle = pickle.dumps(mk_instance)
  792. response = HttpResponse(content_type='text/plain', content=application_pickle)
  793. response['Content-Disposition'] = f'attachment; filename="{application.name}.mk"'
  794. return response
  795. except Exception as e:
  796. return result.error(str(e), response_status=status.HTTP_500_INTERNAL_SERVER_ERROR)
  797. @staticmethod
  798. def to_tool_dict(tool, tool_workflow_dict):
  799. if tool.tool_type == ToolType.WORKFLOW:
  800. return {**ToolExportModelSerializer(tool).data, 'work_flow': tool_workflow_dict.get(tool.id).work_flow}
  801. return ToolExportModelSerializer(tool).data
  802. @staticmethod
  803. def reset_application_version(application_version, application):
  804. update_field_dict = {
  805. 'application_name': 'name', 'desc': 'desc', 'prologue': 'prologue', 'dialogue_number': 'dialogue_number',
  806. 'user_id': 'user_id', 'model_id': 'model_id', 'knowledge_setting': 'knowledge_setting',
  807. 'model_setting': 'model_setting', 'model_params_setting': 'model_params_setting',
  808. 'tts_model_params_setting': 'tts_model_params_setting',
  809. 'stt_model_params_setting': 'stt_model_params_setting',
  810. 'problem_optimization': 'problem_optimization', 'icon': 'icon', 'work_flow': 'work_flow',
  811. 'problem_optimization_prompt': 'problem_optimization_prompt', 'tts_model_id': 'tts_model_id',
  812. 'stt_model_id': 'stt_model_id', 'tts_model_enable': 'tts_model_enable',
  813. 'stt_model_enable': 'stt_model_enable', 'tts_type': 'tts_type',
  814. 'tts_autoplay': 'tts_autoplay', 'stt_autosend': 'stt_autosend', 'file_upload_enable': 'file_upload_enable',
  815. 'file_upload_setting': 'file_upload_setting',
  816. 'mcp_enable': 'mcp_enable', 'mcp_tool_ids': 'mcp_tool_ids', 'mcp_servers': 'mcp_servers',
  817. 'mcp_source': 'mcp_source', 'tool_enable': 'tool_enable', 'tool_ids': 'tool_ids',
  818. 'application_enable': 'application_enable', 'application_ids': 'application_ids',
  819. 'skill_tool_ids': 'skill_tool_ids',
  820. 'mcp_output_enable': 'mcp_output_enable',
  821. 'type': 'type'
  822. }
  823. for (version_field, app_field) in update_field_dict.items():
  824. _v = getattr(application, app_field)
  825. setattr(application_version, version_field, _v)
  826. @transaction.atomic
  827. def publish(self, instance, with_valid=True):
  828. if with_valid:
  829. self.is_valid()
  830. user_id = self.data.get('user_id')
  831. workspace_id = self.data.get("workspace_id")
  832. user = QuerySet(User).filter(id=user_id).first()
  833. application = QuerySet(Application).filter(id=self.data.get("application_id"),
  834. workspace_id=workspace_id).first()
  835. if application.type == ApplicationTypeChoices.WORK_FLOW:
  836. work_flow = application.work_flow
  837. if work_flow is None:
  838. raise AppApiException(500, _("work_flow is a required field"))
  839. Workflow.new_instance(work_flow).is_valid()
  840. base_node = get_base_node_work_flow(work_flow)
  841. if base_node is not None:
  842. node_data = base_node.get('properties').get('node_data')
  843. if node_data is not None:
  844. application.name = node_data.get('name')
  845. application.desc = node_data.get('desc')
  846. application.prologue = node_data.get('prologue')
  847. application.work_flow = work_flow
  848. application.publish_time = timezone.now()
  849. application.is_publish = True
  850. application.save()
  851. work_flow_version = ApplicationVersion(work_flow=application.work_flow, application=application,
  852. name=timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S'),
  853. publish_user_id=user_id,
  854. publish_user_name=user.username,
  855. workspace_id=workspace_id)
  856. self.reset_application_version(work_flow_version, application)
  857. work_flow_version.save()
  858. access_token = hashlib.md5(
  859. str(uuid.uuid7()).encode()).hexdigest()[
  860. 8:24]
  861. application_access_token = QuerySet(ApplicationAccessToken).filter(
  862. application_id=application.id).first()
  863. if application_access_token is None:
  864. application_access_token = ApplicationAccessToken(application_id=application.id,
  865. access_token=access_token, is_active=True)
  866. application_access_token.save()
  867. else:
  868. access_token = application_access_token.access_token
  869. del_application_access_token(access_token)
  870. QuerySet(TriggerTask).filter(source_type="APPLICATION", source_id=self.data.get("application_id")).update(
  871. is_active=True)
  872. return self.one(with_valid=False)
  873. @staticmethod
  874. def update_work_flow_model(instance):
  875. if 'nodes' not in instance.get('work_flow'):
  876. return
  877. nodes = instance.get('work_flow')['nodes']
  878. for node in nodes:
  879. if node['id'] == 'base-node':
  880. node_data = node['properties']['node_data']
  881. if 'stt_model_id' in node_data:
  882. instance['stt_model_id'] = node_data['stt_model_id']
  883. if 'tts_model_id' in node_data:
  884. instance['tts_model_id'] = node_data['tts_model_id']
  885. if 'stt_model_enable' in node_data:
  886. instance['stt_model_enable'] = node_data['stt_model_enable']
  887. if 'tts_model_enable' in node_data:
  888. instance['tts_model_enable'] = node_data['tts_model_enable']
  889. if 'tts_type' in node_data:
  890. instance['tts_type'] = node_data['tts_type']
  891. if 'tts_autoplay' in node_data:
  892. instance['tts_autoplay'] = node_data['tts_autoplay']
  893. if 'stt_autosend' in node_data:
  894. instance['stt_autosend'] = node_data['stt_autosend']
  895. if 'tts_model_params_setting' in node_data:
  896. instance['tts_model_params_setting'] = node_data['tts_model_params_setting']
  897. if 'stt_model_params_setting' in node_data:
  898. instance['stt_model_params_setting'] = node_data['stt_model_params_setting']
  899. if 'file_upload_enable' in node_data:
  900. instance['file_upload_enable'] = node_data['file_upload_enable']
  901. if 'file_upload_setting' in node_data:
  902. instance['file_upload_setting'] = node_data['file_upload_setting']
  903. if 'name' in node_data:
  904. instance['name'] = node_data['name']
  905. break
  906. knowledge_node_list = ApplicationOperateSerializer.get_search_node(instance.get('work_flow'))
  907. for knowledge_node in knowledge_node_list:
  908. node_data = knowledge_node.get('properties').get('node_data')
  909. # 全部知识库id
  910. all_knowledge_id_list = node_data.get('all_knowledge_id_list') or []
  911. # 用户修改的知识库id
  912. knowledge_id_list = node_data.get('knowledge_id_list') or []
  913. # 用户可以看到的知识库
  914. knowledge_list = node_data.get('knowledge_list') or []
  915. view_knowledge_id_list = [knowledge.get('id') for knowledge in knowledge_list]
  916. other_knowledge_id_list = [knowledge_id for knowledge_id in all_knowledge_id_list if
  917. not view_knowledge_id_list.__contains__(knowledge_id)]
  918. node_data['knowledge_id_list'] = other_knowledge_id_list + knowledge_id_list
  919. def move(self, folder_id: str):
  920. self.is_valid(raise_exception=True)
  921. application_id = self.data.get("application_id")
  922. application = QuerySet(Application).get(id=application_id)
  923. application.folder_id = folder_id
  924. application.save()
  925. return True
  926. @transaction.atomic
  927. def edit(self, instance: Dict, with_valid=True):
  928. if with_valid:
  929. self.is_valid()
  930. ApplicationEditSerializer(data=instance).is_valid(
  931. raise_exception=True)
  932. application_id = self.data.get("application_id")
  933. application = QuerySet(Application).get(id=application_id)
  934. # 处理工作流模板逻辑
  935. if 'work_flow_template' in instance:
  936. return self.update_template_workflow(instance, application)
  937. if instance.get('model_id') is None or len(instance.get('model_id')) == 0:
  938. application.model_id = None
  939. else:
  940. model = QuerySet(Model).filter(
  941. id=instance.get('model_id')).first()
  942. if model is None:
  943. raise AppApiException(500, _("Model does not exist"))
  944. if instance.get('stt_model_id') is None or len(instance.get('stt_model_id')) == 0:
  945. application.stt_model_id = None
  946. else:
  947. model = QuerySet(Model).filter(
  948. id=instance.get('stt_model_id')).first()
  949. if model is None:
  950. raise AppApiException(500, _("Model does not exist"))
  951. if instance.get('tts_model_id') is None or len(instance.get('tts_model_id')) == 0:
  952. application.tts_model_id = None
  953. else:
  954. model = QuerySet(Model).filter(
  955. id=instance.get('tts_model_id')).first()
  956. if model is None:
  957. raise AppApiException(500, _("Model does not exist"))
  958. if 'work_flow' in instance:
  959. # 修改语音配置相关
  960. self.update_work_flow_model(instance)
  961. if 'mcp_servers' in instance and len(instance.get('mcp_servers', {})) > 0:
  962. ToolExecutor().validate_mcp_transport(json.dumps(instance.get('mcp_servers')))
  963. update_keys = ['name', 'desc', 'model_id', 'multiple_rounds_dialogue', 'prologue', 'status',
  964. 'knowledge_setting', 'model_setting', 'problem_optimization', 'dialogue_number',
  965. 'stt_model_id', 'tts_model_id', 'tts_model_enable', 'stt_model_enable', 'tts_type',
  966. 'tts_autoplay', 'stt_autosend', 'file_upload_enable', 'file_upload_setting',
  967. 'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting',
  968. 'stt_model_params_setting',
  969. 'mcp_enable', 'mcp_tool_ids', 'mcp_servers', 'mcp_source', 'tool_enable', 'tool_ids',
  970. 'mcp_output_enable', 'application_enable', 'application_ids', 'skill_tool_ids',
  971. 'problem_optimization_prompt', 'clean_time', 'file_clean_time', 'folder_id']
  972. for update_key in update_keys:
  973. if update_key in instance and instance.get(update_key) is not None:
  974. application.__setattr__(update_key, instance.get(update_key))
  975. application.save()
  976. # 当前用户可修改关联的知识库列表
  977. application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in
  978. self.list_knowledge(with_valid=False)]
  979. knowledge_id_list = []
  980. if 'knowledge_id_list' in instance:
  981. # 当前用户可修改关联的知识库列表
  982. application_knowledge_id_list = [str(knowledge.get('id')) for knowledge in
  983. self.list_knowledge(with_valid=False)]
  984. knowledge_id_list = instance.get('knowledge_id_list')
  985. for knowledge_id in knowledge_id_list:
  986. if not application_knowledge_id_list.__contains__(knowledge_id):
  987. message = lazy_format(_('Unknown knowledge base id {dataset_id}, unable to associate'),
  988. dataset_id=knowledge_id)
  989. raise AppApiException(500, str(message))
  990. update_resource_mapping_by_application(application_id,
  991. self.get_application_knowledge_mapping(application_knowledge_id_list,
  992. knowledge_id_list,
  993. application_id))
  994. return self.one(with_valid=False)
  995. def update_template_workflow(self, instance: Dict, app: Application):
  996. self.is_valid(raise_exception=True)
  997. work_flow_template = instance.get('work_flow_template')
  998. download_url = work_flow_template.get('downloadUrl')
  999. # 查找匹配的版本名称
  1000. res = requests.get(download_url, timeout=5)
  1001. try:
  1002. mk_instance = restricted_loads(res.content)
  1003. except Exception as e:
  1004. raise AppApiException(1001, _("Unsupported file format"))
  1005. application = mk_instance.application
  1006. tool_list = mk_instance.get_tool_list()
  1007. update_tool_map = {}
  1008. if len(tool_list) > 0:
  1009. tool_id_list = reduce(lambda x, y: [*x, *y],
  1010. [[tool.get('id'), generate_uuid((tool.get('id') + app.workspace_id or ''))]
  1011. for tool
  1012. in
  1013. tool_list], [])
  1014. # 存在的工具列表
  1015. exits_tool_id_list = [str(tool.id) for tool in
  1016. QuerySet(Tool).filter(id__in=tool_id_list, workspace_id=app.workspace_id)]
  1017. # 需要更新的工具集合
  1018. update_tool_map = {tool.get('id'): generate_uuid((tool.get('id') + app.workspace_id or '')) for tool
  1019. in
  1020. tool_list if
  1021. not exits_tool_id_list.__contains__(
  1022. tool.get('id'))}
  1023. tool_list = [{**tool, 'id': update_tool_map.get(tool.get('id'))} for tool in tool_list if
  1024. not exits_tool_id_list.__contains__(
  1025. tool.get('id')) and not exits_tool_id_list.__contains__(
  1026. generate_uuid((tool.get('id') + app.workspace_id or '')))]
  1027. tool_model_list = [self.to_tool(f, app.workspace_id, self.data.get('user_id')) for f in tool_list]
  1028. work_flow = application.get('work_flow')
  1029. for node in work_flow.get('nodes', []):
  1030. hand_node(node, update_tool_map)
  1031. if node.get('type') == 'loop-node':
  1032. for n in node.get('properties', {}).get('node_data', {}).get('loop_body', {}).get('nodes', []):
  1033. hand_node(n, update_tool_map)
  1034. app.work_flow = work_flow
  1035. application = mk_instance.application
  1036. app.name = application.get('name')
  1037. app.desc = application.get('desc')
  1038. app.save()
  1039. if len(tool_model_list) > 0:
  1040. QuerySet(Tool).bulk_create(tool_model_list)
  1041. UserResourcePermissionSerializer(data={
  1042. 'workspace_id': app.workspace_id,
  1043. 'user_id': self.data.get('user_id'),
  1044. 'auth_target_type': AuthTargetType.TOOL.value
  1045. }).auth_resource_batch([t.id for t in tool_model_list])
  1046. try:
  1047. requests.get(work_flow_template.get('downloadCallbackUrl'), timeout=5)
  1048. except Exception as e:
  1049. maxkb_logger.error(f"callback appstore tool download error: {e}")
  1050. return self.one(with_valid=False)
  1051. @staticmethod
  1052. def to_tool(tool, workspace_id, user_id):
  1053. return Tool(
  1054. id=tool.get('id'),
  1055. user_id=user_id,
  1056. name=tool.get('name'),
  1057. code=tool.get('code'),
  1058. template_id=tool.get('template_id'),
  1059. input_field_list=tool.get('input_field_list'),
  1060. init_field_list=tool.get('init_field_list'),
  1061. is_active=False if len((tool.get('init_field_list') or [])) > 0 else tool.get('is_active'),
  1062. scope=ToolScope.WORKSPACE,
  1063. folder_id=workspace_id,
  1064. workspace_id=workspace_id
  1065. )
  1066. def one(self, with_valid=True):
  1067. if with_valid:
  1068. self.is_valid()
  1069. application_id = self.data.get("application_id")
  1070. application = QuerySet(Application).get(id=application_id)
  1071. available_knowledge_list = self.list_knowledge(with_valid=False)
  1072. available_knowledge_dict = {knowledge.get('id'): knowledge for knowledge in available_knowledge_list}
  1073. knowledge_list = []
  1074. knowledge_id_list = []
  1075. if application.type == 'SIMPLE':
  1076. mapping_knowledge_list = QuerySet(ResourceMapping).filter(source_id=application_id,
  1077. source_type="APPLICATION",
  1078. target_type="KNOWLEDGE")
  1079. knowledge_list = [available_knowledge_dict.get(str(km.target_id)) for km in mapping_knowledge_list if
  1080. available_knowledge_dict.__contains__(str(km.target_id))]
  1081. knowledge_id_list = [k.get('id') for k in knowledge_list]
  1082. else:
  1083. self.update_knowledge_node(application.work_flow, available_knowledge_dict)
  1084. return {**ApplicationSerializerModel(application).data,
  1085. 'knowledge_id_list': knowledge_id_list,
  1086. 'knowledge_list': knowledge_list}
  1087. @staticmethod
  1088. def get_search_node(work_flow):
  1089. if work_flow is None:
  1090. return []
  1091. response = []
  1092. if 'nodes' in work_flow:
  1093. for node in work_flow.get('nodes'):
  1094. if node.get('type', '') == 'search-knowledge-node':
  1095. response.append(node)
  1096. if node.get('type') == 'loop-node':
  1097. r = ApplicationOperateSerializer.get_search_node(
  1098. node.get('properties', {}).get('node_data', {}).get('loop_body'))
  1099. for rn in r:
  1100. response.append(rn)
  1101. return response
  1102. def update_knowledge_node(self, workflow, available_knowledge_dict):
  1103. """
  1104. 修改知识库检索节点 数据
  1105. 定义 all_knowledge_id_list: 所有的关联知识库
  1106. knowledge_id_list: 当前用户可看到的关联知识库列表
  1107. knowledge_list: 用户
  1108. @param workflow: 知识库
  1109. @param available_knowledge_dict: 当前用户可用的知识库
  1110. @return:
  1111. """
  1112. knowledge_node_list = self.get_search_node(workflow)
  1113. for search_node in knowledge_node_list:
  1114. node_data = search_node.get('properties', {}).get('node_data', {})
  1115. # 当前知识库关联的所有知识库
  1116. knowledge_id_list = node_data.get('knowledge_id_list', [])
  1117. knowledge_list = [available_knowledge_dict.get(knowledge_id) for knowledge_id in knowledge_id_list if
  1118. available_knowledge_dict.__contains__(knowledge_id)]
  1119. node_data['all_knowledge_id_list'] = knowledge_id_list
  1120. node_data['knowledge_id_list'] = [knowledge.get('id') for knowledge in knowledge_list]
  1121. node_data['knowledge_list'] = knowledge_list
  1122. def list_knowledge(self, with_valid=True):
  1123. if with_valid:
  1124. self.is_valid(raise_exception=True)
  1125. workspace_id = self.data.get("workspace_id")
  1126. user_id = self.data.get('user_id')
  1127. knowledge_workspace_authorization_model = DatabaseModelManage.get_model('knowledge_workspace_authorization')
  1128. share_knowledge_list = []
  1129. if knowledge_workspace_authorization_model is not None:
  1130. white_list_condition = Q(authentication_type='WHITE_LIST') & Q(
  1131. workspace_id_list__contains=[workspace_id])
  1132. default_condition = ~Q(authentication_type='WHITE_LIST') & ~Q(
  1133. workspace_id_list__contains=[workspace_id])
  1134. # 组合查询
  1135. query = white_list_condition | default_condition
  1136. inner = QuerySet(knowledge_workspace_authorization_model).filter(query)
  1137. share_knowledge_list = [{**KnowledgeModelSerializer(k).data, 'scope': 'SHARED'} for k in
  1138. QuerySet(Knowledge).filter(id__in=inner)]
  1139. workspace_knowledge_list = [{**k, 'scope': 'WORKSPACE'} for k in KnowledgeSerializer.Query(
  1140. data={
  1141. 'workspace_id': workspace_id,
  1142. 'scope': KnowledgeScope.WORKSPACE,
  1143. 'user_id': user_id
  1144. }
  1145. ).list() if k.get('resource_type') == 'knowledge']
  1146. return [*workspace_knowledge_list, *share_knowledge_list]
  1147. @staticmethod
  1148. def save_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id):
  1149. # 需要排除已删除的数据集
  1150. knowledge_id_list = [knowledge.id for knowledge in QuerySet(Knowledge).filter(id__in=knowledge_id_list)]
  1151. # 删除已经关联的id
  1152. QuerySet(ResourceMapping).filter(target_id__in=application_knowledge_id_list,
  1153. source_id=application_id,
  1154. source_type='APPLICATION',
  1155. target_type="KNOWLEDGE").delete()
  1156. # 插入
  1157. QuerySet(ResourceMapping).bulk_create(
  1158. [ResourceMapping(source_id=application_id, target_id=knowledge_id, source_type='APPLICATION',
  1159. target_type="KNOWLEDGE") for knowledge_id in
  1160. knowledge_id_list]) if len(knowledge_id_list) > 0 else None
  1161. @staticmethod
  1162. def get_application_knowledge_mapping(application_knowledge_id_list, knowledge_id_list, application_id):
  1163. """
  1164. @param application_knowledge_id_list: 当前应用可修改的知识库列表
  1165. @param knowledge_id_list: 用户修改的知识库列表
  1166. @param application_id: 应用id
  1167. @return:
  1168. """
  1169. # 当前知识库和应用已关联列表
  1170. knowledge_application_mapping_list = QuerySet(ResourceMapping).filter(source_id=application_id,
  1171. source_type='APPLICATION',
  1172. target_type="KNOWLEDGE",
  1173. ).exclude(
  1174. target_id__in=application_knowledge_id_list)
  1175. edit_knowledge_list = [ResourceMapping(source_id=application_id, target_id=knowledge_id,
  1176. source_type='APPLICATION',
  1177. target_type="KNOWLEDGE")
  1178. for knowledge_id in knowledge_id_list]
  1179. return list(knowledge_application_mapping_list) + edit_knowledge_list
  1180. def speech_to_text(self, instance, debug=True, with_valid=True):
  1181. if with_valid:
  1182. self.is_valid(raise_exception=True)
  1183. SpeechToTextRequest(data=instance).is_valid(raise_exception=True)
  1184. application_id = self.data.get('application_id')
  1185. if debug:
  1186. application = QuerySet(Application).filter(id=application_id).first()
  1187. else:
  1188. application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by(
  1189. '-create_time').first()
  1190. if application.stt_model_enable:
  1191. model = get_model_instance_by_model_workspace_id(application.stt_model_id, application.workspace_id,
  1192. **application.stt_model_params_setting)
  1193. text = model.speech_to_text(instance.get('file'))
  1194. return text
  1195. def text_to_speech(self, instance, debug=True, with_valid=True):
  1196. if with_valid:
  1197. self.is_valid(raise_exception=True)
  1198. TextToSpeechRequest(data=instance).is_valid(raise_exception=True)
  1199. application_id = self.data.get('application_id')
  1200. if debug:
  1201. application = QuerySet(Application).filter(id=application_id).first()
  1202. else:
  1203. application = QuerySet(ApplicationVersion).filter(application_id=application_id).order_by(
  1204. '-create_time').first()
  1205. if application.tts_model_enable:
  1206. model = get_model_instance_by_model_workspace_id(application.tts_model_id, application.workspace_id,
  1207. **application.tts_model_params_setting)
  1208. content = _remove_empty_lines(instance.get('text', ''))
  1209. return model.text_to_speech(content)
  1210. def play_demo_text(self, instance, with_valid=True):
  1211. text = '你好,这里是语音播放测试'
  1212. if with_valid:
  1213. self.is_valid(raise_exception=True)
  1214. PlayDemoTextRequest(data=instance).is_valid(raise_exception=True)
  1215. tts_model_id = instance.pop('tts_model_id')
  1216. model = get_model_instance_by_model_workspace_id(tts_model_id, self.data.get('workspace_id'), **instance)
  1217. return model.text_to_speech(text)
  1218. class ApplicationBatchOperateSerializer(serializers.Serializer):
  1219. workspace_id = serializers.CharField(required=True, label=_("Workspace ID"))
  1220. def is_valid(self, *, raise_exception=False):
  1221. super().is_valid(raise_exception=True)
  1222. @transaction.atomic
  1223. def batch_delete(self, instance: Dict, with_valid=True):
  1224. from trigger.handler.simple_tools import deploy
  1225. from trigger.serializers.trigger import TriggerModelSerializer
  1226. if with_valid:
  1227. BatchSerializer(data=instance).is_valid(model=Application, raise_exception=True)
  1228. self.is_valid(raise_exception=True)
  1229. id_list = instance.get("id_list")
  1230. workspace_id = self.data.get('workspace_id')
  1231. QuerySet(ApplicationVersion).filter(application_id__in=id_list).delete()
  1232. QuerySet(ResourceMapping).filter(
  1233. Q(target_id__in=id_list) | Q(source_id__in=id_list)
  1234. ).delete()
  1235. QuerySet(WorkspaceUserResourcePermission).filter(target__in=id_list).delete()
  1236. QuerySet(Application).filter(id__in=id_list, workspace_id=workspace_id).delete()
  1237. trigger_ids = list(
  1238. QuerySet(TriggerTask).filter(
  1239. source_type="APPLICATION", source_id__in=id_list
  1240. ).values('trigger_id').distinct()
  1241. )
  1242. QuerySet(TriggerTask).filter(source_type="APPLICATION", source_id__in=id_list).delete()
  1243. for trigger_id in trigger_ids:
  1244. trigger = Trigger.objects.filter(id=trigger_id['trigger_id']).first()
  1245. if trigger and trigger.is_active:
  1246. deploy(TriggerModelSerializer(trigger).data, **{})
  1247. return True
  1248. def batch_move(self, instance: Dict, with_valid=True):
  1249. if with_valid:
  1250. BatchMoveSerializer(data=instance).is_valid(model=Application, raise_exception=True)
  1251. self.is_valid(raise_exception=True)
  1252. id_list = instance.get("id_list")
  1253. folder_id = instance.get("folder_id")
  1254. workspace_id = self.data.get('workspace_id')
  1255. QuerySet(Application).filter(id__in=id_list, workspace_id=workspace_id).update(folder_id=folder_id)
  1256. return True