用 Langfuse 打造终极 LLMOps 体系——Langfuse 中的 Prompt 管理

0 阅读33分钟

引言

Prompt 是任何 LLM 驱动应用的基础,有效管理 prompt 对于构建可靠且成本高效的系统至关重要。本章介绍 Langfuse 的 prompt 管理能力,这些能力为创建、版本化和迭代 prompt 提供了一套结构化工作流。我们将学习如何组织 prompt、比较版本,并在需要时回滚更改——这与软件开发中的版本控制实践类似。我们还将探索 Langfuse 如何支持 prompt 实验,使我们更容易对不同 prompt 策略进行 A/B 测试,追踪不同变体之间的性能指标,并确保跨部署环境的行为一致性。

本章还会强调 prompt 模板、参数化,以及与 LangChain 等工具集成如何加速实验。到本章结束时,我们将对 prompt 生命周期管理建立扎实基础,并为下一章中的评估做好准备。

结构

本章将覆盖以下主题:

  • Prompt Engineering 导论
  • 理解 Langfuse 中的 Prompts 数据模型
  • 在 Langfuse 中编写第一个 Prompt
  • Langfuse 中的 Prompt 管理功能
  • 版本控制与标签
  • 可组合性
  • 在应用中使用 Prompts
  • A/B 测试
  • 缓存
  • Config
  • Langfuse 中的 LLM Playground
  • Webhooks 与 Slack 集成

Prompt Engineering 导论

在开始在 Langfuse 中编写 prompt 之前,我们先对 Prompt Engineering 做一个非常简短的介绍。这里不会深入展开,因为这不是本书的主题,但它可以强化我们为 LLM 编写有效 prompt 的最佳实践。

Prompt engineering 是一门设计结构化输入的科学。我们把这些结构化输入提供给生成式 AI,使模型能够产生有用、可靠且可重复的输出。从核心上看,prompt engineering 回答三个实际问题:模型到底应该做什么?它应该以什么格式回答?我们如何知道它的回答是可接受的?《Prompt Engineering for Generative AI》(James Phoenix 和 Mike Taylor)一书用一组简洁、模型无关、面向生产的原则概括了这些思想,每个 prompt 都应该应用这些原则。这五个原则是:给出方向、指定格式、提供示例、评估质量、拆分任务。

当我们在团队或项目中处理大量 prompt 时,小问题很快就会变成大问题。写得不好的 prompt 会导致结果不一致、需要更多人工审查,并造成不可靠的自动化。它们还会让调试变得困难,并可能降低用户信任。Langfuse 通过将所有 prompt 集中管理、追踪变更、测试不同版本并监控性能来提供帮助。然而,仅有好工具并不够,强 prompt 设计仍然必不可少。通过应用 prompt engineering 的五个关键原则,我们可以让 prompt 更可靠、更一致,也更容易维护,即使模型或设置发生变化也如此。接下来的几节会用简单、实用的建议解释每个原则,你可以立刻开始使用。

给出方向

明确说明任务、角色或 persona、约束,以及任何相关上下文。尽可能减少关于意图的歧义。

LLM 是统计模式匹配器,模糊指令会导致输出变化很大。清晰方向可以减少方差,并使模型的概率分布与我们期望的目标对齐。

实用建议:

以简短任务声明开头,例如:“你是一位经验丰富的产品文案。请为……生成五个简洁的产品名称。”

在一开始就包含范围和约束:可接受长度、语气、禁用词,或监管限制。

指定格式

当下游系统依赖结果时,应要求严格的输出格式。

结构化格式,例如 JSON、YAML、CSV,以及带编号标题的项目符号列表,可以让解析更确定,并减少下游错误。

实用建议:

提供一个示例 schema,并明确说明 “Return only”,例如:返回有效 JSON,包含 keys:title、bullets(array)、estimated tokens(integer)。JSON 外不要包含任何 prose。

将输出验证器作为 prompt 管理流水线的一部分,也就是在一个新 prompt 版本被提升时运行的测试。

提供示例

添加 few-shot 示例,也就是输入 / 期望输出对,尤其适用于细微复杂的任务。

示例可以将模型分布引导到期望的结构、风格和内容上。它们是塑造输出最可靠的手段之一。

实用建议:

使用固定长度、有代表性的示例,覆盖常见边界情况。

区分正面和负面示例:展示 “good” 和 “bad” 输出,并标注它们。

评估质量

为输出设计明确的评估规则和指标,包括自动评估和人在回路评估。

如果没有客观检查,我们就无法衡量回归、比较 prompt,也无法知道模型更新是否改变了行为。

实用建议:

定义通过 / 失败测试:格式验证、事实检查、禁用词检查、与参考答案之间的语义相似度阈值。

对 prompt 进行埋点以收集遥测数据:成功率、重试次数、用户编辑次数,以及接受所需时间。

自动化常规检查,例如 schema、脏话、简单事实查找,并将不确定案例路由给人工审查者。使用这些标签来改进 prompt 和示例。

拆分任务

将复杂任务拆分成更小的步骤或 prompt 链,例如规划 → 执行 → 验证,而不是要求一个庞大的 prompt 完成所有事情。

任务拆解可以降低模型的认知负担,提升可解释性,也让错误定位更简单。

实用建议:

使用 “先规划再执行” 模式:先让模型列出步骤,审查计划,然后生成最终输出。

实现 prompt chains 或 function-calling 模式,让每个子任务都有自己的 prompt 和验证器。

将每个子 prompt 作为独立工件追踪,这样我们可以对每一步进行版本化、测试和审计。

下表展示了使用上述原则的一些好 prompt 示例:

原则不好的示例好的示例
给出方向“Write something about our new phone.”“Explain data privacy.”“You are a tech journalist. Write a 150-word news article announcing our new smartphone, focusing on camera and battery life.”“Explain data privacy to a high school student in simple, clear language with one real-life example.”
指定格式“Summarize this article.”“Extract details from this invoice.”“Summarize this article in exactly three bullet points, each under 20 words.”“Extract the fields InvoiceNumber, Date, TotalAmount, and VendorName from the invoice and return only valid JSON.”
提供示例“Write a short product review.”“Create a chatbot greeting.”“Example → Input: ‘Wireless earbuds with long battery life.’ Output: ‘Great sound and comfortable fit. The battery lasts all day!’ Now write one for: ‘Noise-cancelling headphones.’”“Example → Input: ‘Customer opens chat at 9 AM.’ Output: ‘Good morning! How can I help today?’ Now write one for: ‘Customer opens chat at 7 PM.’”
评估质量接受每一个模型响应,而不检查它是否准确或格式是否正确。检查每个回答是否包含三个关键点、没有拼写错误,并使用友好语气。自动验证 JSON 格式,并追踪用户反馈,以 1–5 分评价准确性和清晰度。
拆分任务写一篇关于欧洲 EV 行业的 1000 字详细报告。Step 1:总结 EV 行业历史。Step 2:列出前五个品牌及其优势。Step 3:写一段 200 字的预测总结。

表 6.1:Prompt Engineering 五个原则的示例

在实践中,我们主要编写两类 prompt:纯文本 prompt 和基于聊天的 prompt。

纯文本 prompt 是发送给模型的一整块文本。这是早期 AI 模型的标准格式,例如 GPT-3,或者没有 chat API 的 instruction-tuned 模型。所有内容,包括上下文、角色和指令,都必须写在同一条消息中。

You are an expert product copywriter.
Write a short, friendly product description for a pair of wireless earbuds.
Include 3 bullet points and end with a call to action.

这些 prompt 仍然非常有用,因为它们构造简单,也容易通过参数化模板进行测试。不过,在高级用例中,我们应该按角色类型分离指令,例如 system 或 user,尤其是在处理多轮对话时。现代模型,例如 GPT、Claude 或 Gemini,使用 chat message 格式,其中多个角色会构造对话结构。这让模型能够理解谁在说话,以及为什么说。

角色目的示例内容
System设置模型的全局行为和身份。“You are an experienced financial analyst who writes concise, neutral summaries.”
User提供来自最终用户的实际问题、任务或输入。“Summarize this quarterly report into three main points.”
Assistant表示模型之前的响应,可选。对多轮对话有用。“Here’s your summary from last quarter…”

表 6.2:Prompt 中的不同角色

在 Langfuse 中,我们可以同时版本化纯文本 prompt 和基于聊天的 prompt。

对于纯文本 prompt,我们通常会存储一个带参数的模板字符串,例如 {{product_name}}{{tone}}

对于基于聊天的 prompt,Langfuse 允许我们分别存储 system、user,以及可选的 assistant message,使我们更容易追踪更新,并按 message 类型分析性能。

带着这些背景,我们现在来看 Langfuse 中的 Prompts 数据模型。

理解 Langfuse 中的 Prompts 数据模型

与可观测性类似,Prompts 在 Langfuse 中也有特定的数据模型。相比可观测性模型,Prompts 模型要简单得多,本质上只有一个主要对象,称为 Prompt Object。让我们看看这个模型的关键字段,并理解每个字段如何有助于可靠的 prompt 管理。

Name

name 字段在我们的 Langfuse 项目中唯一标识一个 prompt。

目的:
确保同一个逻辑 prompt 的不同版本能够保持关联。

最佳实践:
使用简短、有描述性的名称来反映用途,例如 "support_reply""product_tagline""invoice_parser"

示例:

name: "product_description_v2"

Type

type 定义我们的 prompt 是基于文本的 prompt,还是基于聊天的 prompt。默认类型是 "text"

Text prompt 是单块指令,适用于摘要、分类或补全。

Chat prompt 包含多条 message,每条 message 都有 system、user 或 assistant 等角色,非常适合对话式或多轮上下文。

示例:

Text: "Write a 50-word summary of the following paragraph."
Chat: [
  {"role": "system", "content": "You are a helpful tutor."},
  {"role": "user", "content": "Explain Newton’s laws in simple terms."}
]

Prompt

这个字段包含模型实际看到的 prompt 文本或 message 数组。它可以包含占位符,例如 {{product_name}},用于运行时动态替换。

对于 Text Prompts,它是单个字符串。

对于 Chat Prompts,它是一个 {role, content} 对列表。

示例:

[  { "role": "system", "content": "You are an expert recruiter." },  { "role": "user", "content": "Review this resume for {{job_title}}." }]

Config

config 字段保存与 prompt 关联的所有模型特定或行为参数。它是一个灵活的 JSON 对象,我们可以在其中定义:

  • 模型名称,例如 "gpt-4.1""claude-3-haiku"
  • 模型参数,例如 temperature、top_p 或 max_tokens。
  • 工具 schema 或输出格式,类似 JSON mode。
  • 任何其他会影响 prompt 行为的设置。

这确保 prompt 文本和它的运行时行为一起被版本化。

示例:

"config": {
  "model": "gpt-4.1",
  "temperature": 0.01,
  "max_tokens": 500
}

Version

无论是一个小的措辞调整,还是一个新的模型配置,每次对 prompt 的变更都会创建一个新版本。版本号让 Langfuse 能够保留完整历史,使我们可以比较不同迭代之间的性能,或者在需要时回滚。版本号在创建新 prompt 版本时会自动分配并递增。

Labels

Labels 是映射到特定版本的人类可读快捷方式。不需要记住版本号,我们可以把它们标记为 "staging""production""test"。这可以实现无缝环境管理:当你将新版本提升到 production 时,只需要移动 label,不需要改代码。

Tags

Tags 让我们更容易在 Langfuse dashboard 中组织和过滤 prompt。我们可以按功能、产品区域或负责人对 prompt 分组。

示例 tags:

"customer-support", "email-generator", "ecommerce", "analytics"

到这里,我们已经覆盖了 Prompt Management 的理论部分。现在来看如何在 Langfuse 中创建第一个 prompt。

在 Langfuse 中编写第一个 Prompt

添加 prompt 最简单的方式是使用 Langfuse UI。在左侧导航中,在 Prompt Management 下选择 Prompts。这会显示已有 prompt 列表;如果还没有 prompt,则会显示创建新 prompt 的选项。Langfuse 让非技术用户也能够很容易地进行 prompt 管理,这是该平台的关键特性之一。 image.png

图 6.1:在 Langfuse 中添加新 Prompt

这个页面上的字段与上一节中看到的数据模型完全一致。Langfuse 还允许我们通过在 prompt name 中使用 / 来将 prompt 组织到文件夹中。例如:

product/product_description_generator

填写所有字段后,点击 Create Prompt 创建该 prompt。

image.png

图 6.2:新创建的 Prompt

正如我们所看到的,我们的 prompt 有 3 个变量:product_name、features 和 audience,它们写在双大括号中。要给 prompt 添加 tags,可以点击 prompt 名称旁边的 tags 图标并添加 tags。要添加新版本,可以点击 New Version 按钮。

添加 chat 类型 prompt 也遵循同样步骤,只是不选择 Text,而是选择 Chat。对于基于聊天的 prompt,我们可以添加 system、user 和 assistant 等不同角色。这类 prompt 在开发具有对话能力的应用时很有用,或者当我们希望将 system prompt 与 user prompt 分开时也很有用。

image.png

图 6.3:Chat 风格 Prompt

Prompts 也可以通过 Langfuse SDK 以编程方式创建。

from langfuse import get_client
from dotenv import load_dotenv

load_dotenv()
langfuse_client = get_client()

langfuse_client.create_prompt(
    name="product_description_generator_new",
    type="text",
    prompt="Write a compelling and concise product description for an e-commerce listing.\n\nProduct Name: {{product_name}}\nKey Features: {{features}}\nTarget Audience: {{audience}}\n\nWrite in a friendly, trustworthy tone. Limit your response to 80 words. End with a short call to action.",
    config={
        "model": "gpt-4o-mini",
        "temperature": 0.6,
        "max_tokens": 150,
        "top_p": 0.9,
    },
    labels=["staging"],
    tags=["marketing", "ecommerce", "copywriting"],
)

如果同名 prompt 已经存在,这段代码会为同一个 prompt 添加一个新版本。

正如我们可以看到的,在 Langfuse 中添加新 prompt 非常直接且直观。下一节中,我们会看更多 prompt 管理功能,以及如何在应用中使用这些 prompt。

Langfuse 中的 Prompt 管理功能

到目前为止,我们已经看到如何在 Langfuse 中创建 prompt。前面的示例都比较基础,可以作为开始做 prompt 管理的起点。由于 prompt 是任何 LLM 应用的构建块,它们的生命周期管理需要的不只是创建新 prompt 的能力。

随着应用增长,prompt 往往会演进。我们会调整措辞、添加示例、调整语气,或者改变它们与模型交互的方式。我们经常需要知道:哪个版本正在上线,哪个版本正在测试,两个编辑版本之间发生了什么变化?Langfuse 通过提供 prompt 管理功能来解决这些问题,它让 prompt 的存储、更新和部署具备结构和控制。

Langfuse 中的 prompt 管理很像代码版本控制,但它是专门为 prompt 设计的。它帮助我们追踪每一次变更,安全地进行实验,并在不影响主应用逻辑的情况下切换版本。让我们探索使这一切成为可能的关键功能。

版本控制与标签

在 Langfuse 中,我们对 prompt 做出的每一次变更都会自动创建一个新版本。这为我们提供了 prompt 随时间发展的完整历史。我们可以查看过去版本、比较它们,或者在需要时回滚到早期版本。Version ID 是数字,并且在创建新版本时自动递增。除了这些 ID,prompt 也可以基于 labels 识别,例如 “production”、“staging” 或 “latest”。

默认情况下,每个 prompt 首次创建时,都会有 version ID 1,以及 labels “latest” 和 “production”。每次 prompt 发生变更时,版本号都会加一。只有一个版本可以被标记为 “production”,这是通过 SDK 请求时的默认版本。对于每个新版本,label “latest” 会被应用到最新版本上。

新的 labels 可以通过 UI 或 SDK 创建。Labels 绑定到某个特定 prompt,可以被该 prompt 的任意版本使用。Labels 不能在不同 prompt 之间共享。

image.png

图 6.4:向 Prompt 添加新 Label

在 UI 中,我们也可以比较两个 prompt 版本以检查差异。

image.png

图 6.5:比较两个 Prompt 版本

一旦我们对 prompt 进行了版本化和标记,就可以在应用中使用 SDK 获取它。

from langfuse import get_client
from dotenv import load_dotenv

load_dotenv()
langfuse_client = get_client()

# 如果没有指定 version 或 label,默认返回带有 production 标签的 prompt
default_prompt = langfuse_client.get_prompt("product_description_generator")

# 按 label 获取
prompt_by_label = langfuse_client.get_prompt("product_description_generator", label="latest")

# 按版本号获取
prompt_by_version = langfuse_client.get_prompt("product_description_generator", version=1)

print(prompt_by_version.__dict__)

返回的 prompt object 包含该 prompt 的所有重要信息,我们可以在应用其他部分使用这些信息。这包括文本、labels、tags,甚至 prompt 的 configuration。我们会在本章后面看到,如何使用 config 来调用 LLM 生成响应。

{
  "name": "product_description_generator",
  "version": 1,
  "config": {
    "model": "gpt-4o-mini",
    "temperature": 0.6,
    "max_tokens": 150,
    "top_p": 0.9
  },
  "labels": ["production"],
  "tags": ["product", "staging"],
  "commit_message": "First version of the product_description_generator prompt",
  "is_fallback": false,
  "prompt": "Write a compelling and concise product description for an e-commerce listing.\nProduct Name: {{product_name}}\nKey Features: {{features}}\nTarget Audience: {{audience}}\nWrite in a friendly, trustworthy tone. Limit your response to 80 words. End with a short call to action.\n"
}

应该限制通过版本号获取 prompt 的方式,因为这需要改代码,并且可能需要部署新的应用版本,才能使用 prompt 的新版本。

当我们将一个新版本提升到 production 时,不需要修改代码。我们只需要在 Langfuse 中将 “production” label 移动到最新版本即可。这使更新变得无缝、一致,并且在跨环境管理时更加容易。不过,这件事需要通过适当验证和实验来完成,因为它可能对系统功能产生重大影响。如果出现负面效果,我们也可以同样轻松地将之前可工作的 prompt 版本重新标记为 “production”。

可组合性

Prompt Composability 指的是我们可以在其他 prompt 中复用 prompt。与其从头编写每个 prompt,并重复共享指令、上下文或示例,我们可以创建模块化 prompt 组件,然后引用它们。这样可以避免重复,并让一个基础 prompt 服务于多个依赖 prompt。

例如,我们可能有一个名为 “Common Instructions for Tone & Style” 的 prompt 片段,并在每个客户支持 prompt 中引用它。这样,如果我们改变公司语气,只需要更新这一个片段,所有依赖 prompt 都会自动获得这一变更。

可组合性的实际用途

减少重复并更容易维护:
当许多 prompt 共享相同上下文,例如规则、品牌语气、免责声明时,我们可以避免复制粘贴。如果某条规则发生变化,例如 “use friendly tone” 变成 “use professional tone”,我们只需更新一个片段,而不是手动编辑几十个 prompt。

更快迭代并减少错误:
通过模块化结构,我们可以专注于更小部分,例如只关注上下文片段,并知道变更会传播到依赖 prompt。这意味着出现不一致的机会更少。

更好的治理与审计能力:
我们可以清楚识别 prompt 中哪一部分是共享的,哪一部分是独有的。当出现问题时,可以检查共享片段版本,看看错误是否来自那里。

支持大型团队或大量 Prompt 的扩展性:
随着我们构建更多流程,不会盲目爆炸式增加唯一 prompt 模板数量。我们会构建一个模块化组件库。

在整体范围内保持一致品牌语气和规则:
由于共享上下文被复用,我们可以更有信心地确保系统所有部分都与品牌指南或监管要求保持一致。

要添加 composed prompt,我们需要先将各个 prompt 部分分别创建为新的 prompt。在主 prompt 中,也就是需要发生组合的位置,我们可以点击 Add prompt reference 按钮,通过 label / version 将 prompt 添加到当前 prompt 中。

image.png

图 6.6:在 Langfuse 中组合 Prompts

保存 prompt 后,它会显示成如下形式:

image.png

图 6.7:由另外两个 Prompts 组成的 Prompt

点击 Resolved Prompt 标签页,可以看到完整 prompt。

在 SDK 中,当这个 prompt 被获取时,它会返回完整解析后的 prompt。

from langfuse import get_client
from dotenv import load_dotenv

load_dotenv()
langfuse_client = get_client()

prompt = langfuse_client.get_prompt("recommender/product_recommender")
print(prompt.__dict__)
{
  "name": "recommender/product_recommender",
  "version": 1,
  "config": {},
  "labels": ["latest", "production"],
  "tags": [],
  "commit_message": null,
  "is_fallback": false,
  "prompt": "You are an AI assistant for a company. Always speak in a friendly, informative, and honest tone. Avoid exaggerations. Mention sustainability when relevant. Keep answers under 150 words. End with a positive note.\n\nWe offer eco-friendly products such as reusable water bottles, bamboo toothbrushes, organic cotton towels, and natural skincare kits. Each product is sourced responsibly and supports small local producers.\n\nUser request: {{user_query}}\nTask: Based on the user’s request, suggest 2 products that best fit their needs. Include one sentence explaining why each product is suitable.\n"
}

使用 references 时,我们必须谨慎管理版本和 labels,因为如果不了解依赖关系就更改被引用 prompt 的版本,可能会导致意外变化。我们应该记录哪些 prompt 依赖哪些共享片段,以避免意外副作用。即使是模块化 prompt 也需要评估,因为基础片段更新并不保证每个依赖 prompt 仍然表现良好。我们可能需要测试它们。还应该使用清晰的命名约定,使团队成员能够快速理解意图。

在应用中使用 Prompts

现在我们已经创建了 prompt,是时候看看如何在应用中使用它们了。我们将使用前面创建的 prompt 之一:product_description_generator,调用它来获取响应。我们会从 Langfuse 获取 prompt 内容,以及绑定到该 prompt 的 config。

这个 prompt 还期望 3 个变量:product_name、features 和 audience,我们需要把它们传给 prompt。

from langfuse import get_client
from dotenv import load_dotenv
from langfuse.openai import openai

load_dotenv()
langfuse_client = get_client()

# 获取默认 prompt 版本,即 label="production"
prompt = langfuse_client.get_prompt("product_description_generator")

def generate_product_description(product_name: str, features: list[str], audience):
    # 将变量值传给 prompt
    prompt_content = prompt.compile(
        product_name=product_name, features=features, audience=audience
    )

    # 从 prompt 中获取 config,并在调用 openai 时使用
    config = prompt.config

    completion = openai.chat.completions.create(
        model=config.get("model", "gpt-4.1-mini"),
        messages=[{"role": "system", "content": prompt_content}],
        temperature=config.get("temperature", 0.1),
        max_tokens=config.get("max_tokens", 200),
        top_p=config.get("top_p", 1),
        # 将 generation 链接到 prompt
        langfuse_prompt=prompt,
    )

    return completion.choices[0].message.content

generate_product_description(
    product_name="PulsePro Smartwatch",
    features=[
        "Heart rate and sleep tracking",
        "built-in GPS",
        "waterproof design",
        "7-day battery life",
        "smartphone notifications",
    ],
    audience="Active professionals and fitness enthusiasts",
)

变量值可以通过 prompt 的 compile 方法以 key-value 对传入。对于 temperature、max_tokens 和 top_p 等模型参数,值也从 prompt 保存的 config 中获取。

上面代码产生的 trace 在 Langfuse 中会如下所示:

image.png

图 6.8:使用来自 Langfuse 的 Prompt

通过在 OpenAI 调用中传入 langfuse_prompt=prompt,我们还能够将 Langfuse 中的 prompt 与 trace 关联起来。这是一个非常强大的功能,它允许我们在 prompt 本身上追踪一些关键指标,例如延迟、tokens 和成本。创建带有关联 prompt 的 trace 后,我们可以访问该 prompt 的 Metrics 标签页,按 prompt version 查看这些指标。

image.png

图 6.9:Prompt 级别指标

Prompts 也可以在 LangChain 等其他集成中链接到 traces。下面是使用 LangChain 将 prompt 链接到生成 trace 的同一示例。

from langfuse import get_client
from dotenv import load_dotenv
from langfuse.langchain import CallbackHandler
from langchain_core.prompts import PromptTemplate
from langchain_openai import OpenAI

load_dotenv()
langfuse_client = get_client()

# 初始化 LangChain 的 Langfuse handler
langfuse_handler = CallbackHandler()

# 获取默认 prompt 版本,即 label="production"
prompt = langfuse_client.get_prompt("product_description_generator")

langchain_prompt = PromptTemplate.from_template(
    prompt.get_langchain_prompt(),
    metadata={"langfuse_prompt": prompt}
)

def generate_product_description(product_name: str, features: list[str], audience):
    llm = OpenAI()
    completion_chain = langchain_prompt | llm

    completion_chain.invoke(
        {"product_name": product_name, "features": features, "audience": audience},
        config={"callbacks": [langfuse_handler]}
    )

generate_product_description(
    product_name="PulsePro Smartwatch",
    features=[
        "Heart rate and sleep tracking",
        "built-in GPS",
        "waterproof design",
        "7-day battery life",
        "smartphone notifications",
    ],
    audience="Active professionals and fitness enthusiasts",
)

这里我们使用 metadata={"langfuse_prompt": prompt} 将 prompt 与 trace 关联起来。

A/B 测试

A/B testing,也称为 split testing,是一种简单但强大的方法,用于比较两个不同版本的内容。例如,两个网站设计、两封邮件,或者在我们的场景中,两个 prompt 版本。版本 A 展示给一组用户,而版本 B 展示给另一组用户。收集足够数据后,我们基于可衡量结果比较哪个版本表现更好,例如用户满意度、响应清晰度、速度或成本。这个过程帮助我们做数据驱动决策,而不是依赖猜测或个人偏好。A/B testing 是一种受控方式,可以直接在生产环境中对真实用户测试变体,而不是完全依赖离线测试方法。

在 Langfuse 中,我们可以为 prompt 的不同版本打 label,例如 version-a 和 version-b,然后在它们之间拆分流量,从而收集指标并决定哪个版本更好。

例如,一个变体可能写着 “Be concise and professional”,另一个变体则写着 “Be friendly and conversational”。我们用真实用户运行两个版本,衡量它们各自表现,包括响应质量、用户参与度、成本、延迟,并选择胜出者。

Langfuse 中的 A/B 测试如何工作

我们取一个 prompt,并创建至少两个不同版本。然后为它们分配 labels,例如 version-a 和 version-b,这样我们就可以在代码或配置中清楚引用每个变体。在应用代码中,不再始终使用同一个 prompt 版本,而是随机选择两个或多个已标记版本之一。例如:

import random

prompt_a = langfuse.get_prompt("my_prompt", label="version-a")
prompt_b = langfuse.get_prompt("my_prompt", label="version-b")
selected = random.choice([prompt_a, prompt_b])

然后,我们使用 selected 调用模型并传入用户输入。正如上一节看到的,Langfuse 会记录每个 prompt version 的性能指标:响应延迟、token 使用量、每次调用成本、质量 / 评估分数,如果已定义。这些数据随后可以在 UI 中查看。一旦我们收集了足够数据,就可以比较两个版本。哪个版本给出了更好的响应,根据指标或人工反馈判断?哪个版本成本更低或速度更快?基于这些结果,我们可以通过 labels 将某个版本提升为 production。

使用 Langfuse 运行有效 A/B 测试

我们需要足够流量,使测试结果有意义。小样本量可能产生误导。

开始前应该定义清晰指标,例如 “没有 follow-up question 的 session 百分比”、“响应时间低于 800 ms”、“每次响应成本低于 0.005”。

我们必须谨慎管理 labels 和 versions。被测试版本应该隔离并被正确追踪,这样我们才能准确知道哪些响应来自哪个变体。

当用户影响可衡量,并且系统可以在测试阶段容忍变化时,A/B testing 最有用。如果处于高风险场景,我们可能更偏向于只在充分测试后进行完整发布。

在后续章节中,我们会看到一个在 Langfuse 上运行 A/B test 的完整示例。在这个示例中,我们还会利用 Evaluations 功能,在选择 prompt version 前做出有依据的决策。

缓存

当我们的应用使用存储在 Langfuse 中的 prompt 时,每次获取 prompt 都可能涉及网络调用和数据库查询。如果每个请求都这样做,就可能增加延迟、影响用户体验并降低响应能力,尤其是在实时或高吞吐应用中。

Langfuse 通过在本地内存中保留一份 prompt 副本来解决这个问题,因此后续对同一 prompt 的请求可以避免完整检索过程。在 Langfuse 中,prompt caching 确保获取 prompt 基本上可以瞬间完成,在保持应用低延迟的同时,仍允许 prompt 被集中管理和更新。

Langfuse 中的缓存如何工作

当我们调用 SDK 方法,例如 get_prompt("my_prompt") 时,第一次调用会从 Langfuse 服务中获取 prompt。之后:

Prompt 会被存储在 SDK 的本地缓存中,并带有可配置 TTL,即 Time-to-Live,例如默认 60 秒。

在 TTL 内的后续调用中,SDK 会立即返回缓存版本,没有网络或重处理延迟。

如果 TTL 已经过期,SDK 可能会立即返回 stale version 并在后台刷新缓存,也可能根据配置直接获取新版本。这意味着我们很少会阻塞在 prompt 检索上。

在 Langfuse server 端,尤其是自托管时,系统会利用缓存,例如 Redis,进行 prompt 检索。这可以减少数据库负载,并加速频繁使用的 prompt version 的查找。另外,当 prompt 更新,也就是创建新版本时,缓存会失效,这样我们不会继续提供过时 prompt。

要使用固定 TTL 获取 prompt,可以这样写:

prompt = langfuse.get_prompt("product_recommender", cache_ttl_seconds=300)

类似地,要禁用缓存,将 cache_ttl_seconds 设置为 0:

prompt = langfuse.get_prompt("product_recommender", cache_ttl_seconds=0)

使用缓存的实用建议

对于生产中的 prompt,使用中等 TTL 的缓存,例如 300 秒。

对于处于活跃开发或测试中的 prompt,可以考虑低 TTL 或禁用缓存,以便立即看到更改。

应用启动时,可以考虑预取关键 prompt,这样第一个用户请求不必等待首次网络获取。这在微服务或 serverless 环境中特别有用。

在日志中监控 cache hit-rate 和 prompt retrieval latency。

注意依赖关系:如果一个 prompt 通过 composability 引用了另一个 prompt,那么被引用 prompt 的变化应该触发失效或刷新,使依赖 prompt 不会 stale。

要在启动时获取 prompt,并在发生错误时提供 fallback,可以这样做:

from langfuse import get_client
from dotenv import load_dotenv
from langfuse.model import TextPromptClient

load_dotenv()
langfuse_client = get_client()

fallback_prompt = (
    "Write a compelling and concise product description for an e-commerce listing"
)

def fetch_prompt() -> TextPromptClient:
    try:
        # 获取并缓存 prompt
        langfuse_client.get_prompt(
            "product_description_generator",
            cache_ttl_seconds=300,
            fallback=fallback_prompt,
        )
    except Exception as e:
        print(f"Failed to fetch prompt : {e}")
        # 这里我们可能会抛出错误或退出应用
        # 也可以返回 fallback prompt
        return TextPromptClient(prompt=fallback_prompt, is_fallback=True)

fetch_prompt()

这覆盖了两类失败情况:Langfuse server 不可达,或者在获取 prompt 时发生 Exception。

Config

我们已经看过一些与 prompt 一起使用 config 的示例。Langfuse 中的 config 字段用于存储所有额外设置,并与 prompt 本身一起版本化。除了 top_p、temperature 等模型参数,我们还可以存储 JSON schemas,并将它们映射到模型响应。Config 是可选的,并附着到 prompt 的某个特定版本。Config 还可以包含与 prompt 相关的 metadata。

Config 可以使用的示例场景

我们在之前代码示例中看到,如何从检索到的 prompt 中使用模型参数,并把它们用于模型调用:

completion = openai.chat.completions.create(
    model=config.get("model", "gpt-4.1-mini"),
    messages=[{"role": "system", "content": prompt_content}],
    temperature=config.get("temperature", 0.1),
    max_tokens=config.get("max_tokens", 200),
    top_p=config.get("top_p", 1),
)

现在,如果我们需要更换模型或任何参数,就可以只在 Langfuse 上修改 prompt config,而不需要改代码。

Config 也可以用来存储 JSON 对象,用于映射我们期望从模型获得的响应结构。

{
  "model": "gpt-4.1",
  "json_schema": {
    "type": "object",
    "properties": {
      "invoiceId": { "type": "string" },
      "amount": { "type": "number" },
      "vendor": { "type": "string" }
    },
    "required": ["invoiceId", "amount", "vendor"]
  }
}

Config 的另一个实际用途是存储 prompt 或模型调用的动态 metadata。比如我们可能有一个会随语言变化的 prompt。Config 可以包含支持语言或语气。

{
  "model": "gpt-4.1",
  "supported_languages": ["en", "de", "fr"],
  "tone": "playful"
}

如果之后我们想添加西班牙语 “es”,或者将 tone 改为 “formal”,就可以更新 config 并创建一个新版本。这让行为变化与 prompt 文本模板分开,但仍然被捕获在同一条记录中。

到这里,我们已经覆盖了 Langfuse 中 prompt management 最有用的功能。下一节中,我们将看看如何使用 Langfuse 的 Playground 功能,在进入全面评估前快速测试 prompt。

Langfuse 中的 LLM Playground

当我们开始编写或优化 prompt 时,通常需要一个交互式空间来测试不同措辞、模型或设置的表现。Langfuse 的 Playground 功能正是提供了这样一个空间:一个 sandbox,我们可以在其中调整 prompt、实验变量和配置,并立即看到模型如何响应。相比写好 prompt、部署、等待,然后在真实使用中观察行为,Playground 允许我们直接在 Langfuse UI 中运行 prompt。我们可以改变输入变量,切换模型参数,例如 temperature、max_tokens、top_p,并立即观察不同输出。

Playground 位于左侧导航中的 Playground 标题下。

使用这个功能前,我们需要设置一个 LLM Connection。Langfuse 使用 LLM Connections 直接连接到你偏好的 LLM 提供商。这是使用 Playground 以及一些其他功能所必需的。要添加新 connection,我们需要知道该提供商的所有连接参数。不同提供商会有所不同。例如,OpenAI 只需要 API key,但其他提供商可能需要其他字段。对于 OpenAI,Langfuse 会让所有默认模型可用于 Playground。对于其他提供商,可能需要每个模型一个 connection。Langfuse 支持为以下提供商添加 LLM connections:

  • OpenAI
  • Azure OpenAI
  • Anthropic
  • Google Vertex
  • Amazon Bedrock

image.png

图 6.10:添加新的 LLM Connection

添加 connection 后,Playground 就可以使用了。

image.png

图 6.11:使用 Playground 测试 Prompts

Playground 提供了一些非常方便的功能:

我们可以选择模型,并通过点击 model dropdown 旁边的图标添加参数,例如 temperature、max tokens 等。

我们可以连接 tools、定义 response schema,并更改 prompt 变量。

一旦有了想使用的 prompt,可以直接从 Playground 保存到 Prompt Management。

最后,我们可以将屏幕拆分成更多窗口,同时运行三个不同 prompt,并使用不同模型、参数和 / 或变量。这可以很好地对所有版本的输出进行并排比较。

我们也可以在 Playground 中打开已有 prompt,并对它们进行迭代。对于包含 generations 的 traces,也可以将 generation 的 prompt 直接打开到 Playground 中。

image.png

图 6.12:向 Prompt 添加变量

要设置额外参数,可以使用 Additional options,通过 JSON 提供其他模型参数。

image.png

图 6.13:更新模型参数

Playground 中的 split screen 功能通过同时显示 prompt 的多个版本,使测试和优化 prompt 更容易。我们不需要在不同测试运行之间来回切换,而是可以在一个地方查看不同 prompt 变体或模型配置的响应,从而立即进行可视化和定性比较。

每个设置都会保留自己的参数,例如模型类型、temperature 或 system instructions,因此我们可以清楚看到单个调整如何影响生成输出。这种方法有助于我们定位哪些变化真正提升了质量、一致性或语气。

image.png

图 6.14:Split Screen 功能

使用 Playground 的优势

我们可以在广泛部署前,先在 sandbox 中实验。

我们能获得即时反馈,而不必等待生产数据。

非开发人员也可以参与,并直观比较 prompt 变体。

实际用例

Playground 适用于任何需要在部署到生产前快速迭代 prompt 的场景。一些广泛示例包括:

内容生成与语气测试:
我们可以使用 Playground 微调生成文本的 prompt,例如营销文案、邮件或摘要,通过实验语气、长度和风格进行优化。例如,测试 “concise and factual” 与 “friendly and conversational” 响应,可以帮助识别哪种语气最符合我们的品牌声音。

对话式 Agent 的响应优化:
构建聊天机器人或虚拟助手的团队,可以测试多种响应风格或道歉模式,以找出哪些会带来更好的用户满意度。

结构化输出与数据格式化:
对于提取数据或生成 JSON 输出的工作流,我们可以用各种真实输入验证 prompt 模板。Playground 可以帮助确保即使输入数据格式发生变化,模型也能持续遵循预期 schema。

评估模型或参数变化:
当切换到新模型,或调整 temperature、max tokens 等参数时,我们可以使用相同 prompt 文本比较质量、一致性和成本。

Webhooks 与 Slack 集成

在 Langfuse 中维护 prompt 生命周期时,我们可能会有新的 prompt 版本、label 变化、tag 更新或删除。如果这些变化发生时没有通知,生产很容易发生漂移,团队也容易错过重要更新。Langfuse 中的 Webhooks & Slack Integration 让我们可以在 prompt 相关事件发生时,自动收到通知或触发工作流。这意味着每当 prompt 演进时,我们都可以在团队之间保持更好可见性,并更快响应。

工作方式

我们在 Langfuse 中配置一个 automation / webhook,监听 prompt events,例如 created、updated 或 deleted。

我们提供一个 URL endpoint,或者 Slack channel 集成。每当事件触发时,Langfuse 会调用它。Payload 包含 prompt version、name、labels、tags、timestamp 等详细信息。

对于 Slack,Langfuse 提供原生支持,因此通知可以直接进入指定 Slack channel,让团队实时感知变化。

出于安全考虑,每个 webhook 调用可以包含 signature,也就是 x-langfuse-signature,使接收服务能够验证真实性。

image.png

图 6.15:添加新的 Webhook

Webhooks 可以通过导航到 Prompts 区域,然后点击 Automations 来添加。

Automation 总是在某种 Source 类型上创建,例如 Prompts。我们可以选择事件,然后按 prompt Name 过滤,以便只针对某些 prompt 触发 Webhook。

Webhook URL 对触发 webhooks 是必填项;如果选择 Slack,则需要添加 Slack Connection。对于自托管版本上的 Slack alerts,我们需要创建一个新的 Slack app,可以使用下面的指南完成:

https://github.com/langfuse/langfuse/blob/main/web/src/features/slack/README.md

除此之外,我们需要设置这 3 个环境变量:

SLACK_CLIENT_ID=your_client_id_here
SLACK_CLIENT_SECRET=your_client_secret_here
SLACK_STATE_SECRET=your_state_secret_here

我们可以使用下面命令生成 SLACK_STATE_SECRET

openssl rand -base64 32 | tr -d "=+/" | cut -c1-32

Webhooks 的实际用途

生产监控:
当 production prompt version 发生变化时,例如 labels 被切换、新版本上线,我们会收到提醒,从而可以验证行为并防止回归。

团队协作:
不再依赖手动沟通,例如 “Hey, I changed the prompt”,Slack channel 会自动收到通知,让所有利益相关方保持同步。

工作流自动化:
我们可以在 prompt 变化时触发 CI / CD 流水线或同步机制,例如更新文档、推送到 GitHub。

结论

本章深入介绍了 Langfuse 中的 Prompt Management,重点是如何构建更可靠、更高效的 LLM 应用。我们探索了 prompt engineering 中的关键实践,例如提供方向、设置格式、包含示例,同时强调质量检查和协作式工作流。我们还覆盖了 Langfuse 中的 Prompts 数据模型、版本控制、可组合性、A/B 测试和缓存功能,展示这些能力如何帮助我们设计和优化 prompt。Webhooks、Slack 通知和 LLM Playground 等其他功能,则使我们能够以交互方式协作、监控和测试 prompt。现在我们已经覆盖了足够内容,终于可以开始评估我们的应用了。下一章将完全专注于这个主题,我们会看到 Langfuse 中的 Evaluations 如何工作。

参考资料

Prompt Engineering for Generative AI: Future-Proof Inputs for Reliable AI Outputs Paperback English edition by James Phoenix & Mike Taylor - www.oreilly.com/library/vie…

Getting Started with Prompts in Langfuse - langfuse.com/docs/prompt…

Langfuse Prompts Data Model - langfuse.com/docs/prompt…

Prompt Version Control - langfuse.com/docs/prompt…

Prompt Composability - langfuse.com/docs/prompt…

A/B testing prompts - langfuse.com/docs/prompt…

Prompt Caching - langfuse.com/docs/prompt…

Prompt Config - langfuse.com/docs/prompt…

Webhooks and Slack Integration - langfuse.com/docs/prompt…