99import json
1010import re
1111import time
12+ import uuid
1213from functools import reduce
1314from typing import List , Dict
1415
15- from application .flow .i_step_node import NodeResult , INode
16+ from langchain_core .tools import StructuredTool
17+
18+ from application .flow .common import Workflow , WorkflowMode
19+ from application .flow .i_step_node import NodeResult , INode , ToolWorkflowPostHandler , ToolWorkflowCallPostHandler
1620from application .flow .step_node .ai_chat_step_node .i_chat_node import IChatNode
1721from application .flow .tools import Reasoning , mcp_response_generator
1822from application .models import Application , ApplicationApiKey , ApplicationAccessToken
23+ from application .serializers .common import ToolExecute
1924from common .exception .app_exception import AppApiException
2025from common .utils .rsa_util import rsa_long_decrypt
2126from common .utils .shared_resource_auth import filter_authorized_ids
2227from common .utils .tool_code import ToolExecutor
23- from django .db .models import QuerySet
28+ from django .db .models import QuerySet , OuterRef , Subquery
2429from django .utils .translation import gettext as _
2530from langchain_core .messages import BaseMessage , AIMessage , HumanMessage , SystemMessage
2631from models_provider .models import Model
2732from models_provider .tools import get_model_credential , get_model_instance_by_model_workspace_id
28- from tools .models import Tool
33+ from tools .models import Tool , ToolWorkflowVersion , ToolType
34+ from pydantic import BaseModel , Field , create_model
35+ import uuid_utils .compat as uuid
36+
37+
38+ def build_schema (fields : dict ):
39+ return create_model ("dynamicSchema" , ** fields )
40+
41+
42+ def get_type (_type : str ):
43+ if _type == 'float' :
44+ return float
45+ if _type == 'string' :
46+ return str
47+ if _type == 'int' :
48+ return int
49+ if _type == 'dict' :
50+ return dict
51+ if _type == 'array' :
52+ return list
53+ if _type == 'boolean' :
54+ return bool
55+ return object
56+
57+
58+ def get_workflow_args (tool , qv ):
59+ for node in qv .work_flow .get ('nodes' ):
60+ if node .get ('type' ) == 'tool-base-node' :
61+ input_field_list = node .get ('properties' ).get ('user_input_field_list' )
62+ return build_schema (
63+ {field .get ('field' ): (get_type (field .get ('type' )), Field (..., description = field .get ('desc' )))
64+ for field in input_field_list })
65+
66+ return build_schema ({})
67+
68+
69+ def get_workflow_func (tool , qv , workspace_id ):
70+ tool_id = tool .id
71+ tool_record_id = str (uuid .uuid7 ())
72+ took_execute = ToolExecute (tool_id , tool_record_id ,
73+ workspace_id ,
74+ None ,
75+ None ,
76+ True )
77+
78+ def inner (** kwargs ):
79+ from application .flow .tool_workflow_manage import ToolWorkflowManage
80+ work_flow_manage = ToolWorkflowManage (
81+ Workflow .new_instance (qv .work_flow , WorkflowMode .TOOL ),
82+ {
83+ 'chat_record_id' : tool_record_id ,
84+ 'tool_id' : tool_id ,
85+ 'stream' : True ,
86+ 'workspace_id' : workspace_id ,
87+ ** kwargs },
88+
89+ ToolWorkflowCallPostHandler (took_execute , tool_id ),
90+ is_the_task_interrupted = lambda : False ,
91+ child_node = None ,
92+ start_node_id = None ,
93+ start_node_data = None ,
94+ chat_record = None
95+ )
96+ res = work_flow_manage .run ()
97+ for r in res :
98+ pass
99+ return work_flow_manage .out_context
100+
101+ return inner
102+
103+
104+ def get_tools (tool_workflow_ids , workspace_id ):
105+ tools = QuerySet (Tool ).filter (id__in = tool_workflow_ids , tool_type = ToolType .WORKFLOW , workspace_id = workspace_id )
106+ latest_subquery = ToolWorkflowVersion .objects .filter (
107+ tool_id = OuterRef ('tool_id' )
108+ ).order_by ('-create_time' )
109+
110+ qs = ToolWorkflowVersion .objects .filter (
111+ tool_id__in = [t .id for t in tools ],
112+ id = Subquery (latest_subquery .values ('id' )[:1 ])
113+ )
114+ qd = {q .tool_id : q for q in qs }
115+ results = []
116+ for tool in tools :
117+ qv = qd .get (tool .id )
118+ func = get_workflow_func (tool , qv , workspace_id )
119+ args = get_workflow_args (tool , qv )
120+ tool = StructuredTool .from_function (
121+ func = func ,
122+ name = tool .name ,
123+ description = tool .desc ,
124+ args_schema = args ,
125+ )
126+ results .append (tool )
127+
128+ return results
29129
30130
31131def _write_context (node_variable : Dict , workflow_variable : Dict , node : INode , workflow , answer : str ,
@@ -178,7 +278,7 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
178278 model_id = reference_data .get ('model_id' , model_id )
179279 model_params_setting = reference_data .get ('model_params_setting' )
180280
181- if model_params_setting is None and model_id :
281+ if model_params_setting is None and model_id :
182282 model_params_setting = get_default_model_params_setting (model_id )
183283
184284 if model_setting is None :
@@ -216,7 +316,7 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
216316 mcp_result = self ._handle_mcp_request (
217317 mcp_source , mcp_servers , mcp_tool_id , mcp_tool_ids , tool_ids ,
218318 application_ids , skill_tool_ids , mcp_output_enable ,
219- chat_model , message_list , history_message , question , chat_id
319+ chat_model , message_list , history_message , question , chat_id , workspace_id
220320 )
221321 if mcp_result :
222322 return mcp_result
@@ -236,7 +336,8 @@ def execute(self, model_id, system, prompt, dialogue_number, history_chat_record
236336
237337 def _handle_mcp_request (self , mcp_source , mcp_servers , mcp_tool_id , mcp_tool_ids , tool_ids ,
238338 application_ids , skill_tool_ids ,
239- mcp_output_enable , chat_model , message_list , history_message , question , chat_id ):
339+ mcp_output_enable , chat_model , message_list , history_message , question , chat_id ,
340+ workspace_id ):
240341
241342 mcp_servers_config = {}
242343
@@ -259,11 +360,12 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
259360 mcp_servers_config = {** mcp_servers_config , ** json .loads (mcp_tool ['code' ])}
260361 mcp_servers_config = self .handle_variables (mcp_servers_config )
261362 tool_init_params = {}
363+ tools = get_tools (tool_ids , workspace_id )
262364 if tool_ids and len (tool_ids ) > 0 : # 如果有工具ID,则将其转换为MCP
263365 self .context ['tool_ids' ] = tool_ids
264366 for tool_id in tool_ids :
265- tool = QuerySet (Tool ).filter (id = tool_id ).first ()
266- if not tool .is_active :
367+ tool = QuerySet (Tool ).filter (id = tool_id , tool_type = ToolType . CUSTOM ).first ()
368+ if tool is None or not tool .is_active :
267369 continue
268370 executor = ToolExecutor ()
269371 if tool .init_params is not None :
@@ -323,7 +425,7 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
323425 })
324426 mcp_servers_config ['skills' ] = skill_file_items
325427
326- if len (mcp_servers_config ) > 0 :
428+ if len (mcp_servers_config ) > 0 or len ( tools ) > 0 :
327429 # 安全获取 application
328430 application_id = None
329431 if (self .workflow_manage and
@@ -334,7 +436,7 @@ def _handle_mcp_request(self, mcp_source, mcp_servers, mcp_tool_id, mcp_tool_ids
334436 source_id = application_id or knowledge_id
335437 source_type = 'APPLICATION' if application_id else 'KNOWLEDGE'
336438 r = mcp_response_generator (chat_model , message_list , json .dumps (mcp_servers_config ), mcp_output_enable ,
337- tool_init_params , source_id , source_type , chat_id )
439+ tool_init_params , source_id , source_type , chat_id , tools )
338440 return NodeResult (
339441 {'result' : r , 'chat_model' : chat_model , 'message_list' : message_list ,
340442 'history_message' : [{'content' : message .content , 'role' : message .type } for message in
0 commit comments