1313# limitations under the License.
1414
1515import logging
16- from typing import List , Tuple
16+ from typing import List , Optional , Tuple
1717
1818from pydantic import BaseModel , Field
1919
3131
3232
3333class RerankInput (BaseModel ):
34- model : str = Field (..., description = "Rerank model name" )
35- model_service_provider : str = Field (..., description = "Model service provider" )
36- custom_llm_provider : str = Field (..., description = "Custom LLM provider (e.g., 'jina_ai', 'openai')" )
34+ use_rerank_service : bool = Field (default = True , description = "Whether to use rerank service or fallback strategy" )
35+ model : Optional [str ] = Field (default = None , description = "Rerank model name" )
36+ model_service_provider : Optional [str ] = Field (default = None , description = "Model service provider" )
37+ custom_llm_provider : Optional [str ] = Field (
38+ default = None , description = "Custom LLM provider (e.g., 'jina_ai', 'openai')"
39+ )
3740 docs : List [DocumentWithScore ]
3841
3942
@@ -49,88 +52,145 @@ class RerankOutput(BaseModel):
4952class RerankNodeRunner (BaseNodeRunner ):
5053 async def run (self , ui : RerankInput , si : SystemInput ) -> Tuple [RerankOutput , dict ]:
5154 """
52- Run rerank node. ui: user input; si: system input (SystemInput).
53- Returns (output, system_output)
55+ Smart rerank node:
56+ - use_rerank_service=False: directly use fallback strategy
57+ - use_rerank_service=True: try rerank service, fallback on failure
5458 """
55- query = si .query
5659 docs = ui .docs
57- result = []
5860
5961 if not docs :
6062 logger .info ("No documents to rerank, returning empty result" )
63+ return RerankOutput (docs = []), {}
64+
65+ # Strategy 1: If not using rerank service, directly use fallback strategy
66+ if not ui .use_rerank_service :
67+ logger .info ("Rerank service disabled, using fallback strategy" )
68+ result = self ._apply_fallback_strategy (docs )
6169 return RerankOutput (docs = result ), {}
6270
71+ # Strategy 2: Try to use rerank service
6372 try :
64- # Validate input configuration
65- if not ui .model_service_provider :
66- raise InvalidConfigurationError (
67- "model_service_provider" , ui .model_service_provider , "Model service provider cannot be empty"
68- )
69-
70- if not ui .model :
71- raise InvalidConfigurationError ("model" , ui .model , "Model name cannot be empty" )
72-
73- if not ui .custom_llm_provider :
74- raise InvalidConfigurationError (
75- "custom_llm_provider" , ui .custom_llm_provider , "Custom LLM provider cannot be empty"
76- )
77-
78- # Get API key and base URL from user's model service provider settings
79- api_key = await async_db_ops .query_provider_api_key (ui .model_service_provider , si .user )
80- if not api_key :
81- raise InvalidConfigurationError (
82- "api_key" , api_key , f"API KEY not found for LLM Provider:{ ui .model_service_provider } "
83- )
84-
85- # Get base_url from LLMProvider
86- try :
87- llm_provider = await async_db_ops .query_llm_provider_by_name (ui .model_service_provider )
88- if not llm_provider :
89- raise ProviderNotFoundError (ui .model_service_provider , "Rerank" )
90- base_url = llm_provider .base_url
91- except Exception as e :
92- logger .error (f"Failed to query LLM provider '{ ui .model_service_provider } ': { str (e )} " )
93- raise ProviderNotFoundError (ui .model_service_provider , "Rerank" ) from e
94-
95- if not base_url :
96- raise InvalidConfigurationError (
97- "base_url" , base_url , f"Base URL not configured for provider '{ ui .model_service_provider } '"
98- )
99-
100- # Create rerank service with configuration from model service provider
101- rerank_service = RerankService (
102- rerank_provider = ui .custom_llm_provider ,
103- rerank_model = ui .model ,
104- rerank_service_url = base_url ,
105- rerank_service_api_key = api_key ,
73+ # Check configuration completeness
74+ if not self ._is_rerank_config_valid (ui ):
75+ logger .info ("Rerank service configuration incomplete, using fallback strategy" )
76+ result = self ._apply_fallback_strategy (docs )
77+ return RerankOutput (docs = result ), {}
78+
79+ # Execute actual rerank
80+ result = await self ._perform_actual_rerank (ui , si )
81+ logger .info (f"Successfully reranked { len (result )} documents using rerank service" )
82+ return RerankOutput (docs = result ), {}
83+
84+ except (InvalidConfigurationError , ProviderNotFoundError ) as e :
85+ logger .warning (f"Rerank service configuration error, using fallback strategy: { str (e )} " )
86+ result = self ._apply_fallback_strategy (docs )
87+ return RerankOutput (docs = result ), {}
88+
89+ except RerankError as e :
90+ logger .warning (f"Rerank service operation failed, using fallback strategy: { str (e )} " )
91+ result = self ._apply_fallback_strategy (docs )
92+ return RerankOutput (docs = result ), {}
93+
94+ except Exception as e :
95+ logger .error (f"Unexpected error during rerank service, using fallback strategy: { str (e )} " )
96+ result = self ._apply_fallback_strategy (docs )
97+ return RerankOutput (docs = result ), {}
98+
99+ def _is_rerank_config_valid (self , ui : RerankInput ) -> bool :
100+ """Check if rerank service configuration is valid"""
101+ return (
102+ ui .model
103+ and ui .model .strip ()
104+ and ui .model_service_provider
105+ and ui .model_service_provider .strip ()
106+ and ui .custom_llm_provider
107+ and ui .custom_llm_provider .strip ()
108+ )
109+
110+ async def _perform_actual_rerank (self , ui : RerankInput , si : SystemInput ) -> List [DocumentWithScore ]:
111+ """Execute actual rerank operation"""
112+ query = si .query
113+ docs = ui .docs
114+
115+ # Validate configuration
116+ if not ui .model_service_provider :
117+ raise InvalidConfigurationError (
118+ "model_service_provider" , ui .model_service_provider , "Model service provider cannot be empty"
106119 )
107120
108- # Validate the service configuration
109- rerank_service . validate_configuration ( )
121+ if not ui . model :
122+ raise InvalidConfigurationError ( "model" , ui . model , "Model name cannot be empty" )
110123
111- logger . info (
112- f"Using rerank service with provider: { ui . model_service_provider } , "
113- f"model: { ui .model } , url: { base_url } , max_docs: { rerank_service . max_documents } "
124+ if not ui . custom_llm_provider :
125+ raise InvalidConfigurationError (
126+ "custom_llm_provider" , ui .custom_llm_provider , "Custom LLM provider cannot be empty "
114127 )
115128
116- # Perform reranking
117- result = await rerank_service .async_rerank (query , docs )
118- logger .info (f"Successfully reranked { len (result )} documents" )
129+ # Get API key and base_url
130+ api_key = await async_db_ops .query_provider_api_key (ui .model_service_provider , si .user )
131+ if not api_key :
132+ raise InvalidConfigurationError (
133+ "api_key" , api_key , f"API KEY not found for LLM Provider:{ ui .model_service_provider } "
134+ )
119135
120- except (InvalidConfigurationError , ProviderNotFoundError ) as e :
121- # Configuration errors - log and return empty result to gracefully degrade
122- logger .error (f"Rerank configuration error: { str (e )} " )
123- # For flow execution, we gracefully degrade instead of failing the entire flow
124- result = docs # Return original documents without reranking
125- except RerankError as e :
126- # Rerank-specific errors - log and return original documents
127- logger .error (f"Rerank operation failed: { str (e )} " )
128- # For flow execution, we gracefully degrade instead of failing the entire flow
129- result = docs # Return original documents without reranking
136+ try :
137+ llm_provider = await async_db_ops .query_llm_provider_by_name (ui .model_service_provider )
138+ if not llm_provider :
139+ raise ProviderNotFoundError (ui .model_service_provider , "Rerank" )
140+ base_url = llm_provider .base_url
130141 except Exception as e :
131- # Unexpected errors - log and return original documents
132- logger .error (f"Unexpected error during rerank: { str (e )} " )
133- # For flow execution, we gracefully degrade instead of failing the entire flow
134- result = docs # Return original documents without reranking
142+ logger .error (f"Failed to query LLM provider '{ ui .model_service_provider } ': { str (e )} " )
143+ raise ProviderNotFoundError (ui .model_service_provider , "Rerank" ) from e
144+
145+ if not base_url :
146+ raise InvalidConfigurationError (
147+ "base_url" , base_url , f"Base URL not configured for provider '{ ui .model_service_provider } '"
148+ )
149+
150+ # Create and execute rerank service
151+ rerank_service = RerankService (
152+ rerank_provider = ui .custom_llm_provider ,
153+ rerank_model = ui .model ,
154+ rerank_service_url = base_url ,
155+ rerank_service_api_key = api_key ,
156+ )
157+
158+ rerank_service .validate_configuration ()
159+
160+ logger .info (
161+ f"Using rerank service with provider: { ui .model_service_provider } , "
162+ f"model: { ui .model } , url: { base_url } , max_docs: { rerank_service .max_documents } "
163+ )
164+
165+ return await rerank_service .async_rerank (query , docs )
166+
167+ def _apply_fallback_strategy (self , docs : List [DocumentWithScore ]) -> List [DocumentWithScore ]:
168+ """
169+ Apply fallback rerank strategy:
170+ 1. Graph search results first (better quality, typically 1 result)
171+ 2. Sort remaining vector and fulltext results by score in descending order
172+ """
173+ if not docs :
174+ return docs
175+
176+ graph_results = []
177+ other_results = []
178+
179+ for doc in docs :
180+ recall_type = doc .metadata .get ("recall_type" , "" )
181+ if recall_type == "graph_search" :
182+ graph_results .append (doc )
183+ else :
184+ other_results .append (doc )
185+
186+ # Sort other results by score in descending order
187+ other_results .sort (key = lambda x : x .score if x .score is not None else 0.0 , reverse = True )
188+
189+ result = graph_results + other_results
190+
191+ logger .info (
192+ f"Applied fallback rerank strategy: { len (graph_results )} graph results, "
193+ f"{ len (other_results )} other results sorted by score"
194+ )
135195
136- return RerankOutput ( docs = result ), {}
196+ return result
0 commit comments