为什么要把 prompt 从代码里拎出来
很多项目早期把 prompt 硬编码在源码里——改 prompt 要发版,发版要排期,排期要过 code review。结果是:产品同学改 prompt 的成本比写代码还高。
把 prompt 挪到 Langfuse 之后:
- 非工程同学可以在 UI 里直接改 prompt,改完点"发布"就上线
- 每次改都自动产生一个新版本,出问题随时切回上一版
- 通过
production/staginglabel 做灰度:先灰到staging,看 trace 和评分没问题再 promote 到production - A/B 同一个 name 下的两个 label(如
variant-a/variant-b),按用户分桶
数据模型:name / version / label
name
prompt 的业务标识,稳定,通常与代码里的调用点对应。例如
customer-bot-system、summarize-article。version
单调递增整数。每次更新自动 +1,不会复用。版本永远不可变——改了就是新版本。
label
可变的字符串标签,指向某个具体版本。默认
latest 永远指最新版本;production / staging 之类由你控制。一个版本可以同时挂多个 label。type
text(单字符串模板)或 chat(messages 数组,带 role)。Chat 类型能被 LangChain / OpenAI SDK 直接当 messages 用。
label 是 prompt 的"指针"
代码里永远拉
代码里永远拉
label=production,不写死 version=7。这样产品同学切 label 就能灰度/回滚,代码零改动。Git 分支用 tag 的思路。
创建与更新:SDK + UI 双通道
SDK 侧创建
from langfuse import Langfuse lf = Langfuse() lf.create_prompt( name="customer-bot-system", type="chat", prompt=[ {"role": "system", "content": "你是客服小古,只回答和订单、物流、退款相关的问题。\n" "用户姓名: {{user_name}}\n等级: {{tier}}"}, ], labels=["production", "latest"], config={"model": "gpt-4o-mini", "temperature": 0.2}, tags=["customer-bot", "zh"], )
几个要点:
{{user_name}}是 Mustache 风格占位符,运行时用.compile(user_name="...")渲染labels=["production"]会把这版直接打到生产;想先灰度,改成["staging"]即可config放和 prompt 一起绑定的运行参数(model、temperature、top_p),SDK 运行时能直接读tags便于 UI 过滤,按业务/语言/模型划分
UI 侧改 prompt
实际工作流通常是:
- 工程首次把 prompt create 进 Langfuse(上面 SDK 那段,放在迁移脚本里)
- 之后产品/运营在 UI 里 Prompts → customer-bot-system → Edit,改完保存
- 新版本默认只挂
latest,不自动进production - 在 staging 环境跑 dataset 回归(下一章)、LLM-as-Judge(第 7 章)
- UI 里把
productionlabel 从旧版本挪到新版本,上线
运行时:拉 + 编译 + 绑 trace
from langfuse import Langfuse import openai lf = Langfuse() # 1) 拉 prompt(默认本地缓存 60s) prompt = lf.get_prompt("customer-bot-system", label="production") # 2) 编译出实际 messages messages = prompt.compile(user_name="小王", tier="VIP") # 3) 调模型, 顺带把 prompt 绑到 trace 上 rsp = openai.chat.completions.create( model=prompt.config["model"], temperature=prompt.config["temperature"], messages=messages + [{"role": "user", "content": user_query}], # langfuse.openai 包装器识别这个参数, 自动关联 langfuse_prompt=prompt, )
关联后,在 Trace 详情页的 generation span 上,你会看到一个 "Linked Prompt: customer-bot-system v7" 跳转,点进去能看到那一版的完整内容。一条 trace 挂一个 prompt 版本——A/B / 回归分析的基础。
@observe 方式
from langfuse.decorators import observe, langfuse_context @observe(as_type="generation") def answer(query: str, user_name: str): prompt = lf.get_prompt("customer-bot-system", label="production") messages = prompt.compile(user_name=user_name, tier="VIP") langfuse_context.update_current_observation(prompt=prompt) # 关联 return openai.chat.completions.create( model=prompt.config["model"], messages=messages + [{"role": "user", "content": query}], )
缓存:别把 Langfuse 拉爆
默认 SDK 本地缓存 prompt 60 秒。没有缓存,每次 LLM 调用都往 Langfuse Web 打一个 HTTP 请求——QPS 一上来立刻打爆。
# 默认 60s 缓存 prompt = lf.get_prompt("customer-bot-system", label="production") # 改缓存 TTL(单位秒), 0 = 禁用 prompt = lf.get_prompt("customer-bot-system", label="production", cache_ttl_seconds=300) # 禁用缓存(开发调试时有用) prompt = lf.get_prompt("customer-bot-system", label="production", cache_ttl_seconds=0)
fallback 很关键
拉 prompt 的网络抖动别让业务挂。SDK 支持
拉 prompt 的网络抖动别让业务挂。SDK 支持
fallback 参数:Langfuse 宕机时用本地写死的 prompt 兜底,保证服务不中断。
prompt = lf.get_prompt( "customer-bot-system", label="production", fallback=[{"role": "system", "content": "你是客服小古..."}], )
灰度发布:label 挪动就是流量切换
典型的三段式灰度:
- 开发阶段:新版本挂
devlabel,本地环境拉label=dev - 灰度阶段:挂
staging,staging 环境代码label=staging,跑 CI 回归 + 少量真实流量 - 上线:把
productionlabel 从旧版本挪到新版本——瞬间全量切换。出事了直接挪回旧版本,秒级回滚
# 用 SDK 改 label(也能在 UI 点) lf.update_prompt( name="customer-bot-system", version=8, new_labels=["production"], # 从旧版本把 production 抢过来 )
A/B 分桶:两个 label 按用户切
Langfuse 本身不做流量分桶,但它不拦着你。最常见的做法是在应用层按 user_id 哈希,决定去拉哪个 label:
import hashlib def pick_variant(user_id: str) -> str: # 50/50 稳定分桶 h = int(hashlib.md5(user_id.encode()).hexdigest(), 16) return "variant-a" if h % 2 == 0 else "variant-b" label = pick_variant(user_id) prompt = lf.get_prompt("customer-bot-system", label=label) # 把分桶信息写入 trace,后续按 label 分组分析 langfuse_context.update_current_trace(tags=[f"ab:{label}"])
之后在 Traces 面板按 ab:variant-a / ab:variant-b 过滤,对比 cost / latency / score 三个维度的差异。分桶足够大几天就能看出统计显著性。
和 LangChain 绑定
Langfuse 的 ChatPromptClient 可以直接转 LangChain 的 ChatPromptTemplate:
from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langfuse.callback import CallbackHandler handler = CallbackHandler() lf_prompt = lf.get_prompt("customer-bot-system", label="production") # 关键: 转 LangChain, 同时保留 langfuse_prompt 关联 lc_prompt = ChatPromptTemplate.from_messages(lf_prompt.get_langchain_prompt()) lc_prompt.metadata = {"langfuse_prompt": lf_prompt} llm = ChatOpenAI(model=lf_prompt.config["model"]) chain = lc_prompt | llm rsp = chain.invoke( {"user_name": "小王", "tier": "VIP"}, config={"callbacks": [handler]}, )
LangChain 在渲染占位符时会自动识别 metadata["langfuse_prompt"],把那条 generation 关联到对应 prompt 版本。
和 LlamaIndex 绑定
from llama_index.core import PromptTemplate from langfuse.llama_index import LlamaIndexInstrumentor LlamaIndexInstrumentor().start() lf_prompt = lf.get_prompt("rag-answer", label="production", type="text") # LlamaIndex 的 PromptTemplate 用 {var} 占位, 注意转换 template_str = lf_prompt.get_langchain_prompt() # 返回 {var} 风格 qa_prompt = PromptTemplate(template_str) query_engine.update_prompts({"response_synthesizer:text_qa_template": qa_prompt})
回滚策略:三条腿
Label 回切(秒级)
UI 把
production 从 v8 挪回 v7。生效时间 = SDK cache TTL(默认 60s 以内)。日常 99% 场景够用。代码强制钉版本(应急)
极端情况 Langfuse 宕机或 label 挂错,代码里临时写
version=7。出事时最快能控住,但要赶紧改回 label。fallback 兜底(灾备)
get_prompt 带 fallback 参数,任何拉取失败都用本地写死的版本。保证服务永远有 prompt 可用,代价是 fallback 可能落后几个版本。
不要删 prompt 版本
历史版本是 trace 审计链的一部分——两个月前那条 bad trace 关联的是 v3,v3 被删就永远查不到当时的 prompt。Langfuse UI 有 "deprecate" 但没有"硬删"是刻意为之。
历史版本是 trace 审计链的一部分——两个月前那条 bad trace 关联的是 v3,v3 被删就永远查不到当时的 prompt。Langfuse UI 有 "deprecate" 但没有"硬删"是刻意为之。
本章小结
- Prompt 数据模型:name + version(不可变) + label(可变指针) + type(text/chat)
- 代码永远按 label 拉,不写死 version;label 挪动就是发布/回滚
- SDK 默认 60s 本地缓存,必配
fallback兜底 - 灰度三段式:dev → staging → production,每一段跑回归和 eval
- A/B 在应用层按 user_id 哈希分桶,拉不同 label,trace 打 tag 便于后续对比
- LangChain / LlamaIndex 都能把 langfuse_prompt 绑到 generation,自动关联版本