1919class ModelScopeGateway :
2020 BASE_URL = "https://www.modelscope.cn"
2121 SUGGEST_ENDPOINT = f"{ BASE_URL } /api/v1/dolphin/model/suggestv2"
22-
22+ SEARCH_ENDPOINT = f"{ BASE_URL } /api/v1/dolphin/models"
23+ SEARCH_SINGLE_ENDPOINT = f"{ BASE_URL } /api/v1/models"
24+
2325 def __init__ (self , timeout : float = 10.0 , retries : int = 3 , backoff : float = 0.5 ):
2426 self .timeout = timeout
2527
@@ -36,13 +38,75 @@ def __init__(self, timeout: float = 10.0, retries: int = 3, backoff: float = 0.5
3638 total = retries ,
3739 backoff_factor = backoff ,
3840 status_forcelist = [429 , 500 , 502 , 503 , 504 ],
39- allowed_methods = ["GET" , "POST" ],
41+ allowed_methods = ["GET" , "POST" , "PUT" ],
4042 raise_on_status = False ,
4143 )
4244 adapter = HTTPAdapter (max_retries = retry_cfg )
4345 self .session .mount ("https://" , adapter )
4446 self .session .mount ("http://" , adapter )
4547
48+ def formatData (self , data : Any ) -> Dict [str , Any ]:
49+ inner = data .get ('Model' , {}) if isinstance (data , dict ) else {}
50+ path = data .get ('Path' ) or inner .get ('Path' )
51+ name = data .get ('Name' ) or inner .get ('Name' )
52+ revision = data .get ('Revision' ) or inner .get ('Revision' )
53+ # size = self.get_model_size(path, name, revision)
54+ return {
55+ "Libraries" : data .get ("Libraries" ) or inner .get ("Libraries" ),
56+ "ChineseName" : data .get ("ChineseName" ) or inner .get ("ChineseName" ),
57+ "Id" : data .get ("Id" ) or inner .get ("Id" ) or data .get ("ModelId" ) or inner .get ("ModelId" ),
58+ "Name" : data .get ("Name" ) or inner .get ("Name" ),
59+ "Path" : data .get ("Path" ) or inner .get ("Path" ),
60+ "LastUpdatedTime" : data .get ("LastUpdatedTime" ) or inner .get ("LastUpdatedTime" ) or data .get ("LastUpdatedAt" ) or inner .get ("LastUpdatedAt" ),
61+ "Downloads" : data .get ("Downloads" ) or inner .get ("Downloads" ) or data .get ("DownloadCount" ) or inner .get ("DownloadCount" ),
62+ # "Size": size or 0
63+ }
64+
65+ def get_single_model (self , path : str , name : str ) -> Optional [Dict [str , Any ]]:
66+ """
67+ 调用单模型详情接口。
68+ 返回原始 JSON(尽量保持结构,便于 formatData 处理),失败返回 None。
69+ """
70+ if path is None or name is None :
71+ return None
72+ try :
73+ url = f"{ self .SEARCH_SINGLE_ENDPOINT } /{ path } /{ name } "
74+ resp = self .session .get (url , timeout = self .timeout )
75+ resp .raise_for_status ()
76+ body = resp .json ()
77+ # 兼容不同返回包裹层级
78+ if isinstance (body , dict ):
79+ data = body .get ("Data" ) or body .get ("data" ) or body
80+ return data
81+ return body
82+ except Exception as e :
83+ log .error (f"ModelScope single fetch failed for path={ path } : name={ name } : { e } " )
84+ return None
85+
86+ def get_model_size (self , path : str , name : str , revision : str , root : str = '' ) -> int :
87+ """
88+ 调用单模型详情接口。
89+ 返回原始 JSON(尽量保持结构,便于 formatData 处理),失败返回 None。
90+ """
91+ if path is None or name is None or revision is None :
92+ return 0
93+ try :
94+ url = f"{ self .SEARCH_SINGLE_ENDPOINT } /{ path } /{ name } /repo/files?Revision={ revision } &Root={ root } "
95+ resp = self .session .get (url , timeout = self .timeout )
96+ resp .raise_for_status ()
97+ body = resp .json ()
98+ # 兼容不同返回包裹层级
99+ if isinstance (body , dict ):
100+ data = body ["Data" ]["Files" ]
101+ size = 0
102+ for item in data :
103+ size += item .get ("Size" ) or 0
104+ return size
105+ return 0
106+ except Exception as e :
107+ log .error (f"ModelScope model size fetch failed for path={ path } : name={ name } : rversion={ rversion } : { e } " )
108+ return 0
109+
46110 def suggest (
47111 self ,
48112 name : str ,
@@ -63,7 +127,11 @@ def suggest(
63127 "ChineseName": "中文名称",
64128 "Id": 294066,
65129 "Name": "SD3-Controlnet-Pose",
66- "Path": "InstantX"
130+ "Path": "InstantX",
131+ "Libraries": ["pytorch","lora","safetensors"],
132+ "LastUpdatedTime": "1733042611",
133+ "Downloads": 100,
134+ "Size": 100000
67135 }
68136 ]
69137 }
@@ -88,7 +156,77 @@ def suggest(
88156 or body ['Data' ]['Model' ]['Suggests' ] is None or (len (body ['Data' ]['Model' ]['Suggests' ]) == 0 ):
89157 log .error (f"ModelScope suggest failed: { body } , request: { payload } " )
90158 return {"data" : None }
91- return {"data" : body ['Data' ]['Model' ]['Suggests' ]}
159+ models = body ['Data' ]['Model' ]['Suggests' ]
160+ picked : List [Dict [str , Any ]] = []
161+ for item in models :
162+ base = item or {}
163+ inner = base .get ('Model' , {}) if isinstance (base , dict ) else {}
164+ path = base .get ('Path' ) or inner .get ('Path' )
165+ name = base .get ('Name' ) or inner .get ('Name' )
166+ detail = self .get_single_model (path , name )
167+ data = self .formatData (detail or base )
168+ picked .append (data )
169+ total = body ['Data' ]['Model' ].get ('TotalCount' ) or body ['Data' ]['Model' ].get ('Total' ) or 0
170+ return {"data" : picked , "total" : total }
171+
172+ def search (
173+ self ,
174+ name : str ,
175+ page : int = 1 ,
176+ page_size : int = 30 ,
177+ sort_by : str = "Default" ,
178+ target : str = "" ,
179+ single_criterion : Optional [List [Dict [str , Any ]]] = None ,
180+ criterion : Optional [List [Dict [str , Any ]]] = None ,
181+ ) -> Dict [str , Any ]:
182+ """
183+ 调用 models 模糊搜索接口;返回 body 与当前 Cookie。
184+
185+ Returns:
186+ Dict[str, Any]: 返回的模型列表,格式为:
187+ {
188+ "data": [
189+ {
190+ "ChineseName": "中文名称",
191+ "Id": 294066,
192+ "Name": "SD3-Controlnet-Pose",
193+ "Path": "InstantX",
194+ "Libraries": ["pytorch","lora","safetensors"],
195+ "LastUpdatedTime": "1733042611",
196+ "Downloads": 100,
197+ "Size": 100000
198+ }
199+ ]
200+ }
201+ """
202+ payload = {
203+ "PageSize" : page_size ,
204+ "PageNumber" : page ,
205+ "SortBy" : sort_by ,
206+ "Target" : target ,
207+ "SingleCriterion" : single_criterion or [],
208+ "Name" : name ,
209+ "Criterion" : criterion
210+ }
211+
212+ resp = self .session .put (
213+ self .SEARCH_ENDPOINT ,
214+ json = payload ,
215+ timeout = self .timeout ,
216+ )
217+ resp .raise_for_status ()
218+ body = resp .json ()
219+ if body ['Data' ] is None or body ['Data' ]['Model' ] is None \
220+ or body ['Data' ]['Model' ]['Models' ] is None or (len (body ['Data' ]['Model' ]['Models' ]) == 0 ):
221+ log .error (f"ModelScope search failed: { body } , request: { payload } " )
222+ return {"data" : None }
223+ models = body ['Data' ]['Model' ]['Models' ] or []
224+ picked : List [Dict [str , Any ]] = []
225+ for item in models :
226+ data = self .formatData (item or {})
227+ picked .append (data )
228+ total = body ['Data' ]['Model' ].get ('TotalCount' ) or body ['Data' ]['Model' ].get ('Total' ) or 0
229+ return {"data" : picked , "total" : total }
92230
93231 def download_with_sdk (
94232 self ,
0 commit comments