ai-writing-content

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Build 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:
  1. What type of content? (blog post, product description, report, newsletter, docs?)
  2. What tone and voice? (professional, casual, technical, marketing, brand-specific?)
  3. How long? (tweet, paragraph, 500-word post, 2000-word article?)
  4. Does it need research? (factual claims grounded in sources, or creative/opinion?)
  5. Any brand guidelines? (words to avoid, style rules, required sections?)
向用户确认以下信息:
  1. 内容类型?(博客文章、产品描述、报告、新闻通讯、文档?)
  2. 语气与风格?(专业、随意、技术向、营销向、品牌特定风格?)
  3. 篇幅长度?(推文、段落、500字短文、2000字长文?)
  4. 是否需要调研支持?(基于来源的事实性内容,还是创意/观点类内容?)
  5. 是否有品牌规范?(禁用词汇、格式规则、必填章节?)

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 result

Usage

Usage

writer = StyledWriter(brand_rules=[ {"type": "forbidden_word", "word": "utilize", "alternative": "use"}, {"type": "forbidden_word", "word": "leverage", "alternative": "use"}, {"type": "required_section", "heading": "Conclusion"}, ])
undefined
writer = StyledWriter(brand_rules=[ {"type": "forbidden_word", "word": "utilize", "alternative": "use"}, {"type": "forbidden_word", "word": "leverage", "alternative": "use"}, {"type": "required_section", "heading": "Conclusion"}, ])
undefined

Step 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) / 2
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) / 2

AI-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) / 3
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) / 3

Optimize

优化模型

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:
    /ai-improving-accuracy
    to measure and improve your content writer
  • 如需完整示例(博客文章、产品描述、新闻通讯),请查看examples.md
  • 如需内容总结而非生成,请使用
    /ai-summarizing
  • 如需内容之外的多步骤管道,请使用
    /ai-building-pipelines
  • 下一步:使用
    /ai-improving-accuracy
    来衡量和优化你的内容写作工具