一个金融场景的三条 Prompt 流水线

这是「提示词工程」这门课的综合落地章,用同一个金融行业场景把 Zero-Shot / Few-Shot / In-context Learning 串成三条完整流水线:金融文本分类、金融信息抽取、金融文本匹配。它的价值不在算法,而在一个可复制的项目模板:角色设定 → 类别/字段 Schema → Few-Shot 示例 → 输出格式约束 → 逐条推理 → 后处理。


一、这份材料解决什么问题

前面几章讲的是”提示词怎么写才规范”(5 大原则、6 类进阶技术)。但知道怎么写和能交付一个功能之间隔着一整条工程流水线。这一章补的就是这一段:

给定一个真实业务场景(金融),如何从零设计出一套能跑的 Prompt 方案?

它的三个业务落点不是随便挑的,而是 LLM 在文本处理上的三种最基础的任务范式:

任务 本质 输出形态
金融文本分类 N 选 1 的判别 一个类别名
金融文本信息抽取 从自由文本里抠结构化字段 一个 JSON 对象
金融文本匹配 两段文本的关系判定 一个是/否布尔值

这三种输出形态(枚举值 / 结构化 JSON / 布尔)恰好覆盖了 LLM 应用里 90% 的输出解析场景。学会用提示词把模型”夹”到这三种格式上,后面不管做 RAG 的答案抽取、Agent 的工具参数生成,还是新闻分类项目的新闻标题分类,都是同一套手法。

它在新闻分类流水线里的位置:新闻分类项目的 LLM 环节(DeepSeek API 做零样本/少样本分类)和这一章是同一件事,只换了业务和模型。这一章可以当作新闻分类项目 LLM 环节的”预演版”,只是类别数是 4 而不是 10、模型是 Qwen 而不是 DeepSeek。所以第七节我会把它逐环节映射过去。

课件的学习目标原文(也是本报告的权重来源):了解项目背景、了解整体项目任务、掌握 Few-Shot / Zero-Shot 的思想、完成课程代码开发。也就是说,背景是【了解】,提示词范式是【掌握】,代码是要跑通的实操。


二、核心结论

  1. 一个万能四步流水线:加载模型 → 构建提示词(讲清任务 + 钉死输出格式)→ 模型预测 → 后处理。三条任务线的”项目思路”完全是这四步的复制粘贴,说明这是一套模板化方法而不是三次独立踩坑。

  2. Prompt 设计永远只抓两件事:课件在三个任务里反复写了同一句话,”需要向模型解释什么叫作「XX 任务」” + “需要让模型按照我们指定的格式输出”。第一句决定模型懂不懂,第二句决定程序能不能解析。这是全章最该背下来的一句话。

  3. Few-Shot 的载体有两种,适用范围不同:分类任务把示例拼进一条 user 消息(pre_prompt + 问句);抽取和匹配任务用多轮对话历史([system, user, assistant, user, assistant, ...],调用时 messages = history + messages)。前者省事,后者能借 role 把”标准答案”和”提问”分得更干净。

  4. 示例(Bot/assistant 部分)是人工写死的:课件两处都特意强调”Bot 的部分也是由人工输入的”。模型的 In-context Learning 学的不是你说了什么道理,而是你给的那对「输入→输出」的形状。

  5. 一套方案能不能上线,卡在后处理而不是模型:三个任务的输出都是自然语言(新闻报道。、{'日期': ['2023-01-10'], ...}、是/不是),必须 strip()、剥 markdown 代码块、json.loads()、与 class_list 精确比对。这中间任何一步失败,模型答对了也没用。


三、知识地图

3.1 基于Prompt工程的金融行业项目
├── 学习目标【了解+掌握】
│     ├── 了解项目背景
│     ├── 了解整体项目任务
│     ├── 掌握 Few-Shot / Zero-Shot 的思想          ← 【掌握】本章核心
│     └── 完成课程代码开发
│
├── 一、项目介绍【了解】
│     ├── 1 项目背景:金融数据激增 → 需要自动化分析
│     │     └── AI 在金融的三个应用方向
│     │           ├── 风险评估(风险事件识别 / 反欺诈 / 信用评分)
│     │           ├── 投资决策(历史数据 / 财务报表分析)
│     │           └── 客户服务(智能客服)
│     └── 2 项目任务与方法
│           ├── 三大业务场景:文本分类 / 信息抽取 / 文本匹配
│           ├── 大模型选择:Qwen  (实际调用 `qwen-plus`)
│           └── 方法:Few-Shot + Zero-Shot 设计 prompt,激发"涌现能力"
│
├── 二、LLM 实现金融文本分类【掌握】
│     ├── 1 需求:4 段金融文本 → 判定所属报告类型
│     ├── 2 项目思路:加载模型 → 构建提示词 → 模型预测 → 后处理
│     ├── 3 Prompt 设计:解释「文本分类」 + 指定输出格式 + Few-Shot 示例
│     └── 4 代码实现:class_examples → init_prompts() → model_chat() → 循环推理
│
├── 三、LLM 实现金融文本信息抽取【掌握】
│     ├── 1 需求:股票信息文本 → SPO / 实体字段,Schema = {'金融': [日期, 股票名称, 开盘价, 收盘价, 成交量]}
│     ├── 3 Prompt 设计:解释「信息抽取」 + 要求 JSON 输出 + 缺失值约定
│     └── 4 代码实现:ie_examples → IE_PATTERN → build_prompt()(多轮 history)→ model_chat()
│
├── 四、LLM 实现金融文本匹配【掌握】
│     ├── 1 需求:3 个句子对 → 判定语义是否相似
│     ├── 3 Prompt 设计:解释「文本匹配」 + "只回答是否相似,不要多余回答"
│     └── 4 代码实现:examples{'是':[...], '不是':[...]} → init_prompts() → model_chat()
│
└── 五、本章小结:Zero-Shot / Few-Shot 完成三任务闭环


四、原理详解

4.1 为什么金融行业是 Prompt 工程的天然靶场

课件的背景论证很直白:金融数据量激增 → 投资者和研究者想靠深度分析减少决策失误 → 所以”针对金融数据的分析方法研究是目前十分有益且热门的话题”。

它列出的三个 AI 应用方向,其实对应三种对准确率极度敏感的场景:

方向 课件原文描述 对 Prompt 的要求
风险评估 识别不同金融风险事件类型、反欺诈行为、信用评分低等风险,全面评估贷款人信用状况 类别边界必须稳,宁可说”不确定”也不能错判
投资决策 分析历史数据、财务报表,提供精准投资决策支持 抽取的数值必须一字不差,单位不能丢
客户服务 智能客服,提供便捷而准确的答案 输出格式稳定,方便程序直接消费

这就解释了为什么课件选的三个任务全部是判别/抽取类而不是生成类:金融场景不要”文采”,要可解析、可复核、可审计的结构化输出。这也是所有 to-B LLM 项目的共同特征。

注意课件的一处表述:它说方法目的是”激发大模型的涌现能力”。严格讲,Prompt 里的 Few-Shot 激活的是上下文学习(In-context Learning)能力,模型在预训练中已经学会了”看几个例子然后照做”这个模式,示例只是把这个能力引导出来,并没有让模型产生新能力。被追问时用”In-context Learning”这个词比”涌现”更准确。

4.2 通用四步流水线(三条任务线共用)

课件在分类、抽取、匹配三节的”项目思路”里,写了完全相同的四步:

加载模型 → 构建提示词(描述清楚任务及输出格式) → 模型预测 → 后处理

拆开看每一步的工程含义:

步骤 课件里的实际代码 工程含义
加载模型 openai.OpenAI(api_key=os.environ['DASHSCOPE_API_KEY'], base_url='https://dashscope.aliyuncs.com/compatible-mode/v1') 不自己部署模型,走 DashScope 的 OpenAI 兼容模式。换厂商只改 base_url + model,业务代码不动
构建提示词 init_prompts() / build_prompt() 把”任务说明 + 类别/字段定义 + 示例 + 格式要求”一次性组装好,与推理逻辑解耦(这俩函数是纯函数,不碰网络)
模型预测 client.chat.completions.create(model='qwen-plus', messages=..., stream=False) 单次同步调用,stream=False(分类场景不需要流式)
后处理 print("class result: ", resp) / 人眼核对 / json.dumps(..., ensure_ascii=False) 课件在这里最弱,这是它最大的工程缺口,见 4.7

这个四步结构本身就是最该带走的东西:它把”提示词”从一个字符串变成了一个有输入、有组装函数、有调用接口、有解析出口的模块。

4.3 任务一:金融文本分类(Few-Shot 拼进单条 prompt)

需求:4 段金融文本,判定每段属于 ['新闻报道', '公司公告', '财务公告', '分析师报告'] 中的哪一类。
业务价值(课件列出):金融新闻自动分类、公司文档智能归档、投资研究材料整理、金融数据预处理。

Prompt 设计的两条考虑(课件原文):
1. 需要向模型解释什么叫作「文本分类任务」;
2. 需要让模型按照我们指定的格式输出。

具体做法:先做一次 Zero-Shot 定位,用一句 '你是一个金融专家,需要对输入的金融领域文本进行分析...' 把角色和任务说清楚;再用 Few-Shot 铸造形状,给模型展示几组”User 问 → Bot 答”的正确示范。课件明确指出:“上述例子中 Bot 的部分也是由人工输入的,其目的是希望看到在看到类似 User 中的句子时,模型应当做出类似 Bot 的回答。”

代码结构:

# 1. 四类,每类一个人工示例(这是 Few-Shot 的"示例池")
class_examples = {
    '新闻报道': '今日,股市经历了一轮震荡,受到宏观经济数据和全球贸易紧张局势的影响。...',
    '财务报告': '本公司年度财务报告显示,去年公司实现了稳步增长的盈利,...',
    '公司公告': '本公司高兴地宣布成功完成最新一轮并购交易,收购了一家在人工智能领域领先的公司。...',
    '分析师报告': '最新的行业分析报告指出,科技公司的创新将成为未来增长的主要推动力。...'
}

# 2. 组装前置 prompt(把示例"拼"进一条字符串)
def init_prompts(class_examples):
    class_list = list(class_examples.keys())
    pre_prompt = f'你是一个金融专家,需要对输入的金融领域文本进行分析,将类型归类到{class_list},对于不在这四类中的数据,输出‘不清楚类型’。以下是几个示例分类:'
    for class_type, example in class_examples.items():
        pre_prompt = pre_prompt + f'"""{example}是{class_list}里的什么类别?"""' + f'"""{class_type}。"""'
    return {'class_list': class_list, 'pre_prompt': pre_prompt}

组装出来的 pre_prompt 长这样:

你是一个金融专家,需要对输入的金融领域文本进行分析,将类型归类到['新闻报道', '财务报告', '公司公告', '分析师报告'],对于不在这四类中的数据,输出'不清楚类型'。以下是几个示例分类:
"""今日,股市经历了一轮震荡...是['新闻报道', '财务报告', '公司公告', '分析师报告']里的什么类别?"""
"""新闻报道。"""
"""本公司年度财务报告显示...是[...]里的什么类别?"""
"""财务报告。"""
(其余 2 组同理)

这段 prompt 里有 4 个设计决策,每一个都值得单独记住:

设计决策 原文片段 为什么这么做
角色设定 你是一个金融专家 用一个 persona 把模型的输出分布收窄到金融语域,减少”通用助手”式的解释性废话
类别枚举 将类型归类到['新闻报道', ...] 把闭集写进 prompt。不写清楚类别全集,模型会自由发明标签
兜底类别 对于不在这四类中的数据,输出'不清楚类型' 给模型一个合法的”我不知道”出口,防止它硬凑一个类别。这是分类任务的经典防幻觉手段
示例锚定格式 """...是[...]里的什么类别?""" / """新闻报道。""" 用三引号把”问句”和”答案”隔开,让模型看到答案就是类别名本身,不带解释

推理时:

for sentence in sentences:
    content = f"{prompts_info['pre_prompt']}" + f'"""{sentence}是 {prompts_info["class_list"]} 里的什么类别?"""'
    resp = model_chat(content)

注意这里:示例部分 + 当前问题被拼成了一条 user 消息(messages = [{"role": "user", "content": content}])。这是 Few-Shot 最省事的实现方式。

4.4 任务二:金融文本信息抽取(多轮 history + JSON 约束)

需求:从股票信息文本中抽取实体。课件给的 Schema 是一个 dict:

# 不同实体下的具备属性
schema = {
    '金融': ['日期', '股票名称', '开盘价', '收盘价', '成交量'],
}

期望输出(严格 JSON):

{"日期": ["2023-02-15"], "股票名称": ["佰笃[BD]美股"], "开盘价": ["10美元"], "收盘价": ["13美元"], "成交量": ["460,000"]}
{"日期": ["2023-04-05"], "股票名称": ["盘古(0021)"], "开盘价": ["23元"], "收盘价": ["26美元"], "成交量": ["310,000"]}

刻意注意输出结构的两个细节:所有值都是列表(即使只有一个值),这样才能表达”一句话里出现多个日期/多只股票”。这是 Schema 设计的通用习惯:单值字段也用 list 包,避免后续改结构。

Prompt 设计的两条考虑(与分类同构):
1. 需要向模型解释什么叫作「信息抽取任务」;
2. 需要让模型按照我们指定的格式(json)输出。

三条输出纪律(写进示例的 user 消息里):
– 按 JSON 格式输出;
– 句子中没有的信息用 ['原文中未提及'] 表示;
– 多个值之间用 ',' 分隔。

代码结构,这次改用多轮对话历史:

ie_examples = [{
    'content': '2023-01-10,股市震荡。股票古哥-D[EOOE]美股今日开盘价100美元,一度飙升至105美元,随后回落至98美元,最终以102美元收盘,成交量达到520000。',
    'answers': {'日期': ['2023-01-10'], '股票名称': ['古哥-D[EOOE]美股'], '开盘价': ['100美元'],
                '收盘价': ['102美元'], '成交量': ['520000']}
}]

IE_PATTERN = "{}\n\n提取上述句子中的实体,并输出,上述句子中不存在的信息用['原文中未提及']来表示。"

def build_prompt(ie_examples):
    history_list = [{'role': 'system',
                     'content': "你是信息提取专家,需要需要完成信息抽取任务。"
                                "我会给你一个句子,你需要提取句子中的实体,并输出,如果句子中有不存在的信息用['原文中未提及']来表示。"}]
    for example in ie_examples:
        history_list.append({'role': 'user', 'content': IE_PATTERN.format(example['content'])})
        history_list.append({'role': 'assistant', 'content': json.dumps(example['answers'], ensure_ascii=False)})
    return {'history_list': history_list}

调用时把历史拼在当前问题前面:

messages = [{"role": "user", "content": content}]
response = client.chat.completions.create(model='qwen-plus', messages=history + messages, stream=False)

两个容易被忽略的工程点:

  1. json.dumps(example['answers'], ensure_ascii=False), ensure_ascii=False 让中文原样输出而不是 \u65e5\u671f 转义。人类看得懂、token 也更省。写 Few-Shot 的 assistant 示例时几乎总要加这个参数。
  2. role 分工的语义:system 讲规矩(你是抽取专家 + 缺失值约定),user 给任务(这句文本要抽什么),assistant 示范答案(标准 JSON)。比分类任务”全部塞进一条 user 字符串”更接近真实对话格式,模型的遵守度通常更高。

4.5 任务三:金融文本匹配(布尔输出 + 强抑制”多余回答”)

需求:3 个句子对,判断语义是否相似。期望输出 ['相似', '不相似', '相似']。

Prompt 设计的两条考虑:
1. 需要向模型解释什么叫作「文本匹配任务」;
2. 需要让模型按照我们指定的格式输出。

示例池按”标签分桶”组织,这是本节最值得抄的一个结构:

examples = {
    '是': [
        ('公司ABC发布了季度财报,显示盈利增长。', '财报披露,公司ABC利润上升。'),
    ],
    '不是': [
        ('黄金价格下跌,投资者抛售。', '外汇市场交易额创下新高。'),
        ('央行降息,刺激经济增长。', '新能源技术的创新。')
    ]
}

负例给了 2 组、正例只给 1 组,而且两个负例是两种不同的”不相似”:
– 黄金价格下跌 / 外汇市场交易额创下新高,同一个大金融领域,但讲的是两个不同事实(易混淆的”领域相同 ≠ 语义相同”);
– 央行降息 / 新能源技术创新,跨领域,一眼不同。

这就是 Few-Shot 里”示例要覆盖边界情况”的具体做法:不要只给显然的例子,要专门给容易被模型判错的那一类。

system 里的”话痨抑制”:

'现在你需要帮助我完成文本匹配任务,当我给你两个句子时,你需要回答我这两句话语义是否相似。'
'只需要回答是否相似,不要做多余的回答。'

不要做多余的回答 这七个字是判断题场景的标配。不加它,模型很爱输出”这两句话都涉及金融市场,但一句讲……另一句讲……因此不相似。”,人看着挺好,程序解析直接崩。

统一提问模板(保证示例和真实请求格式完全一致):

sentence_with_prompt = f'句子一: {sentence1}\n句子二: {sentence2}\n上面两句话是相似的语义吗?'

这是 Few-Shot 的隐含铁律:示例里的问题模板和推理时的问题模板必须逐字符一致,否则模型学到的是”某种格式”而不是”这个任务”。

4.6 三种任务的 Prompt 组装方式对比(本章的技术主线)

维度 分类 信息抽取 文本匹配
输出形态 枚举标签 结构化 JSON 布尔(是/不是)
示例载体 拼进单条 user 字符串 多轮 messages(system+user+assistant) 多轮 messages(system+user+assistant)
角色设定 你是一个金融专家 你是信息提取专家 现在你需要帮助我完成文本匹配任务
Schema 显式给出 是(4 个类别) 是(5 个字段)但只在需求描述里,没进代码(见 5.6) 否(不需要)
兜底机制 不清楚类型 ['原文中未提及'] 只允许”是/不是”两个词
格式约束强度 中(示例锚定) 强(JSON + 缺失值约定 + 分隔符约定) 强(禁用多余回答)
示例数量 4 组(每类 1 组) 1 组(完整覆盖 5 字段) 3 组(1 正 2 负)
业务难点 类别边界(新闻报道 vs 分析师报告) 字段完整性与单位保真 领域相同但语义不同的干扰

为什么分类用拼接、后两个用 history?课件没有解释,但从结构看是自然演化的:分类任务的”问答”是同一句话(输入即问题),拼在一起语义通顺;抽取和匹配的”问题”是一个固定模板(IE_PATTERN / 句子一...句子二...),把它和标准答案拆成 user/assistant 两轮更贴合聊天模型的训练格式。实践建议:能用多轮 messages 就用多轮,因为 role 提供了额外的结构信号,而且示例池可以动态增删而不必改字符串模板。

4.7 后处理:课件里最薄、工程上最厚的一环

课件在三处都写了”后处理”是流水线的第四步,但代码里只写到 print(resp) 就结束了,分类任务打印 class result: ...,抽取任务直接 print(result),匹配任务打印 result-->...。

也就是说课件的后处理是”人眼核对”。真实上线必须自己补上:

import json, re

def parse_label(raw: str, class_list: list) -> str:
    """分类后处理:先清洗,再对齐枚举,最后兜底"""
    text = raw.strip().strip('。.’').strip()
    # 1) 完全命中枚举 → 直接用
    if text in class_list:
        return text
    # 2) 模型多说了几句 → 找出唯一命中的类别名
    hits = [c for c in class_list if c in text]
    if len(hits) == 1:
        return hits[0]
    # 3) 命中兜底词 / 命中多个 / 没命中 → 标记待人工或降级
    return '__UNKNOWN__'

def parse_json(raw: str) -> dict:
    """抽取后处理:剥 markdown 代码块 + 容错解析"""
    text = raw.strip()
    text = re.sub(r'^

(?:json)?|

try:
return json.loads(text)
except json.JSONDecodeError:
return {} # 解析失败 → 走重试或降级

这三个函数的每一行都对应一个真实会发生的失败模式(详见第五节)。

4.8 关于 Zero-Shot 与 Few-Shot 的准确理解

课件把方法表述为”基于 Few-Shot + Zero-Shot 的思想,设计 prompt”。严格说,本章三条任务线实际用的都是 Few-Shot(都给了示例)。Zero-Shot 在这里的角色是基线:

  • Zero-Shot:不提供示例,直接靠模型预训练知识完成任务(”像问专家问题,他凭经验直接回答”)。分类任务里那句 你是一个金融专家,需要对输入的金融领域文本进行分析... 就是 Zero-Shot 的第一层;但一旦后面附上 4 组示例,整体就升级为 Few-Shot 了。
  • Few-Shot:通过 1~3 个参考示例引导模型理解模式。范例的作用是把模糊的任务定义”实例化”——与其解释一百字”什么叫分类”,不如给一组例子。
  • In-context Learning:这才是底层机制的名字。示例不更新任何权重,它们只是待在上下文里,让模型在前向推理时就”照葫芦画瓢”。所以示例越多、越贴近真实分布,效果越好;但它们不改变模型的长期知识,换个会话就没了。

五、易错点与常见误解

5.1 ❌ 示例里的答案(Bot 部分)是模型生成的 → ✅ 是人工写死的
课件在两处(分类、抽取)都特意写明”Bot 的部分也是由人工输入的”。Few-Shot 的质量完全取决于你写的这几个标准答案,写错了模型就学错。
为什么:In-context Learning 学的就是”输入→输出”的映射形状。


5.2 ❌ 类别标签可以在不同地方换着写(财务报告 / 财务公告)→ ✅ 标签字符串必须全局唯一、逐字符一致
这是课件里真实存在的标签漂移:需求里期望输出写的是 ['新闻报道', '公司公告', '财务公告 '分析师报告'](还漏了一个逗号),而代码里的 class_examples 用的是 '财务报告';Few-Shot 示例的 Bot 答案也是 财务报告。。
为什么:后处理是拿模型输出跟 class_list 做字符串比对的。只要模型学会了写成 财务公告(因为它在你给的期望列表里见过这个词),你的 if text in class_list 就永远命中不了,模型答得”对”也全被判为错。


5.3 ❌ 期望输出格式可以每次写不一样 → ✅ 期望输出必须和 prompt 里的格式约定完全对齐
匹配任务的需求里写”期望模型输出的结果为 ['相似', '不相似', '相似']“,但 prompt 里的 system 说”回答是否相似”、示例的 assistant 答案写的是 '是' / '不是'。
为什么:模型只会遵守 prompt 里那套约定,不会遵守你脑子里那套。评测脚本按 ['相似','不相似'] 判定的话,准确率会直接归零。这类不一致是”模型没做对”和”评测写错了”最难分辨的一类 bug。


5.4 ❌ Few-Shot 示例给一份就够了、当然是越多越好 → ✅ 示例要覆盖边界,不是堆数量
匹配任务的示例池给的是 1 正 2 负,两个负例分别代表”同领域不同事实“和”跨领域“。
为什么:示例的作用是划定决策边界。只给显然的例子(”股票涨/股票大涨 = 是”),模型学不到”同属金融但不相似该怎么判”——而这恰恰是唯一的难点。示例该从混淆矩阵里挑,不该从数据里随手抽。


5.5 ❌ 分类任务不需要兜底,模型总能选一个 → ✅ 必须有 不清楚类型 这样的出口
课件在 pre_prompt 里显式写了”对于不在这四类中的数据,输出’不清楚类型'”。
为什么:闭集分类的 prompt 不给出口时,模型面对域外文本会硬塞一个类别(这是幻觉在分类任务上的表现形态)。给了出口之后,”不清楚类型”本身就是一个可被监控的信号(兜底率突然上升 = 输入分布漂移),工程价值比硬猜高得多。


5.6 ❌ prompt 设计稿和代码里的 prompt 是一回事 → ✅ 代码里 inline 的版本常常比设计稿”缩水”
抽取任务的设计稿里,user 消息包含”提取上述句子中’金融'('日期', '股票名称', '开盘价', '收盘价', '成交量')类型的实体,并按照 JSON 格式输出”;但代码里的 IE_PATTERN 只剩 "{}\n\n提取上述句子中的实体,并输出,上述句子中不存在的信息用['原文中未提及']来表示。"——字段枚举和”JSON 格式”这两个关键约束在代码里消失了,靠 assistant 示例里 json.dumps() 的答案间接暗示”要输出 JSON”。
为什么:这类”文档-代码漂移”是项目里最常见的隐性 bug。示例确实能隐含格式,但缺了字段枚举,模型就不知道 schema 里的五个字段才是抽取目标,很容易多抽(比如把”一度飙升至105美元”也抽出来)。约束要写进 prompt,不要只靠示例暗示。


5.7 ❌ 抽取出来的数字就是数字 → ✅ 抽出来的是”字符串字面量”,单位必须原样保留
课件的数据本身就是脏的:'2023-04-05,...股票盘古(0021)开盘价23元,...最终以26美元收盘' —— 同一句话里开盘价用”元”、收盘价用”美元”;'成交量微幅增加至460,000' 里带千分位逗号。
期望输出里保留了原样:["23元"]、["26美元"]、["460,000"]。
为什么:LLM 做的是文本抽取,不是单位换算。让它顺手做归一化,等于把”抽取”和”计算”两个错误来源混在一起了。正确做法:抽原样 → 后处理阶段再用代码/规则统一单位和量纲。这也是所有”LLM 输出 → 下游系统消费”场景的通用原则。


5.8 ❌ messages 只要把历史拼在前面就行,顺序无所谓 → ✅ 必须 history + messages,当前问题永远在最后
课件三处调用的都是 messages=history + messages(messages 是当前这一轮的 user 消息)。Python 的 list 相加顺序就是消息顺序。
为什么:聊天模型的输入是有序的消息序列,最后一条 user 消息才是”要回答的问题”。顺序反了,模型会把示例当成待处理的输入,或者直接困惑。


5.9 ❌ 数据版本、日期这类小细节不重要 → ✅ 需求稿和代码里的样本要同步
抽取任务的需求里两条样本日期是 2023-02-15 / 2023-04-05,代码里是 2025-02-15 / 2025-04-05,其余文字完全相同。
为什么:这类不一致说明”文档更新了代码没更新”(或者反过来)。在 LLM 项目里它会直接污染评估——你按文档写的期望输出,跑代码时可能不一样,最后算出来的准确率是假的。


六、横向对比与研究脉络

6.1 三条任务线的技术方案对比

维度 金融文本分类 金融信息抽取 金融文本匹配
Prompt 范式 Few-Shot(4 类各 1 示例) Few-Shot(1 个完整示例) Few-Shot(1 正 2 负示例)
消息结构 单条 user(示例拼接) [system, user, assistant] ×1 [system, (user, assistant) ×3]
输出约束手段 类别枚举 + 示例锚定 JSON + 缺失值约定 + 分隔符约定 自然语言强抑制(”不要多余回答”)
参数敏感度 高(标签必须精确匹配) 高(JSON 必须可解析) 中(两个词二选一)
主要失败模式 标签漂移、域外硬猜 漏抽/多抽、JSON 包 markdown、字段名错 输出解释性长句
后处理关键动作 strip → 枚举对齐 → 兜底 剥 `

| 可批量性 | 高(可一次请求多条标题) | 中(可批,但 JSON 数组更易错) | 高 |

### 6.2 Few-Shot 示例的注入方式对比

| 方式 | 实现 | 优点 | 缺点 | 本章用例 |
|---|---|---|---|---|
| 字符串拼接进单条 user | `pre_prompt + 当前问句` | 实现最简单,一次调用一块上下文 | 示例和问题的边界靠三引号人肉区分,示例改动要动模板 | 分类 |
| 多轮 messages | `[system, user, assistant]...` + 当前 user | 借 `role` 显式区分"规矩/问题/答案",示例池可动态增删 | 消息条数多,token 消耗略高 | 抽取、匹配 |

### 6.3 Prompt 方案 vs 传统/微调方案的对比(选型视角)

| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| Zero-Shot + LLM | 靠预训练知识直接判 | 零标注、零训练、改需求只改 prompt | 边界不稳、输出格式不受控 | 快速验证、类别数少且语义区分度大 |
| Few-Shot + LLM | 上下文里给"输入→输出"示例 | 无需训练即可对齐格式和边界,迭代成本以分钟计 | token 成本随示例线性上升;示例质量决定上限 | 类别/字段明确的判别与抽取任务(本章三个任务) |
| TF-IDF + 传统 ML | 词频特征 + 统计模型 | 极快、极省、可解释、可离线 | 抓不住语义,同义改写即失效 | 数据量大、类别区分靠关键词、追求低延迟 |
| 微调预训练模型(BERT 类) | 用标注数据更新全部/部分权重 | 精度通常最高、推理便宜、输出天然受控 | 需要大量标注 + 训练 + 算力;改需求要重训 | 有标注、类别固定、上线后调用量巨大的生产场景 |
| 双塔 / Cross-encoder 语义匹配 | 句向量相似度或交互式打分 | 匹配任务上精度高、可做检索 | 需要标注对 + 训练;阈值难定 | 大规模文本匹配、去重、召回 |

选型心法:本章这套 Prompt 方案的真正优势是"改需求不用重训",四类改成十类,只改 `class_examples` 的 key。新闻分类项目里四模型逐级对比的用意也在这里:先用 TF-IDF 建立可用的基线,再用 LLM 零样本/少样本快速拿到"不训练就能用"的效果,最后用 BERT 微调换取生产级的精度与成本结构。

### 6.4 文本处理范式的演进脉络
规则 / 词典匹配
└─ 问题:写法千变万化,规则写不完
↓
TF-IDF + 传统机器学习(随机森林 / SVM / 朴素贝叶斯)
└─ 解决:从”写规则”变成”从数据里学”,但只看得见词、看不见语义
↓
预训练 + 微调(BERT / FastText / 各种 encoder)
└─ 解决:词向量 + 上下文语义,判别任务精度大幅提升
└─ 新增问题:需要标注数据与训练流程,任务一变就得重来
↓
Prompt + 大模型(Zero-Shot / Few-Shot / CoT / Self-Consistency)
└─ 解决:不训练就能做任务,需求变更只改提示词
└─ 新增问题:输出格式不受控、幻觉、token 成本、上下文长度限制
↓
工程化收口:格式约束(JSON Schema / 结构化输出)+ 后处理校验 + 重试降级 + 多路投票
└─ 也就是本章”后处理”这一步必须补齐的东西

这条链的每一层都不是被推翻,而是被收编:今天一个生产系统里,往往是规则做兜底、TF-IDF/小模型做初筛、LLM 做难例,这是最经济的组合。

---

## 七、在新闻分类项目中的落地

> 新闻分类项目:类今日头条的新闻标题 10 分类模块;训练 18w / 测试 1w / 验证 1w,数据格式 `标题\t标签`;技术路线 TF-IDF+随机森林 → FastText → BERT 微调 → LLM(DeepSeek API 零样本/少样本);部署 Flask + Streamlit。

本章的金融案例说到底就是新闻分类项目 LLM 环节的"4 类小样版"。逐环节搬法如下:

### 7.1 环节对应表(一张表看清怎么搬)

| # | 金融案例环节 | 新闻分类项目对应做法 | 搬运要点 |
|---|---|---|---|
| 1 | 业务场景:4 类金融文本 | 10 类新闻标题 | 类别数从 4 变 10,类别清单必须来自项目的标签定义文件,写死在代码里当一个 `LABELS` 常量,禁止在多处重复书写(见 5.2) |
| 2 | Schema 定义:`class_examples` 的 key 即类别全集 | 从训练集 `标签` 列统计出 10 个唯一值,作为 `LABELS = [...]` | 用 `df['标签'].unique()` 生成,不要手打,手打很容易出现同类标签被写成两种名字(如"财经"与"财经新闻")的一字符之差漂移 |
| 3 | 角色设定:`你是一个金融专家` | `你是一个新闻分类专家` / `你是一名资深新闻编辑` | 角色把输出分布收窄到"新闻语域",减少模型发挥 |
| 4 | Few-Shot 示例池:4 类各 1 个 | 先 Zero-Shot 找出易混类对,再针对性给示例 | 10 类全给示例 token 成本高。做法:Zero-Shot 跑 200 条 → 画混淆矩阵 → 取 top-3 混淆类对,每对给 1~2 个示例,其余类不给 |
| 5 | 示例来源:人工手写 4 段 | 从训练集里精选(不是随机抽) | 用"类内最典型 + 类间最易混"两条标准挑。注意隐私/版权时可用改写样本 |
| 6 | 输出格式约束:`['新闻报道','财务报告',...]` + `不清楚类型` | 10 类枚举 + `其他` 兜底 | 新闻分类项目测试集里出现不属于任何类别的标题完全可能(多标签、纯符号标题),没有兜底就会硬猜,准确率被这些样本白白吃掉 |
| 7 | 追问模板:`"""{}是{类别列表}里的什么类别?"""` | 同样钉死一个模板字符串 | 示例和真实请求用同一个 `f-string`,逐字符一致 |
| 8 | 消息结构:单条 user 拼接 | 分类任务照搬即可(示例不多时足够) | 若改成 10 类多示例,建议切到 多轮 messages,示例池可动态增删(见 6.2) |
| 9 | 模型调用:DashScope 兼容模式 + `qwen-plus` | 换成 DeepSeek:改 `base_url` + `model` 即可 | 接口结构完全同构(都是 OpenAI 兼容)。`DASHSCOPE_API_KEY` → `DEEPSEEK_API_KEY` |
| 10 | 后处理:`print(resp)`(人眼核对) | 必须写解析函数 + 统计准确率 | 见 7.2,这是新闻分类项目 LLM 环节能不能进对比表的关键 |
| 11 | (课件缺失)温度 | 显式设 `temperature=0` | 判别任务要的是稳定不是多样;SDK 不传时取默认值(OpenAI 默认 `temperature=1`),会让同一条标题多次调用结果不同 |
| 12 | (课件缺失)评估 | 抽样 500~1000 条测试集,算 accuracy / macro-F1 | 见 7.4 |
| 13 | (课件缺失)部署 | Flask 暴露 `/classify`,Streamlit 调它 | 见 7.5 |

### 7.2 提示词版本迭代路线(新闻分类项目 LLM 环节该怎么一步步调)

金融案例里的标签不一致(5.2/5.3)和 IE_PATTERN 缩水(5.6)正好演示了"不版本化就会失控"。新闻分类项目建议按下面四版推进,每版都记录 prompt 全文 + 抽样准确率:

| 版本 | 提示词形态 | 预期现象 | 下一步动作 |
|---|---|---|---|
| V1 Zero-Shot | `"把下面的新闻标题分类到 10 个类别之一:{标题}"` | 输出带解释、类别名不规范、偶发发明类别 | 从错例里读出模型"误解"了什么 |
| V2 加枚举 + 兜底 | 补上 `LABELS` 枚举 + `只输出类别名,不要解释` + `不属于任何类别时输出"其他"` | 格式大幅收敛,但易混类仍错 | 用混淆矩阵定位易混类对 |
| V3 加 Few-Shot | 针对 top-3 易混类对各给 1~2 个示例(多轮 messages) | 易混类明显改善;token 成本上升 | 权衡"示例数 vs 收益",做成本实验 |
| V4 加投票(Self-Consistency) | 温度调高跑 3~5 次,取多数类别;或对低置信样本二次询问 | 偶发错判进一步下降,成本线性上升 | 只对"低置信/易混"样本启用,控制成本 |

记一条铁律:每次只改一个变量(改类别定义 / 改示例 / 改温度),否则你无法判断准确率变化是谁带来的。这也是课件里"同一个 prompt 里同时有角色、枚举、示例、兜底"这种写法最需要警惕的地方,它好用于交付,不好用于调优。调优阶段要把它们拆成可开关的段落。

### 7.3 输出解析与降级策略(新闻分类项目版)

金融案例的后处理只写了"人眼核对",新闻分类项目必须程序化:

python

LABELS = […] # 10 类,从训练集标签列生成
FALLBACK = [‘其他’, ‘不清楚类型’, ‘未知’]

def postprocess(raw: str):
text = raw.strip().strip(‘。.!!’).strip()
text = text.replace(‘类别:’, ”).replace(‘类型:’, ”).strip()
if text in LABELS:
return text, ‘ok’
hits = [l for l in LABELS if l in text]
if len(hits) == 1:
return hits[0], ‘fuzzy’
return ‘其他’, ‘fallback’


三级降级链(每一级都要记日志,方便事后统计各档占比):

1. 重试:解析失败 → 重新调用一次(把失败原因追加到 prompt,比如"上次输出无法解析,请只输出类别名");
2. 模型降级:LLM 连续失败或超时 → 回退到已训好的 BERT 模型分类;
3. 兜底:BERT 也没把握 → 归入 `其他` 并打标"待人工复核",绝不让异常值污染准确率统计。

### 7.4 评估方式(课件没写,但必被问)

| 评估项 | 金融案例现状 | 新闻分类项目做法 |
|---|---|---|
| 测试集 | 4 条,人眼看 | 从 1w 测试集分层抽样 500~1000 条(每类等量),控制 API 成本 |
| 指标 | 无 | accuracy + macro-F1 + 各类 P/R(10 类不均衡时 accuracy 会骗人,macro-F1 更诚实) |
| 对照 | 无 | 与 TF-IDF+RF / FastText / BERT 用同一份抽样集对比,形成四模型对照表 |
| 稳定性 | 无 | 同一条标题跑 3 次看结果是否一致(检验 temperature 与采样抖动) |
| 成本 | 无 | 记录 平均 prompt token / completion token / 单条成本,再乘 1w 推算全量成本 |
| 兜底率 | 无 | 统计 `其他` 占比;占比异常升高说明类别体系或数据分布有问题 |

一个必须提的取舍:LLM 逐条调用在 1w 测试集上是明显偏贵且慢的。工程上要么抽样评估(推荐)、要么批处理(一次请求塞 20 条标题让模型返回 JSON 数组,但要承担"少返/漏返/顺序错乱"的解析风险)。被追问时主动说出这个取舍,比只报一个准确率分数加分得多。

### 7.5 上线部署(Flask + Streamlit 场景)

| 环节 | 做法 |
|---|---|
| 服务化 | 把本章的 `model_chat()` 封装成 Flask 路由 `POST /classify`,入参 `{"title": "..."}`,出参 `{"label": "...", "raw": "...", "status": "ok/fuzzy/fallback", "latency_ms": ...}` |
| 客户端 | Streamlit 页面提供输入框,人输一条标题看分类结果 + 命中状态(这个"命中状态"字段就是本章后处理思想的直接体现) |
| 提示词外置 | `pre_prompt` 不要硬编码在 py 里,放到 `prompts.yaml` / 环境变量,改提示词不用重新发版,这是避免 5.6"文档-代码漂移"的结构性解法 |
| 密钥 | 只从环境变量读(课件用 `os.environ['DASHSCOPE_API_KEY']`,新闻分类项目同理用 `os.environ['DEEPSEEK_API_KEY']`),绝不写进代码或提交到仓库 |
| 健壮性 | 请求超时(如 30s)+ 指数退避重试(2~3 次)+ 并发上限;相同标题走本地缓存直接返回 |
| 可观测 | 落库 `(标题, prompt, raw_output, 解析结果, status, tokens, latency)`。这份日志既是 bug 复现的唯一依据,也是后续挖难例、补 Few-Shot 示例的数据源 |
| 批量离线 | 与线上服务分离:跑批脚本(带并发 + 断点续跑 + 结果落 csv),用于生成四模型对照表 |

### 7.6 与新闻分类项目其它方案的配合关系

TF-IDF + 随机森林 → 基线。全量 18w 训练秒级完成,给 LLM 一个”及格线”参考
↓ 对照
FastText → 轻量中线。训练快、推理快,提供”便宜但不弱”的选项
↓ 对照
BERT 微调 → 精度主力 + LLM 的降级后备(7.3 的第 2 级)
↓ 对照
LLM(DeepSeek) → 零样本/少样本对照项。核心价值是证明”不训练也能到多少分”
以及”改类别体系时的迁移成本极低”
↓
模型压缩(量化/蒸馏/剪枝)→ 针对 BERT/FastText 这条线做部署优化。
注意:LLM 走 API 时不涉及本地压缩,压缩是”若要把小模型塞进
低成本服务器/端侧”时才用得上
“`

一句话表述:LLM 在新闻分类项目里不是要取代 BERT,而是作为一个对照组回答”加提示词能提升多少、不训练能到什么水平”,这正好是评审者最想听的工程判断力。


八、重点问答

Q1:这个金融项目的整体技术方案是什么?
A:总的是”一个场景、三种任务、一套提示词方法”。我用金融行业的公开文本做了三类基础任务:文本分类(判断文本属于新闻报道/财务报告/公司公告/分析师报告哪一类)、信息抽取(从股票信息里抽日期、股票名称、开盘价、收盘价、成交量五个字段)、文本匹配(判断两个句子语义是否相似)。三条线走的是同一个四步流水线,加载模型、构建提示词、模型预测、后处理,方法上统一用 Few-Shot 加 Zero-Shot,模型用的是 Qwen 的 qwen-plus,通过 DashScope 的 OpenAI 兼容接口调用。也就是说,我交付的不是三个脚本,而是一套可以复制的 Prompt 工程模板。

Q2:你的 Prompt 是怎么设计的?设计时只考虑哪两件事?
A:我每个任务都只抓两件事。第一是让模型明白”这是什么任务”,第二是让模型按我指定的格式输出。第一件事靠角色设定加任务描述,比如”你是一个金融专家,需要对输入的金融领域文本进行分析”;第二件事靠三样东西,类别或字段的显式枚举、输出格式的明确要求、以及 Few-Shot 示例把格式”演示”一遍。补充一点,我做分类的时候还专门加了一个兜底类别”不清楚类型”,就是为了让模型面对域外文本时有一个合法的”不知道”,不要硬凑一个答案。

Q3:Few-Shot 里的示例是怎么用的?为什么说它有效?
A:Few-Shot 就是在 prompt 里塞几组”输入→输出”的示范,让模型照着做。关键是这些示范里的答案部分是我人工写死的,不是模型生成的,模型的机制是 In-context Learning,它不更新任何权重,只是在看到几组形状一致的样例后,推断出”哦,这个任务要的输出长这样”,然后对新的输入产生同形状的输出。所以示例的作用其实是两重的:既教会模型任务边界(比如”同属金融领域但讲的是两件事,也应该判不相似”),又把输出格式钉死成可直接解析的样子。示例的质量直接决定效果上限。

Q4:示例放在消息里,你有几种放法?怎么选?
A:两种。第一种是全部拼进一条 user 消息,把角色、类别枚举、几组示例、当前问题用一个字符串串起来,实现最简单,我在分类任务上用的就是这种。第二种是用多轮 messages,把 system 用来讲规矩、user 用来给任务、assistant 用来给标准答案,一组示例占两条消息,调用的时候把历史列表和当前这条 user 消息拼起来,注意顺序一定是 history + messages,当前问题在最后。信息抽取和文本匹配我用的是第二种。选多轮的好处是角色分工清晰、示例池可以动态增删不用改字符串模板;代价是消息条数变多,token 稍贵。

Q5:信息抽取任务为什么要求输出 JSON?直接让模型说人话不行吗?
A:不行,因为下游是程序。我用一个 schema 字典定义要抽的字段,金融类型下有日期、股票名称、开盘价、收盘价、成交量五个;然后要求模型按 JSON 输出,并且约定两件事:原文里没有的信息用 ['原文中未提及'] 表示,多个值之间用逗号分隔。所有字段我都用列表来装,哪怕只有一个值,因为一句话里可能提到多个日期或多只股票。这里有个很容易踩的坑:模型经常把 JSON 包在 markdown 代码块里输出,直接 json.loads 会报错,所以必须先剥掉反引号再解析,解析失败就走重试或降级。另外我要求模型抽原样,数据里同一句话可能”开盘价 23 元、收盘价 26 美元”,我要求原样保留单位字符串,单位归一化放到后处理用规则做,把”抽取”和”计算”这两类错误来源分开。

Q6:文本匹配任务你做了什么特别的设计?
A:两个地方。第一个是示例的组织方式,我按标签分桶:正例”是”给一组,负例”不是”给两组,而且这两组负例是故意不一样的,一组是”同属金融领域但讲的是两件不同的事”,另一组是”跨领域完全无关”。这么设计是因为模型最容易错的不是明显不相似的,而是那种领域相同、看起来都在讲金融、其实说的不是一回事的句子,负例必须专门覆盖这种边界。第二个是抑制话痨,我在 system 里明确写”只需要回答是否相似,不要做多余的回答”。不加这句,模型很爱输出一段分析过程,人看着挺合理,但程序没法解析。

Q7:这套方案上线,你觉得最薄弱的环节在哪?
A:后处理。课件里三个任务的第四步都叫”后处理”,但代码只打印了模型返回结果,靠人眼核对。真正上线必须补三件事:一是解析,分类要 strip、去标点、跟类别枚举做精确匹配,抽取消掉 markdown 代码块再 json.loads;二是校验,字段缺失要补默认值,类别命中不了要标成异常;三是降级,我设计了三级链,解析失败先重试一次并把失败原因追加进提示词、还失败就回退到已经训好的 BERT 模型、再兜不住就归入”其他”并打标待人工复核。还有两个细节:一是判别任务要显式把 temperature 设成 0,不然同一条输入多次调用结果可能不一样;二是所有 prompt 和原始输出都要落日志,这是复现 bug 和挖难例补示例的唯一数据源。

Q8:你会在生产里用 LLM 做分类,还是用 BERT 微调?
A:看阶段和约束。LLM 加提示词的优势是零训练、改需求只改提示词,四类改十类,我只改示例字典的 key,几分钟就能出新版本,这在需求快速变化的早期阶段价值极高。BERT 微调的优势是精度上限高、推理便宜、输出天然受控,适合类别固定、调用量巨大的稳定阶段。所以我的实际做法是两条腿走:用 LLM 做快速验证和难例挖掘,用 BERT 做生产主力,同时把 BERT 当成 LLM 失败时的降级后备。至于成本,LLM 逐条调 API 在万级数据上很贵,评估阶段我会分层抽样几百到一千条来算准确率和 macro-F1,而不是全量跑。


九、延伸阅读

官方文档 / 工具
– 通义千问(Qwen)与 DashScope 模型服务文档:https://dashscope.aliyuncs.com/compatible-mode/v1 是本章使用的 OpenAI 兼容模式端点,改 base_url + model 即可切换厂商
– OpenAI Python SDK(openai):本章所有调用的客户端,client.chat.completions.create() 的 messages 结构是通用协议
– 阿里云百炼(Model Studio)/ DashScope API-KEY 管理:本章通过 DASHSCOPE_API_KEY 环境变量注入

论文(奠定 Few-Shot / Zero-Shot 的理论基础)
– Brown et al., Language Models are Few-Shot Learners, 2020,GPT-3 论文,In-context Learning / Few-Shot 的奠基之作
– Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models, 2022,思维链
– Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models, 2022,自一致性投票,对应本章方案的稳定性增强方向

本课件内的关联章节(同一目录树)
– index.html(课程总览):核心能力五条主线与本章的定位
– ② 大模型提示词工程指南/01-大模型Prompt工程指南:编写规范与 5 大原则
– ② 大模型提示词工程指南/02-大模型提示词工程进阶:Zero-Shot(1.1)、Few-Shot(1.2)、CoT(2.1)、Self-Consistency 等进阶技术的原理与示例
– 本目录 ③ 大模型提示词项目案例 后续章节:同一套方法在其它行业场景的延续

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇