feat(llm-client): add post-extraction validation with one-shot retry
After LLM returns scene IR, validate that critical fields (contentType, responsePath, workflowArchetype) are present. If missing, send one follow-up prompt to fill gaps. Merges repaired fields without overwriting valid data from the first extraction. 🤖 Generated with [Qoder][https://qoder.com]
This commit is contained in:
@@ -747,6 +747,52 @@ async function analyzeScene(sourceDir, dirContents, config) {
|
||||
};
|
||||
}
|
||||
|
||||
function validateExtractedSceneInfo(sceneIr) {
|
||||
const issues = [];
|
||||
|
||||
// Check: at least one apiEndpoint has contentType
|
||||
const endpointsWithCt = (sceneIr.apiEndpoints || []).filter(
|
||||
ep => ep && ep.contentType
|
||||
);
|
||||
if ((sceneIr.apiEndpoints || []).length > 0 && endpointsWithCt.length === 0) {
|
||||
issues.push("missing_contentType_on_endpoints");
|
||||
}
|
||||
|
||||
// Check: at least one mode has responsePath (if modes exist)
|
||||
if ((sceneIr.modes || []).length > 0) {
|
||||
const modesWithPath = sceneIr.modes.filter(m => m.responsePath !== undefined && m.responsePath !== null);
|
||||
if (modesWithPath.length === 0) {
|
||||
issues.push("missing_responsePath_on_modes");
|
||||
}
|
||||
}
|
||||
|
||||
// Check: workflowArchetype is set
|
||||
if (!sceneIr.workflowArchetype) {
|
||||
issues.push("missing_workflowArchetype");
|
||||
}
|
||||
|
||||
return issues;
|
||||
}
|
||||
|
||||
function mergeSceneIrFields(repaired, original) {
|
||||
const merged = {};
|
||||
// Only fill fields that were empty/missing in original
|
||||
const criticalFields = ['workflowArchetype', 'defaultMode', 'modeSwitchField'];
|
||||
for (const field of criticalFields) {
|
||||
if (!original[field] && repaired[field]) {
|
||||
merged[field] = repaired[field];
|
||||
}
|
||||
}
|
||||
// For arrays, merge if original is empty
|
||||
if ((!original.apiEndpoints || original.apiEndpoints.length === 0) && repaired.apiEndpoints) {
|
||||
merged.apiEndpoints = repaired.apiEndpoints;
|
||||
}
|
||||
if ((!original.modes || original.modes.length === 0) && repaired.modes) {
|
||||
merged.modes = repaired.modes;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
async function analyzeSceneDeep(sourceDir, dirContents, config) {
|
||||
const content = await requestChatCompletionWithRetry(
|
||||
[
|
||||
@@ -807,6 +853,30 @@ async function analyzeSceneDeep(sourceDir, dirContents, config) {
|
||||
}
|
||||
}
|
||||
|
||||
// POST-EXTRACTION VALIDATION: one-shot retry for missing fields
|
||||
const issues = validateExtractedSceneInfo(normalized);
|
||||
if (issues.length > 0) {
|
||||
const followUpPrompt = `The previous extraction has these issues:\n${issues.join('\n')}\nPlease re-analyze the source snippets and fill in the missing fields. Use defaults if truly unavailable.`;
|
||||
|
||||
try {
|
||||
const followUpContent = await requestChatCompletionWithRetry(
|
||||
[
|
||||
{ role: "system", content: DEEP_SYSTEM_PROMPT },
|
||||
{ role: "user", content: followUpPrompt },
|
||||
],
|
||||
{ ...config, maxTokens: 2400, timeoutMs: DEEP_REQUEST_TIMEOUT_MS, retryAttempts: 1 }
|
||||
);
|
||||
|
||||
const repaired = normalizeSceneIr(await extractJsonFromResponseWithRepair(followUpContent, config));
|
||||
// Merge repaired fields into normalized (only fill empty fields)
|
||||
const mergeResult = mergeSceneIrFields(repaired, normalized);
|
||||
Object.assign(normalized, mergeResult);
|
||||
} catch (error) {
|
||||
// Silently continue with original extraction if retry fails
|
||||
// The auto-wrap logic (above) will handle empty modes
|
||||
}
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user