refactor(agent): rename md_to_html to readme_processor, fix section extraction, widget parsing, and list_base_models

- Rename md_to_html.py → readme_processor.py (file no longer just HTML conversion)
- _extract_section: include YAML frontmatter, use heading-level-aware forward
  walk (sub-headings under # are included), increase walk limit past 30 lines
- _is_heading: exclude </hN> closing tags from boundary detection
- _heading_level: new helper for heading-level-aware section matching
- css: yield 0 for heading like closing tags, was unexpectedly caught by _is_heading
- extract_gallery_images: fix YAML block scalar (text: >-) prompt extraction;
  use endswith instead of == to detect the block marker
- _strip_widget_section: add to clean_readme_for_llm (widget text is handled
  by post-processor, not needed in LLM prompt)
- _strip_standalone_images: keep markdown image URLs intact for LLM preview
  extraction (was stripping to alt text only)
- list_base_models: switch from scanner-cache aggregation to
  CivitaiBaseModelService.get_base_models() - always returns full list
- Ollama: add num_ctx=32768 to payload options so thinking models have room
  to both reason and produce output
- Add tests/agent_cli/test_readme_processor.py: 59 tests covering extraction,
  cleaning, section matching, heading detection
- Update existing tests for behavioral changes
This commit is contained in:
Will Miao
2026-07-05 06:39:54 +08:00
parent 905c37290f
commit dd3aa97d0a
8 changed files with 733 additions and 159 deletions

View File

@@ -113,38 +113,29 @@ async def identify_model_type(model_path: str) -> str:
async def list_base_models(limit: int = 0) -> List[str]:
"""Return deduplicated base model names from all model caches.
"""Return all valid CivitAI base model names.
The result is ordered by frequency (most common first). Pass
*limit* = 0 (default) for all models.
Uses ``CivitaiBaseModelService.get_base_models()`` which merges a
hardcoded list (``SUPPORTED_DOWNLOAD_SKIP_BASE_MODELS``) with remote
models fetched from the CivitAI API. Never empty — the hardcoded
fallback always provides a complete set.
The result is sorted alphabetically. Pass *limit* = 0 for all models.
"""
from ..services.service_registry import ServiceRegistry
from ..services.civitai_base_model_service import (
CivitaiBaseModelService,
)
counts: Dict[str, int] = {}
for getter_name in (
"get_lora_scanner",
"get_checkpoint_scanner",
"get_embedding_scanner",
):
getter = getattr(ServiceRegistry, getter_name, None)
if getter is None:
continue
try:
scanner = await getter()
if scanner is None:
continue
cache = await scanner.get_cached_data()
for entry in cache.raw_data:
bm = entry.get("base_model")
if bm:
counts[bm] = counts.get(bm, 0) + 1
except Exception as exc:
logger.debug("list_base_models scanner %s error: %s", getter_name, exc)
sorted_names = [name for name, _ in sorted(counts.items(), key=lambda x: -x[1])]
try:
service = await CivitaiBaseModelService.get_instance()
response = await service.get_base_models()
names: List[str] = response.get("models", [])
except Exception as exc:
logger.warning("list_base_models failed: %s", exc)
names = []
if limit > 0:
return sorted_names[:limit]
return sorted_names
return names[:limit]
return names
async def read_metadata(model_path: str) -> Dict[str, Any]: