提示词不是代码的一部分,而是数据。数据该有版本、该能回滚、该能脱离发版独立变更。Prompt Hub 做的就是把这个『数据』从源码里抽出来,给它 git 式的 commit 历史。

为什么要把 prompt 搬出代码

维度硬编码在代码里放进 Prompt Hub
改一句话改源码 → PR → 发版Playground 改完 commit,秒级生效
谁能改只有工程师产品/运营也能在 UI 改
历史版本翻 git log 拼上下文每个 commit 独立可拉取
回滚revert 代码再发版拉旧 commit hash 即可
A/B 对照手动维护两份字符串两个 commit 喂同一份 Dataset
口诀口诀:提示词当数据管,commit 当版本号,tag 当发布指针。

安装与环境准备

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 提示词

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 才会从『靠人记』变成『系统保证』。

— 本页要点