1. 问题背景:为什么通用Prompt在金融领域失效了

上个月接手了一个项目:需要从3000份PDF研报中抽取公司财务数据,构建问答系统。最初版本直接用"You are a helpful assistant"开局,结果让人血压飙升——模型经常把“截至2023年Q3”理解成“2023年全年”,对“营收同比增长”和“环比增长”概念混淆。

测试集是50个手工标注的财务事实型问题,初始准确率只有38%。这个数字意味着业务方根本没法用。当时我意识到问题不在模型能力(GPT-4o-mini足够强),而在Prompt没有约束模型的推理路径。

2. 环境与版本:复现所需配置

  • 模型gpt-4o-mini-2024-07-18(温度0.1,top_p 0.9)
  • 开发语言:Python 3.11.5
  • SDKopenai==1.35.3
  • 向量数据库:ChromaDB 0.4.24(用于检索相关研报片段)
  • 上下文窗口:每轮检索Top-3片段,每片段约500 tokens,系统Prompt+用户Prompt合计控制在1800 tokens内

所有实验使用同一批50个问题、同一检索结果,变量仅为Prompt结构,保证可对比性。

3. 方案设计:从零构建三层Prompt架构

我设计了三个实验组,每组包含系统Prompt、用户Prompt、Few-shot示例三个模块:

  • 实验A(基线):单句指令,无示例
  • 实验B(结构化):添加角色定义、输出格式、否定规则
  • 实验C(结构化+3-shot):在B基础上加入3个带标注的问答对

核心设计原则是:把财务分析师的隐性推理逻辑显性化。比如要求模型先定位“时间基准”,再区分“同比/环比”,最后提取数值。

4. 核心实现:可运行的Prompt迭代实验代码

以下代码展示了实验B和实验C的Prompt模板差异(完整代码见我的GitHub):

# experiment_b.py - 结构化指令版
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])

SYSTEM_PROMPT_B = """
你是金融研报分析助手。你的任务是回答关于公司财务数据的问题。
必须遵循以下规则:
1. 时间基准识别:如果问题中提到"截至X年X月",必须以该日期为截止点,忽略之后的数据。
2. 增长口径区分:明确区分"同比"(YoY)与"环比"(QoQ),若问题未指明口径,默认使用同比。
3. 数值提取:只从给定上下文中提取数据,禁止编造。如果上下文无答案,回复"上下文信息不足"。
输出格式:JSON对象,包含"answer"(字符串)、"confidence"(0-1浮点数)、"basis"(引用上下文原文)。
"""

def ask_structured(question, context_chunks):
    context_text = "\n---\n".join(context_chunks)
    user_prompt = f"研报上下文:\n{context_text}\n\n问题:{question}"

    resp = client.chat.completions.create(
        model="gpt-4o-mini-2024-07-18",
        temperature=0.1,
        top_p=0.9,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT_B},
            {"role": "user", "content": user_prompt}
        ],
        response_format={"type": "json_object"}  # 强制JSON输出
    )
    return resp.choices[0].message.content
# experiment_c.py - 增加3-shot示例
SYSTEM_PROMPT_C = SYSTEM_PROMPT_B + """

以下是正确回答的示例:

示例1:
上下文:"2023年Q3营收为52.4亿元,同比增长18.2%,环比增长5.1%。"
问题:"2023年Q3营收同比增长多少?"
正确回答:{"answer": "18.2%", "confidence": 0.98, "basis": "同比增长18.2%"}

示例2:
上下文:"截至2023年6月30日,公司总资产达389亿元。全年营收预期120亿元。"
问题:"截至2023年6月30日的总资产是多少?"
正确回答:{"answer": "389亿元", "confidence": 0.99, "basis": "总资产达389亿元"}

示例3:
上下文:"管理层预计2024年毛利率提升至35%。2023年实际毛利率为28%。"
问题:"2024年预期毛利率是多少?"
正确回答:{"answer": "35%", "confidence": 0.95, "basis": "预计2024年毛利率提升至35%"}

请严格按照示例的格式和推理逻辑回答。
"""

运行方式:python experiment_b.pypython experiment_c.py,输出结果存入results/目录。

5. 踩坑与优化:三个致命陷阱

陷阱1:response_format={"type":"json_object"} 并不总是生效
在实验B中,有7个回答返回了合法JSON但结构不对——缺少confidence字段。排查发现是模型在推理中途“走神”,生成了部分JSON。解决方案是增加一个resilience机制:解析失败时自动重试一次,并在用户Prompt末尾追加请务必输出完整JSON,不要省略任何字段

陷阱2:Few-shot示例的“锚定效应”
实验C中,我一开始用了5个示例,准确率反而降到82%。分析发现示例3(关于毛利率)让模型产生了“锚定”——当问题涉及“净利润率”时,模型倾向于输出35%附近的数值。砍到3个示例后,准确率回升至91%。经验法则:Few-shot示例数量控制在3个以内,且示例的答案数值分布要分散

陷阱3:token消耗的隐性膨胀
实验B比实验A的token消耗增加了41%,但准确率提升仅15%。关键因素是否定指令——“禁止编造”这类话术会让模型生成更多自我纠正的冗余token。优化方案:将否定指令改写为肯定指令(“仅从上下文中提取数值”),token消耗降低12%,准确率提升3%。

6. 效果数据:五轮迭代的量化对比

下表是完整实验数据(50个问题,3次运行取平均值):

版本 系统Prompt字数 平均输入tokens 平均输出tokens 准确率 平均响应延迟(ms)
A(基线) 12 1420 210 38% 680
B(结构化) 180 1630 195 61% 720
C(B+3-shot) 480 1890 185 91% 810
C-修订(肯定指令) 460 1780 172 94% 795
C-修订+温度0.05 460 1780 170 94%(稳定) 790

关键结论:
- 3-shot示例贡献了30%的准确率提升,但输入token增加16%,属于“用算力换精度”的典型场景。
- 温度从0.1降至0.05对准确率无显著影响,但方差从±4%收窄至±1.5%,生产环境建议使用0.05。
- 最终版本平均每请求消耗1952 tokens(含检索上下文),单日10万请求的token成本约为$120,比无Prompt调优版本节省了约$80——因为准确率提升后减少了用户重试次数。

7. 总结:Prompt Engineering的工程化思路

这次调优让我确信Prompt Engineering不是玄学,而是有迹可循的工程活动。核心方法论可归纳为:明确任务约束(时间基准/口径识别)→ 用Few-shot固化推理模式 → 用输出格式强制结构化 → 用肯定指令压缩token

如果只留一条建议:不要盲目堆叠示例数量,3个精心设计的反例胜过10个泛泛的正例。下一步我计划测试gpt-4o-2024-08-06在相同Prompt下的效果,看看更强大的模型能否在更低token消耗下达到同等精度。欢迎在评论区交流你们的调优数据。