@@ -71,7 +71,136 @@ private async Task<FlowEngine> BuildEngineAsync(Dictionary<string, DataSourceInf
7171 aiChatFunc : req => _aiService . ChatRequestAsync ( req ) ,
7272 redisConnStrs : await GetRedisConnStrsAsync ( ) ,
7373 kbSearchFunc : ( kbId , query , topK ) => _kbService . SearchAsContextAsync ( kbId , query , topK ) ,
74- skillResolver : BuildSkillsPromptAsync ) ;
74+ skillResolver : BuildSkillsPromptAsync ,
75+ toolsResolver : BuildToolDefsAsync ,
76+ toolRunner : ( name , argsJson ) => RunToolAsync ( name , argsJson , dsInfos , staticVars , flowContentLoader ) ) ;
77+
78+ /// <summary>构建工具定义(AI 节点函数调用:接口/流程 → OpenAI tools JSON 数组)。</summary>
79+ private async Task < string > BuildToolDefsAsync ( string toolApis , string toolFlows )
80+ {
81+ var tools = new List < Dictionary < string , object ? > > ( ) ;
82+
83+ foreach ( var code in SplitCsv ( toolApis ) )
84+ {
85+ var api = await _db . Apis . FirstOrDefaultAsync ( a => a . MethodCode == code && a . Deleted == 0 ) ;
86+ if ( api == null ) continue ;
87+ var apiParams = await _db . Parameters
88+ . Where ( p => p . OwnerId == api . Id && p . ParamType == 1 && p . Deleted == 0 )
89+ . OrderBy ( p => p . SortNum ) . ToListAsync ( ) ;
90+ var props = new Dictionary < string , object ? > ( ) ;
91+ foreach ( var p in apiParams )
92+ {
93+ var prop = new Dictionary < string , object ? > { [ "type" ] = "string" } ;
94+ if ( ! string . IsNullOrEmpty ( p . Description ) ) prop [ "description" ] = p . Description ;
95+ props [ p . ParamCode ?? "" ] = prop ;
96+ }
97+ tools . Add ( new Dictionary < string , object ? >
98+ {
99+ [ "type" ] = "function" ,
100+ [ "function" ] = new Dictionary < string , object ? >
101+ {
102+ [ "name" ] = $ "api:{ api . MethodCode } ",
103+ [ "description" ] = $ "{ api . MethodName } 。{ api . MethodDesc } 调用方式:{ api . RequestType } { api . Url } ",
104+ [ "parameters" ] = new Dictionary < string , object ? > { [ "type" ] = "object" , [ "properties" ] = props }
105+ }
106+ } ) ;
107+ }
108+
109+ foreach ( var key in SplitCsv ( toolFlows ) )
110+ {
111+ var flow = await _db . FlowDefinitions . FirstOrDefaultAsync ( f => f . FlowKey == key && f . Deleted == 0 ) ;
112+ if ( flow == null ) continue ;
113+ var flowParams = await _db . Parameters
114+ . Where ( p => p . OwnerId == flow . Id && p . ParamType == 5 && p . Deleted == 0 )
115+ . OrderBy ( p => p . SortNum ) . ToListAsync ( ) ;
116+ var props = new Dictionary < string , object ? > ( ) ;
117+ foreach ( var p in flowParams )
118+ {
119+ var prop = new Dictionary < string , object ? > { [ "type" ] = "string" } ;
120+ if ( ! string . IsNullOrEmpty ( p . Description ) ) prop [ "description" ] = p . Description ;
121+ props [ p . ParamCode ?? "" ] = prop ;
122+ }
123+ tools . Add ( new Dictionary < string , object ? >
124+ {
125+ [ "type" ] = "function" ,
126+ [ "function" ] = new Dictionary < string , object ? >
127+ {
128+ [ "name" ] = $ "flow:{ flow . FlowKey } ",
129+ [ "description" ] = $ "{ flow . FlowName } 。{ flow . FlowDesc } 流程编排调用",
130+ [ "parameters" ] = new Dictionary < string , object ? > { [ "type" ] = "object" , [ "properties" ] = props }
131+ }
132+ } ) ;
133+ }
134+
135+ return JsonSerializer . Serialize ( tools ) ;
136+ }
137+
138+ /// <summary>执行工具(AI 节点函数调用:api:xxx 直接 HTTP 调用,flow:xxx 递归执行子流程 → 结果文本)。</summary>
139+ private async Task < string > RunToolAsync ( string name , string argsJson ,
140+ Dictionary < string , DataSourceInfo > dsInfos , Dictionary < string , string ? > staticVars ,
141+ Func < string , Task < string ? > > flowContentLoader )
142+ {
143+ Dictionary < string , object ? > args ;
144+ try { args = JsonSerializer . Deserialize < Dictionary < string , object ? > > ( argsJson ) ?? new ( ) ; }
145+ catch { args = new ( ) ; }
146+
147+ if ( name . StartsWith ( "api:" ) )
148+ {
149+ var methodCode = name [ 4 ..] ;
150+ var api = await _db . Apis . FirstOrDefaultAsync ( a => a . MethodCode == methodCode && a . Deleted == 0 )
151+ ?? throw new Exception ( $ "接口 [{ methodCode } ] 不存在") ;
152+ if ( ! string . IsNullOrEmpty ( api . MockJson ) ) return api . MockJson ;
153+
154+ var client = _httpClientFactory . CreateClient ( ) ;
155+ client . Timeout = TimeSpan . FromSeconds ( 60 ) ;
156+ var requestType = ( api . RequestType ?? "GET" ) . ToUpper ( ) ;
157+ string body ;
158+ if ( requestType == "GET" || requestType == "DELETE" )
159+ {
160+ var query = string . Join ( "&" , args . Select ( kv =>
161+ $ "{ Uri . EscapeDataString ( kv . Key ) } ={ Uri . EscapeDataString ( kv . Value ? . ToString ( ) ?? "" ) } ") ) ;
162+ var url = api . Url ?? "" ;
163+ if ( query . Length > 0 ) url = url . Contains ( '?' ) ? $ "{ url } &{ query } " : $ "{ url } ?{ query } ";
164+ var resp = requestType == "GET" ? await client . GetAsync ( url ) : await client . DeleteAsync ( url ) ;
165+ body = await resp . Content . ReadAsStringAsync ( ) ;
166+ }
167+ else
168+ {
169+ HttpContent content ;
170+ if ( api . ContentType == "FORM" )
171+ {
172+ var formStr = string . Join ( "&" , args . Select ( kv =>
173+ $ "{ Uri . EscapeDataString ( kv . Key ) } ={ Uri . EscapeDataString ( kv . Value ? . ToString ( ) ?? "" ) } ") ) ;
174+ content = new StringContent ( formStr , System . Text . Encoding . UTF8 , "application/x-www-form-urlencoded" ) ;
175+ }
176+ else
177+ {
178+ content = new StringContent ( JsonSerializer . Serialize ( args ) , System . Text . Encoding . UTF8 , "application/json" ) ;
179+ }
180+ var resp = requestType == "PUT" ? await client . PutAsync ( api . Url , content ) : await client . PostAsync ( api . Url , content ) ;
181+ body = await resp . Content . ReadAsStringAsync ( ) ;
182+ }
183+ return body . Length > 8000 ? body [ ..8000 ] : body ;
184+ }
185+
186+ if ( name . StartsWith ( "flow:" ) )
187+ {
188+ var flowKey = name [ 5 ..] ;
189+ var flowContent = await flowContentLoader ( flowKey ) ;
190+ if ( string . IsNullOrEmpty ( flowContent ) || flowContent == "[]" )
191+ throw new Exception ( $ "流程 [{ flowKey } ] 不存在或未发布") ;
192+ var engine = await BuildEngineAsync ( dsInfos , staticVars , flowContentLoader ) ;
193+ var result = await engine . ExecuteAsync ( flowContent , args , flowKey ) ;
194+ if ( ! result . Success ) throw new Exception ( result . ErrorMessage ) ;
195+ return JsonSerializer . Serialize ( result . OutputData ) ;
196+ }
197+
198+ throw new Exception ( $ "未知工具: { name } ") ;
199+ }
200+
201+ /// <summary>拆分逗号分隔列表(去空白去空项)。</summary>
202+ private static List < string > SplitCsv ( string csv )
203+ => ( csv ?? "" ) . Split ( ',' , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) . ToList ( ) ;
75204
76205 // ────────────────────────────────────────────────────────────────
77206 // 数据源
0 commit comments