新增人味儿文案优化提示词
This commit is contained in:
@@ -4,10 +4,12 @@ import {
|
||||
ARTICLE_OPTIMIZER_SYSTEM_PROMPT,
|
||||
FACT_EXTRACTOR_SYSTEM_PROMPT,
|
||||
QUALITY_INSPECTOR_SYSTEM_PROMPT,
|
||||
RENWEI_COPY_OPTIMIZER_SYSTEM_PROMPT,
|
||||
TARGETED_REWRITER_SYSTEM_PROMPT,
|
||||
buildArticleOptimizerPrompt,
|
||||
buildFactExtractorPrompt,
|
||||
buildQualityInspectorPrompt,
|
||||
buildRenweiCopyOptimizationPrompt,
|
||||
buildTargetedRewritePrompt,
|
||||
} from "../prompts";
|
||||
|
||||
@@ -168,4 +170,39 @@ describe("LLM prompt builders", () => {
|
||||
expect(prompt).toContain("body_quality");
|
||||
expect(prompt).toContain("不得新增事实");
|
||||
});
|
||||
|
||||
it("renwei copy prompt keeps the author present and defaults to small edits", () => {
|
||||
const prompt = `${RENWEI_COPY_OPTIMIZER_SYSTEM_PROMPT}\n${buildRenweiCopyOptimizationPrompt({
|
||||
source_text: "我观察到大家越来越难进入心流了。",
|
||||
goal: "保留原意,减少 AI 味",
|
||||
intensity: "light",
|
||||
user_instructions: "保留作者的口语感。",
|
||||
})}`;
|
||||
|
||||
expect(prompt).toContain("少动");
|
||||
expect(prompt).toContain("保留作者");
|
||||
expect(prompt).toContain("手迹");
|
||||
expect(prompt).toContain("不凭空新增时间、地点、数字、案例");
|
||||
expect(prompt).toContain("不写过度金句");
|
||||
expect(prompt).toContain("逐处说明");
|
||||
expect(prompt).toContain("只检查被改动句子");
|
||||
expect(prompt).toContain("source_text");
|
||||
});
|
||||
|
||||
it("renwei copy prompt describes all result fields and intensity modes", () => {
|
||||
const prompt = buildRenweiCopyOptimizationPrompt({
|
||||
source_text: "这是一段文案。",
|
||||
goal: "更自然",
|
||||
intensity: "conversational",
|
||||
user_instructions: "",
|
||||
});
|
||||
|
||||
expect(prompt).toContain("optimized_text");
|
||||
expect(prompt).toContain("change_notes");
|
||||
expect(prompt).toContain("ai_taste_checks");
|
||||
expect(prompt).toContain("warnings");
|
||||
expect(prompt).toContain("light");
|
||||
expect(prompt).toContain("medium");
|
||||
expect(prompt).toContain("conversational");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@ export type LlmTaskName =
|
||||
| "fact_extractor"
|
||||
| "article_optimizer"
|
||||
| "quality_inspector"
|
||||
| "targeted_rewriter";
|
||||
| "targeted_rewriter"
|
||||
| "renwei_copy_optimizer";
|
||||
|
||||
export interface GenerateInput {
|
||||
system?: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type {
|
||||
ArticleInput,
|
||||
CopyOptimizationRequest,
|
||||
OptimizationFactCard,
|
||||
OptimizedArticle,
|
||||
PublishPlatform,
|
||||
@@ -97,6 +98,44 @@ const QA_OUTPUT_CONTRACT = [
|
||||
),
|
||||
].join("\n");
|
||||
|
||||
const RENWEI_OUTPUT_CONTRACT = [
|
||||
"Output type contract:",
|
||||
"- optimized_text must be a string.",
|
||||
"- change_notes must be an array of { original, revised, reason, confidence, revertible }.",
|
||||
"- confidence must be confident or uncertain.",
|
||||
"- revertible must be boolean.",
|
||||
"- ai_taste_checks must be an array of { rule_id, status, evidence, suggestion }.",
|
||||
"- rule_id must be one of meaning_inflation, promotion_tone, formulaic_sentence, format_trace, chat_trace, filler_hedging.",
|
||||
"- status must be pass or warn.",
|
||||
"- warnings must be string[].",
|
||||
"Example:",
|
||||
JSON.stringify(
|
||||
{
|
||||
optimized_text: "我把句子顺了一下。",
|
||||
change_notes: [
|
||||
{
|
||||
original: "我把句子顺顺。",
|
||||
revised: "我把句子顺了一下。",
|
||||
reason: "修掉重复,让句子自然收住。",
|
||||
confidence: "confident",
|
||||
revertible: false,
|
||||
},
|
||||
],
|
||||
ai_taste_checks: [
|
||||
{
|
||||
rule_id: "promotion_tone",
|
||||
status: "pass",
|
||||
evidence: "没有新增宣传腔。",
|
||||
suggestion: "",
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
].join("\n");
|
||||
|
||||
function formatPlatformGuidance(selected: PublishPlatform) {
|
||||
return [
|
||||
"平台模板选项:",
|
||||
@@ -135,6 +174,29 @@ export const TARGETED_REWRITER_SYSTEM_PROMPT = [
|
||||
JSON_ONLY_PROMPT,
|
||||
].join(" ");
|
||||
|
||||
export const RENWEI_COPY_OPTIMIZER_SYSTEM_PROMPT = [
|
||||
"你是中文普通文案编辑,不是营销代笔工具。",
|
||||
"目标是让文字更顺,但改完后作者还在。",
|
||||
"硬性规则:少动,只改真正打绊的地方;保留作者的位置、语气、口头习惯和可识别手迹。",
|
||||
"不凭空新增时间、地点、数字、案例、情绪、场景、资质、效果承诺或没有来源的事实。",
|
||||
"不写过度金句,不要把普通句子改成宣传腔、排比、格言、万能展望或意义拔高。",
|
||||
"拿不准时白描,不拔高;拿不准的改动必须标记为 uncertain 且 revertible 为 true。",
|
||||
"改后只检查被改动句子是否引入 AI 味,并逐处说明改了什么、为什么改。",
|
||||
JSON_ONLY_PROMPT,
|
||||
].join(" ");
|
||||
|
||||
function describeRenweiIntensity(
|
||||
intensity: CopyOptimizationRequest["intensity"],
|
||||
) {
|
||||
const descriptions = {
|
||||
light: "light / 轻微整理:尽量只修顺病句、错别字、明显卡顿。",
|
||||
medium: "medium / 适度润色:允许调整句序和连接,但不改变作者表达的粗糙感。",
|
||||
conversational:
|
||||
"conversational / 更口语自然:让口吻更像真人说话,但不凭空增加表演性口语。",
|
||||
} satisfies Record<CopyOptimizationRequest["intensity"], string>;
|
||||
return descriptions[intensity];
|
||||
}
|
||||
|
||||
export function buildFactExtractorPrompt(input: ArticleInput) {
|
||||
return [
|
||||
"Return a CandidateFactCard JSON object with these exact keys:",
|
||||
@@ -247,3 +309,42 @@ export function buildTargetedRewritePrompt(input: {
|
||||
JSON.stringify(input.failedChecks, null, 2),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function buildRenweiCopyOptimizationPrompt(
|
||||
input: CopyOptimizationRequest,
|
||||
) {
|
||||
return [
|
||||
"Return a CopyOptimizationResult JSON object with these exact keys:",
|
||||
"optimized_text, change_notes, ai_taste_checks, warnings.",
|
||||
"",
|
||||
RENWEI_OUTPUT_CONTRACT,
|
||||
"",
|
||||
"强度选项:",
|
||||
"- light:轻微整理。",
|
||||
"- medium:适度润色。",
|
||||
"- conversational:更口语自然。",
|
||||
`当前修改强度:${describeRenweiIntensity(input.intensity)}`,
|
||||
"",
|
||||
"改稿要求:",
|
||||
"- 默认少动,不追求更漂亮,只清掉绊脚处。",
|
||||
"- 毛边先假设是作者手迹,不要机械删口语、停顿或重复。",
|
||||
"- 不写“不是X,而是Y”、排比三连、万能展望、宣传腔或假深刻句式。",
|
||||
"- 不使用破折号作为装饰性解释。",
|
||||
"- 必须逐处说明改动;未改动的句子不要编造改动说明。",
|
||||
"- AI 味检查只检查被改动句子。",
|
||||
"",
|
||||
"User request:",
|
||||
JSON.stringify(
|
||||
{
|
||||
goal: input.goal,
|
||||
intensity: input.intensity,
|
||||
user_instructions: input.user_instructions,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"",
|
||||
"source_text:",
|
||||
input.source_text,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ vi.mock("../../llm/client", async () => {
|
||||
import { extractCandidateFactCard } from "../fact-extractor";
|
||||
import { optimizeArticle } from "../article-optimizer";
|
||||
import { inspectQualityWithLlm } from "../quality-inspector";
|
||||
import { optimizeRenweiCopy } from "../renwei-copy-optimizer";
|
||||
import { rewriteFailedSections } from "../targeted-rewriter";
|
||||
|
||||
describe("LLM workflow integration", () => {
|
||||
@@ -356,4 +357,43 @@ describe("LLM workflow integration", () => {
|
||||
expect(hallucinationCheck?.status).toBe("warn");
|
||||
expect(report.overall_status).toBe("warn");
|
||||
});
|
||||
|
||||
it("uses validated LLM output for renwei copy optimization", async () => {
|
||||
llmMocks.generateValidatedJson.mockResolvedValueOnce({
|
||||
optimized_text: "我观察到大家越来越难进入心流了。",
|
||||
change_notes: [
|
||||
{
|
||||
original: "我观察到大家越来越难进入心流",
|
||||
revised: "我观察到大家越来越难进入心流了。",
|
||||
reason: "补足句尾语气,让句子自然收住。",
|
||||
confidence: "confident",
|
||||
revertible: false,
|
||||
},
|
||||
],
|
||||
ai_taste_checks: [
|
||||
{
|
||||
rule_id: "promotion_tone",
|
||||
status: "pass",
|
||||
evidence: "没有新增宣传腔。",
|
||||
suggestion: "",
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
});
|
||||
|
||||
const result = await optimizeRenweiCopy({
|
||||
source_text: "我观察到大家越来越难进入心流",
|
||||
goal: "保留原意,减少 AI 味",
|
||||
intensity: "light",
|
||||
user_instructions: "",
|
||||
});
|
||||
|
||||
expect(result.optimized_text).toBe("我观察到大家越来越难进入心流了。");
|
||||
expect(llmMocks.generateValidatedJson).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
task: "renwei_copy_optimizer",
|
||||
temperature: 0.2,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type {
|
||||
CopyOptimizationRequest,
|
||||
CopyOptimizationResult,
|
||||
} from "../domain/types";
|
||||
import { copyOptimizationResultSchema } from "../domain/validation";
|
||||
import { generateValidatedJson } from "../llm/client";
|
||||
import {
|
||||
RENWEI_COPY_OPTIMIZER_SYSTEM_PROMPT,
|
||||
buildRenweiCopyOptimizationPrompt,
|
||||
} from "../llm/prompts";
|
||||
|
||||
export async function optimizeRenweiCopy(
|
||||
input: CopyOptimizationRequest,
|
||||
): Promise<CopyOptimizationResult> {
|
||||
return generateValidatedJson({
|
||||
schema: copyOptimizationResultSchema,
|
||||
system: RENWEI_COPY_OPTIMIZER_SYSTEM_PROMPT,
|
||||
prompt: buildRenweiCopyOptimizationPrompt(input),
|
||||
temperature: 0.2,
|
||||
task: "renwei_copy_optimizer",
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user