Handle object-shaped changed sections
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
articleInputSchema,
|
articleInputSchema,
|
||||||
confirmedFactCardSchema,
|
confirmedFactCardSchema,
|
||||||
candidateFactCardSchema,
|
candidateFactCardSchema,
|
||||||
|
optimizedArticleSchema,
|
||||||
qaReportSchema,
|
qaReportSchema,
|
||||||
} from "../validation";
|
} from "../validation";
|
||||||
|
|
||||||
@@ -144,4 +145,23 @@ describe("domain validation", () => {
|
|||||||
}),
|
}),
|
||||||
).toThrow();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("normalizes object-shaped changed sections from LLM output", () => {
|
||||||
|
const parsed = optimizedArticleSchema.parse({
|
||||||
|
title: "Optimized article",
|
||||||
|
summary: "Summary constrained by the confirmed fact card.",
|
||||||
|
body_markdown: "## Body\nUpdated body.",
|
||||||
|
image_suggestions: [],
|
||||||
|
changed_sections: [
|
||||||
|
{ section: "title", change: "Improved keyword clarity." },
|
||||||
|
{ name: "body", reason: "Fixed sentence flow." },
|
||||||
|
],
|
||||||
|
requires_user_confirmation: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(parsed.changed_sections).toEqual([
|
||||||
|
"title: Improved keyword clarity.",
|
||||||
|
"body: Fixed sentence flow.",
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -117,6 +117,60 @@ export const imageSuggestionSchema = z.object({
|
|||||||
suggestion: z.string().trim().min(1),
|
suggestion: z.string().trim().min(1),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function firstStringField(record: Record<string, unknown>, keys: string[]) {
|
||||||
|
for (const key of keys) {
|
||||||
|
const value = record[key];
|
||||||
|
if (typeof value === "string" && value.trim().length > 0) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeChangedSection(value: unknown) {
|
||||||
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const record = value as Record<string, unknown>;
|
||||||
|
const section = firstStringField(record, [
|
||||||
|
"section",
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"field",
|
||||||
|
"target",
|
||||||
|
"part",
|
||||||
|
]);
|
||||||
|
const detail = firstStringField(record, [
|
||||||
|
"change",
|
||||||
|
"changed",
|
||||||
|
"summary",
|
||||||
|
"reason",
|
||||||
|
"description",
|
||||||
|
"fix",
|
||||||
|
"after",
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (section && detail && section !== detail) {
|
||||||
|
return `${section}: ${detail}`;
|
||||||
|
}
|
||||||
|
if (section || detail) {
|
||||||
|
return section ?? detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stringValues = Object.values(record)
|
||||||
|
.filter((entry): entry is string => typeof entry === "string")
|
||||||
|
.map((entry) => entry.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
return stringValues.join("; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedSectionSchema = z.preprocess(
|
||||||
|
normalizeChangedSection,
|
||||||
|
z.string().trim().min(1),
|
||||||
|
);
|
||||||
|
|
||||||
export const optimizedArticleSchema = z.object({
|
export const optimizedArticleSchema = z.object({
|
||||||
job_id: z.string().trim().min(1).optional(),
|
job_id: z.string().trim().min(1).optional(),
|
||||||
revision: z.number().int().positive().optional(),
|
revision: z.number().int().positive().optional(),
|
||||||
@@ -124,7 +178,7 @@ export const optimizedArticleSchema = z.object({
|
|||||||
summary: z.string().trim().min(1),
|
summary: z.string().trim().min(1),
|
||||||
body_markdown: z.string().trim().min(1),
|
body_markdown: z.string().trim().min(1),
|
||||||
image_suggestions: z.array(imageSuggestionSchema).default([]),
|
image_suggestions: z.array(imageSuggestionSchema).default([]),
|
||||||
changed_sections: z.array(z.string().trim().min(1)).default([]),
|
changed_sections: z.array(changedSectionSchema).default([]),
|
||||||
requires_user_confirmation: z.array(z.string().trim().min(1)).default([]),
|
requires_user_confirmation: z.array(z.string().trim().min(1)).default([]),
|
||||||
}) satisfies z.ZodType<OptimizedArticle>;
|
}) satisfies z.ZodType<OptimizedArticle>;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user