feat: verifica OpenCV DINAMICA — script generato dall'LLM per ogni requisito
La verifica OpenCV in agy_create_verified non è più hardcodata: l'LLM genera a
runtime uno script Python/OpenCV specifico per i requisiti dell'immagine.
- geminiText: chiamata Gemini generica (solo testo, no tool)
- generateOpenCVScript: genera lo script OpenCV ad hoc dai requisiti
- runOpenCVScript: esegue lo script e restituisce il JSON su stdout
- Contratto JSON: {pass, score, findings, errors}
- Il tool genera lo script UNA volta (i requisiti sono fissi) e lo riusa nel loop
- La decisione di conformità combina visione PASS + OpenCV PASS; se OpenCV rileva
non conformità, le aggiunge alle issue per guidare la rigenerazione
- Testato: script generato per 'quadrato rosso' ha rilevato correttamente
forma/4 vertici/simmetria/riempimento/sfondo bianco (pass=true, score=1.0)
This commit is contained in:
+101
-4
@@ -467,6 +467,77 @@ async function webSearchGemini(query: string, signal?: AbortSignal): Promise<str
|
||||
}
|
||||
}
|
||||
|
||||
// Chiamata generica alla Gemini API (solo testo, nessun tool). Restituisce la
|
||||
// risposta completa oppure stringa vuota in caso di errore.
|
||||
async function geminiText(prompt: string, signal?: AbortSignal): Promise<string> {
|
||||
let key = getConfig("geminiApiKey") ?? process.env.GEMINI_API_KEY ?? "";
|
||||
if (!key) {
|
||||
try {
|
||||
key = fs.readFileSync(path.join(AGY_CHAT_DIR, "gemini-key"), "utf8").trim();
|
||||
} catch {
|
||||
/* ignora */
|
||||
}
|
||||
}
|
||||
if (!key) return "";
|
||||
const model = process.env.AGY_GEMINI_MODEL ?? "gemini-3.5-flash";
|
||||
try {
|
||||
const res = await fetch(
|
||||
`https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${encodeURIComponent(key)}`,
|
||||
{
|
||||
method: "POST",
|
||||
signal,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
contents: [{ role: "user", parts: [{ text: prompt }] }],
|
||||
}),
|
||||
},
|
||||
);
|
||||
if (!res.ok) return "";
|
||||
const data: any = await res.json();
|
||||
const text = data?.candidates?.[0]?.content?.parts?.map((p: any) => p.text ?? "").join(" ") ?? "";
|
||||
return text.trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Genera DINAMICAMENTE uno script Python/OpenCV specifico per i requisiti,
|
||||
// usando l'LLM. Lo script legge l'immagine da argv[1] e stampa su stdout un JSON
|
||||
// standard: {"pass": bool, "score": 0..1, "findings": [...], "errors": [...]}.
|
||||
async function generateOpenCVScript(requirements: string): Promise<string> {
|
||||
const prompt =
|
||||
`Sei un esperto di computer vision con OpenCV. Scrivi UN SINGOLO script Python (solo codice, senza markdown) che verifichi un'immagine rispetto a questi requisiti:\n\n` +
|
||||
`REQUISITI: ${requirements}\n\n` +
|
||||
`Lo script:\n` +
|
||||
`- legge il percorso immagine da sys.argv[1]\n` +
|
||||
`- importa solo cv2, numpy, json, sys (e eventuali standard library)\n` +
|
||||
`- esegue verifiche OGGETTIVE pertinenti ai requisiti (es. intervalli di colore HSV, forme/contorni, simmetria, testo, dimensioni, orientamento, ecc.)\n` +
|
||||
`- stampa su stdout UN SOLO oggetto JSON: {"pass": true/false, "score": 0..1, "findings": [stringhe], "errors": [stringhe]}\n` +
|
||||
` - pass: true se l'immagine rispetta i requisiti\n` +
|
||||
` - score: grado di conformità da 0 a 1\n` +
|
||||
` - findings: brevi note oggettive misurate (es. "colore dominante rosso RGB(200,30,30)")\n` +
|
||||
` - errors: eventuali problemi di lettura/verifica\n` +
|
||||
`- NON deve fallire se l'immagine è leggibile: gestisci con try/except e metti l'errore in errors\n` +
|
||||
`- usa solo OpenCV, numpy e librerie standard (nessuna libreria esterna)\n\n` +
|
||||
`Restituisci SOLO il codice Python, nessun testo aggiuntivo, nessun blocco markdown.`;
|
||||
const code = await geminiText(prompt);
|
||||
// rimuovi eventuali fence markdown
|
||||
return code.replace(/```python|```/g, "").trim();
|
||||
}
|
||||
|
||||
// Esegue lo script OpenCV generato sull'immagine e restituisce il JSON su stdout.
|
||||
async function runOpenCVScript(script: string, imagePath: string): Promise<string> {
|
||||
if (!script) return "";
|
||||
try {
|
||||
const { stdout } = await execFileAsync("python3", ["-c", script, imagePath], {
|
||||
timeout: 20_000,
|
||||
});
|
||||
return stdout.trim();
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
// Cache anti-ridondanza: evita ricerche web ripetute sullo stesso topic in una
|
||||
// finestra breve (il multi-step reasoning lancia prompt simili in sequenza).
|
||||
let lastSearch = { topic: "", time: 0 };
|
||||
@@ -1692,6 +1763,14 @@ export default function agyExtension(pi: ExtensionAPI) {
|
||||
}[] = [];
|
||||
let finalText = "";
|
||||
|
||||
// Genera DINAMICAMENTE lo script OpenCV specifico per i requisiti (una volta,
|
||||
// i requisiti sono fissi) e lo riusa in ogni iterazione del loop.
|
||||
let cvScript = "";
|
||||
if (useCV) {
|
||||
toolUpdate(onUpdate, "agy_create_verified: generazione script OpenCV dinamico...");
|
||||
cvScript = await generateOpenCVScript(requirements);
|
||||
}
|
||||
|
||||
for (let iter = 1; iter <= maxIter; iter++) {
|
||||
toolUpdate(
|
||||
onUpdate,
|
||||
@@ -1734,15 +1813,33 @@ export default function agyExtension(pi: ExtensionAPI) {
|
||||
signal,
|
||||
});
|
||||
const verdict = judgeRes.text.trim();
|
||||
const pass = /^\s*PASS:?/i.test(verdict);
|
||||
const visionPass = /^\s*PASS:?/i.test(verdict);
|
||||
lastIssues = verdict;
|
||||
|
||||
// 3) Metriche OpenCV oggettive (opzionale)
|
||||
// 3) Verifica OpenCV DINAMICA (script generato dall'LLM per i requisiti)
|
||||
let cvMetrics = "";
|
||||
if (useCV) {
|
||||
cvMetrics = await runOpenCV(imgPath);
|
||||
let ocvPass: boolean | null = null;
|
||||
if (useCV && cvScript) {
|
||||
const out = await runOpenCVScript(cvScript, imgPath);
|
||||
cvMetrics = out;
|
||||
try {
|
||||
const parsed = JSON.parse(out);
|
||||
ocvPass = typeof parsed.pass === "boolean" ? parsed.pass : null;
|
||||
if (typeof parsed.score === "number") cvMetrics += `\nscore: ${parsed.score}`;
|
||||
if (Array.isArray(parsed.findings) && parsed.findings.length)
|
||||
cvMetrics += `\nfindings: ${parsed.findings.join("; ")}`;
|
||||
} catch {
|
||||
ocvPass = null;
|
||||
}
|
||||
// Se OpenCV rileva non conformità, le aggiunge alle issue per guidare la rigenerazione
|
||||
if (ocvPass === false) {
|
||||
lastIssues = `${verdict}\nVerifica OpenCV: ${cvMetrics}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Conforme solo se visione PASS e (se disponibile) anche OpenCV PASS
|
||||
const pass = visionPass && (ocvPass === null || ocvPass === true);
|
||||
|
||||
report.push({ iteration: iter, pass, imagePath: imgPath, verdict, opencv: cvMetrics });
|
||||
|
||||
if (pass) {
|
||||
|
||||
Reference in New Issue
Block a user