mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-07-05 17:01:16 -03:00
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:
@@ -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": {
|
||||||
|
|||||||
@@ -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`)::
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
@@ -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':
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user