fix: 兼容QA检查LLM字段
This commit is contained in:
@@ -182,6 +182,40 @@ describe("domain validation", () => {
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it("normalizes near-valid LLM QA report values", () => {
|
||||
const parsed = qaReportSchema.parse({
|
||||
overall_status: "警告",
|
||||
checks: [
|
||||
{
|
||||
rule_id: "标题质量",
|
||||
status: "警告",
|
||||
evidence: { detail: "标题仍然偏营销化" },
|
||||
reason: { reason: "官网标题需要更克制" },
|
||||
suggested_fix: null,
|
||||
target_agent: "",
|
||||
},
|
||||
{
|
||||
rule_id: "company_name_integrity",
|
||||
status: "通过",
|
||||
evidence: "公司全称一致",
|
||||
reason: "正文保留了事实卡中的公司全称",
|
||||
target_agent: "无",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(parsed.overall_status).toBe("warn");
|
||||
expect(parsed.checks[0]).toEqual({
|
||||
rule_id: "title_quality",
|
||||
status: "warn",
|
||||
evidence: "标题仍然偏营销化",
|
||||
reason: "官网标题需要更克制",
|
||||
suggested_fix: "",
|
||||
target_agent: null,
|
||||
});
|
||||
expect(parsed.checks[1]?.target_agent).toBeNull();
|
||||
});
|
||||
|
||||
it("normalizes object-shaped changed sections from LLM output", () => {
|
||||
const parsed = optimizedArticleSchema.parse({
|
||||
title: "Optimized article",
|
||||
|
||||
+107
-11
@@ -20,13 +20,77 @@ export const publishPlatformSchema = z.enum([
|
||||
"recommendation_list",
|
||||
]) satisfies z.ZodType<PublishPlatform>;
|
||||
|
||||
export const checkStatusSchema = z.enum([
|
||||
"pass",
|
||||
"warn",
|
||||
"fail",
|
||||
]) satisfies z.ZodType<CheckStatus>;
|
||||
const checkStatusAliases: Record<string, CheckStatus> = {
|
||||
pass: "pass",
|
||||
passed: "pass",
|
||||
ok: "pass",
|
||||
"通过": "pass",
|
||||
"合格": "pass",
|
||||
warn: "warn",
|
||||
warning: "warn",
|
||||
"警告": "warn",
|
||||
"提醒": "warn",
|
||||
fail: "fail",
|
||||
failed: "fail",
|
||||
failure: "fail",
|
||||
"失败": "fail",
|
||||
"不通过": "fail",
|
||||
};
|
||||
|
||||
export const qualityRuleIdSchema = z.enum([
|
||||
function normalizeCheckStatus(value: unknown): unknown {
|
||||
const normalized = normalizedStringOrNull(value);
|
||||
if (!normalized) return value;
|
||||
return (
|
||||
checkStatusAliases[normalized.toLowerCase()] ??
|
||||
checkStatusAliases[normalized] ??
|
||||
normalized
|
||||
);
|
||||
}
|
||||
|
||||
export const checkStatusSchema = z.preprocess(
|
||||
normalizeCheckStatus,
|
||||
z.enum(["pass", "warn", "fail"]),
|
||||
) satisfies z.ZodType<CheckStatus>;
|
||||
|
||||
const qualityRuleIdAliases: Record<string, QualityRuleId> = {
|
||||
industry_alignment: "industry_alignment",
|
||||
"行业对齐": "industry_alignment",
|
||||
"行业一致性": "industry_alignment",
|
||||
image_text_match: "image_text_match",
|
||||
"图文匹配": "image_text_match",
|
||||
"图片文本匹配": "image_text_match",
|
||||
voice_consistency: "voice_consistency",
|
||||
"语气一致性": "voice_consistency",
|
||||
"口吻一致性": "voice_consistency",
|
||||
platform_fit: "platform_fit",
|
||||
"平台适配": "platform_fit",
|
||||
company_name_integrity: "company_name_integrity",
|
||||
"公司名一致性": "company_name_integrity",
|
||||
"公司名称一致性": "company_name_integrity",
|
||||
title_quality: "title_quality",
|
||||
"标题质量": "title_quality",
|
||||
body_quality: "body_quality",
|
||||
"正文质量": "body_quality",
|
||||
hallucination_risk: "hallucination_risk",
|
||||
"幻觉风险": "hallucination_risk",
|
||||
"虚构风险": "hallucination_risk",
|
||||
claim_consistency: "claim_consistency",
|
||||
"事实一致性": "claim_consistency",
|
||||
"主张一致性": "claim_consistency",
|
||||
context_sensitive_terms: "context_sensitive_terms",
|
||||
"语境敏感词": "context_sensitive_terms",
|
||||
"敏感词": "context_sensitive_terms",
|
||||
};
|
||||
|
||||
function normalizeQualityRuleId(value: unknown): unknown {
|
||||
const normalized = normalizedStringOrNull(value);
|
||||
if (!normalized) return value;
|
||||
return qualityRuleIdAliases[normalized] ?? normalized;
|
||||
}
|
||||
|
||||
export const qualityRuleIdSchema = z.preprocess(
|
||||
normalizeQualityRuleId,
|
||||
z.enum([
|
||||
"industry_alignment",
|
||||
"image_text_match",
|
||||
"voice_consistency",
|
||||
@@ -37,7 +101,8 @@ export const qualityRuleIdSchema = z.enum([
|
||||
"hallucination_risk",
|
||||
"claim_consistency",
|
||||
"context_sensitive_terms",
|
||||
]) satisfies z.ZodType<QualityRuleId>;
|
||||
]),
|
||||
) satisfies z.ZodType<QualityRuleId>;
|
||||
|
||||
export const imageInputSchema = z.object({
|
||||
type: z.enum(["description", "link"]),
|
||||
@@ -308,13 +373,44 @@ export const optimizedArticleSchema = z.object({
|
||||
requires_user_confirmation: stringListSchema,
|
||||
}) satisfies z.ZodType<OptimizedArticle>;
|
||||
|
||||
function normalizeTargetAgent(value: unknown): unknown {
|
||||
const normalized = normalizedStringOrNull(value);
|
||||
if (!normalized) return null;
|
||||
const aliases: Record<string, string | null> = {
|
||||
none: null,
|
||||
null: null,
|
||||
"无": null,
|
||||
"无需": null,
|
||||
title: "title",
|
||||
"标题": "title",
|
||||
body: "body",
|
||||
"正文": "body",
|
||||
fact_card: "fact_card",
|
||||
factcard: "fact_card",
|
||||
"事实卡": "fact_card",
|
||||
};
|
||||
const normalizedKey = normalized.toLowerCase();
|
||||
if (Object.prototype.hasOwnProperty.call(aliases, normalizedKey)) {
|
||||
return aliases[normalizedKey];
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(aliases, normalized)) {
|
||||
return aliases[normalized];
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
const targetAgentSchema = z.preprocess(
|
||||
normalizeTargetAgent,
|
||||
z.string().trim().min(1).nullable().default(null),
|
||||
);
|
||||
|
||||
export const qaCheckSchema = z.object({
|
||||
rule_id: qualityRuleIdSchema,
|
||||
status: checkStatusSchema,
|
||||
evidence: z.string().trim().min(1),
|
||||
reason: z.string().trim().min(1),
|
||||
suggested_fix: z.string().trim().default(""),
|
||||
target_agent: z.string().trim().min(1).nullable().default(null),
|
||||
evidence: requiredLlmStringSchema,
|
||||
reason: requiredLlmStringSchema,
|
||||
suggested_fix: optionalLlmStringSchema,
|
||||
target_agent: targetAgentSchema,
|
||||
}) satisfies z.ZodType<QaCheck>;
|
||||
|
||||
export const qaReportSchema = z.object({
|
||||
|
||||
Reference in New Issue
Block a user