refactor(agent): align 'Agent' naming to 'AI/LLM' to match current implementation

- locales/en.json: 'Enrich Metadata (Agent)' -> 'Enrich Metadata (AI)'
- Rename SKILL.md -> prompt.md with backward compat in skill_registry.py
- JS context menu action IDs: enrich-hf-agent -> enrich-hf-llm
- HTML template data-action attributes synced to match
- docstring cleanup: 'agent skill' -> 'skill pipeline' / 'feature'
This commit is contained in:
Will Miao
2026-07-04 14:06:50 +08:00
parent 170c8068c5
commit 646f1ddfb1
9 changed files with 52 additions and 26 deletions

View File

@@ -781,7 +781,7 @@
"complete": "Auto-organize complete", "complete": "Auto-organize complete",
"error": "Error: {error}" "error": "Error: {error}"
}, },
"enrichHfAgent": "Enrich Metadata (Agent)" "enrichHfAgent": "Enrich Metadata (AI)"
}, },
"contextMenu": { "contextMenu": {
"refreshMetadata": "Refresh Civitai Data", "refreshMetadata": "Refresh Civitai Data",
@@ -806,7 +806,7 @@
"viewAllLoras": "View All LoRAs", "viewAllLoras": "View All LoRAs",
"downloadMissingLoras": "Download Missing LoRAs", "downloadMissingLoras": "Download Missing LoRAs",
"deleteRecipe": "Delete Recipe", "deleteRecipe": "Delete Recipe",
"enrichHfAgent": "Enrich Metadata (Agent)" "enrichHfAgent": "Enrich Metadata (AI)"
} }
}, },
"recipes": { "recipes": {

View File

@@ -1,4 +1,4 @@
"""Post-processing engine for agent skill outputs. """Post-processing engine for skill pipeline outputs.
The :class:`PostProcessor` takes the LLM's structured JSON output and applies 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.agent_cli` functions.
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
class PostProcessor: class PostProcessor:
"""Deterministic post-processor for agent skill outputs. """Deterministic post-processor for skill pipeline outputs.
Usage (called by :class:`~py.services.agent.agent_service.AgentService`):: Usage (called by :class:`~py.services.agent.agent_service.AgentService`)::

View File

@@ -1,7 +1,7 @@
"""Discovery and loading of agent skills. """Discovery and loading of prompt-based skills.
Skills live in ``py/services/agent/skills/<name>/`` directories. Each Skills live in ``py/services/agent/skills/<name>/`` directories. Each
directory must contain a ``SKILL.md`` file with YAML frontmatter:: directory must contain a ``prompt.md`` file with YAML frontmatter::
--- ---
name: my_skill name: my_skill
@@ -12,6 +12,8 @@ directory must contain a ``SKILL.md`` file with YAML frontmatter::
Prompt template with ``{{variable}}`` placeholders. Prompt template with ``{{variable}}`` placeholders.
Legacy ``SKILL.md`` files are also supported for backward compatibility.
The registry scans the skills directory on first access and caches results. The registry scans the skills directory on first access and caches results.
""" """
@@ -32,6 +34,11 @@ logger = logging.getLogger(__name__)
# Directory where built-in skills are stored # Directory where built-in skills are stored
_SKILLS_DIR = Path(__file__).parent / "skills" _SKILLS_DIR = Path(__file__).parent / "skills"
#: Preferred file names for prompt definition files (tried in order).
#: ``prompt.md`` is the current convention; ``SKILL.md`` is the legacy name
#: kept for backward compatibility.
_PROMPT_FILE_NAMES: tuple[str, ...] = ("prompt.md", "SKILL.md")
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Frontmatter parser # Frontmatter parser
@@ -43,7 +50,8 @@ _FRONTMATTER_RE = re.compile(
def _parse_skill_file(path: Path) -> tuple[dict, str]: def _parse_skill_file(path: Path) -> tuple[dict, str]:
"""Read a ``SKILL.md`` file and return (frontmatter_dict, body_text). """Read a prompt definition file (``prompt.md`` or legacy ``SKILL.md``) and
return (frontmatter_dict, body_text).
Raises ``ValueError`` if the file lacks valid YAML frontmatter. Raises ``ValueError`` if the file lacks valid YAML frontmatter.
""" """
@@ -95,6 +103,20 @@ class SkillRegistry:
# Discovery # Discovery
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@staticmethod
def _find_prompt_file(skill_dir: Path) -> Path | None:
"""Return the first prompt definition file that exists in *skill_dir*.
Tries ``_PROMPT_FILE_NAMES`` in order so that new conventions
(``prompt.md``) take precedence while legacy ``SKILL.md`` files
still load without changes.
"""
for name in _PROMPT_FILE_NAMES:
candidate = skill_dir / name
if candidate.exists():
return candidate
return None
def _discover(self) -> None: def _discover(self) -> None:
"""Scan the skills directory and load all valid skill definitions.""" """Scan the skills directory and load all valid skill definitions."""
@@ -107,31 +129,32 @@ class SkillRegistry:
for entry in sorted(self._skills_dir.iterdir()): for entry in sorted(self._skills_dir.iterdir()):
if not entry.is_dir(): if not entry.is_dir():
continue continue
skill_md = entry / "SKILL.md" prompt_file = self._find_prompt_file(entry)
if not skill_md.exists(): if prompt_file is None:
continue continue
try: try:
definition = self._load_skill_definition(skill_md) definition = self._load_skill_definition(prompt_file)
if definition is not None: if definition is not None:
self._skills[definition.name] = definition self._skills[definition.name] = definition
logger.debug("Loaded skill: %s", definition.name) logger.debug("Loaded skill: %s", definition.name)
except Exception as exc: except Exception as exc:
logger.warning("Failed to load skill from %s: %s", skill_md, exc) logger.warning("Failed to load skill from %s: %s", prompt_file, exc)
self._loaded = True self._loaded = True
logger.info("Discovered %d agent skills", len(self._skills)) logger.info("Discovered %d agent skills", len(self._skills))
def _load_skill_definition(self, path: Path) -> Optional[SkillDefinition]: def _load_skill_definition(self, path: Path) -> Optional[SkillDefinition]:
"""Parse a ``SKILL.md`` frontmatter into a :class:`SkillDefinition`.""" """Parse a prompt definition file's frontmatter into a
:class:`SkillDefinition`."""
try: try:
data, _body = _parse_skill_file(path) data, _body = _parse_skill_file(path)
except (ValueError, yaml.YAMLError) as exc: except (ValueError, yaml.YAMLError) as exc:
logger.warning("Failed to parse SKILL.md %s: %s", path, exc) logger.warning("Failed to parse prompt file %s: %s", path, exc)
return None return None
if "name" not in data: if "name" not in data:
logger.warning("SKILL.md missing required 'name' field: %s", path) logger.warning("Prompt file %s missing required 'name' field", path)
return None return None
perm_data = data.get("permissions", {}) perm_data = data.get("permissions", {})
@@ -171,12 +194,15 @@ class SkillRegistry:
return self._skills.get(name) return self._skills.get(name)
def load_prompt(self, name: str) -> str: def load_prompt(self, name: str) -> str:
"""Load and return the prompt template body from a skill's ``SKILL.md``.""" """Load and return the prompt template body for the named skill."""
skill_dir = self._skills_dir / name skill_dir = self._skills_dir / name
skill_path = skill_dir / "SKILL.md" skill_path = self._find_prompt_file(skill_dir)
if not skill_path.exists(): if skill_path is None:
raise FileNotFoundError(f"SKILL.md not found: {skill_path}") raise FileNotFoundError(
f"Prompt file not found for skill '{name}' in {skill_dir} "
f"(tried {list(_PROMPT_FILE_NAMES)})"
)
try: try:
_frontmatter, body = _parse_skill_file(skill_path) _frontmatter, body = _parse_skill_file(skill_path)
return body return body

View File

@@ -1,8 +1,8 @@
"""Inline markdown-to-HTML converter and LLM-prompt cleaner for HF README content. """Inline markdown-to-HTML converter and LLM-prompt cleaner for HF README content.
No external dependencies. Strips YAML frontmatter, ``<Gallery />`` sections, No external dependencies. Strips YAML frontmatter, ``<Gallery />`` sections,
badge images, and HTML comments before rendering. Only used by the badge images, and HTML comments before rendering. Used by the
``enrich_hf_metadata`` skill. ``enrich_hf_metadata`` feature.
Also provides :func:`clean_readme_for_llm` which pre-processes the raw README Also provides :func:`clean_readme_for_llm` which pre-processes the raw README
before it is injected into the LLM prompt, removing content that has zero value before it is injected into the LLM prompt, removing content that has zero value

View File

@@ -274,7 +274,7 @@ export class BulkContextMenu extends BaseContextMenu {
case 'resume-metadata-refresh': case 'resume-metadata-refresh':
bulkManager.setSkipMetadataRefresh(false); bulkManager.setSkipMetadataRefresh(false);
break; break;
case 'enrich-hf-agent-bulk': case 'enrich-hf-llm-bulk':
this.enrichBulkWithAgent(); this.enrichBulkWithAgent();
break; break;
case 'delete-all': case 'delete-all':

View File

@@ -63,7 +63,7 @@ export class LoraContextMenu extends BaseContextMenu {
case 'refresh-metadata': case 'refresh-metadata':
getModelApiClient().refreshSingleModelMetadata(this.currentCard.dataset.filepath); getModelApiClient().refreshSingleModelMetadata(this.currentCard.dataset.filepath);
break; break;
case 'enrich-hf-agent': case 'enrich-hf-llm':
this.enrichWithAgent(this.currentCard.dataset.filepath); this.enrichWithAgent(this.currentCard.dataset.filepath);
break; break;
case 'exclude': case 'exclude':

View File

@@ -12,7 +12,7 @@
<div class="context-menu-item" data-action="check-updates"> <div class="context-menu-item" data-action="check-updates">
<i class="fas fa-bell"></i> <span>{{ t('loras.contextMenu.checkUpdates') }}</span> <i class="fas fa-bell"></i> <span>{{ t('loras.contextMenu.checkUpdates') }}</span>
</div> </div>
<div class="context-menu-item" data-action="enrich-hf-agent"> <div class="context-menu-item" data-action="enrich-hf-llm">
<i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.contextMenu.enrichHfAgent') }}</span> <i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.contextMenu.enrichHfAgent') }}</span>
</div> </div>
<div class="context-menu-item" data-action="relink-civitai"> <div class="context-menu-item" data-action="relink-civitai">
@@ -86,7 +86,7 @@
<div class="context-menu-item" data-action="resume-metadata-refresh"> <div class="context-menu-item" data-action="resume-metadata-refresh">
<i class="fas fa-redo"></i> <span>{{ t('loras.bulkOperations.resumeMetadataRefresh') }}</span> <i class="fas fa-redo"></i> <span>{{ t('loras.bulkOperations.resumeMetadataRefresh') }}</span>
</div> </div>
<div class="context-menu-item" data-action="enrich-hf-agent-bulk"> <div class="context-menu-item" data-action="enrich-hf-llm-bulk">
<i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.bulkOperations.enrichHfAgent') }}</span> <i class="fas fa-wand-magic-sparkles"></i> <span>{{ t('loras.bulkOperations.enrichHfAgent') }}</span>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
"""Tests for the SkillRegistry.""" """Tests for the SkillRegistry (``prompt.md`` discovery + prompt loading)."""
from __future__ import annotations from __future__ import annotations
@@ -30,7 +30,7 @@ class TestSkillRegistryDiscovery:
def test_skill_has_correct_model_type_filter(self, registry): def test_skill_has_correct_model_type_filter(self, registry):
skill = registry.get_skill("enrich_hf_metadata") skill = registry.get_skill("enrich_hf_metadata")
# model_type_filter was removed from SKILL.md — defaults to None (all types) # model_type_filter was removed from prompt.md — defaults to None (all types)
assert skill.model_type_filter is None assert skill.model_type_filter is None
def test_skill_has_permissions(self, registry): def test_skill_has_permissions(self, registry):