diff --git a/py/agent_cli/__init__.py b/py/metadata_ops/__init__.py similarity index 95% rename from py/agent_cli/__init__.py rename to py/metadata_ops/__init__.py index 6e89e8cf..7cdaea69 100644 --- a/py/agent_cli/__init__.py +++ b/py/metadata_ops/__init__.py @@ -1,4 +1,4 @@ -"""Agent CLI — thin in-process wrappers around LoRA Manager internal services. +"""Metadata operations — thin in-process wrappers around LoRA Manager internal services. All functions are simple Python async functions that delegate to the appropriate internal service. They use **relative imports** within the @@ -7,15 +7,15 @@ risk of double import or circular dependencies. Usage (in-process, primary):: - from py.agent_cli import list_base_models, read_metadata + from py.metadata_ops import list_base_models, read_metadata models = await list_base_models() meta = await read_metadata("/path/to/model.safetensors") Usage (subprocess, debugging / external):: - python -m py.agent_cli base-models list - python -m py.agent_cli metadata read /path/to/model.safetensors + python -m py.metadata_ops base-models list + python -m py.metadata_ops metadata read /path/to/model.safetensors """ from __future__ import annotations @@ -214,7 +214,6 @@ async def download_preview( ) with open(output_path, "wb") as f: f.write(optimized_data) - logger.info("Preview downloaded and optimised for %s", model_path) return output_path except Exception as exc: logger.warning("Preview optimisation failed, saving raw: %s", exc) @@ -224,7 +223,6 @@ async def download_preview( try: ok, _ = await downloader.download_file(url, output_path, use_auth=False) if ok: - logger.info("Preview downloaded (fallback) for %s", model_path) return output_path except Exception as exc: logger.warning("Preview fallback download failed for %s: %s", model_path, exc) diff --git a/py/agent_cli/__main__.py b/py/metadata_ops/__main__.py similarity index 84% rename from py/agent_cli/__main__.py rename to py/metadata_ops/__main__.py index 7c3711af..4a9857a8 100644 --- a/py/agent_cli/__main__.py +++ b/py/metadata_ops/__main__.py @@ -1,17 +1,12 @@ -"""Subprocess entry point for AgentCLI (debugging / external use). +"""Subprocess entry point for ``metadata_ops`` (debugging / external use). Usage:: - python -m py.agent_cli base-models list [--limit N] - python -m py.agent_cli metadata read - python -m py.agent_cli metadata update --json '{...}' - python -m py.agent_cli preview download --url - python -m py.agent_cli cache refresh - -NOTE: This is an **optional** convenience wrapper. The primary consumer of -AgentCLI is the :mod:`AgentService` (in-process). This entry point exists -for manual debugging and future integration with subprocess-based agent -frameworks. + python -m py.metadata_ops base-models list [--limit N] + python -m py.metadata_ops metadata read + python -m py.metadata_ops metadata update --json '{...}' + python -m py.metadata_ops preview download --url + python -m py.metadata_ops cache refresh """ from __future__ import annotations diff --git a/py/routes/handlers/agent_handlers.py b/py/routes/handlers/agent_handlers.py index 30182b75..a1f2ca55 100644 --- a/py/routes/handlers/agent_handlers.py +++ b/py/routes/handlers/agent_handlers.py @@ -99,12 +99,11 @@ class AgentHandler: # Launch execution in the background progress_reporter = AgentProgressReporter() logger.info( - "LLM enrichment '%s' starting for %d model(s) in background task", + "LLM enrichment '%s' starting for %d model(s)", skill_name, len(model_paths), ) async def _run() -> None: - logger.info("Background task started for enrichment '%s'", skill_name) try: result = await service.execute_skill( skill_name=skill_name, @@ -137,8 +136,7 @@ class AgentHandler: ) # Fire and forget — progress comes via WebSocket - task = asyncio.create_task(_run()) - logger.info("LLM enrichment '%s' background task created (id=%s)", skill_name, task) + asyncio.create_task(_run()) return web.json_response( { diff --git a/py/services/agent/__init__.py b/py/services/agent/__init__.py index 79b3d86f..6c6a7bdb 100644 --- a/py/services/agent/__init__.py +++ b/py/services/agent/__init__.py @@ -1,8 +1,12 @@ -"""Agent-powered skill system for LoRA Manager. +"""LLM-powered metadata enrichment pipeline infrastructure. -This package provides the orchestration layer for LLM/agent-powered features. +This package provides the orchestration layer for LLM-powered features. Skills define *what* to do (prompt template). The :class:`AgentService` handles *how* (LLM calls, context gathering, validation, progress). + +NOTE: The current implementation is a code-driven pipeline, not a true +agent loop. Future agent orchestration (LLM-driven tool selection) will +live alongside this package with its own namespace. """ from __future__ import annotations diff --git a/py/services/agent/agent_service.py b/py/services/agent/agent_service.py index 4fd6051a..f1cc3ba0 100644 --- a/py/services/agent/agent_service.py +++ b/py/services/agent/agent_service.py @@ -1,16 +1,17 @@ -"""Agent orchestration service. +"""Pipeline orchestration service. -The :class:`AgentService` coordinates skill execution: +The :class:`AgentService` coordinates LLM-powered pipeline execution: -1. Look up the skill in :class:`SkillRegistry` -2. Validate input against the skill's ``input_schema`` -3. Prepare context via :mod:`~py.agent_cli` (read metadata, list base models, fetch HF README) +1. Look up the pipeline definition in :class:`SkillRegistry` +2. Validate input against its ``input_schema`` +3. Prepare context via :mod:`~py.metadata_ops` (read metadata, list base models, fetch HF README) 4. If ``llm_required``: call :class:`LLMService` with the rendered prompt -5. Post-process via :class:`PostProcessor` (delegates I/O to :mod:`~py.agent_cli`) +5. Post-process via :class:`PostProcessor` (delegates I/O to :mod:`~py.metadata_ops`) 6. Broadcast progress and completion via :class:`WebSocketManager` -Skills define *what* to do (prompt template). The AgentService handles *how* -(LLM calls, context gathering, validation, progress). +Pipeline definitions (*skills*) describe *what* to do (prompt template). +The AgentService handles *how* (LLM calls, context gathering, validation, +progress). """ from __future__ import annotations @@ -202,11 +203,11 @@ class AgentService: input_data: Dict[str, Any], progress_callback: Optional[AgentProgressReporter] = None, ) -> SkillResult: - """Execute an agent skill. + """Execute a pipeline (skill) on the given models. Args: - skill_name: Name of the skill to execute - input_data: Input validated against the skill's ``input_schema`` + skill_name: Name of the pipeline to execute + input_data: Input validated against the pipeline's ``input_schema`` progress_callback: Optional WebSocket progress reporter Returns: @@ -214,7 +215,6 @@ class AgentService: """ registry = await self._ensure_registry() - logger.info("execute_skill '%s': looking up skill", skill_name) skill = registry.get_skill(skill_name) if skill is None: return SkillResult( @@ -246,7 +246,6 @@ class AgentService: errors: List[str] = [] post_processor = PostProcessor() - logger.info("execute_skill '%s': starting with %d model(s)", skill_name, total) await self._emit_progress( progress_callback, skill_name, status="started", total=total, processed=0, success=0, @@ -256,13 +255,14 @@ class AgentService: llm_configured = llm.is_configured() if skill.llm_required else True for model_path in model_paths: + model_filename = os.path.basename(model_path) logger.info( - "execute_skill '%s': processing model %d/%d: %s", - skill_name, processed + 1, total, model_path, + "[%s] [%d/%d] %s", + skill_name, processed + 1, total, model_filename, ) updated_data: Dict[str, Any] = {} try: - from ...agent_cli import read_metadata + from ...metadata_ops import read_metadata metadata = await read_metadata(model_path) prompt_vars: Dict[str, Any] = {"model_path": model_path} @@ -275,10 +275,6 @@ class AgentService: if skill.llm_required and llm_configured: prompt_template = registry.load_prompt(skill_name) rendered = _render_prompt(prompt_template, prompt_vars) - logger.info( - "execute_skill '%s': LLM call for %s (prompt=%d chars)", - skill_name, model_path, len(rendered), - ) llm_response = await llm.chat_completion_json( system_prompt=prompt_vars.get( "system_prompt", @@ -286,6 +282,13 @@ class AgentService: ), user_prompt=rendered, ) + if llm_response: + logger.info( + "[%s] [%d/%d] %s → base_model=%s confidence=%s", + skill_name, processed + 1, total, model_filename, + (llm_response.get("base_model") or "?")[:50], + llm_response.get("confidence", "?"), + ) model_result = await post_processor.process( skill_name=skill_name, @@ -329,7 +332,6 @@ class AgentService: summary=f"Processed {processed}/{total} models, {success_count} succeeded", ) - logger.info("execute_skill '%s': done — %s", skill_name, result.summary) await self._emit_progress( progress_callback, skill_name, status="completed", total=total, processed=processed, success=success_count, @@ -366,7 +368,7 @@ class AgentService: base models, loads user priority tags, and returns a dict that maps to ``{{variable}}`` placeholders in ``prompt.md``. """ - from ...agent_cli import identify_model_type, list_base_models + from ...metadata_ops import identify_model_type, list_base_models from ..settings_manager import SettingsManager context: Dict[str, Any] = { @@ -411,10 +413,6 @@ class AgentService: cleaned = clean_readme_for_llm(readme) if readme else "" context["readme_content"] = cleaned if cleaned else "(README not available)" context["readme_content_full"] = readme or "" - logger.info( - "Cleaned README for %s (%d chars): ---BEGIN---\n%s\n---END---", - repo, len(cleaned), cleaned[:800] if cleaned else "(empty)", - ) try: raw_models = await list_base_models() diff --git a/py/services/agent/post_processor.py b/py/services/agent/post_processor.py index 720393d4..a13c8975 100644 --- a/py/services/agent/post_processor.py +++ b/py/services/agent/post_processor.py @@ -1,11 +1,11 @@ """Post-processing engine for skill pipeline outputs. The :class:`PostProcessor` takes the LLM's structured JSON output and applies -it to a model's on-disk metadata via the :mod:`~py.agent_cli` functions. +it to a model's on-disk metadata via the :mod:`~py.metadata_ops` functions. It handles all the skill-specific business logic — conditions, transformations, and orchestration of multiple side-effects (write metadata, download preview, -refresh cache). All actual I/O is delegated to :mod:`~py.agent_cli`. +refresh cache). All actual I/O is delegated to :mod:`~py.metadata_ops`. """ from __future__ import annotations @@ -30,7 +30,7 @@ class PostProcessor: skill_name="enrich_hf_metadata", model_path="/path/to/model.safetensors", llm_output={...}, - metadata={...}, # from agent_cli.read_metadata() + metadata={...}, # from metadata_ops.read_metadata() ) """ @@ -73,7 +73,7 @@ class PostProcessor: metadata: Dict[str, Any], readme_content: str = "", ) -> Dict[str, Any]: - from ...agent_cli import ( + from ...metadata_ops import ( apply_metadata_updates, download_preview, refresh_cache, diff --git a/py/services/llm_service.py b/py/services/llm_service.py index 48305def..54625184 100644 --- a/py/services/llm_service.py +++ b/py/services/llm_service.py @@ -521,15 +521,9 @@ class LLMService: try: parsed = json.loads(result["content"]) - logger.info( - "LLM response base_model=%s tags=%s confidence=%s", - parsed.get("base_model", "?")[:50], - parsed.get("tags", []), - parsed.get("confidence", "?"), - ) - logger.info( + logger.debug( "LLM raw content: %s", - (result.get("content") or "")[:1200], + json.dumps(parsed, ensure_ascii=False)[:2000], ) return parsed except (json.JSONDecodeError, TypeError) as exc: diff --git a/tests/enrich_hf_validation/config.py b/tests/enrich_hf_validation/config.py index 2538e651..49fec9c1 100644 --- a/tests/enrich_hf_validation/config.py +++ b/tests/enrich_hf_validation/config.py @@ -72,7 +72,7 @@ _FALLBACK_BASE_MODELS: List[str] = [ async def init_supported_base_models() -> None: """Populate ``SUPPORTED_BASE_MODELS`` from the production codebase. - Calls ``py.agent_cli.list_base_models()`` which merges a hardcoded + Calls ``py.metadata_ops.list_base_models()`` which merges a hardcoded fallback with models fetched from the CivitAI API. When the call fails (e.g. offline, API error), falls back to ``_FALLBACK_BASE_MODELS``. @@ -80,7 +80,7 @@ async def init_supported_base_models() -> None: ``run_validation.main()``, not at module level). """ try: - from py.agent_cli import list_base_models + from py.metadata_ops import list_base_models models = await list_base_models() if models: diff --git a/tests/agent_cli/__init__.py b/tests/metadata_ops/__init__.py similarity index 100% rename from tests/agent_cli/__init__.py rename to tests/metadata_ops/__init__.py diff --git a/tests/agent_cli/test_agent_cli.py b/tests/metadata_ops/test_metadata_ops.py similarity index 98% rename from tests/agent_cli/test_agent_cli.py rename to tests/metadata_ops/test_metadata_ops.py index e84a2e45..ec93454d 100644 --- a/tests/agent_cli/test_agent_cli.py +++ b/tests/metadata_ops/test_metadata_ops.py @@ -1,7 +1,7 @@ -"""Tests for the AgentCLI module (py/agent_cli/). +"""Tests for the metadata_ops module (py/metadata_ops/). All tests mock the underlying services (scanner, MetadataManager, downloader) -since the AgentCLI is a thin delegation layer. +since it is a thin delegation layer. Mock targets must match where imports are resolved inside each function (lazy imports via ``from X import Y`` inside function body). @@ -13,7 +13,7 @@ from unittest import mock import pytest -from py.agent_cli import ( +from py.metadata_ops import ( list_base_models, read_metadata, apply_metadata_updates, @@ -160,7 +160,7 @@ class TestApplyMetadataUpdates: @pytest.mark.asyncio async def test_updates_field(self): with ( - mock.patch("py.agent_cli.read_metadata") as mock_read, + mock.patch("py.metadata_ops.read_metadata") as mock_read, mock.patch("py.utils.metadata_manager.MetadataManager") as mm, ): mock_read.return_value = {"base_model": "", "tags": []} @@ -176,7 +176,7 @@ class TestApplyMetadataUpdates: @pytest.mark.asyncio async def test_noop_when_value_unchanged(self): with ( - mock.patch("py.agent_cli.read_metadata") as mock_read, + mock.patch("py.metadata_ops.read_metadata") as mock_read, mock.patch("py.utils.metadata_manager.MetadataManager") as mm, ): mock_read.return_value = {"base_model": "Flux.1 D"} @@ -189,7 +189,7 @@ class TestApplyMetadataUpdates: @pytest.mark.asyncio async def test_multiple_fields(self): with ( - mock.patch("py.agent_cli.read_metadata") as mock_read, + mock.patch("py.metadata_ops.read_metadata") as mock_read, mock.patch("py.utils.metadata_manager.MetadataManager") as mm, ): mm.save_metadata = mock.AsyncMock(return_value=True) @@ -207,7 +207,7 @@ class TestApplyMetadataUpdates: @pytest.mark.asyncio async def test_empty_updates_noop(self): with ( - mock.patch("py.agent_cli.read_metadata"), + mock.patch("py.metadata_ops.read_metadata"), mock.patch("py.utils.metadata_manager.MetadataManager") as mm, ): updated = await apply_metadata_updates("/p.safetensors", {}) @@ -277,7 +277,7 @@ class TestRefreshCache: get_checkpoint_scanner=mock.AsyncMock(return_value=None), get_embedding_scanner=mock.AsyncMock(return_value=None), ), - mock.patch("py.agent_cli.read_metadata") as mock_read, + mock.patch("py.metadata_ops.read_metadata") as mock_read, ): mock_read.return_value = {"base_model": "SDXL 1.0"} result = await refresh_cache("/some/path.safetensors") @@ -306,7 +306,7 @@ class TestRefreshCache: get_checkpoint_scanner=mock.AsyncMock(return_value=None), get_embedding_scanner=mock.AsyncMock(return_value=None), ), - mock.patch("py.agent_cli.read_metadata") as mock_read, + mock.patch("py.metadata_ops.read_metadata") as mock_read, ): mock_read.return_value = {} result = await refresh_cache("/some/path.safetensors") diff --git a/tests/agent_cli/test_readme_processor.py b/tests/metadata_ops/test_readme_processor.py similarity index 100% rename from tests/agent_cli/test_readme_processor.py rename to tests/metadata_ops/test_readme_processor.py diff --git a/tests/services/test_post_processor.py b/tests/services/test_post_processor.py index 49d13115..3779fe30 100644 --- a/tests/services/test_post_processor.py +++ b/tests/services/test_post_processor.py @@ -39,9 +39,9 @@ class TestProcessDispatch: @pytest.mark.asyncio async def test_enrich_hf_metadata_routes_correctly(self, processor): with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview") as mock_dl, - mock.patch("py.agent_cli.refresh_cache") as mock_ref, + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview") as mock_dl, + mock.patch("py.metadata_ops.refresh_cache") as mock_ref, ): mock_apply.return_value = ["metadata_source"] mock_dl.return_value = None @@ -82,9 +82,9 @@ class TestEnrichHfMetadata: """Empty current base_model → new value is applied.""" llm = {**self.MIN_LLM_OUTPUT, "base_model": "Flux.1 D"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -100,9 +100,9 @@ class TestEnrichHfMetadata: """Existing base_model from CivitAI → not overwritten.""" llm = {**self.MIN_LLM_OUTPUT, "base_model": "Flux.1 D"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -119,9 +119,9 @@ class TestEnrichHfMetadata: """Existing base_model from HF → overwritten (LLM is more reliable).""" llm = {**self.MIN_LLM_OUTPUT, "base_model": "Flux.1 D"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -136,9 +136,9 @@ class TestEnrichHfMetadata: async def test_base_model_skipped_when_llm_empty(self, processor): """LLM returns empty base_model → nothing written.""" with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -156,9 +156,9 @@ class TestEnrichHfMetadata: """New trigger words written when current list is empty.""" llm = {**self.MIN_LLM_OUTPUT, "trigger_words": ["trigger1", "trigger2"]} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -176,9 +176,9 @@ class TestEnrichHfMetadata: """short_description written to civitai.description for HF models.""" llm = {**self.MIN_LLM_OUTPUT, "short_description": "A short summary"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -194,9 +194,9 @@ class TestEnrichHfMetadata: """short_description NOT written for CivitAI models (has own description).""" llm = {**self.MIN_LLM_OUTPUT, "short_description": "A short summary"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -213,9 +213,9 @@ class TestEnrichHfMetadata: async def test_readme_content_converted_to_model_description(self, processor): """Raw README converted to HTML and stored as modelDescription.""" with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -232,9 +232,9 @@ class TestEnrichHfMetadata: async def test_readme_content_skipped_for_civitai_model(self, processor): """README content NOT converted for CivitAI models.""" with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -260,9 +260,9 @@ widget: Content """ with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -286,9 +286,9 @@ Content async def test_gallery_images_skipped_for_civitai_model(self, processor): """Gallery images NOT extracted for CivitAI models.""" with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=None), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=None), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -310,9 +310,9 @@ Content async def test_tags_merged_and_deduplicated(self, processor): llm = {**self.MIN_LLM_OUTPUT, "tags": ["flux", "lora", "STYLE"]} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -332,9 +332,9 @@ Content @pytest.mark.asyncio async def test_audit_fields_always_set(self, processor): with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache"), ): await processor.process( skill_name="enrich_hf_metadata", @@ -352,9 +352,9 @@ Content async def test_preview_downloaded_when_url_provided(self, processor): llm = {**self.MIN_LLM_OUTPUT, "preview_url": "https://ex.com/img.png"} with ( - mock.patch("py.agent_cli.apply_metadata_updates") as mock_apply, - mock.patch("py.agent_cli.download_preview") as mock_dl, - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates") as mock_apply, + mock.patch("py.metadata_ops.download_preview") as mock_dl, + mock.patch("py.metadata_ops.refresh_cache"), ): mock_dl.return_value = "/p.webp" result = await processor.process( @@ -373,9 +373,9 @@ Content """If current_preview file exists on disk, skip download.""" llm = {**self.MIN_LLM_OUTPUT, "preview_url": "https://ex.com/img.png"} with ( - mock.patch("py.agent_cli.apply_metadata_updates"), - mock.patch("py.agent_cli.download_preview") as mock_dl, - mock.patch("py.agent_cli.refresh_cache"), + mock.patch("py.metadata_ops.apply_metadata_updates"), + mock.patch("py.metadata_ops.download_preview") as mock_dl, + mock.patch("py.metadata_ops.refresh_cache"), mock.patch("os.path.exists", return_value=True), ): await processor.process( @@ -392,9 +392,9 @@ Content async def test_cache_refreshed_when_updates_applied(self, processor): llm = {**self.MIN_LLM_OUTPUT, "base_model": "Flux.1 D"} with ( - mock.patch("py.agent_cli.apply_metadata_updates", return_value=["base_model"]), - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache") as mock_ref, + mock.patch("py.metadata_ops.apply_metadata_updates", return_value=["base_model"]), + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache") as mock_ref, ): await processor.process( skill_name="enrich_hf_metadata", @@ -407,9 +407,9 @@ Content @pytest.mark.asyncio async def test_cache_not_refreshed_when_nothing_changed(self, processor): with ( - mock.patch("py.agent_cli.apply_metadata_updates", return_value=[]), - mock.patch("py.agent_cli.download_preview", return_value=False), - mock.patch("py.agent_cli.refresh_cache") as mock_ref, + mock.patch("py.metadata_ops.apply_metadata_updates", return_value=[]), + mock.patch("py.metadata_ops.download_preview", return_value=False), + mock.patch("py.metadata_ops.refresh_cache") as mock_ref, ): await processor.process( skill_name="enrich_hf_metadata",