Handle object-shaped changed sections

This commit is contained in:
Codex
2026-06-21 23:43:03 +08:00
parent adf844f752
commit 5a627bcaa8
2 changed files with 75 additions and 1 deletions
@@ -4,6 +4,7 @@ import {
articleInputSchema,
confirmedFactCardSchema,
candidateFactCardSchema,
optimizedArticleSchema,
qaReportSchema,
} from "../validation";
@@ -144,4 +145,23 @@ describe("domain validation", () => {
}),
).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.",
]);
});
});
+55 -1
View File
@@ -117,6 +117,60 @@ export const imageSuggestionSchema = z.object({
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({
job_id: z.string().trim().min(1).optional(),
revision: z.number().int().positive().optional(),
@@ -124,7 +178,7 @@ export const optimizedArticleSchema = z.object({
summary: z.string().trim().min(1),
body_markdown: z.string().trim().min(1),
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([]),
}) satisfies z.ZodType<OptimizedArticle>;