首页
/ 用 Promptfoo 系统化评测 Claude 分类提示词:claude-cookbooks 中从配置、运行到结果分析的完整实践

用 Promptfoo 系统化评测 Claude 分类提示词:claude-cookbooks 中从配置、运行到结果分析的完整实践

2026-09-05 18:08:46作者:韦蓉瑛

本篇指南围绕 claude-cookbooks 仓库中 capabilities/classification 教程的评测环节展开:如何使用 Promptfoo 对三种保险工单分类提示词(简单分类、RAG 增强、RAG+思维链)在 5 组不同 temperature 下的表现做自动化 A/B 评测。读完后,你将掌握 Promptfoo 配置文件(promptfooconfig.yaml)中 prompts、providers、tests、transform、output 五大配置段的完整写法,学会用命令行运行评测,并能用 pandas 读取结果文件做准确率归因分析。

背景:为什么需要 Promptfoo 做分类评测

capabilities/classification/guide.ipynb 演示了用 Claude 将保险客服工单分到 10 个业务类别(Billing Inquiries、Claims Assistance、Billing Disputes 等)的完整过程:从约 10% 的随机基线,到 ~70% 的简单提示词,再到 94% 的 RAG 增强版和 97% 的 RAG+思维链版本。Notebook 适合快速迭代,但它难以支撑生产级的评测需求——更大的测试集、多提示词变体并行对比、不同模型/温度参数的系统性比较、以及提示词改动后的回归检测。

本仓库的解法是把评测从 Notebook 中拆出来,交给开源 LLM 评测工具 Promptfoo 编排:同一份 68 条测试工单,同时跑 3 个提示词函数 × 5 个 temperature(0.0/0.2/0.4/0.6/0.8)共 15 组配置,输出自动化的 PASS/FAIL 判定,结果落盘为 CSV 供 Notebook 汇总分析。本指南的主体就是 评测说明文档 所描述的这套流程,并结合仓库中的配置文件与源码逐一展开。

前置条件

Promptfoo 是 Node.js 生态工具,运行前需要确认环境:

  • 系统已安装 Node.js 与 npm(可参考 npm 官方安装指南,官方文档不在此列出链接);
  • Promptfoo 可以通过 npm 安装后使用,也可以直接用 npx 免安装运行。本指南采用 npx 方式,无需本地全局安装。

一个重要的省事细节:由于 evaluation 目录下已经提供了初始化好的 promptfooconfig.yaml不需要再执行 npx promptfoo@latest init 交互式初始化,直接进入配置阅读与运行阶段即可。

Python 侧的依赖则来自 RAG 检索组件。evaluation 目录下的提示词函数会复用 vectordb.py 中的 VectorDB 类,运行评测前需要安装 voyageainumpy 等包;由于向量库文件 vector_db.pkl 已在仓库中预构建(由 68 条训练工单用 VoyageAI 的 voyage-2 模型编码而成),无需重新计算 embedding,也因此在只读评测场景下 VoyageAI 密钥的消耗仅来自查询侧 embedding(代码中还有查询缓存进一步降低调用次数)。

promptfooconfig.yaml:评测编排的五个配置段

整个评测由 promptfooconfig.yaml 一个文件编排,官方文档将其核心能力概括为四类:提示词导入、多 LLM 供应商接入、内置断言测试、以及结果输出。本仓库的配置覆盖了其中所有关键环节,逐段拆解如下。

1. Prompts:以 Python 函数导入三个提示词

# prompts defined in the prompts.py file
prompts:
  - prompts.py:simple_classify
  - prompts.py:rag_classify
  - prompts.py:rag_chain_of_thought_classify

Promptfoo 支持以多种格式导入提示词,这里选择的是 prompt function(Python 函数)形式:prompts.py:函数名。三个函数定义在 prompts.py 中,与 guide.ipynb 中的分类函数一一对应,但有一个关键差异——它们不调用 Claude API,而是只返回提示词字符串

def simple_classify(context: dict):
    X = context["vars"]["text"]
    prompt = (
        textwrap.dedent("""
    You will classify a customer support ticket into one of the following categories:
    <categories>
        {{categories}}
    </categories>

    Here is the customer support ticket:
    <ticket>
        {{ticket}}
    </ticket>

    Respond with just the label of the category between category tags.
    """)
        .replace("{{categories}}", categories)
        .replace("{{ticket}}", X)
    )
    return prompt

API 调用的编排、结果存储全部交由 Promptfoo 处理。选择 Python 函数而非静态模板字符串,还有一个实际收益:模块顶层会直接实例化并加载向量库(见 prompts.py#L1-L7),使得 RAG 类提示词函数可以在生成提示词时同步完成检索:

from vectordb import VectorDB

vectordb = VectorDB()
# Load the vector database
vectordb.load_db()
  • simple_classify:类别定义 + 工单文本,要求模型把标签写在 <category> 标签内;
  • rag_classify:在检索到语义最相似的 5 条训练样例后,以 <example><query>...</query><label>...</label></example> 的 XML 结构注入 few-shot 示例;
  • rag_chain_of_thought_classify:在 RAG 基础上追加思维链指令,要求模型先在 <scratchpad> 标签内逐步推理,再在 <category> 标签内给出最终类别。

三个函数的类别定义(10 个 <category><label>...<content>... 块)与 Notebook 中完全一致,保证了评测口径的统一。

2. Providers:同一模型 × 5 档温度参数

providers:
  - id: anthropic:messages:claude-haiku-4-5
    label: "Haiku: T-0.0"
    config:
      max_tokens: 4096
      temperature: 0
  - id: anthropic:messages:claude-haiku-4-5
    label: "Haiku: T-0.2"
    config:
      max_tokens: 4096
      temperature: 0.2
  # ... T-0.4 / T-0.6 / T-0.8 共 5 个 provider

Promptfoo 允许接入不同平台的多款 LLM 做交叉对比,本例的用途更聚焦:固定使用 claude-haiku-4-5(与 Notebook 中 MODEL = "claude-haiku-4-5" 一致),通过重复声明 provider 并以 label 区分,构成一个温度扫描实验矩阵。每个 provider 统一设置 max_tokens: 4096temperature 依次取 0、0.2、0.4、0.6、0.8。这直接回答了一个分类任务中很实际的问题:Notebook 中固定的 temperature=0.0 是否真的是最优选择?评测矩阵会自动给出答案。

3. Tests:CSV 逐行断言 + 全局断言的两层设计

tests: dataset.csv

测试数据来自 dataset.csv,即 guide.ipynb 使用的同一份 68 条测试工单(与 data/test.tsv 同源)。Promptfoo 支持大量内置确定性断言,本例采用两层断言结构,原因是"评测条件随每一行变化 vs. 对所有测试用例一致":

  • 逐行断言(定义在 CSV 中)dataset.csv 的列结构为 text,label,__expected,其中 __expected 列写明该条工单的真值标签,例如:
text,label,__expected
I'm confused about a charge on my recent auto insurance bill...,Billing Inquiries,contains:Billing Inquiries

即"该条预测结果必须包含真值类别"。这是精确判分依据。

  • 全局断言(定义在配置文件的 defaultTest 中):对所有 15 组配置都成立的最低条件——输出必须命中 10 个合法类别之一,否则视为格式/输出失败:
assert:
- type: icontains-any
  value:
    - 'Billing Inquiries'
    - 'Policy Administration'
    - 'Claims Assistance'
    - 'Coverage Explanations'
    - 'Quotes and Proposals'
    - 'Account Management'
    - 'Billing Disputes'
    - 'Claims Disputes'
    - 'Policy Comparisons'
    - 'General Inquiries'

icontains-any 是不区分大小写的"包含任一值"断言。这样,"输出跑题/空输出"和"输出正确类别但判错真值"两类错误可以被区分开。

4. Transform:从自由文本中抽取待测标签

defaultTest:
  options:
    transform: file://transform.py

由于模型输出是一段自然语言(思维链版本还包含大段推理文本),断言不能直接作用于原始响应,需要先用一个 Python 变换函数把待测标签抽出来。transform.py 全文如下:

def get_transform(output, context):
    try:
        return output.split("<category>")[1].split("</category>")[0].strip()
    except Exception as e:
        print(f"Error in get_transform: {e}")
        return output

函数约定:Promptfoo 会以 get_transform(output, context) 签名调用它。这里取第一个 <category></category> 之间的文本并去空白;解析失败时打印错误并原样返回输出(此时全局断言大概率判 FAIL,错误因此可观测)。这一点与 Notebook 中 CoT 版本的解析逻辑(response.content[0].text.split("<category>")[1].strip())是同一思路,只是抽离成了可复用、可被评测框架直接调用的纯函数。

值得注意的是一个实现差异:Notebook 中的 simple_classify/rag_classify 通过 assistant prefill("<category>")加 stop_sequences=["</category>"] 强制模型只输出标签;而 promptfoo 版本的提示词函数无法在返回的字符串中表达 prefill,因此改为依赖提示词中的格式指令("Respond with just the label of the category between category tags")+ transform 抽取。从 promptfooconfig.yaml 的 provider 配置中也可以确认没有设置 prefill 相关参数,两条实现路径在输出约束机制上是等价的近似。

5. Output:结果落盘路径

outputPath: ../data/results.csv

outputPath 相对于评测运行目录(即 evaluation/)解析,实际写入 results.csv。Promptfoo 支持多种输出格式,也支持用其 Web UI 查看,但这里选择 CSV,是因为 guide.ipynb 中的结果分析单元直接用 pandas 读取该文件(见下文)。

运行评测

评测说明 的步骤执行:

  1. 打开终端,进入评测目录(仓库内的 capabilities/classification/evaluation);
  2. 设置两个必需的环境变量——ANTHROPIC_API_KEY 用于调用 Claude,VOYAGE_API_KEY 用于 RAG 提示词函数中的查询 embedding:
export ANTHROPIC_API_KEY=YOUR_API_KEY
export VOYAGE_API_KEY=YOUR_API_KEY
  1. 执行评测:
npx promptfoo@latest eval
  1. 需要提高请求并发度时(默认并发为 4),用 -j 参数调整:
npx promptfoo@latest eval -j 25

评测完成后,终端会按 dataset.csv 的每行打印各配置的判定结果。此时仓库中已提交的 results.csv 即是一次完整运行的产物:每个单元格是一段 JSON,包含 provider(如 "Haiku: T-0.0")、idmetrics(含 scoretestPassCounttestFailCounttotalLatencyMstokenUsagecost)以及 [PASS]/[FAIL] 文本,可以直接回到 guide.ipynb 做汇总分析。

结果分析:回到 guide.ipynb 读取结果

Notebook 末尾的分析单元(guide.ipynb 第 33 个代码单元)展示了如何消费这份 CSV:

import json

import pandas as pd

promptfoo_results = pd.read_csv("./data/results.csv")
examples_columns = promptfoo_results.columns[2:]

number_of_providers = 5
number_of_prompts = 3

prompts = ["Simple", "RAG", "RAG w/ CoT"]

columns = ["label", "text"] + [
    json.loads(examples_columns[prompt * number_of_providers + provider])["provider"]
    + " Prompt: "
    + str(prompts[prompt])
    for prompt in range(number_of_prompts)
    for provider in range(number_of_providers)
]

promptfoo_results.columns = columns

result = (
    promptfoo_results.iloc[:, 2:].astype(str).apply(lambda x: x.str.count("PASS")).sum()
    / len(promptfoo_results)
    * 100
).sort_values(ascending=False)

print(result)

思路是:15 个结果列按"3 提示词 × 5 provider"的顺序排列,先解析每列 JSON 中的 provider 字段把列名重命名为 Haiku: T-x.x Prompt: xxx,再统计每列 PASS 出现次数除以总行数得到准确率,降序输出。

基于仓库中已提交的 results.csv 与 Notebook 的结论记录,关键发现可以交叉验证:

提示词 temperature testPassCount(n=68) 准确率 单轮成本
Simple 0.0 48/68 ~70.6% $0.0136
RAG 0.0 64/68 ~94.1% $0.0183
RAG w/ CoT 0.0 65/68 ~95.6% $0.0391

Notebook 记录的规律与 results.csv 中的 metrics 完全吻合:

  • CoT 对温度几乎不敏感:RAG w/ CoT 在 T=0.0/0.2/0.8 下 testPassCount 均为 65/68(约 95.59%),思维链推理过程本身稳定了输出,采样随机性影响很小;
  • RAG 整体稳健但 T=0.0 最优:RAG 各温度下通过率在 89%~94% 区间,T=0.0 达到 94.12%(64/68);
  • Simple 提示词温度无关:各温度下稳定在 70% 上下(48~49/68),说明没有 RAG/CoT 时模型基本只靠类别定义工作;
  • 生产建议temperature=0.0 + RAG w/ CoT 组合一致性与准确率最佳。

metrics 中的 tokenUsage 还提供了成本归因视角:CoT 版本每次运行 completion tokens 约 1.6 万(T=0.0 时为 16557),而 Simple/RAG 仅约 850,接近 20 倍,这正是 CoT 总成本($0.039)明显高于 Simple($0.014)的原因——在准确率收益(约 25 个百分点)与推理成本之间做取舍时,这类数据是决策依据。

源码层面的实现印证

为了让评测结果可信、可复现,有几处仓库源码细节值得核对:

向量检索参数vectordb.py 中的 search(query, k=5, similarity_threshold=0.85) 使用 numpy 点积计算相似度并只保留高于阈值的样例:

similarities = np.dot(self.embeddings, query_embedding)
top_indices = np.argsort(similarities)[::-1]
top_examples = []

for idx in top_indices:
    if similarities[idx] >= similarity_threshold:
        example = {
            "metadata": self.metadata[idx],
            "similarity": similarities[idx],
        }
        top_examples.append(example)
        if len(top_examples) >= k:
            break

注意一个值得留意的版本差异:evaluation/vectordb.py 的相似度阈值默认是 0.85,而 guide.ipynb 内联的 VectorDB 实现为 0.75(见 guide.ipynb 中 VectorDB 代码单元)。从源码结构看,这会导致评测环境下检索到的 few-shot 示例比 Notebook 交互运行时更少、更严格,比较两组结果时应将此纳入考量。此外评测版还内建了 query_cache 查询缓存(vectordb.py#L45-L51),对重复出现的测试文本只调用一次 embedding API,配合预构建的 vector_db.pkldb_path 相对 evaluation/ 目录指向 ../data/vector_db.pkl,即分类教程共享的 data 目录),使评测对 VoyageAI 的依赖降至最低。

向量库内容来源vector_db.pkl 中的训练样例来自 train.tsv(68 条带标签工单),与 test.tsv 同源同构;evaluation/dataset.csv 则与测试集对齐(68 行数据 + 表头)。三个数据文件(train.tsv / test.tsv / dataset.csv)保证了"Notebook 内联评测"与"Promptfoo 批量评测"在同一数据面上可比。

断言与输出的对应关系results.csv 每格 JSON 中的 assertPassCount(如 Simple T-0.0 为 116 = 2 条断言 × 58 次通过)表明每条用例同时跑了 CSV 的 __expected 断言与 defaultTesticontains-any 断言,两层断言都在生效。

要点回顾

  • 一份 promptfooconfig.yaml 即可编排"3 提示词 × 5 温度"的 15 组对照实验,核心配置段为 prompts(Python 提示词函数)、providers(同模型多温度)、tests(CSV 逐行断言 + 全局断言)、defaultTest.transform(Python 抽取函数)、outputPath(CSV 落盘);
  • 提示词函数只负责"生成提示词",API 调用与结果存储交给 Promptfoo;RAG 能力通过复用 vectordb.pyVectorDB 类在提示词生成阶段完成,前提是有预构建的向量库和 VOYAGE_API_KEY
  • 运行只需 export 两个密钥后执行 npx promptfoo@latest eval-j 25 可提升并发,默认 4),产物为带 PASS/FAIL 与完整 metrics 的 CSV;
  • 结果用 pandas 统计每列 PASS 占比即可得到各配置准确率,结合 testPassCounttokenUsagecost 字段可做温度敏感性与成本归因分析;
  • 复现时留意评测版 VectorDBsimilarity_threshold=0.85 与 Notebook 版 0.75 的差异,以及 CoT 方案约 20 倍的输出 token 成本。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384