mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-07-03 07:51:16 -03:00
Introduce an agent skill framework for LLM-driven metadata enrichment: - AgentCLI (py/agent_cli/): in-process wrappers around internal services using standard relative imports, eliminating the need for sys.path hacks - LLMService: centralized BYOK (bring-your-own-key) LLM client supporting OpenAI, Ollama, and custom OpenAI-compatible endpoints - PostProcessor: deterministic engine that applies LLM output via AgentCLI (replaces old handler.py + _BASE_MODEL_ALIASES approach) - SkillRegistry: filesystem-based skill discovery (skill.yaml + prompt.md) - AgentService: orchestrates skill execution with WebSocket progress - Frontend AgentManager: WebSocket listeners, skill execution, config UI - Context menu entries (single + bulk) for "Enrich Metadata (Agent)" - Settings UI for AI Provider configuration (BYOK) - Full i18n support across 9 locales Bug fixes found during review: - aiohttp.web.json_response: status_code= -> status= - settings_modal cancelEditApiKey: wrong argument position - AgentManager.isLlmConfigured: allow Ollama without API key - PostProcessor._merge_tags: lowercase all tags to match TagUpdateService
168 lines
6.1 KiB
Python
168 lines
6.1 KiB
Python
"""HTTP route handlers for agent skill endpoints.
|
|
|
|
These handlers expose the :class:`AgentService` via HTTP, allowing the
|
|
frontend to list available skills and execute them on selected models.
|
|
Progress is reported via WebSocket broadcast.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Any, Dict
|
|
|
|
from aiohttp import web
|
|
|
|
from ...services.agent import AgentService, AgentProgressReporter
|
|
from ...services.llm_service import LLMNotConfiguredError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class AgentHandler:
|
|
"""HTTP handler for agent skill operations."""
|
|
|
|
def __init__(self, agent_service: AgentService | None = None) -> None:
|
|
self._agent_service = agent_service
|
|
|
|
async def _ensure_service(self) -> AgentService:
|
|
if self._agent_service is None:
|
|
self._agent_service = await AgentService.get_instance()
|
|
return self._agent_service
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/lm/agent/skills
|
|
# ------------------------------------------------------------------
|
|
|
|
async def get_agent_skills(self, request: web.Request) -> web.Response:
|
|
"""Return a list of available agent skills."""
|
|
|
|
service = await self._ensure_service()
|
|
skills = await service.list_skills()
|
|
return web.json_response({"skills": skills})
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/lm/agent/execute/{skill_name}
|
|
# ------------------------------------------------------------------
|
|
|
|
async def execute_agent_skill(self, request: web.Request) -> web.Response:
|
|
"""Execute an agent skill on the provided model paths.
|
|
|
|
Request body::
|
|
|
|
{"model_paths": ["/path/to/model1.safetensors", ...], "options": {}}
|
|
|
|
Returns immediately with a task ID. Execution runs in the
|
|
background; progress and completion are pushed via WebSocket
|
|
events of type ``agent_progress``.
|
|
"""
|
|
|
|
skill_name = request.match_info.get("skill_name", "")
|
|
if not skill_name:
|
|
return web.json_response(
|
|
{"error": "Skill name is required"}, status_code=400
|
|
)
|
|
|
|
try:
|
|
body = await request.json()
|
|
except Exception:
|
|
return web.json_response(
|
|
{"error": "Invalid JSON body"}, status_code=400
|
|
)
|
|
|
|
model_paths = body.get("model_paths", [])
|
|
if not model_paths or not isinstance(model_paths, list):
|
|
return web.json_response(
|
|
{"error": "model_paths must be a non-empty array"},
|
|
status_code=400,
|
|
)
|
|
|
|
service = await self._ensure_service()
|
|
|
|
# Validate LLM configuration early for skills that need it
|
|
# (fail fast rather than after starting background work)
|
|
try:
|
|
from ...services.llm_service import LLMService
|
|
|
|
llm = await LLMService.get_instance()
|
|
if not llm.is_configured():
|
|
return web.json_response(
|
|
{
|
|
"error": "LLM provider is not configured. "
|
|
"Enable it in Settings → AI Provider.",
|
|
},
|
|
status=400,
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Failed to check LLM configuration: %s", exc)
|
|
|
|
# Launch execution in the background
|
|
progress_reporter = AgentProgressReporter()
|
|
logger.info(
|
|
"Agent skill '%s' starting for %d model(s) in background task",
|
|
skill_name, len(model_paths),
|
|
)
|
|
|
|
async def _run() -> None:
|
|
logger.info("_run background task started for skill '%s'", skill_name)
|
|
try:
|
|
result = await service.execute_skill(
|
|
skill_name=skill_name,
|
|
input_data={"model_paths": model_paths},
|
|
progress_callback=progress_reporter,
|
|
)
|
|
logger.info(
|
|
"Agent skill '%s' finished: success=%s, summary='%s', errors=%s",
|
|
skill_name, result.success, result.summary, result.errors,
|
|
)
|
|
except LLMNotConfiguredError as exc:
|
|
logger.warning("Agent skill '%s' not configured: %s", skill_name, exc)
|
|
await progress_reporter.on_progress(
|
|
{
|
|
"type": "agent_progress",
|
|
"skill": skill_name,
|
|
"status": "error",
|
|
"error": str(exc),
|
|
}
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Agent skill '%s' failed: %s", skill_name, exc, exc_info=True)
|
|
await progress_reporter.on_progress(
|
|
{
|
|
"type": "agent_progress",
|
|
"skill": skill_name,
|
|
"status": "error",
|
|
"error": str(exc),
|
|
}
|
|
)
|
|
|
|
# Fire and forget — progress comes via WebSocket
|
|
task = asyncio.create_task(_run())
|
|
logger.info("Agent skill '%s' background task created (id=%s)", skill_name, task)
|
|
|
|
return web.json_response(
|
|
{
|
|
"status": "started",
|
|
"skill": skill_name,
|
|
"model_count": len(model_paths),
|
|
}
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/lm/agent/cancel
|
|
# ------------------------------------------------------------------
|
|
|
|
async def cancel_agent_skill(self, request: web.Request) -> web.Response:
|
|
"""Cancel a running agent skill.
|
|
|
|
NOTE: Cancellation is a stub for now — the AgentService processes
|
|
models sequentially and does not yet support mid-execution
|
|
cancellation. This endpoint exists for API completeness.
|
|
"""
|
|
|
|
# TODO: implement cooperative cancellation in AgentService
|
|
return web.json_response(
|
|
{"status": "acknowledged", "note": "Cancellation not yet implemented"},
|
|
status_code=200,
|
|
)
|