ai-writing-content
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseBuild an AI Content Writer
构建AI内容写作工具
Guide the user through building AI that writes articles, reports, and marketing copy. Uses DSPy to create a structured pipeline: outline, draft section-by-section, enrich with research, and polish with feedback loops.
引导用户构建可撰写文章、报告和营销文案的AI工具。使用DSPy创建结构化管道:生成大纲、逐节撰写草稿、结合研究内容丰富文本,并通过反馈循环优化内容。
Step 1: Understand the content task
步骤1:明确内容任务
Ask the user:
- What type of content? (blog post, product description, report, newsletter, docs?)
- What tone and voice? (professional, casual, technical, marketing, brand-specific?)
- How long? (tweet, paragraph, 500-word post, 2000-word article?)
- Does it need research? (factual claims grounded in sources, or creative/opinion?)
- Any brand guidelines? (words to avoid, style rules, required sections?)
向用户确认以下信息:
- 内容类型?(博客文章、产品描述、报告、新闻通讯、文档?)
- 语气与风格?(专业、随意、技术向、营销向、品牌特定风格?)
- 篇幅长度?(推文、段落、500字短文、2000字长文?)
- 是否需要调研支持?(基于来源的事实性内容,还是创意/观点类内容?)
- 是否有品牌规范?(禁用词汇、格式规则、必填章节?)
Step 2: Build an outline generator
步骤2:构建大纲生成器
Start with structure. An outline gives the writer a plan to follow:
python
import dspy
from pydantic import BaseModel, Field
class Section(BaseModel):
heading: str = Field(description="Section heading")
key_points: list[str] = Field(description="Main points to cover in this section")
class ContentOutline(BaseModel):
title: str
sections: list[Section]
class GenerateOutline(dspy.Signature):
"""Create a structured outline for the content."""
topic: str = dspy.InputField(desc="The topic or brief to write about")
content_type: str = dspy.InputField(desc="Type: blog post, report, product description, etc.")
audience: str = dspy.InputField(desc="Who will read this content")
outline: ContentOutline = dspy.OutputField()
outliner = dspy.ChainOfThought(GenerateOutline)先搭建内容框架,大纲能为写作提供清晰的规划:
python
import dspy
from pydantic import BaseModel, Field
class Section(BaseModel):
heading: str = Field(description="Section heading")
key_points: list[str] = Field(description="Main points to cover in this section")
class ContentOutline(BaseModel):
title: str
sections: list[Section]
class GenerateOutline(dspy.Signature):
"""Create a structured outline for the content."""
topic: str = dspy.InputField(desc="The topic or brief to write about")
content_type: str = dspy.InputField(desc="Type: blog post, report, product description, etc.")
audience: str = dspy.InputField(desc="Who will read this content")
outline: ContentOutline = dspy.OutputField()
outliner = dspy.ChainOfThought(GenerateOutline)With research context
结合调研上下文
If the content needs to be grounded in facts:
python
class GenerateResearchedOutline(dspy.Signature):
"""Create a structured outline grounded in the provided research."""
topic: str = dspy.InputField()
content_type: str = dspy.InputField()
audience: str = dspy.InputField()
research: list[str] = dspy.InputField(desc="Research sources and key facts")
outline: ContentOutline = dspy.OutputField()如果内容需要基于事实:
python
class GenerateResearchedOutline(dspy.Signature):
"""Create a structured outline grounded in the provided research."""
topic: str = dspy.InputField()
content_type: str = dspy.InputField()
audience: str = dspy.InputField()
research: list[str] = dspy.InputField(desc="Research sources and key facts")
outline: ContentOutline = dspy.OutputField()Step 3: Generate section by section
步骤3:逐节生成内容
Don't generate the whole article at once. Write one section at a time for better quality:
python
class WriteSection(dspy.Signature):
"""Write one section of the article based on the outline."""
topic: str = dspy.InputField(desc="Overall article topic")
section_heading: str = dspy.InputField(desc="This section's heading")
key_points: list[str] = dspy.InputField(desc="Points to cover in this section")
previous_sections: str = dspy.InputField(desc="What's been written so far, for continuity")
tone: str = dspy.InputField(desc="Writing tone and style")
section_text: str = dspy.OutputField(desc="The written section (2-4 paragraphs)")
class ContentWriter(dspy.Module):
def __init__(self):
self.outline = dspy.ChainOfThought(GenerateOutline)
self.write_section = dspy.ChainOfThought(WriteSection)
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
# Step 1: Generate outline
plan = self.outline(topic=topic, content_type=content_type, audience=audience)
# Step 2: Write each section
sections = []
running_text = ""
for section in plan.outline.sections:
result = self.write_section(
topic=topic,
section_heading=section.heading,
key_points=section.key_points,
previous_sections=running_text[-2000:], # last 2000 chars for context
tone=tone,
)
sections.append(f"## {section.heading}\n\n{result.section_text}")
running_text += result.section_text + "\n\n"
full_article = f"# {plan.outline.title}\n\n" + "\n\n".join(sections)
return dspy.Prediction(
title=plan.outline.title,
outline=plan.outline,
article=full_article,
)不要一次性生成整篇文章,逐节撰写能提升内容质量:
python
class WriteSection(dspy.Signature):
"""Write one section of the article based on the outline."""
topic: str = dspy.InputField(desc="Overall article topic")
section_heading: str = dspy.InputField(desc="This section's heading")
key_points: list[str] = dspy.InputField(desc="Points to cover in this section")
previous_sections: str = dspy.InputField(desc="What's been written so far, for continuity")
tone: str = dspy.InputField(desc="Writing tone and style")
section_text: str = dspy.OutputField(desc="The written section (2-4 paragraphs)")
class ContentWriter(dspy.Module):
def __init__(self):
self.outline = dspy.ChainOfThought(GenerateOutline)
self.write_section = dspy.ChainOfThought(WriteSection)
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
# Step 1: Generate outline
plan = self.outline(topic=topic, content_type=content_type, audience=audience)
# Step 2: Write each section
sections = []
running_text = ""
for section in plan.outline.sections:
result = self.write_section(
topic=topic,
section_heading=section.heading,
key_points=section.key_points,
previous_sections=running_text[-2000:], # last 2000 chars for context
tone=tone,
)
sections.append(f"## {section.heading}\n\n{result.section_text}")
running_text += result.section_text + "\n\n"
full_article = f"# {plan.outline.title}\n\n" + "\n\n".join(sections)
return dspy.Prediction(
title=plan.outline.title,
outline=plan.outline,
article=full_article,
)Step 4: Add research grounding
步骤4:添加调研支撑
For content that needs factual claims backed by sources:
针对需要事实依据的内容:
Retrieval-augmented content
检索增强型内容
python
class ResearchTopic(dspy.Signature):
"""Generate search queries to research this topic."""
topic: str = dspy.InputField()
key_points: list[str] = dspy.InputField(desc="Points that need factual backing")
queries: list[str] = dspy.OutputField(desc="Search queries to find supporting facts")
class WriteSectionWithSources(dspy.Signature):
"""Write a section using the provided sources for factual claims."""
section_heading: str = dspy.InputField()
key_points: list[str] = dspy.InputField()
sources: list[str] = dspy.InputField(desc="Research passages to ground claims in")
previous_sections: str = dspy.InputField()
tone: str = dspy.InputField()
section_text: str = dspy.OutputField(desc="Section text with claims grounded in sources")
class ResearchedWriter(dspy.Module):
def __init__(self):
self.outline = dspy.ChainOfThought(GenerateOutline)
self.research = dspy.ChainOfThought(ResearchTopic)
self.retrieve = dspy.Retrieve(k=3)
self.write = dspy.ChainOfThought(WriteSectionWithSources)
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
plan = self.outline(topic=topic, content_type=content_type, audience=audience)
sections = []
running_text = ""
for section in plan.outline.sections:
# Research this section
queries = self.research(
topic=topic, key_points=section.key_points
).queries
sources = []
for query in queries:
sources.extend(self.retrieve(query).passages)
# Write with sources
result = self.write(
section_heading=section.heading,
key_points=section.key_points,
sources=sources,
previous_sections=running_text[-2000:],
tone=tone,
)
sections.append(f"## {section.heading}\n\n{result.section_text}")
running_text += result.section_text + "\n\n"
return dspy.Prediction(
title=plan.outline.title,
article=f"# {plan.outline.title}\n\n" + "\n\n".join(sections),
)python
class ResearchTopic(dspy.Signature):
"""Generate search queries to research this topic."""
topic: str = dspy.InputField()
key_points: list[str] = dspy.InputField(desc="Points that need factual backing")
queries: list[str] = dspy.OutputField(desc="Search queries to find supporting facts")
class WriteSectionWithSources(dspy.Signature):
"""Write a section using the provided sources for factual claims."""
section_heading: str = dspy.InputField()
key_points: list[str] = dspy.InputField()
sources: list[str] = dspy.InputField(desc="Research passages to ground claims in")
previous_sections: str = dspy.InputField()
tone: str = dspy.InputField()
section_text: str = dspy.OutputField(desc="Section text with claims grounded in sources")
class ResearchedWriter(dspy.Module):
def __init__(self):
self.outline = dspy.ChainOfThought(GenerateOutline)
self.research = dspy.ChainOfThought(ResearchTopic)
self.retrieve = dspy.Retrieve(k=3)
self.write = dspy.ChainOfThought(WriteSectionWithSources)
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
plan = self.outline(topic=topic, content_type=content_type, audience=audience)
sections = []
running_text = ""
for section in plan.outline.sections:
# Research this section
queries = self.research(
topic=topic, key_points=section.key_points
).queries
sources = []
for query in queries:
sources.extend(self.retrieve(query).passages)
# Write with sources
result = self.write(
section_heading=section.heading,
key_points=section.key_points,
sources=sources,
previous_sections=running_text[-2000:],
tone=tone,
)
sections.append(f"## {section.heading}\n\n{result.section_text}")
running_text += result.section_text + "\n\n"
return dspy.Prediction(
title=plan.outline.title,
article=f"# {plan.outline.title}\n\n" + "\n\n".join(sections),
)Step 5: Quality loop — generate, critique, improve
步骤5:质量循环——生成、评审、优化
Add a feedback loop to iteratively improve drafts:
python
class CritiqueContent(dspy.Signature):
"""Critique the written content and suggest improvements."""
content: str = dspy.InputField(desc="The content to critique")
content_type: str = dspy.InputField()
audience: str = dspy.InputField()
is_good_enough: bool = dspy.OutputField(desc="Is this ready to publish?")
feedback: str = dspy.OutputField(desc="Specific feedback for improvement")
class ImproveContent(dspy.Signature):
"""Improve the content based on the feedback."""
content: str = dspy.InputField(desc="Current draft")
feedback: str = dspy.InputField(desc="Feedback to address")
improved_content: str = dspy.OutputField(desc="Improved version")
class QualityWriter(dspy.Module):
def __init__(self, max_revisions=2):
self.writer = ContentWriter()
self.critic = dspy.ChainOfThought(CritiqueContent)
self.improver = dspy.ChainOfThought(ImproveContent)
self.max_revisions = max_revisions
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
# Generate first draft
draft = self.writer(
topic=topic, content_type=content_type, audience=audience, tone=tone
)
article = draft.article
# Critique-improve loop
for _ in range(self.max_revisions):
critique = self.critic(
content=article, content_type=content_type, audience=audience
)
if critique.is_good_enough:
break
improved = self.improver(content=article, feedback=critique.feedback)
article = improved.improved_content
return dspy.Prediction(
title=draft.title,
article=article,
)添加反馈循环来迭代优化草稿:
python
class CritiqueContent(dspy.Signature):
"""Critique the written content and suggest improvements."""
content: str = dspy.InputField(desc="The content to critique")
content_type: str = dspy.InputField()
audience: str = dspy.InputField()
is_good_enough: bool = dspy.OutputField(desc="Is this ready to publish?")
feedback: str = dspy.OutputField(desc="Specific feedback for improvement")
class ImproveContent(dspy.Signature):
"""Improve the content based on the feedback."""
content: str = dspy.InputField(desc="Current draft")
feedback: str = dspy.InputField(desc="Feedback to address")
improved_content: str = dspy.OutputField(desc="Improved version")
class QualityWriter(dspy.Module):
def __init__(self, max_revisions=2):
self.writer = ContentWriter()
self.critic = dspy.ChainOfThought(CritiqueContent)
self.improver = dspy.ChainOfThought(ImproveContent)
self.max_revisions = max_revisions
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
# Generate first draft
draft = self.writer(
topic=topic, content_type=content_type, audience=audience, tone=tone
)
article = draft.article
# Critique-improve loop
for _ in range(self.max_revisions):
critique = self.critic(
content=article, content_type=content_type, audience=audience
)
if critique.is_good_enough:
break
improved = self.improver(content=article, feedback=critique.feedback)
article = improved.improved_content
return dspy.Prediction(
title=draft.title,
article=article,
)Step 6: Voice and style enforcement
步骤6:语气与风格规范
Enforce brand voice and style rules:
python
class StyledWriter(dspy.Module):
def __init__(self, brand_rules=None):
self.writer = ContentWriter()
self.brand_rules = brand_rules or []
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
result = self.writer(topic=topic, content_type=content_type, audience=audience, tone=tone)
# Enforce brand rules
article_lower = result.article.lower()
for rule in self.brand_rules:
if rule.get("type") == "forbidden_word":
dspy.Assert(
rule["word"].lower() not in article_lower,
f"Content must not use the word '{rule['word']}'. "
f"Use '{rule.get('alternative', 'a different word')}' instead."
)
elif rule.get("type") == "required_section":
dspy.Assert(
rule["heading"].lower() in article_lower,
f"Content must include a '{rule['heading']}' section."
)
# General style checks
sentences = result.article.split(".")
avg_sentence_len = sum(len(s.split()) for s in sentences) / max(len(sentences), 1)
dspy.Suggest(
avg_sentence_len < 25,
"Sentences are too long on average. Aim for shorter, punchier sentences."
)
return result强制执行品牌语气和格式规则:
python
class StyledWriter(dspy.Module):
def __init__(self, brand_rules=None):
self.writer = ContentWriter()
self.brand_rules = brand_rules or []
def forward(self, topic, content_type="blog post", audience="general", tone="professional"):
result = self.writer(topic=topic, content_type=content_type, audience=audience, tone=tone)
# Enforce brand rules
article_lower = result.article.lower()
for rule in self.brand_rules:
if rule.get("type") == "forbidden_word":
dspy.Assert(
rule["word"].lower() not in article_lower,
f"Content must not use the word '{rule['word']}'. "
f"Use '{rule.get('alternative', 'a different word')}' instead."
)
elif rule.get("type") == "required_section":
dspy.Assert(
rule["heading"].lower() in article_lower,
f"Content must include a '{rule['heading']}' section."
)
# General style checks
sentences = result.article.split(".")
avg_sentence_len = sum(len(s.split()) for s in sentences) / max(len(sentences), 1)
dspy.Suggest(
avg_sentence_len < 25,
"Sentences are too long on average. Aim for shorter, punchier sentences."
)
return resultUsage
Usage
writer = StyledWriter(brand_rules=[
{"type": "forbidden_word", "word": "utilize", "alternative": "use"},
{"type": "forbidden_word", "word": "leverage", "alternative": "use"},
{"type": "required_section", "heading": "Conclusion"},
])
undefinedwriter = StyledWriter(brand_rules=[
{"type": "forbidden_word", "word": "utilize", "alternative": "use"},
{"type": "forbidden_word", "word": "leverage", "alternative": "use"},
{"type": "required_section", "heading": "Conclusion"},
])
undefinedStep 7: Test and optimize
步骤7:测试与优化
Readability metric
可读性指标
python
def readability_metric(example, prediction, trace=None):
words = prediction.article.split()
sentences = prediction.article.split(".")
if not sentences or not words:
return 0.0
avg_sentence_len = len(words) / len(sentences)
# Penalize very long or very short sentences
readability = 1.0 if 10 < avg_sentence_len < 20 else 0.5
# Penalize very short articles
length_ok = 1.0 if len(words) > 200 else 0.5
return (readability + length_ok) / 2python
def readability_metric(example, prediction, trace=None):
words = prediction.article.split()
sentences = prediction.article.split(".")
if not sentences or not words:
return 0.0
avg_sentence_len = len(words) / len(sentences)
# Penalize very long or very short sentences
readability = 1.0 if 10 < avg_sentence_len < 20 else 0.5
# Penalize very short articles
length_ok = 1.0 if len(words) > 200 else 0.5
return (readability + length_ok) / 2AI-as-judge metric
AI评审指标
python
class JudgeContent(dspy.Signature):
"""Judge the quality of generated content."""
content: str = dspy.InputField()
content_type: str = dspy.InputField()
topic: str = dspy.InputField()
relevance: float = dspy.OutputField(desc="0.0-1.0 — stays on topic")
coherence: float = dspy.OutputField(desc="0.0-1.0 — flows well, logically structured")
engagement: float = dspy.OutputField(desc="0.0-1.0 — interesting to read")
def content_quality_metric(example, prediction, trace=None):
judge = dspy.Predict(JudgeContent)
result = judge(
content=prediction.article,
content_type=example.content_type,
topic=example.topic,
)
return (result.relevance + result.coherence + result.engagement) / 3python
class JudgeContent(dspy.Signature):
"""Judge the quality of generated content."""
content: str = dspy.InputField()
content_type: str = dspy.InputField()
topic: str = dspy.InputField()
relevance: float = dspy.OutputField(desc="0.0-1.0 — stays on topic")
coherence: float = dspy.OutputField(desc="0.0-1.0 — flows well, logically structured")
engagement: float = dspy.OutputField(desc="0.0-1.0 — interesting to read")
def content_quality_metric(example, prediction, trace=None):
judge = dspy.Predict(JudgeContent)
result = judge(
content=prediction.article,
content_type=example.content_type,
topic=example.topic,
)
return (result.relevance + result.coherence + result.engagement) / 3Optimize
优化模型
python
optimizer = dspy.BootstrapFewShot(metric=content_quality_metric, max_bootstrapped_demos=4)
optimized = optimizer.compile(QualityWriter(), trainset=trainset)python
optimizer = dspy.BootstrapFewShot(metric=content_quality_metric, max_bootstrapped_demos=4)
optimized = optimizer.compile(QualityWriter(), trainset=trainset)Key patterns
核心模式
- Outline first, then write — structure prevents rambling and missed points
- Section-by-section generation — writing one section at a time produces better quality than generating the whole article at once
- Retrieve for factual grounding — pull in sources to back up claims
- Critique-improve loop — generate, critique, improve catches issues a single pass misses
- Assert for brand rules — DSPy retries when forbidden words or missing sections are detected
- AI-as-judge for quality — use a judge signature to score relevance, coherence, engagement
- 先搭大纲再写作——框架能避免内容杂乱和遗漏要点
- 逐节生成内容——逐节撰写的质量优于一次性生成整篇文章
- 检索事实支撑——引入来源来佐证观点
- 评审优化循环——生成、评审、优化的流程能发现单次生成的问题
- 品牌规则断言——当检测到禁用词汇或缺失章节时,DSPy会自动重试
- AI评审质量——使用评审签名对相关性、连贯性、吸引力打分
Additional resources
额外资源
- For worked examples (blog posts, product descriptions, newsletters), see examples.md
- Need to summarize content instead of generating it? Use
/ai-summarizing - Need multi-step pipelines beyond content? Use
/ai-building-pipelines - Next: to measure and improve your content writer
/ai-improving-accuracy
- 如需完整示例(博客文章、产品描述、新闻通讯),请查看examples.md
- 如需内容总结而非生成,请使用
/ai-summarizing - 如需内容之外的多步骤管道,请使用
/ai-building-pipelines - 下一步:使用来衡量和优化你的内容写作工具
/ai-improving-accuracy