test: harden AI analysis path

This commit is contained in:
2026-07-06 01:03:11 -07:00
parent ae561a7d45
commit 0d5093ac6c
15 changed files with 1736 additions and 23 deletions

View File

@@ -85,14 +85,39 @@ function extractJsonFromText(value: string): unknown {
return safeJsonParse(trimmed.slice(start, end + 1));
}
function normalizeCitations(output: ElderAiAnalysisOutput, citations: AiCitation[]): ElderAiAnalysisOutput {
function collectReferencedCitationIds(output: ElderAiAnalysisOutput): Set<string> {
const referencedIds = new Set<string>();
for (const finding of output.keyFindings) {
for (const citationId of finding.citationIds) {
referencedIds.add(citationId);
}
}
for (const recommendation of output.recommendations) {
for (const citationId of recommendation.citationIds) {
referencedIds.add(citationId);
}
}
return referencedIds;
}
function normalizeCitations(output: ElderAiAnalysisOutput, citations: AiCitation[]): ElderAiAnalysisOutput | null {
const allowed = new Map(citations.map((citation) => [citation.id, citation]));
const merged = output.citations.filter((citation) => allowed.has(citation.id));
const seenIds = new Set(merged.map((citation) => citation.id));
const missing = citations.filter((citation) => !seenIds.has(citation.id));
for (const citation of output.citations) {
if (!allowed.has(citation.id)) {
return null;
}
}
const referencedIds = collectReferencedCitationIds(output);
for (const citationId of referencedIds) {
if (!allowed.has(citationId)) {
return null;
}
}
return {
...output,
citations: [...merged, ...missing],
citations: citations.filter((citation) => referencedIds.has(citation.id)),
};
}
@@ -283,15 +308,23 @@ export async function generateElderAiAnalysis(
return { success: false, reason: getBriefErrorReason("schema_validation_failed"), status: 502 };
}
const normalizedOutput = normalizeCitations(
knowledgeUnavailableReason
? {
...output,
dataGaps: [...output.dataGaps, "知识库检索不可用,分析未使用内部知识库"],
}
: output,
citations,
);
const outputWithDataGaps = knowledgeUnavailableReason
? {
...output,
dataGaps: [...output.dataGaps, "知识库检索不可用,分析未使用内部知识库"],
}
: output;
const normalizedOutput = normalizeCitations(outputWithDataGaps, citations);
if (!normalizedOutput) {
await saveFailedAnalysis({
context,
organizationId: residentContext.context.organizationId,
elderId,
dataScopes,
category: "schema_validation_failed",
});
return { success: false, reason: getBriefErrorReason("schema_validation_failed"), status: 502 };
}
const database = getDatabase();
const rows = await database
.insert(elderAiAnalyses)