feat: agy_create_verified — generazione immagine iterativa con verifica

Loop self-contained per la generazione di immagini conformi a requisiti:
1. Genera immagine con agy (mode image)
2. Analizza con visione testuale (giudice PASS/FAIL vs requisiti)
3. Verifica oggettiva con script Python/OpenCV (dimensioni, aspect ratio,
   luminosità, densità bordi, colori dominanti)
4. Rigenera con prompt di correzione (ri-attaccando l'immagine base) se FAIL

Ripete fino al soddisfacimento o al limite maxIterations (default 3, max 5).
Restituisce immagine finale + report di verifica (verdetto + metriche per
iterazione). Ideale quando l'orchestratore pi non ha visione.
Aggiunto helper runOpenCV. README aggiornato.
This commit is contained in:
2026-08-11 00:24:13 +02:00
parent db36e78555
commit b2f3cbafb6
2 changed files with 170 additions and 0 deletions
+16
View File
@@ -21,6 +21,7 @@ conversazioni di ragionamento multi-turno.
| Style transfer | `agy_style_transfer` | `agy_style_transfer(baseImage=..., style="Van Gogh")` | | Style transfer | `agy_style_transfer` | `agy_style_transfer(baseImage=..., style="Van Gogh")` |
| Composizione multi-immagine | `agy_compose` | `agy_compose(images=[...], instruction="...")` | | Composizione multi-immagine | `agy_compose` | `agy_compose(images=[...], instruction="...")` |
| Consistenza personaggio | `agy_character` | `agy_character(referenceImage=..., name="Maya", task="...")` | | 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="...")` | | Analisi file (imm/audio/video) | `agy_analyze` | `agy_analyze(filePath=..., question="...")` |
| Trascrizione audio | `agy_transcribe` | `agy_transcribe(filePath="voce.wav", language="italiano")` | | Trascrizione audio | `agy_transcribe` | `agy_transcribe(filePath="voce.wav", language="italiano")` |
| Analisi video | `agy_video` | `agy_video(filePath="clip.mp4", question="...")` | | 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. 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`) ## Configurazione persistente (`/agy:config`)
Tutte le impostazioni dell'estensione si gestiscono con il comando `/agy:config` Tutte le impostazioni dell'estensione si gestiscono con il comando `/agy:config`
+154
View File
@@ -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<string> {
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 // 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) // TOOL: agy_transcribe — trascrizione audio (via Gemini API diretta)
// ========================================================================= // =========================================================================