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