Reject empty repaired LLM JSON

This commit is contained in:
syntaxbullet
2026-06-17 15:48:48 +02:00
parent 1957d0dffa
commit 5da401b8af

View File

@@ -146,7 +146,31 @@ const scoringResponseSchema = z.object({
id: z.string(), id: z.string(),
subcriteria: z.array(scoringSubcriterionSchema), subcriteria: z.array(scoringSubcriterionSchema),
}).strict()), }).strict()),
}).strict(); }).strict().superRefine((value, ctx) => {
const dimensionsById = new Map(value.dimensionen.map((dimension) => [dimension.id, dimension]));
for (const dimension of SCORING_MODEL) {
const parsedDimension = dimensionsById.get(dimension.id);
if (!parsedDimension) {
ctx.addIssue({
code: "custom",
path: ["dimensionen"],
message: `Missing scoring dimension "${dimension.id}".`,
});
continue;
}
const subcriteriaIds = new Set(parsedDimension.subcriteria.map((subcriterion) => subcriterion.id));
for (const subcriterion of dimension.subcriteria) {
if (!subcriteriaIds.has(subcriterion.id)) {
ctx.addIssue({
code: "custom",
path: ["dimensionen", dimension.id, "subcriteria"],
message: `Missing scoring subcriterion "${subcriterion.id}".`,
});
}
}
}
});
const swotResponseSchema = z.object({ const swotResponseSchema = z.object({
staerken: z.array(z.string()), staerken: z.array(z.string()),
@@ -392,6 +416,7 @@ async function createParsedStructuredCompletion<T>(
return await correctStructuredJson( return await correctStructuredJson(
client, client,
model, model,
messages,
content, content,
error, error,
schemaName, schemaName,
@@ -498,6 +523,32 @@ function validationMessage(error: unknown): string {
return errorMessage(error); return errorMessage(error);
} }
function segmentierungValidatorFor(fragen: FrageMitAntwort[]): z.ZodType<z.infer<typeof segmentierungResponseSchema>> {
const expectedIds = fragen.map((frage, index) => frage.id ?? `frage_${index + 1}`);
return segmentierungResponseSchema.superRefine((value, ctx) => {
const returnedIds = new Set(value.fragen.map((frage) => frage.id));
for (const id of expectedIds) {
if (!returnedIds.has(id)) {
ctx.addIssue({
code: "custom",
path: ["fragen"],
message: `Missing segmented answer for "${id}".`,
});
}
}
for (const frage of value.fragen) {
if (!expectedIds.includes(frage.id)) {
ctx.addIssue({
code: "custom",
path: ["fragen", frage.id],
message: `Unexpected segmented answer id "${frage.id}".`,
});
}
}
});
}
function parseAndValidateLlmJson<T>(content: string, context: string, validator: z.ZodType<T>): T { function parseAndValidateLlmJson<T>(content: string, context: string, validator: z.ZodType<T>): T {
const parsed = parseLlmJson<unknown>(content, context); const parsed = parseLlmJson<unknown>(content, context);
const result = validator.safeParse(parsed); const result = validator.safeParse(parsed);
@@ -510,6 +561,7 @@ function parseAndValidateLlmJson<T>(content: string, context: string, validator:
async function correctStructuredJson<T>( async function correctStructuredJson<T>(
client: OpenAI, client: OpenAI,
model: string, model: string,
originalMessages: Array<{ role: "system" | "user"; content: string }>,
invalidContent: string, invalidContent: string,
validationError: unknown, validationError: unknown,
schemaName: string, schemaName: string,
@@ -543,10 +595,13 @@ ${validationMessage(validationError)}
Erwartetes JSON Schema: Erwartetes JSON Schema:
${JSON.stringify(schema, null, 2)} ${JSON.stringify(schema, null, 2)}
Urspruengliche Aufgabe:
${JSON.stringify(originalMessages, null, 2)}
Fehlerhafte Ausgabe: Fehlerhafte Ausgabe:
${invalidContent} ${invalidContent}
Korrigiere nur Syntax, Typen, fehlende Pflichtfelder und enum-Werte. Antworte ausschliesslich mit dem korrigierten JSON.`, Korrigiere Syntax, Typen, fehlende Pflichtfelder, enum-Werte und fehlende Array-Eintraege. Nutze die urspruengliche Aufgabe, wenn die fehlerhafte Ausgabe abgeschnitten ist. Antworte ausschliesslich mit dem korrigierten JSON.`,
}, },
], ],
`${schemaName}_correction`, `${schemaName}_correction`,
@@ -1257,7 +1312,7 @@ Vorgaben:
}, },
required: ["fragen"], required: ["fragen"],
}, },
segmentierungResponseSchema, segmentierungValidatorFor(fragen),
`Could not parse answer segmentation JSON for ${fallbackLabel}`, `Could not parse answer segmentation JSON for ${fallbackLabel}`,
sessionId, sessionId,
{ operation: "segmentierung", label: fallbackLabel, onLlmCall }, { operation: "segmentierung", label: fallbackLabel, onLlmCall },