33
44from nexent .core import MessageObserver
55from nexent .core .models import OpenAIModel , OpenAIVLModel
6- from nexent .core .models .embedding_model import JinaEmbedding , OpenAICompatibleEmbedding
6+ from nexent .core .models .embedding_model import JinaEmbedding , OpenAICompatibleEmbedding , DashScopeMultimodalEmbedding
77from nexent .monitor import set_monitoring_context , set_monitoring_operation
88from nexent .core .models .rerank_model import OpenAICompatibleRerank
99
2020PROVIDER_CATALOG_HEALTHCHECK_FACTORIES = {DASHSCOPE_MODEL_FACTORY , TOKENPONY_MODEL_FACTORY }
2121PROVIDER_CATALOG_HEALTHCHECK_TYPES = {"vlm" , "vlm2" , "vlm3" }
2222
23+ EMBEDDING_TYPES = {"embedding" , "multi_embedding" }
2324
24- def _mask_secret (value : Optional [str ]) -> str :
25- """Mask a secret value, showing only first and last 4 characters."""
26- if not value or len (value ) <= 8 :
27- return "***"
28- return value [:4 ] + "****" + value [- 4 :]
25+
26+ def _normalize_embedding_url (base_url : str ) -> str :
27+ """Append /embeddings suffix to base_url if not already present.
28+
29+ For embedding and multimodal embedding models, the base_url should contain /embeddings.
30+ If the user provides a base URL without the endpoint (e.g., https://api.jina.ai/v1),
31+ this function normalizes it to include /embeddings (e.g., https://api.jina.ai/v1/embeddings).
32+ """
33+ if not base_url or "/embeddings" in base_url :
34+ return base_url
35+ return f"{ base_url .rstrip ('/' )} /embeddings"
36+
37+
38+ def _infer_model_factory (model_type : str , base_url : str , current_factory : Optional [str ] = None ) -> Optional [str ]:
39+ """Infer model_factory from base_url if not already set or is generic.
40+
41+ Currently handles:
42+ - multi_embedding with dashscope URL -> "dashscope"
43+ - embedding with dashscope URL -> "dashscope" (uses OpenAI-compatible endpoint)
44+ """
45+ base_url_lower = base_url .lower ()
46+ if "dashscope" in base_url_lower :
47+ return DASHSCOPE_MODEL_FACTORY
48+
49+ return current_factory
2950
3051
3152async def _embedding_dimension_check (
@@ -34,36 +55,51 @@ async def _embedding_dimension_check(
3455 model_base_url : str ,
3556 model_api_key : str ,
3657 ssl_verify : bool = True ,
58+ model_factory : Optional [str ] = None ,
3759 timeout_seconds : Optional [float ] = None ,
3860):
39- # Test connectivity based on different model types
61+ if model_type in EMBEDDING_TYPES :
62+ model_base_url = _normalize_embedding_url (model_base_url )
63+
64+ effective_timeout = timeout_seconds if timeout_seconds else 5.0
65+
4066 if model_type == "embedding" :
67+ # DashScope text embedding models use OpenAI-compatible endpoint, same as generic
4168 embedding = await OpenAICompatibleEmbedding (
4269 model_name = model_name ,
4370 base_url = model_base_url ,
4471 api_key = model_api_key ,
4572 embedding_dim = 0 ,
4673 ssl_verify = ssl_verify ,
47- timeout_seconds = timeout_seconds ,
48- ).dimension_check ()
74+ ).dimension_check (timeout = effective_timeout )
4975 if len (embedding ) > 0 :
5076 return len (embedding [0 ])
5177 logging .warning (
5278 f"Embedding dimension check for { model_name } gets empty response" )
5379 return 0
5480 elif model_type == "multi_embedding" :
55- embedding = await JinaEmbedding (
56- model_name = model_name ,
57- base_url = model_base_url ,
58- api_key = model_api_key ,
59- embedding_dim = 0 ,
60- ssl_verify = ssl_verify ,
61- timeout_seconds = timeout_seconds ,
62- ).dimension_check ()
63- if len (embedding ) > 0 :
81+ model_factory_lower = (model_factory or "" ).lower ()
82+ if model_factory_lower == "dashscope" :
83+ embedding_instance = DashScopeMultimodalEmbedding (
84+ api_key = model_api_key ,
85+ base_url = model_base_url ,
86+ model_name = model_name ,
87+ embedding_dim = 0 ,
88+ ssl_verify = ssl_verify ,
89+ )
90+ else :
91+ embedding_instance = JinaEmbedding (
92+ api_key = model_api_key ,
93+ base_url = model_base_url ,
94+ model_name = model_name ,
95+ embedding_dim = 0 ,
96+ ssl_verify = ssl_verify ,
97+ )
98+ embedding = await embedding_instance .dimension_check (timeout = effective_timeout )
99+ if isinstance (embedding , list ) and len (embedding ) > 0 and isinstance (embedding [0 ], list ):
64100 return len (embedding [0 ])
65101 logging .warning (
66- f"Embedding dimension check for { model_name } gets empty response" )
102+ f"Embedding dimension check for { model_name } gets unexpected response: { type ( embedding ) } , value: { embedding } " )
67103 return 0
68104 else :
69105 raise ValueError (f"Unsupported model type: { model_type } " )
@@ -123,27 +159,42 @@ async def _perform_connectivity_check(
123159 model_base_url = model_base_url .replace (
124160 LOCALHOST_NAME , DOCKER_INTERNAL_HOST ).replace (LOCALHOST_IP , DOCKER_INTERNAL_HOST )
125161
162+ # Normalize embedding URLs by appending /embeddings if not present
163+ if model_type in EMBEDDING_TYPES :
164+ model_base_url = _normalize_embedding_url (model_base_url )
165+
166+ effective_timeout = timeout_seconds if timeout_seconds else 5.0
126167 connectivity : bool
127168
128- # Test connectivity based on different model types
129169 if model_type == "embedding" :
130- embedding = OpenAICompatibleEmbedding (
170+ emb = await OpenAICompatibleEmbedding (
131171 model_name = model_name ,
132172 base_url = model_base_url ,
133173 api_key = model_api_key ,
134174 embedding_dim = 0 ,
135175 ssl_verify = ssl_verify ,
136- )
137- connectivity = len (await embedding . dimension_check ( timeout = timeout_seconds if timeout_seconds else 5.0 ) ) > 0
176+ ). dimension_check ( timeout = effective_timeout )
177+ connectivity = len (emb ) > 0 and len ( emb [ 0 ] ) > 0
138178 elif model_type == "multi_embedding" :
139- embedding = JinaEmbedding (
140- model_name = model_name ,
141- base_url = model_base_url ,
142- api_key = model_api_key ,
143- embedding_dim = 0 ,
144- ssl_verify = ssl_verify ,
145- )
146- connectivity = len (await embedding .dimension_check (timeout = timeout_seconds if timeout_seconds else 5.0 )) > 0
179+ model_factory_lower = (model_factory or "" ).lower ()
180+ if model_factory_lower == "dashscope" :
181+ embedding = DashScopeMultimodalEmbedding (
182+ api_key = model_api_key ,
183+ base_url = model_base_url ,
184+ model_name = model_name ,
185+ embedding_dim = 0 ,
186+ ssl_verify = ssl_verify ,
187+ )
188+ else :
189+ embedding = JinaEmbedding (
190+ api_key = model_api_key ,
191+ base_url = model_base_url ,
192+ model_name = model_name ,
193+ embedding_dim = 0 ,
194+ ssl_verify = ssl_verify ,
195+ )
196+ emb = await embedding .dimension_check (timeout = effective_timeout )
197+ connectivity = len (emb ) > 0 and len (emb [0 ]) > 0
147198 elif model_type == "llm" :
148199 observer = MessageObserver ()
149200 set_monitoring_operation ("connectivity_check" ,
@@ -335,6 +386,9 @@ async def verify_model_config_connectivity(model_config: dict):
335386 # Get timeout from model config if present
336387 timeout_seconds = model_config .get ("timeout_seconds" )
337388
389+ # Infer model_factory from base_url when not provided
390+ model_factory = _infer_model_factory (model_type , model_base_url , model_config .get ("model_factory" ))
391+
338392 try :
339393 connectivity = await _perform_connectivity_check (
340394 model_name , model_type , model_base_url , model_api_key , ssl_verify ,
@@ -385,22 +439,26 @@ async def embedding_dimension_check(model_config: dict):
385439
386440 try :
387441 ssl_verify = model_config .get ("ssl_verify" , True )
442+ model_factory = _infer_model_factory (model_type , model_base_url , model_config .get ("model_factory" ))
388443 timeout_seconds = model_config .get ("timeout_seconds" )
389444 dimension = await _embedding_dimension_check (
390445 model_name , model_type , model_base_url , model_api_key , ssl_verify ,
391- timeout_seconds = timeout_seconds
446+ model_factory = model_factory , timeout_seconds = timeout_seconds
392447 )
393448 # Fallback to ssl_verify=False if initial check fails
394449 if dimension == 0 and ssl_verify :
395450 dimension = await _embedding_dimension_check (
396451 model_name , model_type , model_base_url , model_api_key , False ,
397- timeout_seconds = timeout_seconds
452+ model_factory = model_factory , timeout_seconds = timeout_seconds
398453 )
454+ if dimension == 0 :
455+ logger .error (f"Embedding dimension check returned 0 for model: { model_name } " )
456+ return None
399457 return dimension
400458 except ValueError as e :
401- logger .error (f"Error checking embedding dimension: { str (e )} " )
402- return 0
459+ logger .error (f"Error checking embedding dimension for { model_name } : { str (e )} " )
460+ return None
403461 except Exception as e :
404462 logger .error (
405- f"Error checking embedding dimension: { model_name } ; Error : { str (e )} " )
406- return 0
463+ f"Error checking embedding dimension for { model_name } : { str (e )} " )
464+ return None
0 commit comments