理论部分

在第 5 篇我们已经能用 “System(角色/规则)+ User(任务/输入)” 把模型输出变得可控。但很多真实业务还会遇到两类问题:

  1. 同一个任务,输出仍然会漂移:格式不稳定、细节忽多忽少、偶尔跑题
  2. 输出难以被程序消费:我们想拿结果去写入数据库、调用工具、做后续计算,但模型给的是一段“看起来对”的自然语言

这时候就需要更“工程化”的提示词策略。最常用的三类是:Few-shot、CoT、结构化输出。

1)Few-shot(少样本提示)

Few-shot 的核心思想是:给模型看我们想要的“示例”,让示例变成隐性的规则

它特别适合:

  • 我们要模型模仿某种输出格式(例如固定字段、固定段落结构)
  • 我们要模型保持口径一致(例如统一的分类标准、统一的评分维度)

示例一般放在 user 内容里或作为多条消息放入 messages,常见形式是:

  • 输入:A → 输出:A’
  • 输入:B → 输出:B’
  • 现在输入:C → 请按同样方式输出:C’

2)CoT(Chain-of-Thought,思维链)

CoT 的核心思想是:让模型把复杂任务分解成步骤再输出结果

它特别适合:

  • 推理/规划型任务(数学、逻辑、步骤规划、长文本分析)
  • 需要“过程正确”才能保证“结果正确”的任务

但要注意:

  • 不是所有任务都需要 CoT,信息抽取这类任务通常更需要“结构化输出”
  • 在产品场景里,你更常见的做法是:让模型在内部推理,但只输出最终结果(避免把中间过程暴露给用户)

3)结构化输出(Structured Output)

结构化输出的核心思想是:我们先定义一个输出 Schema(字段名/类型/含义),再要求模型严格按 Schema 返回

它特别适合:

  • 信息抽取(简历解析、合同关键字段提取、发票/工单解析)
  • 工具调用(把模型输出当作函数参数)
  • 后续要做程序处理(验证、入库、统计、检索)

结构化输出常见两种实现方式:

  • 强提示约束:在 Prompt 里明确声明 JSON 结构、缺失字段填 null、禁止输出解释文字
  • 协议级约束(如果模型/SDK 支持):例如 response_format={"type": "json_object"} 或 JSON Schema 模式

本项目采用的是“强提示约束”的方式:适配性更强,所有兼容的模型都能用。


实践部分

本案例做什么

实现一个“简历解析助手”,输入一段乱糟糟的简历文本,让模型输出一份严格 JSON 格式的结构化结果:

  • 姓名、工作年限、技能、公司、学历、电话、以及一句话总结
  • 如果字段找不到,填 null
  • 输出只能是 JSON,不能夹带解释文字

主要代码

本篇直接使用项目脚本:src/2.2_structured_output.py

import os
import json
from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
client = OpenAI(
    api_key=os.getenv("ZHIPUAI_API_KEY"),
    base_url=os.getenv("ZHIPUAI_BASE_URL")
)

def extract_resume_info(resume_text):
    """
    使用 LLM 提取简历信息为 JSON
    """
    
    # 定义期望的 JSON 结构 (Schema)
    # 这一步非常关键,你描述得越清楚,模型提取越准
    schema_desc = """
    {
        "name": "姓名",
        "years_of_experience": "工作年限(数字)",
        "skills": ["技能列表"],
        "companies": ["工作过的公司列表"],
        "education": "最高学历",
        "phone": "联系电话",
        "summary": "一句话总结求职者的特点"
    }
    """

    system_prompt = f"""
    你是一个专业的 HR 简历解析助手。
    请分析用户的输入,提取关键信息,并以 JSON 格式输出。
    
    输出必须严格遵守以下 JSON 结构:
    {schema_desc}
    
    注意:
    1. 如果某个字段在文本中找不到,请填 null。
    2. 不要输出任何 JSON 以外的解释性文字(如“好的,这是结果...”)。
    3. 直接返回 JSON 字符串。
    """

    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": resume_text}
    ]

    print("🤖 正在解析简历...")
    
    # 调用模型
    # 注:如果模型支持 json_object 模式,建议开启 response_format={"type": "json_object"}
    # 智谱 glm-4-flash 目前主要靠 prompt 约束,我们这里通过 prompt 强力约束
    response = client.chat.completions.create(
        model="glm-4-flash",
        messages=messages,
        temperature=0.1, # 信息提取类任务,温度要低,越严谨越好
    )
    
    content = response.choices[0].message.content
    
    # 清洗数据:有时候模型可能会把 JSON 包裹在 ```json ... ```里,需要去掉
    if "```json" in content:
        content = content.replace("```json", "").replace("```", "")
    
    return content

def main():
    print("=== 📄 智能简历解析助手 (JSON版) ===")
    
    # 模拟一段乱糟糟的简历文本
    default_resume = """
    你好,我叫李四。
    之前在字节跳动干了3年后端,主要用 Go 和 Python。
    后来去了腾讯做了一年架构师。
    我的电话是 186-1234-5678。
    本科毕业于浙江大学。
    希望能找个不加班的工作。
    """
    
    print(f"\n原始文本:\n{default_resume}")
    print("-" * 30)
    
    try:
        json_str = extract_resume_info(default_resume)
        
        # 尝试将字符串解析为 Python 字典,验证是否为合法 JSON
        data = json.loads(json_str)
        
        print("\n✅ 解析成功! 结构化数据如下:\n")
        # 美化打印 JSON
        print(json.dumps(data, indent=4, ensure_ascii=False))
        
        # 演示一下怎么用这个数据
        print("\n[后续代码调用示例]")
        print(f"候选人: {data.get('name')}")
        print(f"技能栈: {', '.join(data.get('skills', []))}")
        
    except json.JSONDecodeError:
        print("\n❌ 解析失败:模型返回的不是标准 JSON。")
        print("模型原始返回:", json_str)
    except Exception as e:
        print(f"\n❌ 发生错误: {e}")

if __name__ == "__main__":
    main()

运行方式

在项目根目录执行:

python3 src/2.2_structured_output.py

运行结果示例

正常情况下,我们会看到模型输出的结构化 JSON(字段值可能略有不同):

=== 📄 智能简历解析助手 (JSON版) ===

原始文本:
...
------------------------------

✅ 解析成功! 结构化数据如下:

{
    "name": "李四",
    "years_of_experience": 4,
    "skills": [
        "Go",
        "Python"
    ],
    "companies": [
        "字节跳动",
        "腾讯"
    ],
    "education": "本科",
    "phone": "186-1234-5678",
    "summary": "..."
}

如果出现 解析失败:模型返回的不是标准 JSON,通常是模型在 JSON 前后输出了额外文字。这个脚本已经做了基础清洗(去掉 ```json 包裹),但也可以进一步加强约束:更严格地声明“只能输出 JSON”,并把温度保持在较低水平。


总结

这一篇我们掌握了三类常用的高级提示词策略:

  • Few-shot:用示例让模型“照着做”
  • CoT:让复杂任务更容易推理正确
  • 结构化输出:让结果可验证、可解析、可直接给程序使用

更多推荐