增加几何校验
This commit is contained in:
+36
-10
@@ -3,7 +3,8 @@ import {execFileSync} from 'node:child_process';
|
||||
import path from 'node:path';
|
||||
import {projectRoot, publicStage, samInfo} from '../lib/config.mjs';
|
||||
import {readImage, sha256} from '../lib/images.mjs';
|
||||
import {partsSchema, parseParts, validateParts} from '../lib/validate-parts.mjs';
|
||||
import {partsSchema, parseParts, validateParts, upgradeParts} from '../lib/validate-parts.mjs';
|
||||
import {geometryPython, prepareReference, groundParts} from '../lib/geometry.mjs';
|
||||
import {annotatedSvg, understandingReport} from '../lib/report.mjs';
|
||||
import {prepareRun, jsonFile, finishRun} from '../lib/run-artifacts.mjs';
|
||||
import {analyze, extractVisionText} from '../providers/vision.mjs';
|
||||
@@ -16,7 +17,8 @@ function codeVersion() {
|
||||
'stages/model-stage.mjs', 'lib/config.mjs', 'lib/images.mjs', 'lib/report.mjs',
|
||||
'lib/run-artifacts.mjs', 'lib/validate-parts.mjs', 'providers/http.mjs',
|
||||
'providers/vision.mjs', 'providers/image-edits.mjs', 'tools/model-check.mjs',
|
||||
'schemas/semantic-parts.schema.json',
|
||||
'schemas/semantic-parts.schema.json', 'schemas/semantic-parts-v1.schema.json',
|
||||
'lib/geometry.mjs', 'lib/python-runtime.mjs', 'geometry_grounding.py', 'normalize_reference.py',
|
||||
];
|
||||
const sourceHash = sha256(files.map((file) => `${file}\n${readFileSync(path.join(projectRoot, file), 'utf8')}`).join('\n'));
|
||||
let commit = null;
|
||||
@@ -33,37 +35,61 @@ function runIdentity(stage, images, extra) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function runUnderstanding({loaded, stage, reference, outDir, resume = false}, transport) {
|
||||
const image = readImage(reference, {pngOnly: true});
|
||||
const identity = runIdentity(stage, [image], {schema_sha256: sha256(JSON.stringify(partsSchema))});
|
||||
export async function runUnderstanding({loaded, stage, reference, outDir, resume = false, python, partsFile}, transport) {
|
||||
const original = readImage(reference, {pngOnly: true});
|
||||
const importedText = partsFile ? readFileSync(partsFile, 'utf8') : null;
|
||||
const identity = runIdentity(stage, [original], {schema_sha256: sha256(JSON.stringify(partsSchema)),
|
||||
input_mode: partsFile ? 'offline_plan' : 'vision_request', imported_plan_sha256: importedText === null ? null : sha256(importedText)});
|
||||
const run = prepareRun(outDir, sha256(JSON.stringify(identity)), resume);
|
||||
if (run.reused) return {out: run.out, reused: true};
|
||||
const manifest = {...identity, fingerprint: sha256(JSON.stringify(identity)), status: 'running', quality_status: 'pending', started_at: new Date().toISOString(), sam: samInfo(loaded)};
|
||||
jsonFile(run.manifestPath, manifest, stage.apiKey);
|
||||
writeFileSync(path.join(run.out, 'source.png'), image.bytes);
|
||||
writeFileSync(path.join(run.out, 'source-original.png'), original.bytes);
|
||||
writeFileSync(path.join(run.out, 'prompt.txt'), stage.prompt);
|
||||
jsonFile(path.join(run.out, 'request.json'), {...identity, reference: 'source.png'}, stage.apiKey);
|
||||
try {
|
||||
const {payload, metadata} = await analyze(stage, image, partsSchema, transport);
|
||||
const runtime = geometryPython(python);
|
||||
const preprocessing = prepareReference(runtime, path.join(run.out, 'source-original.png'), path.join(run.out, 'source.png'));
|
||||
const image = readImage(path.join(run.out, 'source.png'), {pngOnly: true});
|
||||
jsonFile(path.join(run.out, 'preprocessing.json'), preprocessing, stage.apiKey);
|
||||
manifest.prepared_input = {sha256: image.sha256, width: image.width, height: image.height};
|
||||
jsonFile(path.join(run.out, 'request.json'), {...identity, reference: 'source.png', prepared_input: manifest.prepared_input}, stage.apiKey);
|
||||
const {payload, metadata} = importedText !== null
|
||||
? {payload: {imported_plan_text: importedText}, metadata: {source: 'offline_plan', network_requested: false}}
|
||||
: await analyze(stage, image, partsSchema, transport);
|
||||
jsonFile(path.join(run.out, 'response.raw.json'), payload, stage.apiKey);
|
||||
jsonFile(path.join(run.out, 'request-metadata.json'), metadata, stage.apiKey);
|
||||
let data = null;
|
||||
let validation;
|
||||
try {
|
||||
const text = extractVisionText(payload, stage.protocol);
|
||||
const text = importedText ?? extractVisionText(payload, stage.protocol);
|
||||
writeFileSync(path.join(run.out, 'response.txt'), redact(text, stage.apiKey));
|
||||
data = parseParts(text);
|
||||
validation = validateParts(data);
|
||||
if (validation.valid) {
|
||||
const warnings = validation.warnings;
|
||||
data = upgradeParts(data);
|
||||
validation = validateParts(data);
|
||||
validation.warnings = [...new Set([...warnings, ...validation.warnings])];
|
||||
}
|
||||
} catch (error) { validation = {valid: false, errors: [redact(error.message, stage.apiKey)], warnings: []}; }
|
||||
jsonFile(path.join(run.out, 'validation.json'), validation, stage.apiKey);
|
||||
writeFileSync(path.join(run.out, 'report.html'), redact(understandingReport(image, data, validation, metadata), stage.apiKey));
|
||||
if (!validation.valid) throw new Error('结构理解结果未通过本地校验,请查看 validation.json、response.raw.json 和 report.html');
|
||||
jsonFile(path.join(run.out, 'semantic-parts.json'), data, stage.apiKey);
|
||||
writeFileSync(path.join(run.out, 'annotated.svg'), redact(annotatedSvg(image, data.parts), stage.apiKey));
|
||||
const geometry = groundParts(runtime, run.out);
|
||||
// Geometry failures block individual SAM prompts, while retaining the plan
|
||||
// and diagnostics for correction. They never fabricate fallback points.
|
||||
validation.geometry = geometry.summary;
|
||||
for (const part of geometry.parts) for (const warning of part.warnings) validation.warnings.push(`${part.id}: ${warning}`);
|
||||
jsonFile(path.join(run.out, 'validation.json'), validation, stage.apiKey);
|
||||
writeFileSync(path.join(run.out, 'report.html'), redact(understandingReport(image, data, validation, metadata, geometry), stage.apiKey));
|
||||
writeFileSync(path.join(run.out, 'annotated.svg'), redact(annotatedSvg(image, data.parts, geometry), stage.apiKey));
|
||||
manifest.request = metadata;
|
||||
manifest.quality_status = 'needs_review';
|
||||
manifest.geometry = geometry.summary;
|
||||
manifest.completed_at = new Date().toISOString();
|
||||
finishRun(run, manifest, ['source.png', 'prompt.txt', 'request.json', 'response.raw.json', 'response.txt', 'request-metadata.json', 'validation.json', 'report.html', 'semantic-parts.json', 'annotated.svg'], stage.apiKey);
|
||||
finishRun(run, manifest, ['source-original.png', 'source.png', 'preprocessing.json', 'geometry-validation.json', 'segmentation-prompts.json', 'prompt.txt', 'request.json', 'response.raw.json', 'response.txt', 'request-metadata.json', 'validation.json', 'report.html', 'semantic-parts.json', 'annotated.svg'], stage.apiKey);
|
||||
return {out: run.out, reused: false, parts: data.parts.length};
|
||||
} catch (error) {
|
||||
if (error.diagnostics) jsonFile(path.join(run.out, 'response.error.json'), error.diagnostics, stage.apiKey);
|
||||
|
||||
Reference in New Issue
Block a user