From ae561a7d45925ff30dfc5b6ba734b6731d599242 Mon Sep 17 00:00:00 2001 From: TalexDreamSoul Date: Sun, 5 Jul 2026 03:40:17 -0700 Subject: [PATCH] fix: harden AI analysis provider responses --- modules/ai/server/analysis.ts | 6 +++++- modules/ai/server/knowledge.ts | 29 +++++++++++++++++------------ modules/ai/types.ts | 6 ++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/modules/ai/server/analysis.ts b/modules/ai/server/analysis.ts index 385ee62..66826e8 100644 --- a/modules/ai/server/analysis.ts +++ b/modules/ai/server/analysis.ts @@ -30,6 +30,7 @@ function createChatModel(config: { baseUrl: string; apiKey: string; chatModel: s return new ChatOpenAI({ apiKey: config.apiKey, model: config.chatModel, + maxTokens: 1800, temperature: 0.2, configuration, }); @@ -241,6 +242,7 @@ export async function generateElderAiAnalysis( "输出 JSON 字段:overallRiskLevel(low|medium|high|critical|unknown), summary, keyFindings, recommendations, dataGaps, citations, confidence, modelSummary。", "keyFindings[] 字段:category, severity(info|warning|critical), evidence, citationIds。", "recommendations[] 字段:title, priority(low|normal|high|urgent), rationale, suggestedNextStep, citationIds。", + "citations[] 只返回你实际引用过的 citation 对象,citationIds 必须来自可用 citations。", knowledgeUnavailableReason ? `知识库检索不可用:${knowledgeUnavailableReason}。请在 dataGaps 中包含“知识库检索不可用,分析未使用内部知识库”。` : "", `modelSummary 固定为 {"provider":"openai-compatible","chatModel":"${runtimeConfig.config.chatModel}","embeddingModel":"${runtimeConfig.config.embeddingModel}"}。`, `可用 citations:${JSON.stringify(citations)}`, @@ -250,7 +252,9 @@ export async function generateElderAiAnalysis( let rawOutput: unknown; try { - const response = await model.invoke(prompt); + const response = await model.invoke(prompt, { + response_format: { type: "json_object" }, + }); const content = Array.isArray(response.content) ? response.content.map((item) => (typeof item === "string" ? item : "")).join("\n") : String(response.content); diff --git a/modules/ai/server/knowledge.ts b/modules/ai/server/knowledge.ts index 64b48b0..07353a8 100644 --- a/modules/ai/server/knowledge.ts +++ b/modules/ai/server/knowledge.ts @@ -125,8 +125,12 @@ async function buildKnowledgeChunks(input: KnowledgeEntryInput): Promise { @@ -214,15 +218,15 @@ async function updateEntryWithChunks( const rows = await transaction .update(aiKnowledgeEntries) .set({ - scope: input.scope, - organizationId, - title: input.title, - category: input.category, - tags: input.tags, - body: input.body, - status: input.status, - updatedByAccountId: context.account.id, - updatedAt: new Date(), + scope: input.scope, + organizationId, + title: input.title, + category: input.category, + tags: input.tags, + body: input.body, + status: input.status, + updatedByAccountId: context.account.id, + updatedAt: new Date(), }) .where(eq(aiKnowledgeEntries.id, id)) .returning(); @@ -322,7 +326,8 @@ export async function retrieveKnowledge( return { success: false, reason: embeddingsResult.reason, status: 500 }; } - const [embedding] = await embeddingsResult.embeddings.embedDocuments([query]); + const vectors = await embeddingsResult.embeddings.embedDocuments([query]).catch(() => []); + const embedding = vectors[0]; if (!embedding) { return { success: false, reason: "知识库向量生成失败", status: 500 }; } diff --git a/modules/ai/types.ts b/modules/ai/types.ts index 41578bc..eac3012 100644 --- a/modules/ai/types.ts +++ b/modules/ai/types.ts @@ -256,6 +256,7 @@ export function validateElderAiAnalysisOutput(value: unknown): ElderAiAnalysisOu if (!isRecord(value)) { return null; } + const confidenceValue = typeof value.confidence === "string" ? Number(value.confidence) : value.confidence; const keyFindings = Array.isArray(value.keyFindings) ? value.keyFindings.map(validateFinding) : null; const recommendations = Array.isArray(value.recommendations) ? value.recommendations.map(validateRecommendation) : null; const citations = Array.isArray(value.citations) ? value.citations.map(validateCitation) : null; @@ -265,7 +266,8 @@ export function validateElderAiAnalysisOutput(value: unknown): ElderAiAnalysisOu if ( !ELDER_AI_RISK_LEVELS.includes(value.overallRiskLevel as ElderAiRiskLevel) || typeof value.summary !== "string" || - typeof value.confidence !== "number" || + typeof confidenceValue !== "number" || + !Number.isFinite(confidenceValue) || !keyFindings || keyFindings.includes(null) || !recommendations || @@ -285,7 +287,7 @@ export function validateElderAiAnalysisOutput(value: unknown): ElderAiAnalysisOu recommendations: recommendations.filter((item): item is ElderAiRecommendation => Boolean(item)), dataGaps, citations: citations.filter((item): item is AiCitation => Boolean(item)), - confidence: Math.max(0, Math.min(1, value.confidence)), + confidence: Math.max(0, Math.min(1, confidenceValue)), modelSummary, }; }