AI内容生成管线:从提示词工程到多阶段质量控制的架构实践

cover

一、当生成内容不可控:AIGC生产落地的质量困境

大模型内容生成能力在 Demo 阶段看起来很惊艳,但一旦进入生产环境,质量不可控的问题立刻浮现。一个技术博客生成系统,用户输入主题后,模型有时输出结构清晰、论据扎实的文章,有时却生成空洞的套话、重复的段落甚至事实错误。这种不一致性让 AIGC 产品难以建立用户信任。

更具体的场景是:一个营销文案生成平台需要为不同品牌生成风格各异的文案。同一个模型、同一个提示词,不同次调用的输出风格可能差异巨大。品牌 A 需要专业严谨的语气,品牌 B 需要活泼口语化的表达,但模型经常在两种风格之间摇摆。当生成量达到每天数千篇时,人工审核根本跟不上,而自动质量评估又缺乏可靠的标准。

这些问题的根源在于:将内容生成当作单次 LLM 调用,缺乏对生成过程的分阶段控制和输出质量的闭环验证。生产级的内容生成不是"一次调用出结果",而是一个包含意图解析、内容规划、分步生成、质量校验和迭代优化的多阶段管线。

二、多阶段生成管线:从意图到成稿的工程化流程

将内容生成拆分为多个阶段,每个阶段有明确的输入输出和质量标准,是控制生成质量的核心策略。

graph TD
    A[用户输入主题/需求] --> B[意图解析与约束提取]
    B --> C[内容大纲生成]
    C --> D{大纲审核}
    D -->|不通过| C
    D -->|通过| E[分段内容生成]
    E --> F[内容质量校验]
    F --> G{质量达标?}
    G -->|否| H[定位问题段落]
    H --> I[定向重写]
    I --> F
    G -->|是| J[全文润色与格式化]
    J --> K[最终输出]

    subgraph 质量控制层
        D
        F
        G
    end

    subgraph 生成层
        C
        E
        I
        J
    end

意图解析阶段将用户的模糊需求转化为结构化的生成约束:目标读者、文章长度、语气风格、必须包含的要点、禁止出现的内容。这些约束作为后续所有生成阶段的上下文,确保输出始终在预期范围内。

大纲生成阶段让模型先规划结构再填充内容,避免直接生成全文时出现的逻辑混乱和重复。大纲审核可以基于规则(检查是否包含必需章节)或基于 LLM(评估大纲的逻辑完整性)。

分段生成阶段按大纲逐段生成,每段独立调用 LLM,上下文窗口只需要包含当前段落的要求和前文摘要,而非全文。这种方式既降低了 Token 消耗,也提高了每段的生成质量。

三、生产级内容生成管线实现

3.1 意图解析与约束提取

package contentgen

import (
    "context"
    "encoding/json"
    "fmt"
)

// GenerationConstraint 生成约束
type GenerationConstraint struct {
    Topic         string   `json:"topic"`          // 主题
    TargetReader  string   `json:"target_reader"`  // 目标读者
    Tone          string   `json:"tone"`           // 语气风格
    Length        int      `json:"length"`          // 目标字数
    RequiredPoints []string `json:"required_points"` // 必须包含的要点
    ForbiddenWords []string `json:"forbidden_words"` // 禁止出现的词汇
    Format        string   `json:"format"`          // 输出格式
}

// IntentParser 意图解析器
type IntentParser struct {
    llmClient LLMClient
}

// Parse 从用户输入中提取结构化约束
func (ip *IntentParser) Parse(ctx context.Context, userInput string) (*GenerationConstraint, error) {
    prompt := `从以下用户输入中提取内容生成的结构化约束,以JSON格式返回。

用户输入:` + userInput + `

返回格式:
{
    "topic": "文章主题",
    "target_reader": "目标读者描述",
    "tone": "语气风格(如专业/活泼/严谨)",
    "length": 目标字数,
    "required_points": ["必须包含的要点1", "要点2"],
    "forbidden_words": ["禁止词汇1", "禁止词汇2"],
    "format": "输出格式(如markdown/纯文本)"
}

只返回JSON,不要其他内容:`

    resp, err := ip.llmClient.Chat(ctx, ChatRequest{
        Messages: []Message{{Role: "user", Content: prompt}},
        Temperature: 0.1,
    })
    if err != nil {
        return nil, fmt.Errorf("意图解析失败: %w", err)
    }

    var constraint GenerationConstraint
    if err := json.Unmarshal([]byte(resp.Message.Content), &constraint); err != nil {
        return nil, fmt.Errorf("约束解析失败: %w", err)
    }

    return &constraint, nil
}

3.2 大纲生成与审核

// Outline 大纲结构
type Outline struct {
    Title    string        `json:"title"`
    Sections []SectionSpec `json:"sections"`
}

// SectionSpec 章节规格
type SectionSpec struct {
    Heading    string   `json:"heading"`     // 章节标题
    KeyPoints  []string `json:"key_points"`  // 必须覆盖的要点
    WordCount  int      `json:"word_count"`  // 目标字数
}

// OutlineGenerator 大纲生成器
type OutlineGenerator struct {
    llmClient LLMClient
}

// Generate 根据约束生成大纲
func (og *OutlineGenerator) Generate(ctx context.Context, constraint *GenerationConstraint) (*Outline, error) {
    prompt := fmt.Sprintf(`根据以下约束生成文章大纲,以JSON格式返回。

主题:%s
目标读者:%s
语气:%s
目标字数:%d
必须包含的要点:%v

返回格式:
{
    "title": "文章标题",
    "sections": [
        {"heading": "章节标题", "key_points": ["要点1"], "word_count": 300}
    ]
}

要求:
1. 章节数量4-5个
2. 各章节字数之和约等于目标字数
3. 所有必须包含的要点都要分配到具体章节
4. 只返回JSON:`, constraint.Topic, constraint.TargetReader,
        constraint.Tone, constraint.Length, constraint.RequiredPoints)

    resp, err := og.llmClient.Chat(ctx, ChatRequest{
        Messages: []Message{{Role: "user", Content: prompt}},
        Temperature: 0.3,
    })
    if err != nil {
        return nil, fmt.Errorf("大纲生成失败: %w", err)
    }

    var outline Outline
    if err := json.Unmarshal([]byte(resp.Message.Content), &outline); err != nil {
        return nil, fmt.Errorf("大纲解析失败: %w", err)
    }

    return &outline, nil
}

// ValidateOutline 大纲审核
func ValidateOutline(outline *Outline, constraint *GenerationConstraint) error {
    // 检查必须要点是否都被覆盖
    covered := make(map[string]bool)
    for _, sec := range outline.Sections {
        for _, pt := range sec.KeyPoints {
            covered[pt] = true
        }
    }
    for _, req := range constraint.RequiredPoints {
        if !covered[req] {
            return fmt.Errorf("大纲缺少必需要点: %s", req)
        }
    }

    // 检查总字数是否合理
    totalWords := 0
    for _, sec := range outline.Sections {
        totalWords += sec.WordCount
    }
    if totalWords < constraint.Length*80/100 || totalWords > constraint.Length*120/100 {
        return fmt.Errorf("大纲字数%d与目标%d偏差过大", totalWords, constraint.Length)
    }

    return nil
}

3.3 分段生成与质量校验

// SectionGenerator 分段内容生成器
type SectionGenerator struct {
    llmClient LLMClient
}

// GenerateSection 生成单个章节内容
func (sg *SectionGenerator) GenerateSection(ctx context.Context, section SectionSpec, constraint *GenerationConstraint, prevSummary string) (string, error) {
    prompt := fmt.Sprintf(`根据以下要求生成一个章节的内容。

章节标题:%s
必须覆盖的要点:%v
目标字数:%d字
语气风格:%s
目标读者:%s
禁止使用的词汇:%v
%s
请直接输出章节正文,不要输出标题:`,
        section.Heading, section.KeyPoints, section.WordCount,
        constraint.Tone, constraint.TargetReader, constraint.ForbiddenWords,
        conditionalStr(prevSummary != "", "前文摘要(保持衔接):"+prevSummary, ""))

    resp, err := sg.llmClient.Chat(ctx, ChatRequest{
        Messages: []Message{{Role: "user", Content: prompt}},
        Temperature: 0.5,
    })
    if err != nil {
        return "", fmt.Errorf("章节生成失败: %w", err)
    }

    return resp.Message.Content, nil
}

// QualityChecker 内容质量校验器
type QualityChecker struct {
    llmClient LLMClient
}

// QualityReport 质量报告
type QualityReport struct {
    Score       float64  `json:"score"`        // 总分 0-100
    Passed      bool     `json:"passed"`       // 是否达标
    Issues      []string `json:"issues"`       // 问题列表
    Suggestions []string `json:"suggestions"`  // 改进建议
}

// Check 校验内容质量
func (qc *QualityChecker) Check(ctx context.Context, content string, constraint *GenerationConstraint) (*QualityReport, error) {
    prompt := fmt.Sprintf(`评估以下内容的质量,以JSON格式返回评估结果。

评估维度:
1. 主题相关性(0-25分):内容是否紧扣主题"%s"
2. 要点覆盖率(0-25分):是否覆盖了所有要点%v
3. 语气一致性(0-25分):是否符合"%s"语气
4. 内容充实度(0-25分):是否有实质性内容,而非空洞套话

待评估内容:
%s

返回格式:
{
    "score": 总分,
    "passed": 总分>=70为true,
    "issues": ["问题1", "问题2"],
    "suggestions": ["建议1", "建议2"]
}
只返回JSON:`, constraint.Topic, constraint.RequiredPoints, constraint.Tone, content)

    resp, err := qc.llmClient.Chat(ctx, ChatRequest{
        Messages: []Message{{Role: "user", Content: prompt}},
        Temperature: 0.1,
    })
    if err != nil {
        return nil, fmt.Errorf("质量校验失败: %w", err)
    }

    var report QualityReport
    if err := json.Unmarshal([]byte(resp.Message.Content), &report); err != nil {
        return nil, fmt.Errorf("质量报告解析失败: %w", err)
    }

    return &report, nil
}

四、内容生成管线的成本与延迟权衡

多阶段管线的最大代价是 LLM 调用次数增加。一次完整的生成流程可能需要 5-8 次 LLM 调用(意图解析、大纲生成、3-5 段内容生成、质量校验),Token 消耗和延迟都是单次调用的数倍。在成本敏感的场景中,可以通过以下策略优化:使用小模型做意图解析和质量校验(这些任务的复杂度较低),只在大纲生成和内容生成阶段使用大模型;缓存相似主题的大纲模板,减少从零生成的次数。

质量校验的准确性本身也是一个问题。用 LLM 评估 LLM 的输出,存在"同源偏差"——评估模型和生成模型的训练数据重叠,可能对同类错误不敏感。更可靠的做法是结合规则校验(检查必需要点是否出现、字数是否达标、禁止词汇是否包含)和 LLM 校验(评估语义连贯性和逻辑合理性),形成互补。

管线中的重试策略需要谨慎设计。质量不达标时,应该定向重写问题段落而非全文重生成,否则成本和延迟都会失控。定向重写需要质量校验报告精确定位问题所在(如"第二段缺少要点X"),这要求校验阶段的输出足够结构化。

五、总结

AI 内容生成管线的核心设计思想是将"一次生成"拆分为"意图解析-大纲规划-分段生成-质量校验-定向重写"的多阶段流程。每个阶段有明确的输入输出和质量标准,通过结构化约束控制生成方向,通过质量校验闭环保证输出一致性。工程实现中需要平衡调用成本与生成质量:小模型处理低复杂度任务,大模型聚焦核心生成;规则校验兜底确定性要求,LLM 校验评估语义质量。管线化不是增加复杂度,而是将不可控的单次生成转化为可监控、可干预、可迭代的生产流程。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐