remove validateAndRepairJson function and tests
This commit is contained in:
parent
263ff8adcc
commit
0aed4e06a2
@ -2,9 +2,6 @@
|
||||
* Shared OpenRouter API helper for structured JSON responses.
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import { getSetting } from '../repositories/settings.js';
|
||||
|
||||
const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
|
||||
|
||||
export interface JsonSchemaDefinition {
|
||||
@ -170,118 +167,3 @@ export function parseJsonContent<T>(content: string, jobId?: string): T {
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate JSON against a Zod schema and repair with AI if invalid.
|
||||
*
|
||||
* @param data - The JSON object to validate
|
||||
* @param schema - Zod schema to validate against
|
||||
* @param context - Optional context for logging (e.g., job ID)
|
||||
* @returns The validated (and possibly repaired) data
|
||||
*/
|
||||
export async function validateAndRepairJson<T>(
|
||||
data: unknown,
|
||||
schema: z.ZodSchema<T>,
|
||||
context?: string
|
||||
): Promise<{ success: true; data: T; repaired: boolean } | { success: false; error: string }> {
|
||||
const label = context ?? 'unknown';
|
||||
|
||||
// First attempt: validate as-is
|
||||
const result = schema.safeParse(data);
|
||||
if (result.success) {
|
||||
return { success: true, data: result.data, repaired: false };
|
||||
}
|
||||
|
||||
// Validation failed - attempt AI repair
|
||||
console.warn(`⚠️ [${label}] Schema validation failed, attempting AI repair...`);
|
||||
|
||||
const errors = result.error.issues.map((issue: z.ZodIssue) => ({
|
||||
path: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
code: issue.code,
|
||||
}));
|
||||
|
||||
console.warn(` Validation errors:`, errors.slice(0, 5)); // Log first 5 errors
|
||||
|
||||
// Check if API key is available
|
||||
if (!process.env.OPENROUTER_API_KEY) {
|
||||
return {
|
||||
success: false,
|
||||
error: `Schema validation failed and no API key for repair: ${errors.map((e: { path: string; message: string }) => `${e.path}: ${e.message}`).join('; ')}`
|
||||
};
|
||||
}
|
||||
|
||||
const [overrideModel] = await Promise.all([getSetting('model')]);
|
||||
const model = overrideModel || process.env.MODEL || 'openai/gpt-4o-mini';
|
||||
|
||||
const repairPrompt = buildRepairPrompt(data, errors);
|
||||
|
||||
try {
|
||||
const response = await fetch(OPENROUTER_API_URL, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
||||
'Content-Type': 'application/json',
|
||||
'HTTP-Referer': 'JobOps',
|
||||
'X-Title': 'JobOpsSchemaRepair',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
messages: [{ role: 'user', content: repairPrompt }],
|
||||
stream: false,
|
||||
plugins: [{ id: 'response-healing' }],
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorBody = await response.text().catch(() => 'No error body');
|
||||
return { success: false, error: `AI repair request failed: ${response.status} - ${errorBody}` };
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
const content = responseData.choices?.[0]?.message?.content;
|
||||
|
||||
if (!content) {
|
||||
return { success: false, error: 'AI repair returned no content' };
|
||||
}
|
||||
|
||||
// Parse the repaired JSON
|
||||
const repaired = parseJsonContent<unknown>(content, label);
|
||||
|
||||
// Validate the repaired version
|
||||
const repairedResult = schema.safeParse(repaired);
|
||||
if (repairedResult.success) {
|
||||
console.log(`✅ [${label}] AI successfully repaired the JSON`);
|
||||
return { success: true, data: repairedResult.data, repaired: true };
|
||||
}
|
||||
|
||||
// Still invalid after repair
|
||||
const newErrors = repairedResult.error.issues.slice(0, 3).map((i: z.ZodIssue) => `${i.path.join('.')}: ${i.message}`).join('; ');
|
||||
return { success: false, error: `AI repair did not fix all issues: ${newErrors}` };
|
||||
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
return { success: false, error: `AI repair failed: ${message}` };
|
||||
}
|
||||
}
|
||||
|
||||
function buildRepairPrompt(data: unknown, errors: Array<{ path: string; message: string; code: string }>): string {
|
||||
const errorList = errors.slice(0, 10).map(e => `- Path "${e.path}": ${e.message}`).join('\n');
|
||||
|
||||
return `You are fixing a JSON object that failed schema validation.
|
||||
|
||||
VALIDATION ERRORS:
|
||||
${errorList}
|
||||
|
||||
ORIGINAL JSON (may be truncated):
|
||||
${JSON.stringify(data, null, 2).slice(0, 15000)}
|
||||
|
||||
INSTRUCTIONS:
|
||||
1. Fix ONLY the validation errors listed above
|
||||
2. Do NOT remove or modify data that isn't causing errors
|
||||
3. For missing required fields, add them with sensible defaults (empty strings, empty arrays, etc.)
|
||||
4. For type mismatches, convert to the correct type
|
||||
5. Preserve all existing valid data
|
||||
|
||||
Return ONLY the fixed JSON object, no explanation.`;
|
||||
}
|
||||
|
||||
@ -61,14 +61,6 @@ vi.mock('./resumeProjects.js', () => ({
|
||||
})
|
||||
}));
|
||||
|
||||
vi.mock('./openrouter.js', () => ({
|
||||
validateAndRepairJson: vi.fn().mockImplementation(async (data: unknown) => ({
|
||||
success: true,
|
||||
data,
|
||||
repaired: false
|
||||
}))
|
||||
}));
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
spawn: vi.fn().mockImplementation(() => ({
|
||||
stdout: { on: vi.fn() },
|
||||
|
||||
@ -73,13 +73,6 @@ vi.mock('./resumeProjects.js', () => ({
|
||||
})
|
||||
}));
|
||||
|
||||
// Mock validateAndRepairJson to always return success (bypass validation in tests)
|
||||
vi.mock('./openrouter.js', () => ({
|
||||
validateAndRepairJson: vi.fn().mockImplementation((data: unknown) =>
|
||||
Promise.resolve({ success: true, data, repaired: false })
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock('child_process', () => ({
|
||||
spawn: vi.fn().mockImplementation(() => ({
|
||||
stdout: { on: vi.fn() },
|
||||
|
||||
@ -15,8 +15,6 @@ import { pickProjectIdsForJob } from './projectSelection.js';
|
||||
import { extractProjectsFromProfile, resolveResumeProjectsSettings } from './resumeProjects.js';
|
||||
import { getDataDir } from '../config/dataDir.js';
|
||||
import { getProfile } from './profile.js';
|
||||
import { validateAndRepairJson } from './openrouter.js';
|
||||
import { resumeDataSchema } from '../../shared/rxresume-schema.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
@ -168,22 +166,9 @@ export async function generatePdf(
|
||||
console.warn(` ⚠️ Project visibility step failed for job ${jobId}:`, err);
|
||||
}
|
||||
|
||||
// Validate and repair the resume JSON before PDF generation
|
||||
const validationResult = await validateAndRepairJson(baseResume, resumeDataSchema, `pdf-${jobId}`);
|
||||
if (!validationResult.success) {
|
||||
console.error(`❌ [Job ${jobId}] Resume validation failed: ${validationResult.error}`);
|
||||
return { success: false, error: `Resume validation failed: ${validationResult.error}` };
|
||||
}
|
||||
|
||||
if (validationResult.repaired) {
|
||||
console.log(`🔧 [Job ${jobId}] Resume JSON was repaired by AI`);
|
||||
}
|
||||
|
||||
const validatedResume = validationResult.data;
|
||||
|
||||
// Write modified resume to temp file
|
||||
const tempResumePath = join(RESUME_GEN_DIR, `temp_resume_${jobId}.json`);
|
||||
await writeFile(tempResumePath, JSON.stringify(validatedResume, null, 2));
|
||||
await writeFile(tempResumePath, JSON.stringify(baseResume, null, 2));
|
||||
|
||||
// Generate PDF using Python script - output directly to our data folder
|
||||
const outputFilename = `resume_${jobId}.pdf`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user