为提示模板生成合成测试数据

本文介绍如何利用Claude API为提示模板自动生成合成测试数据,以解决真实数据不足或隐私限制的问题。通过提取模板中的变量、构建示例块、生成测试用例并迭代优化,开发者可以评估提示性能并改进提示。文章提供了完整的Python代码实现,包括辅助函数、提示模板构造和API调用,并展示了如何通过修改规划逻辑来定制生成数据的风格。该方法适用于提示评估和多示例提示优化,是AI工程实践中的实用技术。

技术背景

在开发基于大语言模型(LLM)的应用时,提示模板的质量直接影响模型输出。测试提示模板通常需要大量真实输入,但真实数据可能稀缺、涉及隐私或难以获取。合成测试数据生成技术应运而生,它利用LLM本身来生成逼真的测试用例,从而在不依赖真实数据的情况下评估和改进提示。该技术适用于提示调优、模型评估、多示例学习等场景,尤其适合需要快速迭代的AI工程实践。主要限制包括生成数据的分布可能与真实数据存在偏差,以及需要谨慎处理敏感信息。

核心原理

核心原理是利用Claude模型根据提示模板的结构和变量定义,自动生成符合变量语义的测试值。流程包括:1) 使用正则表达式提取模板中的双花括号变量;2) 构造变量块,指示模型为每个变量生成完整、真实的值;3) 构建生成提示,要求模型先进行规划(planning),分析模板目标和每个变量的典型特征,然后输出测试用例;4) 通过API调用Claude,获取生成的测试数据。关键设计决策包括使用XML标签结构化输出,便于解析;通过规划步骤引导模型思考,提高生成质量;支持用户提供示例以指导生成分布。

架构分析

系统由三个主要组件构成:辅助函数模块、提示模板构造模块和API调用模块。辅助函数包括extract_variables(提取变量)、construct_variables_names(构造变量名列表)、construct_variables_block(构造变量块)和construct_example_block(构造示例块)。提示模板构造模块根据是否有用户示例,选择带示例或不带示例的生成提示模板,并填充变量信息。API调用模块通过get_test_data函数调用Claude API,支持自定义规划文本。数据流为:用户输入提示模板和可选示例,系统提取变量并构造生成提示,调用Claude生成测试用例,解析结果并展示。设计上强调模块化和可扩展性,便于集成到现有测试流程。

完整实战教程

想象一下,您有一个大致如下的提示:

"""这里有一些我希望您分析的内容:

<thing>

{{thing1}}

</thing>

<thing>

{{thing2}}

</thing>

这些内容是[内容的描述]。请仔细阅读它们,并[执行某些任务]。"""

在这里,我们将thing1和thing2称为“变量”——您希望您的提示对于thing1和thing2的许多不同可能值都能表现良好。

如何测试这个提示模板?也许您有一些真实值可以替换进去。但也许您没有,或者出于隐私原因,您不允许在已有的真实值上进行测试。别担心——Claude可以编造它们!本手册演示了如何使用Claude和Claude API为您的提示生成合成测试数据。它包括从模板中提取变量、构建示例块、生成测试用例以及迭代优化结果的函数。这样做的好处是双重的:

  1. 提示评估
  2. 您可以使用这些测试用例来查看Claude在现实示例上的表现。

  1. 通过多示例改进提示
  2. 给Claude提供示例可能是提高其性能的最佳方式。本笔记本可以帮助您生成真实的输入,这是获得理想输入/输出对的一半工作。

% pip install anthropic IPython
import re

import anthropic

# Enter your API key here
api_key = ""
CLIENT = anthropic.Anthropic(api_key=api_key)
MODEL_NAME = "claude-sonnet-4-6"

让我们先定义一些辅助函数,这些函数将在整个笔记本中使用。

# First, we have the `extract_variables` function,
# It takes in a prompt template and extracts the double-mustache-bracketed "variables" contained.
def extract_variables(prompt_template):
    """Extract variables from a prompt template."""
    pattern = r"{{([^}]+)}}"
    variables = re.findall(pattern, prompt_template)
    return set(variables)


# Next, we have `construct_variables_names`, which just joins them together connected by newlines.
def construct_variables_names(prompt_template):
    """Construct a string of variable names from a prompt template."""
    variables = extract_variables(prompt_template)
    return "\n".join(variables)


# The `construct_variables_block` function takes in the list of variables, and constructs a "variables block"
# The variables block might look like this, if the variables were 'animal' and 'topic':
"""
<animal>
[a full, complete, value for the variable "animal"]
</animal>
<topic>
[a full, complete, value for the variable "topic"]
</topic>
"""


def construct_variables_block(prompt_template):
    """Construct a variables block for the synthetic test data prompt."""
    variables = extract_variables(prompt_template)
    output = ""
    for v in variables:
        output += f"<{v}>\n"
        output += f'[a full, complete, value for the variable "{v}". (You do not need to repeat the variable name inside the tags.)]\n'
        output += f"</{v}>\n"
    return output.strip()


# `construct_examples` takes a dictionary of {variable: value} and constructs an XML-formatted example.
# E.g. if the dict is
# {'animal': 'cat', 'topic': 'movement patterns'}, then the example would be
"""
<example>
<variables>
<animal>
cat
</animal>
<topic>
movement patterns
</topic>
</variables>
</example>
"""


def construct_example_block(variable_dict):
    """Construct an example block from a dictionary of variables."""
    output = "<example>\n<variables>\n"
    for k, v in variable_dict.items():
        output += f"<{k}>\n{v}\n</{k}>\n"
    output = output.strip()
    output += "\n</variables>\n</example>"
    return output

用于生成数据的提示模板

这些提示模板的总体思路是:获取用户提交的带有变量的提示模板,并为变量构造一些值来填充模板。

下面实际上有两个提示模板;一个的格式假设用户已经提供了示例变量值,另一个则不假设。

它们的共同点是,两个模板都首先向Claude提供有关情况的上下文,并指示Claude在输出测试用例之前,仔细思考每个变量的规格以及用户提供的提示模板整体。

# Formatting Prompt Templates for Synthetic Evaluations

# This function prepares the prompt template for generating synthetic test data.


def format_prompt_template_for_synth_evals(prompt_template, examples=None):
    """Format a prompt template for synthetic evaluations."""
    synth_test_data_prompt_template_with_example = """<Prompt Template>
{{PROMPT_TEMPLATE}}
</Prompt Template>

Your job is to construct a test case for the prompt template above. This template contains "variables", which are placeholders to be filled in later. In this case, the variables are:

<variables>
{{CONSTRUCT_VARIABLES_NAMES}}
</variables>

Here are the example test cases provided by the user.
<examples>
{{EXAMPLES}}
</examples>

First, in <planning> tags, do the following:

1. Summarize the prompt template. What is the goal of the user who created it?
2. For each variable in <variables>, carefully consider what a paradigmatic, realistic example of that variable would look like. You'll want to note who will be responsible "in prod" for supplying values. Written by a human "end user"? Downloaded from a website? Extracted from a database? Think about things like length, format, and tone in addition to semantic content. Use the examples provided by the user to guide this exercise. The goal is to acquire a sense of the statistical distribution the examples are being drawn from. The example you write should be drawn from that same distribution, but sufficiently different from the examples that it provides additional signal. A tricky balancing act, but I have faith in you.

Once you're done, output a test case for this prompt template with a full, complete, value for each variable. The output format should consist of a tagged block for each variable, with the value inside the block, like the below:

<variables>
{{CONSTRUCT_VARIABLES_BLOCK}}
</variables>"""

    synth_test_data_prompt_template_without_example = """<Prompt Template>
{{PROMPT_TEMPLATE}}
</Prompt Template>

Your job is to construct a test case for the prompt template above. This template contains "variables", which are placeholders to be filled in later. In this case, the variables are:

<variables>
{{CONSTRUCT_VARIABLES_NAMES}}
</variables>

First, in <planning> tags, do the following:

1. Summarize the prompt template. What is the goal of the user who created it?
2. For each variable in <variables>, carefully consider what a paradigmatic, realistic example of that variable would look like. You'll want to note who will be responsible "in prod" for supplying values. Written by a human "end user"? Downloaded from a website? Extracted from a database? Think about things like length, format, and tone in addition to semantic content.

Then, output a test case for this prompt template with a full, complete, value for each variable. The output format should consist of a tagged block for each variable, with the value inside the block, like the below:
<variables>
{{CONSTRUCT_VARIABLES_BLOCK}}
</variables>"""

    if examples:
        examples_block = "\n".join([construct_example_block(example) for example in examples])
        return (
            synth_test_data_prompt_template_with_example.replace(
                "{{PROMPT_TEMPLATE}}", prompt_template
            )
            .replace("{{CONSTRUCT_VARIABLES_NAMES}}", construct_variables_names(prompt_template))
            .replace("{{CONSTRUCT_VARIABLES_BLOCK}}", construct_variables_block(prompt_template))
            .replace("{{EXAMPLES}}", examples_block)
        )
    else:
        return (
            synth_test_data_prompt_template_without_example.replace(
                "{{PROMPT_TEMPLATE}}", prompt_template
            )
            .replace("{{CONSTRUCT_VARIABLES_NAMES}}", construct_variables_names(prompt_template))
            .replace("{{CONSTRUCT_VARIABLES_BLOCK}}", construct_variables_block(prompt_template))
        )

接下来,另一个快速辅助函数,用于填充适当的提示模板并调用Claude。

def get_test_data(prompt_template, examples, custom_planning=None):
    """Generate test data using the Claude API."""
    synth_eval_prompt_ready = format_prompt_template_for_synth_evals(prompt_template, examples)

    messages = [
        {
            "role": "user",
            "content": synth_eval_prompt_ready,
        }
    ]
    if custom_planning:
        messages.append(
            {
                "role": "assistant",
                "content": custom_planning,
            }
        )

    message = (
        CLIENT.messages.create(
            max_tokens=4000,
            messages=messages,
            model=MODEL_NAME,
            temperature=1,
        )
        .content[0]
        .text
    )

    return message
# We'll use this function to sample Claude's response to the filled-in template,
# once we have our example values/test case.


def call_claude_with_template(prompt_template, variables):
    """Call Claude with a filled prompt template."""
    filled_template = prompt_template
    for var, value in variables.items():
        filled_template = filled_template.replace(f"{{{{{var}}}}}", value)

    message = (
        CLIENT.messages.create(
            max_tokens=4000,
            messages=[
                {
                    "role": "user",
                    "content": filled_template,
                }
            ],
            model=MODEL_NAME,
            temperature=0.7,
        )
        .content[0]
        .text
    )

    return message

现在我们可以开始把各个部分组合起来了。首先,在这里输入你的提示词模板。

# Replace this with your prompt template!
# Use double-brackets to indicate variables
# Here's an example:
prompt_template = """You are a customer support bot for Acme Corporation.
Here is an FAQ with Acme's relevant policies:

<documents>
{{DOCUMENTS}}
</documents>

Please respond to this customer support question using details from the policies:

<question>
{{QUESTION}}
</question>"""

variables = extract_variables(prompt_template)
print("\nIdentified variables:")
for var in variables:
    print(f"- {var}")

接下来,如果你有任何“黄金示例”的输入和理想输出,可以输入这些内容。相关代码目前已被注释掉。

planning_text = None
USER_EXAMPLES = []

# if input("\nDo you want to provide an example value for your variables? (y/n): ").lower() == 'y':
#     example = {}
#     for var in variables:
#         example[var] = input(f"Enter an example value for {var}: ")
#     USER_EXAMPLES.append(example)

接下来,我们可以用这些信息填充测试用例生成提示模板,并得到一个测试用例!

result = get_test_data(prompt_template, USER_EXAMPLES, planning_text)

现在,让我们看看测试用例以及Claude用于生成该测试用例的规划。

planning_match = re.search(r"<planning>(.*?)</planning>", result, re.DOTALL)
if planning_match and not planning_text:
    planning_text = "<planning>\n" + planning_match.group(1).strip() + "\n</planning>"

extracted_variables = {}
for var in variables:
    var_match = re.search(f"<{var}>(.*?)</{var}>", result[result.index("<variables>") :], re.DOTALL)
    if var_match:
        extracted_variables[var] = var_match.group(1).strip()

USER_EXAMPLES.append(extracted_variables)

print("~~~~~~~~~~~\nGenerated test case:\n~~~~~~~~~~~")
for var, value in extracted_variables.items():
    print(f"{var}:\n{value}\n")

print("~~~~~~~~~~~\nPlanning:\n~~~~~~~~~~~")
print(planning_text)

从这里开始,我们有几种方式可以继续。我们可以生成更多的测试用例,或者修改Claude的规划逻辑。让我们稍微修改一下Claude的规划逻辑。也许我们知道ACME的文档使用编号行。其他一些实际可行的修改可能包括:

planning_text = planning_text.replace(
    "each with a question and answer format",
    "each with a question and answer format and associated number.",
)
# You might have slightly different planning text and therefore need to rewrite the replace.

让我们重置示例,但使用这段规划文本作为预填充。(这样可以节省一点采样时间。)

USER_EXAMPLES = []
result = get_test_data(prompt_template, USER_EXAMPLES, planning_text)

现在让我们看看新的结果。

# Copied and pasted from a cell above.
planning_match = re.search(r"<planning>(.*?)</planning>", result, re.DOTALL)
if planning_match and not planning_text:
    planning_text = "<planning>\n" + planning_match.group(1).strip() + "\n</planning>"

extracted_variables = {}
for var in variables:
    var_match = re.search(f"<{var}>(.*?)</{var}>", result[result.index("<variables>") :], re.DOTALL)
    if var_match:
        extracted_variables[var] = var_match.group(1).strip()

USER_EXAMPLES.append(extracted_variables)

print("~~~~~~~~~~~\nGenerated test case:\n~~~~~~~~~~~")
for var, value in extracted_variables.items():
    print(f"{var}:\n{value}\n")

print("~~~~~~~~~~~\nPlanning:\n~~~~~~~~~~~")
print(planning_text)

很好,它完成了编号问答!

让我们再做一个例子。这个例子将使用我们已经有的示例,所以希望它会有所不同,更有趣。

result = get_test_data(prompt_template, USER_EXAMPLES, planning_text)
# Copied and pasted from a cell above.
planning_match = re.search(r"<planning>(.*?)</planning>", result, re.DOTALL)
if planning_match and not planning_text:
    planning_text = "<planning>\n" + planning_match.group(1).strip() + "\n</planning>"

extracted_variables = {}
for var in variables:
    var_match = re.search(f"<{var}>(.*?)</{var}>", result[result.index("<variables>") :], re.DOTALL)
    if var_match:
        extracted_variables[var] = var_match.group(1).strip()

USER_EXAMPLES.append(extracted_variables)

print("~~~~~~~~~~~\nGenerated test case:\n~~~~~~~~~~~")
for var, value in extracted_variables.items():
    print(f"{var}:\n{value}\n")

print("~~~~~~~~~~~\nPlanning:\n~~~~~~~~~~~")
print(planning_text)

仍然是关于 ACME 公司,但问题不同,知识库也不同。

从这里开始,世界尽在掌握——你可以通过循环运行代码生成更多测试用例,进一步编辑规划,在这些测试用例上评估Claude,并将你制作的测试用例连同标准答案一起作为多示例放入提示中。

要获得标准答案,你可以自己从头编写,或者让Claude编写答案,然后根据需要进行编辑。随着提示缓存的问世,现在是在提示中添加大量示例以提高性能的最佳时机。

常见问题

常见错误包括:1) 变量提取失败,可能因正则表达式不匹配或模板格式错误,需检查双花括号是否匹配;2) API调用失败,可能因API密钥无效或网络问题,需检查密钥和网络连接;3) 生成结果解析失败,可能因模型输出格式不符合预期,需调整正则表达式或增加容错;4) 生成数据质量不佳,可能因规划不充分或示例不足,可增加规划指导或提供更多示例。排查方法包括打印中间结果、检查API响应、逐步调试。生产环境优化建议:使用提示缓存减少API调用成本,批量生成测试用例,以及根据业务需求定制规划逻辑。

企业应用扩展

可扩展方向包括:1) 支持更多变量类型和复杂模板结构;2) 集成自动化评估流程,将生成的测试用例用于批量测试;3) 结合人工反馈迭代优化生成质量;4) 利用Claude的提示缓存功能,在提示中添加大量示例以提高性能;5) 开发可视化界面,方便非技术用户使用。此外,可考虑将生成的数据用于微调或few-shot学习,进一步提升模型在特定任务上的表现。

总结

本文介绍如何利用Claude API为提示模板自动生成合成测试数据,以解决真实数据不足或隐私限制的问题。通过提取模板中的变量、构建示例块、生成测试用例并迭代优化,开发者可以评估提示性能并改进提示。文章提供了完整的Python代码实现,包括辅助函数、提示模板构造和API调用,并展示了如何通过修改规划逻辑来定制生成数据的风格。该方法适用于提示评估和多示例提示优化,是AI工程实践中的实用技术。

内容来源与验证

继续系统学习

站内还有更多完整实战教程

加入会员,解锁全部教程、完整代码与持续更新内容。

加入会员
查看全部模型评估与微调教程