Expose model capability hints and configurable vision projector placement

This commit is contained in:
Mikei386
2026-09-28 22:03:39 +02:00
parent a36a151c94
commit 06c6292cf3
10 changed files with 112 additions and 21 deletions
+15 -6
View File
@@ -39,11 +39,13 @@ def repo_id(value):
def file_role(filename):
"""One classification shared by library UI and profile validation."""
name=filename.lower();parts=PurePosixPath(name).parts
if any(p in ('text_encoder','text_encoders') for p in parts) or re.search(r'(?:^|[/_-])(?:text.encoder|qwen3vl|t5xxl|clip_l|clip_g)(?:[/_.-]|$)',name):
if re.search(r'(?:^|[/_-])mmproj(?:[/_.-]|$)',name):
role,label='vision_projector','Vision-Projektor'
elif any(p in ('text_encoder','text_encoders') for p in parts) or re.search(r'(?:^|[/_-])(?:text.encoder|qwen3vl|t5xxl|clip_l|clip_g)(?:[/_.-]|$)',name):
role,label='text_encoder','Textencoder'
elif 'vae' in parts or re.search(r'(?:^|[/_-])vae(?:[/_.-]|$)',name):
role,label='vae','VAE'
elif re.search(r'(?:^|[/_-])(?:mmproj|lora|adapter)(?:[/_.-]|$)',name):
elif re.search(r'(?:^|[/_-])(?:lora|adapter)(?:[/_.-]|$)',name):
role,label='auxiliary','Zusatzkomponente'
elif name.endswith(('.gguf','.safetensors')):
role,label='model','Modelldatei'
@@ -58,6 +60,13 @@ def is_derived_model(model):
tags=model.get('tags') or []
return any(isinstance(tag,str) and (tag.startswith('base_model:') or tag.lower() in {'lora','peft','adapter','merge','mergekit','finetune','fine-tuned','quantized','gguf','gptq','awq'}) for tag in tags)
def capabilities(model):
tags={t.lower() for t in (model.get('tags') or []) if isinstance(t,str)}
vision=bool(tags & {'image-text-to-text','image-to-text','vision','multimodal'}) or model.get('pipeline_tag') in ('image-text-to-text','image-to-text')
files=any(file_role(x.get('rfilename',''))['role']=='vision_projector' for x in (model.get('siblings') or []))
tools=bool(tags & {'tool-use','tool-calling','function-calling','function_calling'})
return dict(vision='Hinweis laut Hub' if vision else 'Projektordatei vorhanden' if files else 'Unbekannt',tools='Hinweis laut Hub' if tools else 'Unbekannt')
class Catalog:
def __init__(self,root):
self.root=Path(root);self.lock=threading.RLock();self.job=None;self.cancel=threading.Event()
@@ -105,11 +114,11 @@ class Catalog:
elif '/' in q:direct=repo_id(q)
if direct:
item=metadata('/api/models/'+direct)
return dict(models=[dict(repo=item['id'],downloads=item.get('downloads'),gated=item.get('gated',False),created_at=item.get('createdAt'))],base_only=base_only,scanned=1,notice='Direkt ausgewähltes Repository: Kategorie- und Basismodellfilter werden für diesen Treffer nicht angewendet.')
return dict(models=[dict(repo=item['id'],downloads=item.get('downloads'),gated=item.get('gated',False),created_at=item.get('createdAt'),capabilities=capabilities(item))],base_only=base_only,scanned=1,notice='Direkt ausgewähltes Repository: Kategorie- und Basismodellfilter werden für diesen Treffer nicht angewendet.')
if q.lower().endswith('.gguf'):
q=q[:-5];notice='GGUF-Dateiname als Repository-Suchbegriff verwendet. Die Datei wählst du anschließend in den Modelldetails.'
query=dict(search=q,filter=KINDS[kind],limit=100 if base_only else 20,sort='createdAt' if sort=='newest' else 'downloads',direction=-1)
if base_only:query['expand']=['downloads','createdAt','gated','cardData','tags']
query['expand']=['downloads','createdAt','gated','cardData','tags','pipeline_tag']
rows=metadata('/api/models?'+urllib.parse.urlencode(query,doseq=True))
if not rows and q and kind=='chat':
fallback=dict(query,filter='gguf',expand=['downloads','createdAt','gated','cardData','tags','pipeline_tag'])
@@ -118,7 +127,7 @@ class Catalog:
if rows:notice='GGUF-Sprachmodelle ohne reguläres Kategorie-Tag gefunden. Bei aktiver Basismodellauswahl bleiben Quantisierungen ausgeblendet.'
scanned=len(rows)
if base_only:rows=[x for x in rows if not is_derived_model(x)]
return {'models':[dict(repo=x['id'],downloads=x.get('downloads'),gated=x.get('gated',False),created_at=x.get('createdAt')) for x in rows[:20]],'base_only':base_only,'scanned':scanned,'notice':notice}
return {'models':[dict(repo=x['id'],downloads=x.get('downloads'),gated=x.get('gated',False),created_at=x.get('createdAt'),capabilities=capabilities(x)) for x in rows[:20]],'base_only':base_only,'scanned':scanned,'notice':notice}
def files(self,repo):
repo=repo_id(repo)
with self.cache_lock:
@@ -134,7 +143,7 @@ class Catalog:
size=f.get('size',f.get('lfs',{}).get('size'))
if not isinstance(size,int) or size<1:continue
files.append(dict(name=name,size=size,sha256=f.get('lfs',{}).get('sha256')))
result=dict(repo=repo,revision=revision,files=files,gated=data.get('gated',False),license=(data.get('cardData') or {}).get('license'),url='https://huggingface.co/'+repo)
result=dict(capabilities=capabilities(data),repo=repo,revision=revision,files=files,gated=data.get('gated',False),license=(data.get('cardData') or {}).get('license'),url='https://huggingface.co/'+repo)
with self.cache_lock:
if len(self.cache)>=64:self.cache.pop(next(iter(self.cache)))
self.cache[repo]=(time.monotonic(),result)