@@ -14,7 +14,7 @@ use crate::services::XgenApiClient;
1414use crate :: services:: xgen_api:: LlmProviderConfig ;
1515use crate :: services:: tool_search;
1616
17- const MAX_TOOL_ROUNDS : usize = 5 ;
17+ const MAX_TOOL_ROUNDS : usize = 10 ;
1818
1919/// LLM API client — multi-provider support
2020pub struct LlmClient {
@@ -76,20 +76,20 @@ impl LlmClient {
7676
7777 fn system_prompt ( ) -> & ' static str {
7878 r#"당신은 XGEN AI 플랫폼 어시스턴트입니다.
79- 사용자의 요청에 따라 XGEN API를 호출하여 워크플로우 관리, 실행, 모니터링 등을 수행합니다.
8079
81- 역할:
82- - 워크플로우 목록 조회, 생성, 실행, 삭제
83- - 스케줄 생성 및 관리
84- - 노드/도구/LLM 상태 확인
85- - 문서 검색 및 RAG
86- - 사용자 질문에 친절하게 답변
87-
88- tool 호출 결과를 사용자에게 보기 좋게 정리해서 한국어로 답변하세요.
89- JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
90-
91- 주어진 tool 중에서 사용자 요청에 가장 적합한 것을 선택하세요.
92- 적합한 tool이 없으면 tool을 호출하지 말고 직접 답변하세요."#
80+ 도구 사용 규칙:
81+ 1. 사용자 요청을 처리하려면 먼저 search_tools로 관련 API를 검색하세요.
82+ - 검색 쿼리는 반드시 영문 키워드로 작성하세요 (예: "execute workflow", "list agents", "create schedule").
83+ - 한국어 요청이라도 영문으로 변환하여 검색하세요.
84+ - 검색 결과가 부족하면 다른 키워드로 다시 검색하세요.
85+ 2. 검색 결과에서 적절한 tool을 선택하고, call_tool로 호출하세요.
86+ - tool_name은 검색 결과의 정확한 이름을 사용하세요.
87+ - arguments는 검색 결과의 파라미터 스키마에 맞게 구성하세요.
88+ 3. 일반 질문이나 tool이 필요 없는 경우에는 직접 답변하세요.
89+
90+ 응답 규칙:
91+ - API 결과는 핵심 정보만 추려서 한국어로 읽기 쉽게 정리하세요.
92+ - JSON을 그대로 보여주지 마세요."#
9393 }
9494
9595 // ============================================================
@@ -633,26 +633,14 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
633633 messages : & mut Vec < ChatMessage > ,
634634 xgen_api : & XgenApiClient ,
635635 ) -> Result < String > {
636- // 사용자 메시지에서 쿼리 추출하여 관련 tool 동적 검색
637- let user_query = messages. last ( )
638- . and_then ( |m| m. content . as_str ( ) )
639- . unwrap_or ( "help" ) ;
640-
641636 let openapi_source = format ! ( "{}/api/openapi" , xgen_api. base_url( ) ) ;
642- let tools = match tool_search:: search_tools_for_llm ( user_query, & openapi_source, Some ( 7 ) ) . await {
643- Ok ( t) if !t. is_empty ( ) => {
644- println ! ( " [tools] Found {} dynamic tools for '{}'" , t. len( ) , user_query) ;
645- t
646- }
647- Ok ( _) | Err ( _) => {
648- println ! ( " [tools] Fallback to hardcoded tools" ) ;
649- XgenApiClient :: tool_definitions ( )
650- }
651- } ;
637+
638+ // Gateway mode: 고정 meta-tool 2개
639+ let tools = tool_search:: meta_tool_definitions ( ) ;
652640 let mut final_text = String :: new ( ) ;
653641
654642 for round in 0 ..MAX_TOOL_ROUNDS {
655- println ! ( "[CLI test] round {}/{} ({})" , round + 1 , MAX_TOOL_ROUNDS , self . config. provider) ;
643+ println ! ( "[CLI test] gateway round {}/{} ({})" , round + 1 , MAX_TOOL_ROUNDS , self . config. provider) ;
656644
657645 let response = self . call_anthropic_nostream ( messages, & tools) . await ?;
658646
@@ -674,29 +662,36 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
674662 let tool_name = block[ "name" ] . as_str ( ) . unwrap_or ( "" ) ;
675663 let tool_input = block[ "input" ] . clone ( ) ;
676664
677- println ! ( " [tool] {} → {:?}" , tool_name, tool_input) ;
678-
679- // graph-tool-call call로 실행 (OpenAPI 기반 동적 실행)
680- let result = match tool_search:: execute_tool_call (
681- tool_name,
682- & tool_input,
683- & openapi_source,
684- xgen_api. base_url ( ) ,
685- xgen_api. auth_token ( ) ,
686- ) . await {
687- Ok ( v) => {
688- let s = serde_json:: to_string_pretty ( & v) . unwrap_or_default ( ) ;
689- println ! ( " [result] {}..." , s. chars( ) . take( 200 ) . collect:: <String >( ) ) ;
690- s
691- } ,
692- Err ( e) => {
693- // fallback: xgen_api.execute_tool
694- println ! ( " [graph-tool-call fallback] {}" , e) ;
695- match xgen_api. execute_tool ( tool_name, tool_input. clone ( ) ) . await {
696- Ok ( v) => serde_json:: to_string_pretty ( & v) . unwrap_or_default ( ) ,
697- Err ( e2) => format ! ( "Error: {}" , e2) ,
665+ println ! ( " [gateway] {} → {:?}" , tool_name, tool_input) ;
666+
667+ let result = match tool_name {
668+ "search_tools" => {
669+ let query = tool_input[ "query" ] . as_str ( ) . unwrap_or ( "help" ) ;
670+ let top_k = tool_input[ "top_k" ] . as_u64 ( ) . map ( |v| v as usize ) ;
671+ match tool_search:: search_tools_text ( query, & openapi_source, top_k) . await {
672+ Ok ( text) => {
673+ println ! ( " [search] {}..." , text. chars( ) . take( 200 ) . collect:: <String >( ) ) ;
674+ text
675+ }
676+ Err ( e) => format ! ( "Search error: {}" , e) ,
698677 }
699- } ,
678+ }
679+ "call_tool" => {
680+ let actual_tool = tool_input[ "tool_name" ] . as_str ( ) . unwrap_or ( "" ) ;
681+ let args = tool_input. get ( "arguments" ) . cloned ( ) . unwrap_or ( serde_json:: json!( { } ) ) ;
682+ match tool_search:: execute_tool_call (
683+ actual_tool, & args, & openapi_source,
684+ xgen_api. base_url ( ) , xgen_api. auth_token ( ) ,
685+ ) . await {
686+ Ok ( v) => {
687+ let s = serde_json:: to_string_pretty ( & v) . unwrap_or_default ( ) ;
688+ println ! ( " [call] {}..." , s. chars( ) . take( 200 ) . collect:: <String >( ) ) ;
689+ s
690+ }
691+ Err ( e) => format ! ( "Call error: {}" , e) ,
692+ }
693+ }
694+ _ => format ! ( "Unknown tool: {}" , tool_name) ,
700695 } ;
701696
702697 tool_results. push ( serde_json:: json!( {
@@ -752,26 +747,14 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
752747 session_id : & str ,
753748 app : & AppHandle ,
754749 ) -> Result < String > {
755- // 동적 tool 검색
756- let user_query = messages. last ( )
757- . and_then ( |m| m. content . as_str ( ) )
758- . unwrap_or ( "help" ) ;
759-
760750 let openapi_source = format ! ( "{}/api/openapi" , xgen_api. base_url( ) ) ;
761- let tools = match tool_search:: search_tools_for_llm ( user_query, & openapi_source, Some ( 7 ) ) . await {
762- Ok ( t) if !t. is_empty ( ) => {
763- log:: info!( "Found {} dynamic tools for '{}'" , t. len( ) , user_query) ;
764- t
765- }
766- Ok ( _) | Err ( _) => {
767- log:: warn!( "Fallback to hardcoded tools" ) ;
768- XgenApiClient :: tool_definitions ( )
769- }
770- } ;
751+
752+ // Gateway mode: 고정 meta-tool 2개 (search_tools + call_tool)
753+ let tools = tool_search:: meta_tool_definitions ( ) ;
771754 let mut final_text = String :: new ( ) ;
772755
773756 for round in 0 ..MAX_TOOL_ROUNDS {
774- log:: info!( "CLI [{}] tool use round {}/{}" , self . config. provider, round + 1 , MAX_TOOL_ROUNDS ) ;
757+ log:: info!( "CLI [{}] gateway round {}/{}" , self . config. provider, round + 1 , MAX_TOOL_ROUNDS ) ;
775758
776759 let response = self . call_stream ( messages, & tools, session_id, app) . await ?;
777760
@@ -793,29 +776,36 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
793776 let tool_name = block[ "name" ] . as_str ( ) . unwrap_or ( "" ) ;
794777 let tool_input = block[ "input" ] . clone ( ) ;
795778
796- log:: info!( "Executing tool : {}" , tool_name) ;
779+ log:: info!( "Gateway dispatch : {} (id: {}) " , tool_name, tool_id ) ;
797780
798781 let _ = app. emit ( "cli:event" , CliStreamEvent {
799782 session_id : session_id. to_string ( ) ,
800783 event_type : "tool_call" . into ( ) ,
801784 data : serde_json:: json!( { "id" : tool_id, "name" : tool_name, "input" : tool_input} ) ,
802785 } ) ;
803786
804- let result = match tool_search:: execute_tool_call (
805- tool_name,
806- & tool_input,
807- & openapi_source,
808- xgen_api. base_url ( ) ,
809- xgen_api. auth_token ( ) ,
810- ) . await {
811- Ok ( v) => serde_json:: to_string_pretty ( & v) . unwrap_or_default ( ) ,
812- Err ( e) => {
813- log:: warn!( "graph-tool-call fallback: {}" , e) ;
814- match xgen_api. execute_tool ( tool_name, tool_input) . await {
787+ // Meta-tool dispatch
788+ let result = match tool_name {
789+ "search_tools" => {
790+ let query = tool_input[ "query" ] . as_str ( ) . unwrap_or ( "help" ) ;
791+ let top_k = tool_input[ "top_k" ] . as_u64 ( ) . map ( |v| v as usize ) ;
792+ match tool_search:: search_tools_text ( query, & openapi_source, top_k) . await {
793+ Ok ( text) => text,
794+ Err ( e) => format ! ( "Search error: {}" , e) ,
795+ }
796+ }
797+ "call_tool" => {
798+ let actual_tool = tool_input[ "tool_name" ] . as_str ( ) . unwrap_or ( "" ) ;
799+ let args = tool_input. get ( "arguments" ) . cloned ( ) . unwrap_or ( serde_json:: json!( { } ) ) ;
800+ match tool_search:: execute_tool_call (
801+ actual_tool, & args, & openapi_source,
802+ xgen_api. base_url ( ) , xgen_api. auth_token ( ) ,
803+ ) . await {
815804 Ok ( v) => serde_json:: to_string_pretty ( & v) . unwrap_or_default ( ) ,
816- Err ( e2 ) => format ! ( "Error : {}" , e2 ) ,
805+ Err ( e ) => format ! ( "Call error : {}" , e ) ,
817806 }
818807 }
808+ _ => format ! ( "Unknown tool: {}" , tool_name) ,
819809 } ;
820810
821811 let _ = app. emit ( "cli:event" , CliStreamEvent {
@@ -832,17 +822,15 @@ JSON 결과는 핵심 정보만 추려서 읽기 쉽게 정리하세요.
832822 }
833823 }
834824
835- // Validate: every tool_use must have a matching tool_result
825+ // Safety: ensure all tool_use IDs have matching tool_results
836826 let tool_use_ids: Vec < String > = content. iter ( )
837827 . filter ( |b| b[ "type" ] . as_str ( ) == Some ( "tool_use" ) )
838828 . filter_map ( |b| b[ "id" ] . as_str ( ) . map ( |s| s. to_string ( ) ) )
839829 . collect ( ) ;
840830 let tool_result_ids: Vec < String > = tool_results. iter ( )
841831 . filter_map ( |r| r[ "tool_use_id" ] . as_str ( ) . map ( |s| s. to_string ( ) ) )
842832 . collect ( ) ;
843- log:: info!( "tool_use IDs: {:?}, tool_result IDs: {:?}" , tool_use_ids, tool_result_ids) ;
844833
845- // Safety: add placeholder tool_result for any missing IDs
846834 for use_id in & tool_use_ids {
847835 if !tool_result_ids. contains ( use_id) {
848836 log:: warn!( "Missing tool_result for tool_use_id: {}" , use_id) ;
0 commit comments