3、Prompt 模板的艺术:从 f-string 到 PromptTemplate、FewShotPromptTemplate、动态变量注入

说实话,我刚接触 LangChain 那会儿,觉得 Prompt 模板不就是个字符串拼接吗?用 Python 的 f-string 不香吗?

后来在做一个客服问答系统时,我踩了个大坑。用户输入里带了个花括号,直接让整个 Prompt 炸了。嗯,从那以后我才真正重视起 PromptTemplate 的设计。

今天咱们就聊聊,为什么企业级应用里,不能只用 f-string。

3.1 从 f-string 说起:为什么不够用?

先看个最简单的例子。假设你要让 LLM 写一封邮件:

name = "张三"
product = "智能音箱"
prompt = f"请以{name}的名义,写一封关于{product}的投诉邮件。"

看起来没问题对吧?但你想过没有——

  • 如果 name 是用户输入的,里面带了个 {} 怎么办?
  • 如果 product 是空字符串呢?
  • 如果我想复用这个模板,但参数来自不同数据源呢?

我在项目中遇到过,用户把名字写成 {admin},结果 f-string 直接报错。这就是最直接的痛点。

注意:f-string 在运行时直接求值,无法做变量校验、默认值处理、格式控制。企业级场景下,这太脆弱了。

3.2 PromptTemplate:企业级的第一道防线

LangChain 的 PromptTemplate 解决了什么问题?说白了,就是让 Prompt 变得可管理、可复用、可校验。

看个对比:

from langchain.prompts import PromptTemplate

# 定义一个模板
template = """
你是一位{role}。
请根据以下{context},回答用户的问题。
用户问题:{question}
"""

prompt = PromptTemplate(
    input_variables=["role", "context", "question"],
    template=template
)

# 使用时传入变量
formatted_prompt = prompt.format(
    role="资深客服",
    context="用户购买了一台笔记本电脑,但屏幕有坏点",
    question="我应该怎么处理?"
)

我个人习惯把模板单独放在一个 YAML 文件里,这样产品和运营也能直接修改 Prompt,不用动代码。

小技巧:可以用 partial_variables 预填一些固定变量,比如系统时间、版本号。这样每次调用时少传几个参数。

3.3 FewShotPromptTemplate:给 LLM 打个样

你想想看,有时候光给指令不够,LLM 需要看几个例子才知道你要什么格式。

这就是 Few-Shot 的核心思想。

from langchain.prompts import FewShotPromptTemplate, PromptTemplate

# 先定义每个例子的模板
example_template = """
用户:{input}
客服:{output}
"""

example_prompt = PromptTemplate(
    input_variables=["input", "output"],
    template=example_template
)

# 准备几个例子
examples = [
    {"input": "我的订单还没到", "output": "您好,请提供订单号,我帮您查询物流状态。"},
    {"input": "我要退货", "output": "您好,请问您购买的商品是否在7天内?我帮您申请退货。"},
    {"input": "价格不对", "output": "您好,请问您看到的价格是多少?我帮您核实一下。"}
]

# 构建 FewShotPromptTemplate
few_shot_prompt = FewShotPromptTemplate(
    examples=examples,
    example_prompt=example_prompt,
    prefix="你是一位专业的客服,请根据以下示例的风格回答用户问题。",
    suffix="用户:{input}\n客服:",
    input_variables=["input"]
)

# 使用
result = few_shot_prompt.format(input="我的商品有质量问题")

我曾经在做一个智能客服项目时,发现不加 Few-Shot 的例子,LLM 回答的格式五花八门。加了 3-5 个例子后,输出稳定多了。

核心要点:FewShotPromptTemplate 的威力在于「格式控制」。它告诉 LLM 的不是「回答什么」,而是「怎么回答」。

3.4 动态变量注入:让 Prompt 活起来

企业级应用里,变量来源很复杂。可能是数据库、API 返回、用户输入、甚至另一个 LLM 的输出。

动态变量注入,说白了就是让模板能灵活地接入各种数据源。

我常用的几种方式:

  1. 直接传参:最基础的方式,适合变量少、来源固定的场景。
  2. 使用 partial_variables:适合那些「每次请求都一样」的变量,比如系统提示、公司名称。
  3. 从字典或对象中解包:适合从数据库查询结果中直接映射。
  4. 链式注入:前一个 LLM 的输出作为后一个 Prompt 的输入。

看个链式注入的例子:

from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

# 第一步:提取关键信息
extract_template = PromptTemplate(
    input_variables=["user_input"],
    template="从以下用户输入中提取商品名称和问题类型:\n{user_input}"
)

# 第二步:生成回复
reply_template = PromptTemplate(
    input_variables=["product", "issue_type"],
    template="用户反馈了{product}的{issue_type}问题,请生成专业回复。"
)

# 实际使用时,先调用 extract,再调用 reply
# 这就是动态变量注入的典型场景
避坑指南:我曾经在链式注入时,忘了对中间结果做校验。结果第一个 LLM 输出了空字符串,第二个 Prompt 直接崩了。所以,每个环节的输出都要做非空检查。

3.5 实战建议:模板管理的最佳实践

最后,分享几个我在企业项目中沉淀下来的经验:

场景 推荐方案 原因
简单变量替换 PromptTemplate 轻量、易维护
需要示例引导 FewShotPromptTemplate 格式稳定、输出可控
多步骤流程 链式注入 + 校验 避免空值传播
模板需要频繁修改 外部文件 + PromptTemplate 非技术人员也能改

嗯,这里要注意一点:不要为了用模板而用模板。如果你的 Prompt 只有一句话、一个变量,f-string 完全够用。但一旦涉及多变量、多场景、多人协作,PromptTemplate 的价值就体现出来了。

说白了,工具是为人服务的。选哪个,取决于你的团队规模和项目复杂度。