diff --git a/README.md b/README.md index c13268f..c86ce77 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ conversazioni di ragionamento multi-turno. | Style transfer | `agy_style_transfer` | `agy_style_transfer(baseImage=..., style="Van Gogh")` | | Composizione multi-immagine | `agy_compose` | `agy_compose(images=[...], instruction="...")` | | Consistenza personaggio | `agy_character` | `agy_character(referenceImage=..., name="Maya", task="...")` | +| **Generazione verificata iterativa** | **`agy_create_verified`** | `agy_create_verified(requirements=..., maxIterations=3, useOpenCV=true)` | | Analisi file (imm/audio/video) | `agy_analyze` | `agy_analyze(filePath=..., question="...")` | | Trascrizione audio | `agy_transcribe` | `agy_transcribe(filePath="voce.wav", language="italiano")` | | Analisi video | `agy_video` | `agy_video(filePath="clip.mp4", question="...")` | @@ -29,6 +30,21 @@ conversazioni di ragionamento multi-turno. L'agente (pi) decide autonomamente quale strumento usare in base al task richiesto. +### Generazione verificata (`agy_create_verified`) + +Quando l'orchestratore di pi **non ha visione** e serve un'immagine conforme a +specifiche precise, `agy_create_verified` esegue un loop self-contained: + +``` +genera immagine (agy) → analizza con visione (PASS/FAIL vs requisiti) + → verifica OpenCV (dimensioni, colori, luminosità, bordi) + → rigenera con prompt di correzione se FAIL → ripete fino a maxIterations +``` + +Restituisce l'immagine finale + report di verifica (verdetto visione e metriche +OpenCV per ogni iterazione). Parametri: `requirements`, `outputDir`, +`maxIterations` (default 3), `useOpenCV` (default true), `model`. + ## Configurazione persistente (`/agy:config`) Tutte le impostazioni dell'estensione si gestiscono con il comando `/agy:config` diff --git a/extensions/index.ts b/extensions/index.ts index 671f874..d074d0b 100644 --- a/extensions/index.ts +++ b/extensions/index.ts @@ -249,6 +249,43 @@ function copyImage(src: string | undefined, outputDir?: string): string | undefi } } +// Esegue uno script Python/OpenCV sull'immagine per metriche oggettive: +// dimensioni, aspect ratio, luminosità, densità bordi e colori dominanti. +// Restituisce JSON (stringa) o stringa vuota in caso di errore. +async function runOpenCV(imagePath: string): Promise { + const script = ` +import cv2, json, sys, collections +img = cv2.imread(sys.argv[1]) +if img is None: + print(json.dumps({"error": "cannot read image"})) + sys.exit(1) +h, w = img.shape[:2] +gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) +brightness = round(float(gray.mean()), 1) +small = cv2.resize(img, (32, 32), interpolation=cv2.INTER_AREA) +colors = [tuple(int(x) for x in p) for p in small.reshape(-1, 3)] +counts = collections.Counter(colors) +dominant = [{"rgb": list(c), "count": n} for c, n in counts.most_common(3)] +edges = cv2.Canny(gray, 100, 200) +edge_density = round(float(edges.mean() / 255.0), 3) +print(json.dumps({ + "width": w, "height": h, + "aspect_ratio": round(w / h, 3), + "brightness": brightness, + "edge_density": edge_density, + "dominant_colors": dominant +})) +`; + try { + const { stdout } = await execFileAsync("python3", ["-c", script, imagePath], { + timeout: 15_000, + }); + return stdout.trim(); + } catch { + return ""; + } +} + // --------------------------------------------------------------------------- // Helper: esecuzione comune di un tool agy // --------------------------------------------------------------------------- @@ -1617,6 +1654,123 @@ export default function agyExtension(pi: ExtensionAPI) { }, }); + // ========================================================================= + // TOOL: agy_create_verified — generazione iterativa con verifica + // Loop self-contained: genera immagine → analizza con visione → verifica + // OpenCV → rigenera se non rispetta i requisiti (fino a maxIterations). + // ========================================================================= + pi.registerTool({ + name: "agy_create_verified", + label: "agy create verified image", + description: + "Genera un'immagine che soddisfi i requisiti, la verifica iterativamente con visione " + + "testuale e metriche OpenCV, e la rigenera finché non rispetta i requisiti (o fino a un limite). " + + "Ideale quando l'orchestratore non ha visione e serve un'immagine conforme a specifiche precise.", + parameters: Type.Object({ + requirements: Type.String({ description: "Requisiti precisi che l'immagine deve soddisfare." }), + outputDir: Type.Optional(Type.String({ description: "Cartella dove salvare l'immagine finale." })), + maxIterations: Type.Optional(Type.Number({ description: "Limite massimo di iterazioni (default 3, max 5)." })), + useOpenCV: Type.Optional(Type.Boolean({ description: "Esegui metriche OpenCV oggettive (default true)." })), + model: Type.Optional(Type.String({ description: "Modello agy." })), + }), + async execute(toolCallId, params, signal, onUpdate, ctx) { + const p = params as any; + const requirements = String(p.requirements ?? "").trim(); + const maxIter = Math.min(Math.max(Number(p.maxIterations ?? 3) || 3, 1), 5); + const useCV = p.useOpenCV ?? true; + + toolUpdate(onUpdate, "agy_create_verified: avvio loop di creazione verificata..."); + + let currentImage: string | undefined; + let lastIssues = ""; + const report: { + iteration: number; + pass: boolean; + imagePath: string; + verdict: string; + opencv: string; + }[] = []; + let finalText = ""; + + for (let iter = 1; iter <= maxIter; iter++) { + toolUpdate( + onUpdate, + `agy_create_verified: iterazione ${iter}/${maxIter} (${iter === 1 ? "generazione" : "rigenerazione"})...`, + ); + + // 1) Genera (o rigenera) con prompt di correzione se non è la prima + const genPrompt = currentImage + ? `L'immagine precedente (${currentImage}) non rispetta i requisiti per questi motivi: ${lastIssues}. ` + + `Rigenera/rettifica l'immagine per soddisfare esattamente: ${requirements}.` + : `Genera un'immagine che soddisfi esattamente questi requisiti: ${requirements}.`; + const genRes = await executeAgy({ + prompt: genPrompt + IMG_SUFFIX, + mode: "image", + model: p.model, + stateless: true, + filePaths: currentImage ? [currentImage] : [], + outputDir: p.outputDir, + signal, + }); + const imgPath = genRes.imagePath; + if (!imgPath) { + finalText = `Generazione fallita all'iterazione ${iter}: nessuna immagine prodotta.\n${genRes.text}`; + break; + } + currentImage = imgPath; + + // 2) Analisi visione (giudice): PASS/FAIL + problemi rispetto ai requisiti + const judgePrompt = + `Analizza il file al percorso ${imgPath}. Requisiti richiesti: ${requirements}. ` + + `Verifica se l'immagine li rispetta. Rispondi iniziando con \"PASS:\" o \"FAIL:\", ` + + `poi elenca sinteticamente (max 3 punti) le deviazioni rispetto ai requisiti in caso di FAIL.`; + const judgeRes = await executeAgy({ + prompt: judgePrompt, + mode: "analyze", + model: p.model, + stateless: true, + filePaths: [imgPath], + yolo: true, + signal, + }); + const verdict = judgeRes.text.trim(); + const pass = /^\s*PASS:?/i.test(verdict); + lastIssues = verdict; + + // 3) Metriche OpenCV oggettive (opzionale) + let cvMetrics = ""; + if (useCV) { + cvMetrics = await runOpenCV(imgPath); + } + + report.push({ iteration: iter, pass, imagePath: imgPath, verdict, opencv: cvMetrics }); + + if (pass) { + finalText = + `✅ Immagine verificata dopo ${iter} iterazione/i.\nPercorso: ${imgPath}` + + (cvMetrics ? `\nMetriche OpenCV: ${cvMetrics}` : "") + + `\nVerdetto visione:\n${verdict}`; + break; + } + if (iter === maxIter) { + finalText = + `⚠️ Requisiti non soddisfatti dopo ${maxIter} iterazioni.\nPercorso: ${imgPath}` + + (cvMetrics ? `\nMetriche OpenCV: ${cvMetrics}` : "") + + `\nUltimo verdetto visione:\n${verdict}`; + } + } + + return { + content: [{ type: "text", text: finalText || "Nessun output." }], + details: { + iterations: report.length, + imagePath: currentImage, + report, + }, + }; + }, + }); + // ========================================================================= // TOOL: agy_transcribe — trascrizione audio (via Gemini API diretta) // =========================================================================