mcpField
P
promptfoo-evaluation

promptfoo-evaluation

github.com/daymade测试与质量1.5k1.6k 次安装今天更新
代码仓库

关于 promptfoo-evaluation

Promptfoo Evaluation 是一项使用开源命令行工具 Promptfoo 配置和运行大模型评测的技能,涵盖提示词格式、测试用例、断言、自定义 Python 检查及配置排错。

主要功能

  1. 01配置文本和聊天提示词
  2. 02定义测试用例和文件输入
  3. 03使用内置及自定义断言
  4. 04通过 LLM 评分标准评测输出
  5. 05用 echo 预览渲染后的提示词

适用场景

  1. 01比较大模型在不同测试用例中的输出
  2. 02按自定义标准检查回答质量
  3. 03调用 API 前预览提示词渲染
  4. 04排查评测配置问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/daymade/claude-code-skills --skill promptfoo-evaluation

安装后在 Claude Code 中输入/promptfoo-evaluation确认已加载。

附带文件references/promptfoo_api.mdscripts/metrics.py

详细介绍

Promptfoo 评估

概述

本技能提供使用 Promptfoo 配置和运行大模型评估的指南。Promptfoo 是一款开源命令行工具,用于测试和比较大模型的输出。

快速开始

# Initialize a new evaluation project
npx promptfoo@latest init

# Run evaluation
npx promptfoo@latest eval

# View results in browser
npx promptfoo@latest view

配置结构

典型的 Promptfoo 项目结构:

project/
├── promptfooconfig.yaml    # Main configuration
├── prompts/
│   ├── system.md           # System prompt
│   └── chat.json           # Chat format prompt
├── tests/
│   └── cases.yaml          # Test cases
└── scripts/
    └── metrics.py          # Custom Python assertions

核心配置(promptfooconfig.yaml)

# yaml-language-server: $schema=https://promptfoo.dev/config-schema.json
description: "My LLM Evaluation"

# Prompts to test
prompts:
  - file://prompts/system.md
  - file://prompts/chat.json

# Models to compare
providers:
  - id: anthropic:messages:claude-sonnet-4-6
    label: Claude-Sonnet-4.6
  - id: openai:gpt-4.1
    label: GPT-4.1

# Test cases
tests: file://tests/cases.yaml

# Concurrency control (MUST be under commandLineOptions, NOT top-level)
commandLineOptions:
  maxConcurrency: 2

# Default assertions for all tests
defaultTest:
  assert:
    - type: python
      value: file://scripts/metrics.py:custom_assert
    - type: llm-rubric
      value: |
        Evaluate the response quality on a 0-1 scale.
      threshold: 0.7

# Output path
outputPath: results/eval-results.json

提示词格式

文本提示词(system.md)
You are a helpful assistant.

Task: {{task}}
Context: {{context}}
聊天格式(chat.json)
[
  {"role": "system", "content": "{{system_prompt}}"},
  {"role": "user", "content": "{{user_input}}"}
]
Few-Shot 模式

将示例直接嵌入提示词中,或使用包含 assistant 消息的聊天格式:

[
  {"role": "system", "content": "{{system_prompt}}"},
  {"role": "user", "content": "Example input: {{example_input}}"},
  {"role": "assistant", "content": "{{example_output}}"},
  {"role": "user", "content": "Now process: {{actual_input}}"}
]

测试用例(tests/cases.yaml)

- description: "Test case 1"
  vars:
    system_prompt: file://prompts/system.md
    user_input: "Hello world"
    # Load content from files
    context: file://data/context.txt
  assert:
    - type: contains
      value: "expected text"
    - type: python
      value: file://scripts/metrics.py:custom_check
      threshold: 0.8

Python 自定义断言

创建一个 Python 文件来定义自定义断言(例如,scripts/metrics.py):

def get_assert(output: str, context: dict) -> dict:
    """Default assertion function."""
    vars_dict = context.get('vars', {})

    # Access test variables
    expected = vars_dict.get('expected', '')

    # Return result
    return {
        "pass": expected in output,
        "score": 0.8,
        "reason": "Contains expected content",
        "named_scores": {"relevance": 0.9}
    }

def custom_check(output: str, context: dict) -> dict:
    """Custom named assertion."""
    word_count = len(output.split())
    passed = 100 <= word_count <= 500

    return {
        "pass": passed,
        "score": min(1.0, word_count / 300),
        "reason": f"Word count: {word_count}"
    }

要点:

  • 默认函数名为 get_assert
  • 使用 file://path.py:function_name 指定函数
  • 返回 bool、float(分数)或包含 pass/score/reason 的 dict
  • 通过 context['vars'] 访问变量

LLM-as-Judge(llm-rubric)

assert:
  - type: llm-rubric
    value: |
      Evaluate the response based on:
      1. Accuracy of information
      2. Clarity of explanation
      3. Completeness

      Score 0.0-1.0 where 0.7+ is passing.
    threshold: 0.7
    provider: openai:gpt-4.1  # Optional: override grader model

使用中继/代理 API 时,每个 llm-rubric 断言都需要各自的 provider 配置,并设置 apiBaseUrl。否则,评分器会退回到默认的 Anthropic/OpenAI 端点,并出现 401 错误:

assert:
  - type: llm-rubric
    value: |
      Evaluate quality on a 0-1 scale.
    threshold: 0.7
    provider:
      id: anthropic:messages:claude-sonnet-4-6
      config:
        apiBaseUrl: https://your-relay.example.com/api

最佳实践:

  • 提供清晰的评分标准
  • 使用 threshold 设置最低及格分数
  • 默认评分器使用可用的 API 密钥(OpenAI → Anthropic → Google)
  • 使用中继/代理时:每个 llm-rubric 都必须有自己的 provider 和 apiBaseUrl——不会继承主 provider 的 apiBaseUrl

常见断言类型

类型用途示例
contains检查子字符串value: "hello"
icontains不区分大小写value: "HELLO"
equals精确匹配value: "42"
regex模式匹配value: "\\d{4}"
python自定义逻辑value: file://script.py
llm-rubric大模型评分value: "Is professional"
latency响应时间threshold: 1000

文件引用

所有 file:// 路径都相对于 promptfooconfig.yaml 所在位置解析(而非包含该引用的 YAML 文件)。当 tests: 引用了单独的 YAML 文件时,这一点很容易让人忽略——该测试文件中的 file:// 路径仍以配置根目录为基准解析。

# Load file content as variable
vars:
  content: file://data/input.txt

# Load prompt from file
prompts:
  - file://prompts/main.md

# Load test cases from file
tests: file://tests/cases.yaml

# Load Python assertion
assert:
  - type: python
    value: file://scripts/check.py:validate

运行评估

# Basic run
npx promptfoo@latest eval

# With specific config
npx promptfoo@latest eval --config path/to/config.yaml

# Output to file
npx promptfoo@latest eval --output results.json

# Filter tests
npx promptfoo@latest eval --filter-metadata category=math

# View results
npx promptfoo@latest view

中继/代理 API 配置

使用 API 中继或代理,而非直接连接 Anthropic/OpenAI 端点时:

providers:
  - id: anthropic:messages:claude-sonnet-4-6
    label: Claude-Sonnet-4.6
    config:
      max_tokens: 4096
      apiBaseUrl: https://your-relay.example.com/api  # Promptfoo appends /v1/messages

# CRITICAL: maxConcurrency MUST be under commandLineOptions (NOT top-level)
commandLineOptions:
  maxConcurrency: 1  # Respect relay rate limits

主要规则:

  • apiBaseUrl 应放在 providers[].config 中——Promptfoo 会自动追加 /v1/messages
  • maxConcurrency 必须放在 commandLineOptions: 下——放在顶层会被静默忽略
  • 使用中继进行 LLM-as-Judge 时,将 maxConcurrency: 1 设为 1,以避免并发请求限制(生成和评分共用同一个池)
  • 将中继令牌作为 ANTHROPIC_API_KEY 环境变量传入

故障排查

找不到 Python:

export PROMPTFOO_PYTHON=python3

较大的输出被截断: 超过 30000 个字符的输出会被截断。请在断言中使用 head_limit。

找不到文件错误: 所有 file:// 路径都相对于 promptfooconfig.yaml 所在位置解析。

maxConcurrency 被忽略(显示“每次最多 N 个”): maxConcurrency 必须放在 commandLineOptions: 下,而不是 YAML 顶层。这是一个常见错误。

LLM-as-Judge 使用中继 API 时返回 401: 每个 llm-rubric 断言都必须有自己的 provider 和 apiBaseUrl。评分器断言不会继承主 provider 配置。

模型输出中的 HTML 标签导致指标虚高: 模型可能会在结构化内容中输出 <br>、<b> 等标签。在进行测量前,请在 Python 断言中移除 HTML:

import re
clean_text = re.sub(r'<[^>]+>', '', raw_text)

Echo Provider(预览模式)

使用 echo provider 预览渲染后的提示词,而不发起 API 调用:

# promptfooconfig-preview.yaml
providers:
  - echo  # Returns prompt as output, no API calls

tests:
  - vars:
      input: "test content"

适用场景:

  • 在发起耗费较高的 API 调用前预览提示词渲染结果
  • 验证 Few-shot 示例是否正确加载
  • 调试变量替换问题
  • 验证提示词结构
# Run preview mode
npx promptfoo@latest eval --config promptfooconfig-preview.yaml

费用: 免费——不会消耗 API 令牌。

高级少样本实现

多轮对话模式

对于包含完整示例的复杂少样本学习:

[
  {"role": "system", "content": "{{system_prompt}}"},

  // Few-shot Example 1
  {"role": "user", "content": "Task: {{example_input_1}}"},
  {"role": "assistant", "content": "{{example_output_1}}"},

  // Few-shot Example 2 (optional)
  {"role": "user", "content": "Task: {{example_input_2}}"},
  {"role": "assistant", "content": "{{example_output_2}}"},

  // Actual test
  {"role": "user", "content": "Task: {{actual_input}}"}
]

测试用例配置:

tests:
  - vars:
      system_prompt: file://prompts/system.md
      # Few-shot examples
      example_input_1: file://data/examples/input1.txt
      example_output_1: file://data/examples/output1.txt
      example_input_2: file://data/examples/input2.txt
      example_output_2: file://data/examples/output2.txt
      # Actual test
      actual_input: file://data/test1.txt

最佳实践:

  • 使用 1-3 个少样本示例(更多示例可能会降低效果)
  • 确保示例与任务格式完全一致
  • 从文件加载示例,以便更好地维护
  • 先使用 echo provider 验证结构

长文本处理

对于中文/长篇内容评估(10k+ 字符):

配置:

providers:
  - id: anthropic:messages:claude-sonnet-4-6
    config:
      max_tokens: 8192  # Increase for long outputs

defaultTest:
  assert:
    - type: python
      value: file://scripts/metrics.py:check_length

文本指标的 Python 断言:

import re

def strip_tags(text: str) -> str:
    """Remove HTML tags for pure text."""
    return re.sub(r'<[^>]+>', '', text)

def check_length(output: str, context: dict) -> dict:
    """Check output length constraints."""
    raw_input = context['vars'].get('raw_input', '')

    input_len = len(strip_tags(raw_input))
    output_len = len(strip_tags(output))

    reduction_ratio = 1 - (output_len / input_len) if input_len > 0 else 0

    return {
        "pass": 0.7 <= reduction_ratio <= 0.9,
        "score": reduction_ratio,
        "reason": f"Reduction: {reduction_ratio:.1%} (target: 70-90%)",
        "named_scores": {
            "input_length": input_len,
            "output_length": output_len,
            "reduction_ratio": reduction_ratio
        }
    }

实际示例

项目: 基于长篇转录文本整理中文短视频内容

结构:

tiaogaoren/
├── promptfooconfig.yaml          # Production config
├── promptfooconfig-preview.yaml  # Preview config (echo provider)
├── prompts/
│   ├── tiaogaoren-prompt.json   # Chat format with few-shot
│   └── v4/system-v4.md          # System prompt
├── tests/cases.yaml              # 3 test samples
├── scripts/metrics.py            # Custom metrics (reduction ratio, etc.)
├── data/                         # 5 samples (2 few-shot, 3 eval)
└── results/

参见: ./tiaogaoren/(示例项目根目录),查看完整实现。

资源

如需详细的 API 参考和高级模式,请参见 references/promptfoo_api.md。

评论

支持 Markdown