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:
@@ -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`
|
||||
|
||||
@@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -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)
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user