From 3709c64a17479b3326f28f576f76f7c444ca4d14 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 24 Jun 2026 10:10:05 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=A8=BFLLM=E5=8F=AF=E9=80=89=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/domain/__tests__/validation.test.ts | 31 +++++++++++++++++++++ src/lib/domain/validation.ts | 31 +++++++++++++++++---- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/lib/domain/__tests__/validation.test.ts b/src/lib/domain/__tests__/validation.test.ts index 910a36b..1234ca0 100644 --- a/src/lib/domain/__tests__/validation.test.ts +++ b/src/lib/domain/__tests__/validation.test.ts @@ -200,4 +200,35 @@ describe("domain validation", () => { "body: Fixed sentence flow.", ]); }); + + it("normalizes near-valid optimized article LLM optional fields", () => { + const parsed = optimizedArticleSchema.parse({ + title: { text: "示例科技 GEO 内容优化方案" }, + summary: ["围绕事实卡重写官网文章摘要"], + body_markdown: { + markdown: "## 服务能力\n示例科技有限公司提供GEO内容优化服务。", + }, + image_suggestions: [ + "当前版本不生成图片建议", + { source: "image_1" }, + { suggestion: "使用产品后台截图" }, + { source: "image_2", suggestion: "保留原文截图说明" }, + ], + changed_sections: [ + { section: "title", change: "改成中文官网标题" }, + ], + requires_user_confirmation: [ + { claim: "客户案例", reason: "原文没有给出客户名称" }, + ], + }); + + expect(parsed.title).toBe("示例科技 GEO 内容优化方案"); + expect(parsed.summary).toBe("围绕事实卡重写官网文章摘要"); + expect(parsed.body_markdown).toContain("## 服务能力"); + expect(parsed.image_suggestions).toEqual([ + { source: "image_2", suggestion: "保留原文截图说明" }, + ]); + expect(parsed.changed_sections).toEqual(["title: 改成中文官网标题"]); + expect(parsed.requires_user_confirmation).toEqual(["客户案例"]); + }); }); diff --git a/src/lib/domain/validation.ts b/src/lib/domain/validation.ts index c57927e..d1eba38 100644 --- a/src/lib/domain/validation.ts +++ b/src/lib/domain/validation.ts @@ -232,6 +232,27 @@ export const imageSuggestionSchema = z.object({ suggestion: z.string().trim().min(1), }); +function normalizeImageSuggestions(value: unknown): unknown { + if (!Array.isArray(value)) return []; + + return value.flatMap((item) => { + if (!isPlainRecord(item)) return []; + const source = normalizedStringOrNull( + item.source ?? item.image ?? item.name ?? item.title, + ); + const suggestion = normalizedStringOrNull( + item.suggestion ?? item.description ?? item.reason ?? item.fix ?? item.text, + ); + if (!source || !suggestion) return []; + return [{ source, suggestion }]; + }); +} + +const imageSuggestionsSchema = z.preprocess( + normalizeImageSuggestions, + z.array(imageSuggestionSchema).default([]), +); + function normalizeChangedSection(value: unknown) { if (!value || typeof value !== "object" || Array.isArray(value)) { return value; @@ -279,12 +300,12 @@ const changedSectionSchema = z.preprocess( export const optimizedArticleSchema = z.object({ job_id: z.string().trim().min(1).optional(), revision: z.number().int().positive().optional(), - title: z.string().trim().min(1), - summary: z.string().trim().min(1), - body_markdown: z.string().trim().min(1), - image_suggestions: z.array(imageSuggestionSchema).default([]), + title: requiredLlmStringSchema, + summary: requiredLlmStringSchema, + body_markdown: requiredLlmStringSchema, + image_suggestions: imageSuggestionsSchema, changed_sections: z.array(changedSectionSchema).default([]), - requires_user_confirmation: z.array(z.string().trim().min(1)).default([]), + requires_user_confirmation: stringListSchema, }) satisfies z.ZodType; export const qaCheckSchema = z.object({