mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-09-21 03:01:27 -03:00
perf(modelscope): fetch a repository's model card once per run
A collection repository publishes many model files under a single source id, but enrichment re-read the README and the model-detail payload for every one of them: eight checkpoints meant sixteen HTTP requests, each detail payload being 10-22 KB of JSON. Add `ModelSourceCache`, created by `execute_skill()` for the duration of a run and passed to the provider through a new optional `cache` argument on `fetch_model_card_context()`. The agent caches the README (repository-wide and provider-agnostic), and ModelScope caches its detail payload under a provider-namespaced key. Only successful reads are memoised, so a transient failure is still retried for the next file, and the per-file selection is redone from the cached payload so a checkpoint never inherits a sibling's example images. Nothing is retained across runs — a model card can change at any time — and download URLs are not routed through the cache. Measured over the eight checkpoints of one ModelScope repository: 16 requests before, 2 after. To keep the two concerns separable, `_build_card_context()` now turns a detail payload into a `ModelCardContext` as a pure function.
This commit is contained in:
@@ -104,8 +104,10 @@ class TestBuildPromptContext:
|
||||
|
||||
mock_fetch.assert_awaited_once_with("jj3550945163/Krea-2-LORA")
|
||||
# The per-file lookup must receive the basename, not the full path.
|
||||
mock_context.assert_awaited_once_with(
|
||||
"jj3550945163/Krea-2-LORA", "krea.safetensors"
|
||||
mock_context.assert_awaited_once()
|
||||
assert mock_context.call_args.args == (
|
||||
"jj3550945163/Krea-2-LORA",
|
||||
"krea.safetensors",
|
||||
)
|
||||
assert context["source_platform"] == "modelscope"
|
||||
assert context["source_id"] == "jj3550945163/Krea-2-LORA"
|
||||
@@ -444,7 +446,8 @@ class TestLoadSourceCard:
|
||||
},
|
||||
)
|
||||
|
||||
mock_ctx.assert_awaited_once_with("u/r", "krea.safetensors")
|
||||
mock_ctx.assert_awaited_once()
|
||||
assert mock_ctx.call_args.args == ("u/r", "krea.safetensors")
|
||||
assert context is card
|
||||
assert variables["readme_content_full"] == "# card"
|
||||
assert variables["source_description"] == "作者说明"
|
||||
|
||||
@@ -10,6 +10,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from py.services.model_sources import (
|
||||
ModelSourceCache,
|
||||
HuggingFaceSource,
|
||||
ModelScopeSource,
|
||||
TensorArtSource,
|
||||
@@ -688,3 +689,113 @@ class TestDownloadSourceRegistry:
|
||||
assert get_download_source("nope") is None
|
||||
assert get_download_source("modelscope").platform == "modelscope"
|
||||
assert get_download_source("huggingface").platform == "huggingface"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-run model-card cache
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestModelSourceCache:
|
||||
def test_starts_empty(self):
|
||||
cache = ModelSourceCache()
|
||||
assert cache.readmes == {}
|
||||
assert cache.provider == {}
|
||||
|
||||
|
||||
class TestCachedModelCardFetch:
|
||||
@pytest.mark.asyncio
|
||||
async def test_detail_payload_is_fetched_once_per_source_id(self, monkeypatch):
|
||||
"""A collection repo's files share one detail request, not one each."""
|
||||
calls: list[str] = []
|
||||
|
||||
async def fake_fetch_json(url, **_kwargs):
|
||||
calls.append(url)
|
||||
return 200, _modelscope_detail_payload()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.services.model_sources.modelscope.fetch_json", fake_fetch_json
|
||||
)
|
||||
|
||||
source = ModelScopeSource()
|
||||
cache = ModelSourceCache()
|
||||
filenames = [
|
||||
"Krea-2-LORA_c1-st1000.safetensors",
|
||||
"Krea-2-LORA_c1-st8000.safetensors",
|
||||
"Krea-2-LORA_c1-st1000.safetensors",
|
||||
]
|
||||
for name in filenames:
|
||||
await source.fetch_model_card_context("u/r", name, cache=cache)
|
||||
|
||||
assert calls == ["https://modelscope.cn/api/v1/models/u/r"]
|
||||
assert ("modelscope", "detail", "u/r") in cache.provider
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_per_file_selection_still_runs_for_each_file(self, monkeypatch):
|
||||
"""The cached payload must not leak one file's images to another."""
|
||||
|
||||
async def fake_fetch_json(url, **_kwargs):
|
||||
return 200, _modelscope_detail_payload()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.services.model_sources.modelscope.fetch_json", fake_fetch_json
|
||||
)
|
||||
|
||||
source = ModelScopeSource()
|
||||
cache = ModelSourceCache()
|
||||
|
||||
st1000 = await source.fetch_model_card_context(
|
||||
"u/r", "Krea-2-LORA_c1-st1000.safetensors", cache=cache
|
||||
)
|
||||
st8000 = await source.fetch_model_card_context(
|
||||
"u/r", "Krea-2-LORA_c1-st8000.safetensors", cache=cache
|
||||
)
|
||||
|
||||
assert st1000.example_images == [
|
||||
"https://resources.modelscope.cn/cover-images/b.png",
|
||||
"https://resources.modelscope.cn/cover-images/c.png",
|
||||
]
|
||||
assert st8000.example_images == [
|
||||
"https://resources.modelscope.cn/cover-images/a.png"
|
||||
]
|
||||
assert st8000.trigger_words == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_failures_are_not_cached(self, monkeypatch):
|
||||
"""A transient error must be retried for the next file."""
|
||||
calls: list[str] = []
|
||||
|
||||
async def fake_fetch_json(url, **_kwargs):
|
||||
calls.append(url)
|
||||
return 500, None
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.services.model_sources.modelscope.fetch_json", fake_fetch_json
|
||||
)
|
||||
|
||||
source = ModelScopeSource()
|
||||
cache = ModelSourceCache()
|
||||
for _ in range(2):
|
||||
context = await source.fetch_model_card_context("u/r", "a.safetensors", cache=cache)
|
||||
assert context.is_empty()
|
||||
|
||||
assert len(calls) == 2
|
||||
assert cache.provider == {}
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_cache_keeps_the_uncached_behaviour(self, monkeypatch):
|
||||
calls: list[str] = []
|
||||
|
||||
async def fake_fetch_json(url, **_kwargs):
|
||||
calls.append(url)
|
||||
return 200, _modelscope_detail_payload()
|
||||
|
||||
monkeypatch.setattr(
|
||||
"py.services.model_sources.modelscope.fetch_json", fake_fetch_json
|
||||
)
|
||||
|
||||
source = ModelScopeSource()
|
||||
for _ in range(2):
|
||||
await source.fetch_model_card_context("u/r", "a.safetensors")
|
||||
|
||||
assert len(calls) == 2
|
||||
|
||||
Reference in New Issue
Block a user