mirror of
https://github.com/1Panel-dev/MaxKB.git
synced 2025-12-26 01:33:05 +00:00
feat: add application enable flag and application IDs to application model
This commit is contained in:
parent
fe004c235c
commit
54e16acee6
|
|
@ -88,6 +88,8 @@ class IChatStep(IBaseChatPipelineStep):
|
|||
mcp_source = serializers.CharField(label="MCP Source", required=False, default="referencing")
|
||||
tool_enable = serializers.BooleanField(label="工具是否启用", required=False, default=False)
|
||||
tool_ids = serializers.JSONField(label="工具ID列表", required=False, default=list)
|
||||
application_enable = serializers.BooleanField(label="应用是否启用", required=False, default=False)
|
||||
application_ids = serializers.JSONField(label="应用ID列表", required=False, default=list)
|
||||
mcp_output_enable = serializers.BooleanField(label="MCP输出是否启用", required=False, default=True)
|
||||
|
||||
def is_valid(self, *, raise_exception=False):
|
||||
|
|
@ -115,6 +117,6 @@ class IChatStep(IBaseChatPipelineStep):
|
|||
padding_problem_text: str = None, stream: bool = True, chat_user_id=None, chat_user_type=None,
|
||||
no_references_setting=None, model_params_setting=None, model_setting=None,
|
||||
mcp_enable=False, mcp_tool_ids=None, mcp_servers='', mcp_source="referencing",
|
||||
tool_enable=False, tool_ids=None, mcp_output_enable=True,
|
||||
tool_enable=False, tool_ids=None, application_enable=None, application_ids=None, mcp_output_enable=True,
|
||||
**kwargs):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -7,12 +7,11 @@
|
|||
@desc: 对话step Base实现
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import uuid_utils.compat as uuid
|
||||
from typing import List
|
||||
|
||||
import uuid_utils.compat as uuid
|
||||
from django.db.models import QuerySet
|
||||
from django.http import StreamingHttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
|
|
@ -26,11 +25,10 @@ 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, mcp_response_generator
|
||||
from application.models import ApplicationChatUserStats, ChatUserType
|
||||
from application.models import ApplicationChatUserStats, ChatUserType, Application, ApplicationApiKey
|
||||
from common.utils.logger import maxkb_logger
|
||||
from common.utils.rsa_util import rsa_long_decrypt
|
||||
from common.utils.tool_code import ToolExecutor
|
||||
from maxkb.const import CONFIG
|
||||
from models_provider.tools import get_model_instance_by_model_workspace_id
|
||||
from tools.models import Tool
|
||||
|
||||
|
|
@ -59,7 +57,6 @@ def write_context(step, manage, request_token, response_token, all_text):
|
|||
manage.context['answer_tokens'] = manage.context['answer_tokens'] + response_token
|
||||
|
||||
|
||||
|
||||
def event_content(response,
|
||||
chat_id,
|
||||
chat_record_id,
|
||||
|
|
@ -182,6 +179,8 @@ class BaseChatStep(IChatStep):
|
|||
mcp_source="referencing",
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True,
|
||||
**kwargs):
|
||||
chat_model = get_model_instance_by_model_workspace_id(model_id, workspace_id,
|
||||
|
|
@ -193,6 +192,7 @@ class BaseChatStep(IChatStep):
|
|||
no_references_setting,
|
||||
model_setting,
|
||||
mcp_enable, mcp_tool_ids, mcp_servers, mcp_source, tool_enable, tool_ids,
|
||||
application_enable, application_ids,
|
||||
mcp_output_enable)
|
||||
else:
|
||||
return self.execute_block(message_list, chat_id, problem_text, post_response_handler, chat_model,
|
||||
|
|
@ -200,6 +200,7 @@ class BaseChatStep(IChatStep):
|
|||
manage, padding_problem_text, chat_user_id, chat_user_type, no_references_setting,
|
||||
model_setting,
|
||||
mcp_enable, mcp_tool_ids, mcp_servers, mcp_source, tool_enable, tool_ids,
|
||||
application_enable, application_ids,
|
||||
mcp_output_enable)
|
||||
|
||||
def get_details(self, manage, **kwargs):
|
||||
|
|
@ -225,8 +226,9 @@ class BaseChatStep(IChatStep):
|
|||
return result
|
||||
|
||||
def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_ids, tool_ids,
|
||||
application_enable, application_ids,
|
||||
mcp_output_enable, chat_model, message_list):
|
||||
if not mcp_enable and not tool_enable:
|
||||
if not mcp_enable and not tool_enable and not application_enable:
|
||||
return None
|
||||
|
||||
mcp_servers_config = {}
|
||||
|
|
@ -258,10 +260,25 @@ class BaseChatStep(IChatStep):
|
|||
params = json.loads(rsa_long_decrypt(tool.init_params))
|
||||
else:
|
||||
params = {}
|
||||
tool_config = executor.get_tool_mcp_config(tool.code, params)
|
||||
tool_config = executor.get_tool_mcp_config(tool.code, params, tool.name, tool.desc)
|
||||
|
||||
mcp_servers_config[str(tool.id)] = tool_config
|
||||
|
||||
if application_enable:
|
||||
if application_ids and len(application_ids) > 0:
|
||||
self.context['application_ids'] = application_ids
|
||||
for application_id in application_ids:
|
||||
app = QuerySet(Application).filter(id=application_id).first()
|
||||
app_key = QuerySet(ApplicationApiKey).filter(application_id=application_id, is_active=True).first()
|
||||
# TODO 处理api
|
||||
if app_key is not None:
|
||||
api_key = app_key.secret_key
|
||||
else:
|
||||
continue
|
||||
executor = ToolExecutor()
|
||||
app_config = executor.get_app_mcp_config(api_key, app.name, app.desc)
|
||||
mcp_servers_config[str(app.id)] = app_config
|
||||
|
||||
if len(mcp_servers_config) > 0:
|
||||
return mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable)
|
||||
|
||||
|
|
@ -278,6 +295,8 @@ class BaseChatStep(IChatStep):
|
|||
mcp_source="referencing",
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True):
|
||||
if paragraph_list is None:
|
||||
paragraph_list = []
|
||||
|
|
@ -296,7 +315,8 @@ class BaseChatStep(IChatStep):
|
|||
else:
|
||||
# 处理 MCP 请求
|
||||
mcp_result = self._handle_mcp_request(
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_ids, tool_ids, mcp_output_enable, chat_model,
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_ids, tool_ids, application_enable,
|
||||
application_ids, mcp_output_enable, chat_model,
|
||||
message_list,
|
||||
)
|
||||
if mcp_result:
|
||||
|
|
@ -320,10 +340,13 @@ class BaseChatStep(IChatStep):
|
|||
mcp_source="referencing",
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True):
|
||||
chat_result, is_ai_chat = self.get_stream_result(message_list, chat_model, paragraph_list,
|
||||
no_references_setting, problem_text, mcp_enable, mcp_tool_ids,
|
||||
mcp_servers, mcp_source, tool_enable, tool_ids,
|
||||
application_enable, application_ids,
|
||||
mcp_output_enable)
|
||||
chat_record_id = uuid.uuid7()
|
||||
r = StreamingHttpResponse(
|
||||
|
|
@ -347,6 +370,8 @@ class BaseChatStep(IChatStep):
|
|||
mcp_source="referencing",
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True
|
||||
):
|
||||
if paragraph_list is None:
|
||||
|
|
@ -365,7 +390,8 @@ class BaseChatStep(IChatStep):
|
|||
else:
|
||||
# 处理 MCP 请求
|
||||
mcp_result = self._handle_mcp_request(
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_ids, tool_ids, mcp_output_enable,
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_ids, tool_ids, application_enable,
|
||||
application_ids, mcp_output_enable,
|
||||
chat_model, message_list,
|
||||
)
|
||||
if mcp_result:
|
||||
|
|
@ -388,6 +414,8 @@ class BaseChatStep(IChatStep):
|
|||
mcp_source="referencing",
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True):
|
||||
reasoning_content_enable = model_setting.get('reasoning_content_enable', False)
|
||||
reasoning_content_start = model_setting.get('reasoning_content_start', '<think>')
|
||||
|
|
@ -400,7 +428,8 @@ class BaseChatStep(IChatStep):
|
|||
chat_result, is_ai_chat = self.get_block_result(message_list, chat_model, paragraph_list,
|
||||
no_references_setting, problem_text, mcp_enable,
|
||||
mcp_tool_ids, mcp_servers, mcp_source, tool_enable,
|
||||
tool_ids, mcp_output_enable)
|
||||
tool_ids, application_enable, application_ids,
|
||||
mcp_output_enable)
|
||||
if is_ai_chat:
|
||||
request_token = chat_model.get_num_tokens_from_messages(message_list)
|
||||
response_token = chat_model.get_num_tokens(chat_result.content)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ class ChatNodeSerializer(serializers.Serializer):
|
|||
tool_enable = serializers.BooleanField(required=False, default=False, label=_("Whether to enable tools"))
|
||||
tool_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,
|
||||
label=_("Tool IDs"), )
|
||||
application_enable = serializers.BooleanField(required=False, default=False, label=_("Whether to enable apps"))
|
||||
application_ids = serializers.ListField(child=serializers.UUIDField(), required=False, allow_empty=True,
|
||||
label=_("App IDs"), )
|
||||
mcp_output_enable = serializers.BooleanField(required=False, default=True, label=_("Whether to enable MCP output"))
|
||||
|
||||
|
||||
|
|
@ -73,6 +76,8 @@ class IChatNode(INode):
|
|||
mcp_source=None,
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True,
|
||||
**kwargs) -> NodeResult:
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
@desc:
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
from functools import reduce
|
||||
|
|
@ -20,9 +19,9 @@ from langchain_core.messages import BaseMessage, AIMessage
|
|||
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, mcp_response_generator
|
||||
from application.models import Application
|
||||
from common.utils.rsa_util import rsa_long_decrypt
|
||||
from common.utils.tool_code import ToolExecutor
|
||||
from maxkb.const import CONFIG
|
||||
from models_provider.models import Model
|
||||
from models_provider.tools import get_model_credential, get_model_instance_by_model_workspace_id
|
||||
from tools.models import Tool
|
||||
|
|
@ -160,6 +159,8 @@ class BaseChatNode(IChatNode):
|
|||
mcp_source=None,
|
||||
tool_enable=False,
|
||||
tool_ids=None,
|
||||
application_enable=False,
|
||||
application_ids=None,
|
||||
mcp_output_enable=True,
|
||||
**kwargs) -> NodeResult:
|
||||
if dialogue_type is None:
|
||||
|
|
@ -186,7 +187,8 @@ class BaseChatNode(IChatNode):
|
|||
|
||||
# 处理 MCP 请求
|
||||
mcp_result = self._handle_mcp_request(
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids, mcp_output_enable,
|
||||
mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids, application_enable,
|
||||
application_ids, mcp_output_enable,
|
||||
chat_model, message_list, history_message, question
|
||||
)
|
||||
if mcp_result:
|
||||
|
|
@ -208,8 +210,9 @@ class BaseChatNode(IChatNode):
|
|||
_write_context=write_context)
|
||||
|
||||
def _handle_mcp_request(self, mcp_enable, tool_enable, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids, tool_ids,
|
||||
application_enable, application_ids,
|
||||
mcp_output_enable, chat_model, message_list, history_message, question):
|
||||
if not mcp_enable and not tool_enable:
|
||||
if not mcp_enable and not tool_enable and not application_enable:
|
||||
return None
|
||||
|
||||
mcp_servers_config = {}
|
||||
|
|
@ -245,10 +248,19 @@ class BaseChatNode(IChatNode):
|
|||
params = json.loads(rsa_long_decrypt(tool.init_params))
|
||||
else:
|
||||
params = {}
|
||||
tool_config = executor.get_tool_mcp_config(tool.code, params)
|
||||
tool_config = executor.get_tool_mcp_config(tool.code, params, tool.name, tool.desc)
|
||||
|
||||
mcp_servers_config[str(tool.id)] = tool_config
|
||||
|
||||
if application_enable:
|
||||
if application_ids and len(application_ids) > 0:
|
||||
self.context['application_ids'] = application_ids
|
||||
for application_id in application_ids:
|
||||
app = QuerySet(Application).filter(id=application_id).first()
|
||||
executor = ToolExecutor()
|
||||
app_config = executor.get_app_mcp_config(app.id, app.name, app.desc)
|
||||
mcp_servers_config[str(app.id)] = app_config
|
||||
|
||||
if len(mcp_servers_config) > 0:
|
||||
r = mcp_response_generator(chat_model, message_list, json.dumps(mcp_servers_config), mcp_output_enable)
|
||||
return NodeResult(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 5.2.9 on 2025-12-16 08:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('application', '0003_application_stt_model_params_setting_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='application_enable',
|
||||
field=models.BooleanField(default=False, verbose_name='应用是否启用'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='application',
|
||||
name='application_ids',
|
||||
field=models.JSONField(default=list, verbose_name='应用ID列表'),
|
||||
),
|
||||
]
|
||||
|
|
@ -100,6 +100,8 @@ class Application(AppModelMixin):
|
|||
mcp_source = models.CharField(verbose_name="MCP Source", max_length=20, default="referencing")
|
||||
tool_enable = models.BooleanField(verbose_name="工具是否启用", default=False)
|
||||
tool_ids = models.JSONField(verbose_name="工具ID列表", default=list)
|
||||
application_enable = models.BooleanField(verbose_name="应用是否启用", default=False)
|
||||
application_ids = models.JSONField(verbose_name="应用ID列表", default=list)
|
||||
mcp_output_enable = models.BooleanField(verbose_name="MCP输出是否启用", default=True)
|
||||
|
||||
@staticmethod
|
||||
|
|
|
|||
|
|
@ -857,7 +857,7 @@ class ApplicationOperateSerializer(serializers.Serializer):
|
|||
'api_key_is_active', 'icon', 'work_flow', 'model_params_setting', 'tts_model_params_setting',
|
||||
'stt_model_params_setting',
|
||||
'mcp_enable', 'mcp_tool_ids', 'mcp_servers', 'mcp_source', 'tool_enable', 'tool_ids',
|
||||
'mcp_output_enable',
|
||||
'mcp_output_enable', 'application_enable', 'application_ids',
|
||||
'problem_optimization_prompt', 'clean_time', 'folder_id']
|
||||
for update_key in update_keys:
|
||||
if update_key in instance and instance.get(update_key) is not None:
|
||||
|
|
|
|||
|
|
@ -162,6 +162,8 @@ class ChatInfo:
|
|||
'mcp_source': self.application.mcp_source,
|
||||
'tool_enable': self.application.tool_enable,
|
||||
'tool_ids': self.application.tool_ids,
|
||||
'application_enable': self.application.application_enable,
|
||||
'application_ids': self.application.application_ids,
|
||||
'mcp_output_enable': self.application.mcp_output_enable,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,40 @@
|
|||
# coding=utf-8
|
||||
import ast
|
||||
import base64
|
||||
import getpass
|
||||
import gzip
|
||||
import json
|
||||
import os
|
||||
import pwd
|
||||
import random
|
||||
import resource
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import pwd
|
||||
import resource
|
||||
import getpass
|
||||
import random
|
||||
import time
|
||||
import uuid_utils.compat as uuid
|
||||
from contextlib import contextmanager
|
||||
from common.utils.logger import maxkb_logger
|
||||
from textwrap import dedent
|
||||
|
||||
import uuid_utils.compat as uuid
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from common.utils.logger import maxkb_logger
|
||||
from maxkb.const import BASE_DIR, CONFIG
|
||||
from maxkb.const import PROJECT_DIR
|
||||
from textwrap import dedent
|
||||
|
||||
_enable_sandbox = bool(CONFIG.get('SANDBOX', 0))
|
||||
_run_user = 'sandbox' if _enable_sandbox else getpass.getuser()
|
||||
_sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox') if _enable_sandbox else os.path.join(PROJECT_DIR, 'data', 'sandbox')
|
||||
_sandbox_path = CONFIG.get("SANDBOX_HOME", '/opt/maxkb-app/sandbox') if _enable_sandbox else os.path.join(PROJECT_DIR,
|
||||
'data',
|
||||
'sandbox')
|
||||
_process_limit_timeout_seconds = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_TIMEOUT_SECONDS", '3600'))
|
||||
_process_limit_cpu_cores = min(max(int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_CPU_CORES", '1')), 1), len(os.sched_getaffinity(0))) if sys.platform.startswith("linux") else os.cpu_count() # 只支持linux,window和mac不支持
|
||||
_process_limit_cpu_cores = min(max(int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_CPU_CORES", '1')), 1),
|
||||
len(os.sched_getaffinity(0))) if sys.platform.startswith(
|
||||
"linux") else os.cpu_count() # 只支持linux,window和mac不支持
|
||||
_process_limit_mem_mb = int(CONFIG.get("SANDBOX_PYTHON_PROCESS_LIMIT_MEM_MB", '256'))
|
||||
|
||||
|
||||
class ToolExecutor:
|
||||
|
||||
def __init__(self):
|
||||
|
|
@ -124,7 +131,7 @@ sys.stdout.flush()
|
|||
return result.get('data')
|
||||
raise Exception(result.get('msg') + (f'\n{subprocess_result.stderr}' if subprocess_result.stderr else ''))
|
||||
|
||||
def _generate_mcp_server_code(self, _code, params):
|
||||
def _generate_mcp_server_code(self, _code, params, name=None, description=None):
|
||||
# 解析代码,提取导入语句和函数定义
|
||||
try:
|
||||
tree = ast.parse(_code)
|
||||
|
|
@ -139,6 +146,9 @@ sys.stdout.flush()
|
|||
if isinstance(node, ast.Import) or isinstance(node, ast.ImportFrom):
|
||||
imports.append(ast.unparse(node))
|
||||
elif isinstance(node, ast.FunctionDef):
|
||||
if node.name.startswith('_'):
|
||||
other_code.append(ast.unparse(node))
|
||||
continue
|
||||
# 修改函数参数以包含 params 中的默认值
|
||||
arg_names = [arg.arg for arg in node.args.args]
|
||||
|
||||
|
|
@ -173,7 +183,7 @@ sys.stdout.flush()
|
|||
node.args.defaults = defaults
|
||||
|
||||
func_code = ast.unparse(node)
|
||||
functions.append(f"@mcp.tool()\n{func_code}\n")
|
||||
functions.append(f"@mcp.tool(name='{name}', description='{description}')\n{func_code}\n")
|
||||
else:
|
||||
other_code.append(ast.unparse(node))
|
||||
|
||||
|
|
@ -187,9 +197,9 @@ sys.stdout.flush()
|
|||
|
||||
return "\n".join(code_parts)
|
||||
|
||||
def generate_mcp_server_code(self, code_str, params):
|
||||
def generate_mcp_server_code(self, code_str, params, name, description):
|
||||
python_paths = CONFIG.get_sandbox_python_package_paths().split(',')
|
||||
code = self._generate_mcp_server_code(code_str, params)
|
||||
code = self._generate_mcp_server_code(code_str, params, name, description)
|
||||
set_run_user = f'os.setgid({pwd.getpwnam(_run_user).pw_gid});os.setuid({pwd.getpwnam(_run_user).pw_uid});' if _enable_sandbox else ''
|
||||
return f"""
|
||||
import os, sys, logging
|
||||
|
|
@ -204,8 +214,8 @@ os.environ.clear()
|
|||
exec({dedent(code)!a})
|
||||
"""
|
||||
|
||||
def get_tool_mcp_config(self, code, params):
|
||||
_code = self.generate_mcp_server_code(code, params)
|
||||
def get_tool_mcp_config(self, code, params, name, description):
|
||||
_code = self.generate_mcp_server_code(code, params, name, description)
|
||||
maxkb_logger.debug(f"Python code of mcp tool: {_code}")
|
||||
compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode()
|
||||
tool_config = {
|
||||
|
|
@ -222,6 +232,66 @@ exec({dedent(code)!a})
|
|||
}
|
||||
return tool_config
|
||||
|
||||
def get_app_mcp_config(self, api_key, name, description):
|
||||
chat_path = CONFIG.get_chat_path()
|
||||
_code = f'''
|
||||
import requests
|
||||
from typing import Optional
|
||||
|
||||
def _get_chat_id() -> Optional[str]:
|
||||
url = f"http://127.0.0.1:8080/{chat_path}/api/open"
|
||||
headers = {{
|
||||
'accept': '*/*',
|
||||
'Authorization': f'Bearer {api_key}'
|
||||
}}
|
||||
try:
|
||||
resp = requests.get(url, headers=headers, timeout=10)
|
||||
resp.raise_for_status()
|
||||
return resp.json().get("data")
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
|
||||
def _chat_with_ai(chat_id: str, message: str) -> Optional[str]:
|
||||
url = f"http://127.0.0.1:8080/{chat_path}/api/chat_message/{{chat_id}}"
|
||||
headers = {{"Content-Type": "application/json", "Authorization": f'Bearer {api_key}'}}
|
||||
payload = {{
|
||||
"message": message,
|
||||
"re_chat": False,
|
||||
"stream": False
|
||||
}}
|
||||
try:
|
||||
resp = requests.post(url, json=payload, headers=headers, timeout=600)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return str(data.get("data", {{}}).get("content") or data.get("response"))
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def ai_chat(message: str) -> str:
|
||||
chat_id = _get_chat_id()
|
||||
reply = _chat_with_ai(chat_id, message)
|
||||
return reply or "AI 未能生成回复"
|
||||
|
||||
'''
|
||||
_code = self.generate_mcp_server_code(_code, {}, name, description)
|
||||
# print(_code)
|
||||
maxkb_logger.debug(f"Python code of mcp app: {_code}")
|
||||
compressed_and_base64_encoded_code_str = base64.b64encode(gzip.compress(_code.encode())).decode()
|
||||
app_config = {
|
||||
'command': sys.executable,
|
||||
'args': [
|
||||
'-c',
|
||||
f'import base64,gzip; exec(gzip.decompress(base64.b64decode(\'{compressed_and_base64_encoded_code_str}\')).decode())',
|
||||
],
|
||||
'cwd': _sandbox_path,
|
||||
'env': {
|
||||
'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so',
|
||||
},
|
||||
'transport': 'stdio',
|
||||
}
|
||||
return app_config
|
||||
|
||||
def _exec(self, execute_file):
|
||||
kwargs = {'cwd': BASE_DIR, 'env': {
|
||||
'LD_PRELOAD': f'{_sandbox_path}/lib/sandbox.so',
|
||||
|
|
@ -255,4 +325,4 @@ def execution_timer(id=""):
|
|||
try:
|
||||
yield
|
||||
finally:
|
||||
maxkb_logger.debug(f"Tool execution({id}) takes {time.perf_counter() - start:.6f} seconds.")
|
||||
maxkb_logger.debug(f"Tool execution({id}) takes {time.perf_counter() - start:.6f} seconds.")
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ interface ApplicationFormType {
|
|||
mcp_source?: string
|
||||
tool_enable?: boolean
|
||||
tool_ids?: string[]
|
||||
application_enable?: boolean
|
||||
application_ids?: string[]
|
||||
mcp_output_enable?: boolean
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -433,9 +433,73 @@
|
|||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 应用 -->
|
||||
<el-form-item @click.prevent v-if="toolPermissionPrecise.read()">
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
<span class="mr-4">
|
||||
{{ $t('views.application.title') }}
|
||||
</span>
|
||||
<div class="flex">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="openApplicationDialog"
|
||||
@refreshForm="refreshParam"
|
||||
v-if="applicationForm.application_enable"
|
||||
>
|
||||
<AppIcon iconName="app-setting"></AppIcon>
|
||||
</el-button>
|
||||
<el-switch
|
||||
class="ml-8"
|
||||
size="small"
|
||||
v-model="applicationForm.application_enable"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<div
|
||||
class="w-full mb-16"
|
||||
v-if="applicationForm.application_ids && applicationForm.application_ids.length > 0 && toolPermissionPrecise.read()"
|
||||
>
|
||||
<template v-for="(item, index) in applicationForm.application_ids" :key="index">
|
||||
<div
|
||||
v-if="relatedObject(applicationSelectOptions, item, 'id')"
|
||||
class="flex-between border border-r-6 white-bg mb-4"
|
||||
style="padding: 5px 8px"
|
||||
>
|
||||
<div class="flex align-center" style="line-height: 20px">
|
||||
<el-avatar
|
||||
v-if="relatedObject(applicationSelectOptions, item, 'id')?.icon"
|
||||
shape="square"
|
||||
:size="20"
|
||||
style="background: none"
|
||||
class="mr-8"
|
||||
>
|
||||
<img
|
||||
:src="resetUrl(relatedObject(applicationSelectOptions, item, 'id')?.icon)"
|
||||
alt=""
|
||||
/>
|
||||
</el-avatar>
|
||||
<AppIcon v-else class="mr-8" :size="20" />
|
||||
|
||||
<div
|
||||
class="ellipsis"
|
||||
:title="relatedObject(applicationSelectOptions, item, 'id')?.name"
|
||||
>
|
||||
{{ relatedObject(applicationSelectOptions, item, 'id')?.name }}
|
||||
</div>
|
||||
</div>
|
||||
<el-button text @click="removeApplication(item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item
|
||||
@click.prevent
|
||||
v-if="(applicationForm.mcp_enable || applicationForm.tool_enable) && toolPermissionPrecise.read()"
|
||||
v-if="(applicationForm.mcp_enable || applicationForm.tool_enable || applicationForm.application_enable) && toolPermissionPrecise.read()"
|
||||
>
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
|
|
@ -633,6 +697,7 @@
|
|||
/>
|
||||
<McpServersDialog ref="mcpServersDialogRef" @refresh="submitMcpServersDialog" />
|
||||
<ToolDialog ref="toolDialogRef" @refresh="submitToolDialog" />
|
||||
<ApplicationDialog ref="applicationDialogRef" @refresh="submitApplicationDialog" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -658,11 +723,14 @@ import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
|||
import { resetUrl } from '@/utils/common.ts'
|
||||
import McpServersDialog from '@/views/application/component/McpServersDialog.vue'
|
||||
import ToolDialog from '@/views/application/component/ToolDialog.vue'
|
||||
import ApplicationDialog from "@/views/application/component/ApplicationDialog.vue";
|
||||
import useStore from "@/stores";
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const {
|
||||
params: { id },
|
||||
} = route as any
|
||||
const { user, folder } = useStore()
|
||||
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('resource-management')) {
|
||||
|
|
@ -736,6 +804,8 @@ const applicationForm = ref<ApplicationFormType>({
|
|||
tts_model_enable: false,
|
||||
tts_type: 'BROWSER',
|
||||
type: 'SIMPLE',
|
||||
application_enable: false,
|
||||
application_ids: [],
|
||||
mcp_enable: false,
|
||||
mcp_tool_ids: [],
|
||||
mcp_servers: '',
|
||||
|
|
@ -877,6 +947,14 @@ function removeMcpTool(id: any) {
|
|||
}
|
||||
}
|
||||
|
||||
function removeApplication(id: any) {
|
||||
if (applicationForm.value.application_ids) {
|
||||
applicationForm.value.application_ids = applicationForm.value.application_ids.filter(
|
||||
(v: any) => v !== id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mcpServersDialogRef = ref()
|
||||
function openMcpServersDialog() {
|
||||
const config = {
|
||||
|
|
@ -902,6 +980,26 @@ function submitToolDialog(config: any) {
|
|||
applicationForm.value.tool_ids = config.tool_ids
|
||||
}
|
||||
|
||||
const applicationDialogRef = ref()
|
||||
function openApplicationDialog() {
|
||||
applicationDialogRef.value.open(applicationForm.value.application_ids)
|
||||
}
|
||||
|
||||
function submitApplicationDialog(config: any) {
|
||||
applicationForm.value.application_ids = config.application_ids
|
||||
}
|
||||
|
||||
const applicationSelectOptions = ref<any[]>([])
|
||||
function getApplicationSelectOptions() {
|
||||
loadSharedApi({ type: 'application', systemType: apiType.value })
|
||||
.getAllApplication({folder_id: folder.currentFolder?.id || user.getWorkspaceId()})
|
||||
.then((res: any) => {
|
||||
applicationSelectOptions.value = res.data.filter(
|
||||
(item: any) => item.is_publish,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const toolSelectOptions = ref<any[]>([])
|
||||
function getToolSelectOptions() {
|
||||
const obj =
|
||||
|
|
@ -1118,6 +1216,7 @@ onMounted(() => {
|
|||
if (toolPermissionPrecise.value.read()) {
|
||||
getToolSelectOptions();
|
||||
getMcpToolSelectOptions()
|
||||
getApplicationSelectOptions()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
width="1000"
|
||||
append-to-body
|
||||
class="addTool-dialog"
|
||||
align-center
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
>
|
||||
<template #header="{ titleId, titleClass }">
|
||||
<div class="flex-between mb-8">
|
||||
<div class="flex">
|
||||
<h4 :id="titleId" :class="titleClass" class="mr-8">
|
||||
{{ $t('设置应用') }}
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
<el-button link class="mr-24" @click="refresh">
|
||||
<el-icon :size="18"><Refresh /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<LayoutContainer class="application-manage">
|
||||
<template #left>
|
||||
<folder-tree
|
||||
:data="folderList"
|
||||
:currentNodeKey="currentFolder?.id"
|
||||
@handleNodeClick="folderClickHandle"
|
||||
v-loading="folderLoading"
|
||||
:canOperation="false"
|
||||
:treeStyle="{ height: 'calc(100vh - 240px)' }"
|
||||
/>
|
||||
</template>
|
||||
<div class="layout-bg">
|
||||
<div class="flex-between p-16 ml-8">
|
||||
<h4>{{ currentFolder?.name }}</h4>
|
||||
<el-input
|
||||
v-model="searchValue"
|
||||
:placeholder="$t('common.search')"
|
||||
prefix-icon="Search"
|
||||
class="w-240 mr-8"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<el-scrollbar>
|
||||
<div class="p-16-24 pt-0" style="height: calc(100vh - 200px)">
|
||||
<el-row :gutter="12" v-loading="apiLoading" v-if="searchData.length">
|
||||
<el-col :span="12" v-for="(item, index) in searchData" :key="index" class="mb-16">
|
||||
<CardCheckbox
|
||||
value-field="id"
|
||||
:data="item"
|
||||
v-model="checkList"
|
||||
@change="changeHandle"
|
||||
>
|
||||
<template #icon>
|
||||
<el-avatar
|
||||
v-if="item?.icon"
|
||||
shape="square"
|
||||
:size="32"
|
||||
style="background: none"
|
||||
class="mr-8"
|
||||
>
|
||||
<img :src="resetUrl(item?.icon)" alt="" />
|
||||
</el-avatar>
|
||||
<ToolIcon v-else :size="32" :type="item?.tool_type" />
|
||||
</template>
|
||||
<span class="ellipsis cursor ml-12" :title="item.name"> {{ item.name }}</span>
|
||||
</CardCheckbox>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-empty :description="$t('common.noData')" v-else />
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
</div>
|
||||
</LayoutContainer>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex-between">
|
||||
<div class="flex">
|
||||
<el-text type="info" class="color-secondary mr-8" v-if="checkList.length > 0">
|
||||
{{ $t('common.selected') }} {{ checkList.length }}
|
||||
</el-text>
|
||||
<el-button link type="primary" v-if="checkList.length > 0" @click="clearCheck">
|
||||
{{ $t('common.clear') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<span>
|
||||
<el-button @click.prevent="dialogVisible = false">
|
||||
{{ $t('common.cancel') }}
|
||||
</el-button>
|
||||
<el-button type="primary" @click="submitHandle">
|
||||
{{ $t('common.add') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import useStore from '@/stores'
|
||||
import { loadSharedApi } from '@/utils/dynamics-api/shared-api'
|
||||
import { uniqueArray } from '@/utils/array'
|
||||
import { resetUrl } from '@/utils/common'
|
||||
const route = useRoute()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
const { folder, user } = useStore()
|
||||
const apiType = computed(() => {
|
||||
if (route.path.includes('shared')) {
|
||||
return 'systemShare'
|
||||
} else if (route.path.includes('resource-management')) {
|
||||
return 'systemManage'
|
||||
} else {
|
||||
return 'workspace'
|
||||
}
|
||||
})
|
||||
|
||||
const dialogVisible = ref<boolean>(false)
|
||||
const checkList = ref<Array<string>>([])
|
||||
const searchValue = ref('')
|
||||
const searchData = ref<Array<any>>([])
|
||||
const applicationList = ref<Array<any>>([])
|
||||
const apiLoading = ref(false)
|
||||
|
||||
watch(dialogVisible, (bool) => {
|
||||
if (!bool) {
|
||||
checkList.value = []
|
||||
searchValue.value = ''
|
||||
searchData.value = []
|
||||
applicationList.value = []
|
||||
}
|
||||
})
|
||||
|
||||
watch(searchValue, (val) => {
|
||||
if (val) {
|
||||
searchData.value = applicationList.value.filter((v) => v.name.includes(val))
|
||||
} else {
|
||||
searchData.value = applicationList.value
|
||||
}
|
||||
})
|
||||
|
||||
function changeHandle() {}
|
||||
function clearCheck() {
|
||||
checkList.value = []
|
||||
}
|
||||
|
||||
const open = (checked: any) => {
|
||||
checkList.value = checked || []
|
||||
getFolder()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const submitHandle = () => {
|
||||
emit('refresh', {
|
||||
application_ids: checkList.value,
|
||||
})
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
searchValue.value = ''
|
||||
applicationList.value = []
|
||||
getList()
|
||||
}
|
||||
|
||||
const folderList = ref<any[]>([])
|
||||
const currentFolder = ref<any>({})
|
||||
const folderLoading = ref(false)
|
||||
// 文件
|
||||
function folderClickHandle(row: any) {
|
||||
if (row.id === currentFolder.value?.id) {
|
||||
return
|
||||
}
|
||||
currentFolder.value = row
|
||||
getList()
|
||||
}
|
||||
|
||||
function getFolder() {
|
||||
const params = {}
|
||||
folder.asyncGetFolder('APPLICATION', params, apiType.value, folderLoading).then((res: any) => {
|
||||
folderList.value = res.data
|
||||
currentFolder.value = res.data?.[0] || {}
|
||||
getList()
|
||||
})
|
||||
}
|
||||
|
||||
function getList() {
|
||||
const folder_id = currentFolder.value?.id || user.getWorkspaceId()
|
||||
loadSharedApi({
|
||||
type: 'application',
|
||||
isShared: folder_id === 'share',
|
||||
systemType: 'workspace',
|
||||
})
|
||||
.getAllApplication({
|
||||
folder_id: folder_id,
|
||||
})
|
||||
.then((res: any) => {
|
||||
applicationList.value = res.data
|
||||
applicationList.value = applicationList.value?.filter((item: any) => item.is_publish && item.id !== route.params.id)
|
||||
searchData.value = res.data
|
||||
searchData.value = searchData.value?.filter((item: any) => item.is_publish && item.id !== route.params.id)
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ open })
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.addTool-dialog {
|
||||
padding: 0;
|
||||
.el-dialog__header {
|
||||
padding: 12px 20px 4px 24px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
.el-dialog__footer {
|
||||
padding: 12px 24px 12px 24px;
|
||||
border-top: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
|
||||
.el-dialog__headerbtn {
|
||||
top: 2px;
|
||||
right: 6px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -252,7 +252,49 @@
|
|||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item @click.prevent v-if="chat_data.mcp_enable || chat_data.tool_enable">
|
||||
<!-- 应用 -->
|
||||
<div class="flex-between mb-16">
|
||||
<div class="lighter">{{ $t('views.application.title') }}</div>
|
||||
<div>
|
||||
<el-button
|
||||
type="primary"
|
||||
class="mr-4"
|
||||
link
|
||||
@click="openApplicationDialog"
|
||||
@refreshForm="refreshParam"
|
||||
v-if="chat_data.application_enable"
|
||||
>
|
||||
<AppIcon iconName="app-setting"></AppIcon>
|
||||
</el-button>
|
||||
<el-switch size="small" v-model="chat_data.application_enable" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full mb-16" v-if="chat_data.application_ids?.length > 0">
|
||||
<template v-for="(item, index) in chat_data.application_ids" :key="index">
|
||||
<div class="flex-between border border-r-6 white-bg mb-4" style="padding: 5px 8px">
|
||||
<div class="flex align-center" style="line-height: 20px">
|
||||
<el-avatar
|
||||
v-if="relatedObject(applicationSelectOptions, item, 'id')?.icon"
|
||||
shape="square"
|
||||
:size="20"
|
||||
style="background: none"
|
||||
class="mr-8"
|
||||
>
|
||||
<img :src="resetUrl(relatedObject(applicationSelectOptions, item, 'id')?.icon)" alt="" />
|
||||
</el-avatar>
|
||||
<AppIcon v-else class="mr-8" :size="20" />
|
||||
|
||||
<div class="ellipsis" :title="relatedObject(applicationSelectOptions, item, 'id')?.name">
|
||||
{{ relatedObject(applicationSelectOptions, item, 'id')?.name }}
|
||||
</div>
|
||||
</div>
|
||||
<el-button text @click="removeApplication(item)">
|
||||
<el-icon><Close /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<el-form-item @click.prevent v-if="chat_data.mcp_enable || chat_data.tool_enable || chat_data.application_enable">
|
||||
<template #label>
|
||||
<div class="flex-between">
|
||||
<span class="mr-4">
|
||||
|
|
@ -319,6 +361,7 @@
|
|||
/>
|
||||
<McpServersDialog ref="mcpServersDialogRef" @refresh="submitMcpServersDialog" />
|
||||
<ToolDialog ref="toolDialogRef" @refresh="submitToolDialog" />
|
||||
<ApplicationDialog ref="applicationDialogRef" @refresh="submitApplicationDialog" />
|
||||
</NodeContainer>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
|
@ -339,6 +382,7 @@ import { useRoute } from 'vue-router'
|
|||
import { resetUrl } from '@/utils/common'
|
||||
import { relatedObject } from '@/utils/array.ts'
|
||||
import { WorkflowMode } from '@/enums/application'
|
||||
import ApplicationDialog from "@/views/application/component/ApplicationDialog.vue";
|
||||
const workflowMode = (inject('workflowMode') as WorkflowMode) || WorkflowMode.Application
|
||||
const getResourceDetail = inject('getResourceDetail') as any
|
||||
const route = useRoute()
|
||||
|
|
@ -566,6 +610,33 @@ function getMcpToolSelectOptions() {
|
|||
})
|
||||
}
|
||||
|
||||
const applicationSelectOptions = ref<any[]>([])
|
||||
function getApplicationSelectOptions() {
|
||||
loadSharedApi({ type: 'application', systemType: apiType.value })
|
||||
.getAllApplication({folder_id: resource.value?.workspace_id})
|
||||
.then((res: any) => {
|
||||
applicationSelectOptions.value = res.data.filter(
|
||||
(item: any) => item.is_publish,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
const applicationDialogRef = ref()
|
||||
function openApplicationDialog() {
|
||||
applicationDialogRef.value.open(props.nodeModel.properties.node_data.application_ids)
|
||||
}
|
||||
|
||||
function submitApplicationDialog(config: any) {
|
||||
set(props.nodeModel.properties.node_data, 'application_ids', config.application_ids)
|
||||
}
|
||||
function removeApplication(id: any) {
|
||||
if (chat_data.value.application_ids) {
|
||||
chat_data.value.application_ids = chat_data.value.application_ids.filter(
|
||||
(v: any) => v !== id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSelectModel()
|
||||
if (typeof props.nodeModel.properties.node_data?.is_result === 'undefined') {
|
||||
|
|
@ -590,6 +661,7 @@ onMounted(() => {
|
|||
|
||||
getToolSelectOptions()
|
||||
getMcpToolSelectOptions()
|
||||
getApplicationSelectOptions()
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue