提示词不是代码的一部分,而是数据。数据该有版本、该能回滚、该能脱离发版独立变更。Prompt Hub 做的就是把这个『数据』从源码里抽出来,给它 git 式的 commit 历史。
为什么要把 prompt 搬出代码
| 维度 | 硬编码在代码里 | 放进 Prompt Hub |
|---|---|---|
| 改一句话 | 改源码 → PR → 发版 | Playground 改完 commit,秒级生效 |
| 谁能改 | 只有工程师 | 产品/运营也能在 UI 改 |
| 历史版本 | 翻 git log 拼上下文 | 每个 commit 独立可拉取 |
| 回滚 | revert 代码再发版 | 拉旧 commit hash 即可 |
| A/B 对照 | 手动维护两份字符串 | 两个 commit 喂同一份 Dataset |
安装与环境准备
Prompt Hub 的能力在 langsmith SDK 里,但拉回来的对象是 LangChain 的 prompt,所以要同时装 langchain-core。推送含模型的 prompt 时还需要对应的 provider 集成包。
# 核心 SDK + LangChain prompt 对象支持
pip install -U langsmith langchain-core
# 如果要 push 绑定了模型的 prompt(如 ChatOpenAI),再装对应集成
pip install -U langchain-openai
# 必备环境变量(注册后在 https://smith.langchain.com 设置页拿 key)
export LANGSMITH_API_KEY="lsv2_pt_xxxxxxxx"
# 可选:指定工作区 / 自托管 endpoint
# export LANGSMITH_ENDPOINT="https://api.smith.langchain.com"
pull_prompt:拉回来就能跑
pull_prompt 的标识符格式是 名字:版本。版本可以是 commit hash(前 8 位即可)或 tag;不写版本则默认拉最新(latest)。返回值直接是 ChatPromptTemplate / PromptTemplate,可以立刻 invoke 或接进 LCEL 链。
from langsmith import Client
from langchain_openai import ChatOpenAI
client = Client() # 自动读 LANGSMITH_API_KEY
# 1) 拉最新版:返回的就是一个 LangChain ChatPromptTemplate
prompt = client.pull_prompt("my-rag-system-prompt")
# 2) 用 commit hash 锁定某一历史版本(可复现的关键)
prompt_v1 = client.pull_prompt("my-rag-system-prompt:a1b2c3d4")
# 3) 用 tag 锁定(推荐:tag 是可移动指针,如 prod / staging)
prompt_prod = client.pull_prompt("my-rag-system-prompt:prod")
# 拉回来的对象直接可填充变量并 invoke
messages = prompt.invoke({"question": "LangSmith 是什么?", "context": "...检索片段..."})
print(messages)
# 接进 LCEL 链:prompt | model 一行成链
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
chain = prompt | model
answer = chain.invoke({"question": "LangSmith 是什么?", "context": "...检索片段..."})
print(answer.content)
push_prompt:推送与创建新版本
push_prompt 接受一个 LangChain prompt 对象。同名再次 push 不会覆盖,而是追加一个新 commit——这正是版本管理的基础。返回值是该 commit 的可访问 URL。
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
client = Client()
# 构造一个带变量的 ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("system", "你是严谨的客服助手。只依据 {context} 回答,无依据就说不知道。"),
("human", "{question}"),
])
# 首次 push:自动创建这个 prompt 仓库 + 第一个 commit
url = client.push_prompt(
"my-rag-system-prompt",
object=prompt,
description="RAG 客服系统提示词",
tags=["rag", "customer-service"], # 仓库级标签,便于检索
)
print("已推送:", url)
# 改了一句话后再次 push 同名 → 生成新的 commit(旧 commit 仍可拉取)
prompt_v2 = ChatPromptTemplate.from_messages([
("system", "你是严谨的客服助手。严格依据 {context} 回答;"
"无依据时回复『抱歉,我没有这方面的信息』,禁止编造。"),
("human", "{question}"),
])
url_v2 = client.push_prompt("my-rag-system-prompt", object=prompt_v2)
print("新版本:", url_v2) # URL 末尾即新 commit hash
用 tag 给版本起名 + 锁定可复现
commit hash 是不可变的物理版本,tag 是可移动的逻辑指针。生产环境永远引用 tag(如 :prod),上线就是把 tag 移到新 commit,回滚就是把 tag 移回去——代码里一个字都不用改。
from langsmith import Client
client = Client()
# 列出某个 prompt 的所有 commit(拿历史 hash)
for c in client.list_prompt_commits("my-rag-system-prompt"):
print(c.commit_hash[:8], c.created_at)
# 把 commit 打成 tag:上线就是给目标 commit 贴 prod
client.create_prompt_commit_tag(
prompt_identifier="my-rag-system-prompt",
commit_hash="<新版本的完整 hash>",
tag="prod",
)
# 之后线上代码恒定这样写,版本切换全在 LangSmith 侧完成
prod_prompt = client.pull_prompt("my-rag-system-prompt:prod")
# 回滚:把 prod 这个 tag 重新指回旧 commit 即可,代码无需发版
client.create_prompt_commit_tag(
prompt_identifier="my-rag-system-prompt",
commit_hash="<旧版本的完整 hash>",
tag="prod",
)
public 与 private 提示词
- private:默认。只在你的工作区可见,需 API key 才能 pull,业务提示词都该是 private。
- public:
push_prompt(..., is_public=True)后任何人可拉取,常用于分享开源模板或社区 prompt。 - 拉别人 public 的提示词用
owner/prompt-name全限定名,例如client.pull_prompt("langchain-ai/retrieval-qa-chat")。 - 私有提示词默认归属当前工作区,无需写 owner 前缀。
from langsmith import Client
from langchain_core.prompts import ChatPromptTemplate
client = Client()
# 推送一个公开模板(任何持 key 的人都能拉)
tpl = ChatPromptTemplate.from_messages([("human", "用一句话解释 {topic}")])
client.push_prompt("one-line-explainer", object=tpl, is_public=True)
# 拉社区/他人公开的提示词:owner/name 形式
community = client.pull_prompt("langchain-ai/retrieval-qa-chat")
print(community)
解耦之后:提示词 A/B 迭代闭环
prompt 一旦从代码解耦成带版本的对象,它就变成了 Evaluation 里一个干净的实验变量。同一份 Dataset、同一个 evaluator,只换 prompt 的 commit,跑出两条 experiment 直接在 LangSmith UI 横向对比——这就是最严谨的提示词 A/B:唯一变量是提示词版本。
from langsmith import Client, evaluate
from langchain_openai import ChatOpenAI
client = Client()
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
# 用 commit hash 锁定两个待对比的版本(保证可复现)
PROMPT_A = "my-rag-system-prompt:a1b2c3d4" # 旧版
PROMPT_B = "my-rag-system-prompt:e5f6g7h8" # 新版
def make_target(prompt_id):
prompt = client.pull_prompt(prompt_id)
chain = prompt | model
def target(inputs: dict) -> dict:
resp = chain.invoke({"question": inputs["question"], "context": inputs["context"]})
return {"answer": resp.content}
return target
# 一个内置/自定义 evaluator(这里用简单的非空+长度示例)
def has_answer(outputs: dict, reference_outputs: dict) -> dict:
ok = bool(outputs["answer"].strip())
return {"key": "non_empty", "score": 1.0 if ok else 0.0}
# 对同一份 Dataset 跑两次,唯一差异是 prompt 版本
for name, pid in [("prompt-A-old", PROMPT_A), ("prompt-B-new", PROMPT_B)]:
evaluate(
make_target(pid),
data="rag-eval-dataset", # CHAPTER 04 里建好的数据集
evaluators=[has_answer],
experiment_prefix=name, # 在 UI 里就是两条可对比的实验
metadata={"prompt_commit": pid}, # 把版本写进元数据,事后可追溯
)
# 打开 LangSmith → Datasets & Experiments,勾选两条实验即可逐题 diff
✓推荐做法
- 生产代码恒定 pull
:tag(如:prod),上线/回滚只在 LangSmith 侧移动 tag - 评估与复现实验恒定 pull
:commit-hash,并把 hash 写进 experiment metadata - push 时填 description 和 tags,让非工程同事在 UI 里能看懂每个版本改了什么
- A/B 时固定 Dataset 与 evaluator,只让 prompt 版本作为唯一变量
✗不推荐
- 不要在生产代码里硬编码 commit hash——那等于又把版本焊死回代码
- 不要在可复现实验里用
:latest,它随时会被别人 push 改变 - 不要把含敏感业务规则的提示词设成 is_public=True
⚠常见误区
- pull 含模型的链却忘了加 include_model=True,拿到的只是纯 prompt 对象
- push 同名以为是覆盖,其实是新增 commit——别指望它改旧版本内容
- tag 是可移动的,团队多人重贴同一 tag 会互相覆盖,约定好谁能动 prod
改一句提示词后,能在不发版的前提下让线上生效,并能在 60 秒内回滚到任意历史 commit——做到即合格。
pull_prompt 报 NotFound
- 典型表现
- 拉取时抛 not found / 404,提示词明明在 UI 里能看到
- 判断标准
- 确认标识符是否漏了 owner 前缀,或 commit/tag 拼错
- 解决方向
- 私有 prompt 用
name:tag;拉他人 public 的必须写owner/name;commit hash 至少给前 8 位且大小写一致
A/B 结果对不上 / 不可复现
- 典型表现
- 同一段评估代码两次跑分数不同
- 判断标准
- 检查是否用了
:latest或被移动过的 tag - 解决方向
- 把 PROMPT_A/PROMPT_B 换成不可变 commit hash,并在 metadata 里固化记录该 hash
pull 回来缺模型,无法直接出回答
- 典型表现
- 拿到对象只能 invoke 出 messages,不能直接得到模型回复
- 判断标准
- 确认 push 时是否绑定了模型、pull 时是否开了开关
- 解决方向
- push 时传入
prompt | model的可运行对象;pull 时加include_model=True拿回完整链
把提示词当成带 commit 的数据,而不是写死的字符串——版本、回滚、A/B 才会从『靠人记』变成『系统保证』。
— 本页要点