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",
"error": "Error: {error}"
},
"enrichHfAgent": "Enrich Metadata (Agent)"
"enrichHfAgent": "Enrich Metadata (AI)"
},
"contextMenu": {
"refreshMetadata": "Refresh Civitai Data",
@@ -806,7 +806,7 @@
"viewAllLoras": "View All LoRAs",
"downloadMissingLoras": "Download Missing LoRAs",
"deleteRecipe": "Delete Recipe",
"enrichHfAgent": "Enrich Metadata (Agent)"
"enrichHfAgent": "Enrich Metadata (AI)"
}
},
"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
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:
"""Deterministic post-processor for agent skill outputs.
"""Deterministic post-processor for skill pipeline outputs.
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
directory must contain a ``SKILL.md`` file with YAML frontmatter::
directory must contain a ``prompt.md`` file with YAML frontmatter::
---
name: my_skill
@@ -12,6 +12,8 @@ directory must contain a ``SKILL.md`` file with YAML frontmatter::
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.
"""
@@ -32,6 +34,11 @@ logger = logging.getLogger(__name__)
# Directory where built-in skills are stored
_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
@@ -43,7 +50,8 @@ _FRONTMATTER_RE = re.compile(
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.
"""
@@ -95,6 +103,20 @@ class SkillRegistry:
# 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:
"""Scan the skills directory and load all valid skill definitions."""
@@ -107,31 +129,32 @@ class SkillRegistry:
for entry in sorted(self._skills_dir.iterdir()):
if not entry.is_dir():
continue
skill_md = entry / "SKILL.md"
if not skill_md.exists():
prompt_file = self._find_prompt_file(entry)
if prompt_file is None:
continue
try:
definition = self._load_skill_definition(skill_md)
definition = self._load_skill_definition(prompt_file)
if definition is not None:
self._skills[definition.name] = definition
logger.debug("Loaded skill: %s", definition.name)
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
logger.info("Discovered %d agent skills", len(self._skills))
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:
data, _body = _parse_skill_file(path)
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
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
perm_data = data.get("permissions", {})
@@ -171,12 +194,15 @@ class SkillRegistry:
return self._skills.get(name)
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_path = skill_dir / "SKILL.md"
if not skill_path.exists():
raise FileNotFoundError(f"SKILL.md not found: {skill_path}")
skill_path = self._find_prompt_file(skill_dir)
if skill_path is None:
raise FileNotFoundError(
f"Prompt file not found for skill '{name}' in {skill_dir} "
f"(tried {list(_PROMPT_FILE_NAMES)})"
)
try:
_frontmatter, body = _parse_skill_file(skill_path)
return body

View File

@@ -1,8 +1,8 @@
"""Inline markdown-to-HTML converter and LLM-prompt cleaner for HF README content.
No external dependencies. Strips YAML frontmatter, ``<Gallery />`` sections,
badge images, and HTML comments before rendering. Only used by the
``enrich_hf_metadata`` skill.
badge images, and HTML comments before rendering. Used by the
``enrich_hf_metadata`` feature.
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

View File

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

View File

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

View File

@@ -12,7 +12,7 @@
<div class="context-menu-item" data-action="check-updates">
<i class="fas fa-bell"></i> <span>{{ t('loras.contextMenu.checkUpdates') }}</span>
</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>
</div>
<div class="context-menu-item" data-action="relink-civitai">
@@ -86,7 +86,7 @@
<div class="context-menu-item" data-action="resume-metadata-refresh">
<i class="fas fa-redo"></i> <span>{{ t('loras.bulkOperations.resumeMetadataRefresh') }}</span>
</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>
</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
@@ -30,7 +30,7 @@ class TestSkillRegistryDiscovery:
def test_skill_has_correct_model_type_filter(self, registry):
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
def test_skill_has_permissions(self, registry):