Fallback after malformed structured LLM retries

This commit is contained in:
syntaxbullet
2026-06-17 15:33:08 +02:00
parent a8b36ec2c7
commit cb518fb95d

View File

@@ -916,6 +916,28 @@ function buildScoringFallbackBegruendung(
return `${farbe} (${meaning}). Staerkste Bereiche: ${strongestDimensions || "keine"}.${missing}`;
}
function fallbackScoringAssessment(reason: string): Partial<LlmScoringAssessment> & { begruendung?: string } {
return {
begruendung: `Die automatische Detailbewertung konnte nicht vollstaendig ausgewertet werden: ${reason}`,
ausschlussgruende: [],
dimensionen: SCORING_MODEL.map((dimension) => ({
id: dimension.id,
subcriteria: dimension.subcriteria.map((subcriterion) => ({
id: subcriterion.id,
farbe: "unbewertbar",
evidence: "",
begruendung: "",
confidence: 0,
missingReason: `Modellantwort konnte nach ${MAX_LLM_RETRY_ATTEMPTS} Versuchen nicht als gueltiges JSON gelesen werden.`,
})),
})),
};
}
function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : String(error);
}
async function assessTrafficLight(
client: OpenAI,
model: string,
@@ -932,12 +954,7 @@ async function assessTrafficLight(
scoringModel: SCORING_MODEL,
};
const parsed = await createParsedStructuredCompletion<Partial<LlmScoringAssessment> & {
begruendung?: string;
}>(
client,
model,
[
const messages: Array<{ role: "system" | "user"; content: string }> = [
{
role: "system",
content:
@@ -971,7 +988,15 @@ Wichtige Vorgaben:
Bewerbungsdaten:
${JSON.stringify(data, null, 2)}`,
},
],
];
let parsed: Partial<LlmScoringAssessment> & { begruendung?: string };
try {
parsed = await createParsedStructuredCompletion<Partial<LlmScoringAssessment> & {
begruendung?: string;
}>(
client,
model,
messages,
"ampelbewertung",
{
type: "object",
@@ -1016,6 +1041,9 @@ ${JSON.stringify(data, null, 2)}`,
sessionId,
{ operation: "scoring", label: "Ampelbewertung", onLlmCall },
);
} catch (error) {
parsed = fallbackScoringAssessment(errorMessage(error));
}
const ausschlussgruende = Array.isArray(parsed.ausschlussgruende)
? parsed.ausschlussgruende.map((flag) => String(flag).trim()).filter(Boolean)
: [];
@@ -1049,10 +1077,7 @@ async function segmentMultiQuestionAnswer(
return fragen;
}
const parsed = await createParsedStructuredCompletion<{ fragen?: Array<Partial<FrageMitAntwort>> }>(
client,
model,
[
const messages: Array<{ role: "system" | "user"; content: string }> = [
{
role: "system",
content:
@@ -1081,7 +1106,13 @@ Vorgaben:
- Mehrfachnennungen sind erlaubt, wenn ein Abschnitt mehrere Unterfragen klar beantwortet.
- Lasse keine Unterfrage aus.`,
},
],
];
let parsed: { fragen?: Array<Partial<FrageMitAntwort>> };
try {
parsed = await createParsedStructuredCompletion<{ fragen?: Array<Partial<FrageMitAntwort>> }>(
client,
model,
messages,
"segmentierung_unterfragen",
{
type: "object",
@@ -1108,6 +1139,16 @@ Vorgaben:
sessionId,
{ operation: "segmentierung", label: fallbackLabel, onLlmCall },
);
} catch {
parsed = {
fragen: fragen.map((frage) => ({
id: frage.id,
text: frage.text,
antwort: "",
confidence: 0,
})),
};
}
const byId = new Map((parsed.fragen ?? []).map((frage) => [String(frage.id ?? ""), frage]));
return fragen.map((frage) => {
@@ -1139,10 +1180,7 @@ async function analyzeSWOT(
sessionId?: string,
onLlmCall?: SummarizeCompanyOptions["onLlmCall"],
): Promise<SWOTAnalyse> {
const parsed = await createParsedStructuredCompletion<Partial<SWOTAnalyse>>(
client,
model,
[
const messages: Array<{ role: "system" | "user"; content: string }> = [
{
role: "system",
content:
@@ -1165,7 +1203,13 @@ Vorgaben:
Bewerbungsdaten:
${JSON.stringify(data, null, 2)}`,
},
],
];
let parsed: Partial<SWOTAnalyse>;
try {
parsed = await createParsedStructuredCompletion<Partial<SWOTAnalyse>>(
client,
model,
messages,
"swot_analyse",
{
type: "object",
@@ -1182,6 +1226,14 @@ ${JSON.stringify(data, null, 2)}`,
sessionId,
{ operation: "swot", label: "SWOT-Analyse", onLlmCall },
);
} catch (error) {
parsed = {
staerken: [],
schwaechen: [],
chancen: [],
risiken: [`SWOT-Analyse konnte nicht automatisch erstellt werden: ${errorMessage(error)}`],
};
}
const toStringArray = (value: unknown): string[] =>
Array.isArray(value) ? value.map((item) => String(item).trim()).filter(Boolean) : [];