LLM 结构化抽取实战:如何逼迫大模型严格输出"3-7字"核心要素?

7 阅读9分钟

📌 背景:分类只是第一步

在上一篇文章中,我分享了如何用 LLM 把 14,088 条地铁乘客反馈分类到马斯洛需求层次中。

但分类只是第一步。知道"这条反馈属于舒适层"还不够,运营方真正想知道的是:到底什么东西让乘客不舒服?

比如这条反馈:

"南京地铁的空调能不能调高点?每次坐都冻成狗,夏天穿裙子根本扛不住。"

分类结果:舒适层 + 负面

但具体问题是什么?答案是:列车空调

再比如这条:

"工作人员帮我找回了钱包,真的太感谢了!"

分类结果:尊重层 + 正面

具体要素是:安检服务 / 失物招领

这就是本文要解决的问题——从口语化的微博文本中,精准提取 3-7 个字的核心服务要素

🤔 为什么是"3-7 字关键词"?

地铁运营方需要的不是长篇大论,而是可以直接定位到具体设施或服务的名词短语

反馈原文理想提取结果为什么
"工作人员帮我找回了钱包,太感谢了"安检服务指向具体服务类型
"早晚高峰挤得怀疑人生,门都关不上"车厢拥挤可量化、可改进的问题
"报站声音太小,戴着耳机根本听不清"报站音量具体可调节的参数
"毕业季的车厢装饰太浪漫了吧"毕业季装饰具体可复制的活动
"S6号线什么时候通车啊"线路开通具体可追踪的进展

核心原则

  • 长度 3-7 个汉字(最精简的名词短语)
  • 必须指向具体可改进的服务要素
  • 禁止形容词和情感词(如"很好""太烂")
  • 禁止只输出"南京地铁"(没有信息量)

🛠️ 第一版提示词:过于自由,结果失控

我最初写的提示词很简单:

请从以下乘客反馈中提取 1-3 个南京地铁的具体服务要素:

{text}

结果惨不忍睹

反馈原文模型输出问题
"空调太冷了""空调温度过低,希望调高"输出整句话,不是关键词
"工作人员态度特别好""南京地铁工作人员"太长,且包含冗余信息
"每次坐都觉得很干净""干净"形容词,不是可操作的服务要素
"换乘太远了走得腿疼""换乘体验差"包含评价词"差"

问题分析

  1. 没有约束输出格式:模型不知道要输出短语而不是句子
  2. 没有示例:模型不理解什么是"服务要素"
  3. 没有长度限制:输出结果参差不齐
  4. 没有禁止修饰词:模型会输出"好""差"等形容词

🎯 第二版:加格式约束 + 加示例

我开始细化要求:

请从乘客反馈中提取南京地铁的具体服务特征要素,要求:
1. 每个要素 3-7 个汉字
2. 必须是可以被地铁管理部门直接处理的具体问题
3. 采用最精简的名词短语
4. 禁止任何修饰词和情感词
5. 用中文逗号分隔,不要解释

示例:
输入:"坐南京地铁十几年了真有感情" → 输出:地铁历史
输入:"毕业季的车厢装饰太浪漫了" → 输出:毕业季装饰
输入:"列车空调冷得像冰窖" → 输出:列车空调
输入:"安检员帮我找回了钱包" → 输出:安检服务

待分析内容:{text}

效果:输出格式规范了,大部分能输出正确的短语形式。

但新问题出现了:

  • 模型经常输出"无法提取"或"无"
  • 当乘客反馈比较隐晦时,模型不知道该不该提取
  • 多要素场景下,模型有时会把几个要素混在一起

💡 第三版:增加"转化示例",教模型推理

我意识到:模型需要看到从口语到要素的映射过程

于是我在 system prompt 中增加了完整的推理框架:

messages = [
    {
        "role": "system",
        "content": """你作为南京地铁服务分析专家,请严格从乘客反馈中提取南京地铁的具体服务特征要素。请遵守:

        核心原则:
        1. 所有输出关键词必须直接指向南京地铁的具体属性/服务/特征
        2. 表面现象→本质问题:将乘客的具体遭遇映射到南京地铁的可改进点
        3. 采用最精简的名词短语(3-5个汉字)
        4. 禁止任何修饰词和情感词

        输出要求:
        - 必须包含"地铁"或能明确对应其服务的词汇
        - 每个要素3-7个汉字
        - 用中文逗号分隔,不要解释"""
    },
    {
        "role": "user",
        "content": f"""请提取南京地铁的具体服务要素:

        【乘客反馈原文】
        {text}

        【分类信息】
        需求层次:{demand_level}
        情感倾向:{sentiment}

        【转化示例】
         输入:"坐南京地铁十几年了真有感情"
         → 输出:地铁历史

         输入:"毕业季的车厢装饰太浪漫了"
         → 输出:毕业季装饰

         输入:"列车空调冷得像冰窖"
         → 输出:列车空调

         输入:"安检员帮我找回了钱包"
         → 输出:安检服务

        【当前任务】
        请提取1-3个南京地铁具体服务要素:"""
    }
]

这一版的效果

  • 提取成功率从约 70% 提升到 95%
  • 输出格式几乎 100% 符合要求
  • 多要素场景下能正确分离(如"拥挤+空调差" → "车厢拥挤,列车空调")

📊 最终提示词结构

经过迭代,我总结出要素提取提示词的黄金结构:

1. 【System Prompt:角色与原则】
   - 赋予专家身份
   - 明确核心原则(表面现象→本质问题)
   - 规定输出格式(长度、禁止项)

2. 【User Prompt:任务与示例】
   - 提供原文
   - 提供分类信息(需求层次+情感倾向,帮助模型理解语境)
   - 提供转化示例(4个典型案例)
   - 明确当前任务

3. 【后处理】
   - 按逗号分割
   - 过滤长度不符合的
   - 去重

💡 四个核心经验

1. 提供分类信息作为上下文

在提取要素时,我会把该条反馈的"需求层次"和"情感倾向"也传给模型。这让模型更容易理解语境——比如知道这是"舒适层+负面",模型会更倾向于提取环境相关要素而非服务相关要素。

2. 转化示例要覆盖不同场景

我的 4 个示例分别覆盖了:

  • 隐晦表达 → 本质问题("有感情"→"地铁历史")
  • 正面评价 → 具体活动("装饰浪漫"→"毕业季装饰")
  • 负面抱怨 → 具体设施("冷得像冰窖"→"列车空调")
  • 服务互动 → 服务类型("找回钱包"→"安检服务")

3. 后处理过滤很重要

即使提示词很完善,模型偶尔还是会输出不符合要求的內容。我在代码中加入后处理:

factors = [
    f.strip()
    for f in result.replace(",", ",").split(",")
    if f.strip() and 2 <= len(f.strip()) <= 7
]
return factors if factors else ["无法提取"]

4. 长度限制要合理

经过测试,3-7 汉字是最优区间:

  • 太短(1-2字):信息量不足,如"空调"无法区分是列车空调还是站厅空调
  • 太长(8字以上):不够精简,如"车厢空调温度太低"可以简化为"列车空调"

🔧 完整代码

def analyze_factors_with_openai(text, demand_level, sentiment):
    """使用OpenAI API分析情感因素"""
    messages = [
        {
            "role": "system",
            "content": """你作为南京地铁服务分析专家,请严格从乘客反馈中提取南京地铁的具体服务特征要素。请遵守:

            核心原则:
            1. 所有输出关键词必须直接指向南京地铁的具体属性/服务/特征
            2. 表面现象→本质问题:将乘客的具体遭遇映射到南京地铁的可改进点
            3. 采用最精简的名词短语(3-5个汉字)
            4. 禁止任何修饰词和情感词

            输出要求:
            - 必须包含"地铁"或能明确对应其服务的词汇
            - 每个要素3-7个汉字
            - 用中文逗号分隔,不要解释"""
        },
        {
            "role": "user",
            "content": f"""请提取南京地铁的具体服务要素:

            【乘客反馈原文】
            {text}

            【分类信息】
            需求层次:{demand_level}
            情感倾向:{sentiment}

            【转化示例】
             输入:"坐南京地铁十几年了真有感情"
             → 输出:地铁历史

             输入:"毕业季的车厢装饰太浪漫了"
             → 输出:毕业季装饰

             输入:"列车空调冷得像冰窖"
             → 输出:列车空调

             输入:"安检员帮我找回了钱包"
             → 输出:安检服务

            【当前任务】
            请提取1-3个南京地铁具体服务要素:"""
        }
    ]

    try:
        response = client.chat.completions.create(
            model="gpt-3.5-turbo",
            messages=messages,
            temperature=0.1,
            max_tokens=50
        )
        result = response.choices[0].message.content.strip()
        factors = [
            f.strip()
            for f in result.replace(",", ",").split(",")
            if f.strip() and 2 <= len(f.strip()) <= 7
        ]
        return factors if factors else ["无法提取"]
    except Exception as e:
        print(f"API处理出错: {str(e)}")
        return ["API错误"]

📈 提取结果统计

对 13,247 条有效分类数据进行要素提取,统计结果:

需求层次-情感有效提取数高频要素 Top 3
舒适层-负面3,201列车空调(584)、车厢拥挤(390)、噪音异味(182)
尊重层-正面2,481暖心祝福(546)、服务贴心(161)、工作人员(152)
保障层-负面1,756设施不足(295)、换乘不便(232)、电梯缺失(178)
基础层-负面1,782列车故障(179)、安全隐患(285)、延误(150)
共鸣层-正面1,458归属感(320)、怀旧感(280)、城市自豪(195)

🔗 完整代码与项目

完整实现已开源在 GitHub:
👉 nanjing-metro-analysis/scripts/03_topic_extraction/

包含:

  • 完整的提示词模板
  • 批量处理 14,000 条数据的工程代码
  • 统计分析与可视化

📮 写在最后

要素提取看似简单,但要保证输出格式稳定、可解析,需要精心设计提示词和后处理逻辑。核心思路是:给模型明确的角色、清晰的示例、严格的格式约束

下一篇将分享《拒绝拍脑袋!Sentence-BERT 文本聚类如何用轮廓系数自动寻优?》,教你如何把几千个要素聚合成十几个主题。


本文是"南京地铁乘客需求分析"系列的第二篇。