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 直接报错。这就是最直接的痛点。
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 个例子后,输出稳定多了。
3.4 动态变量注入:让 Prompt 活起来
企业级应用里,变量来源很复杂。可能是数据库、API 返回、用户输入、甚至另一个 LLM 的输出。
动态变量注入,说白了就是让模板能灵活地接入各种数据源。
我常用的几种方式:
- 直接传参:最基础的方式,适合变量少、来源固定的场景。
- 使用 partial_variables:适合那些「每次请求都一样」的变量,比如系统提示、公司名称。
- 从字典或对象中解包:适合从数据库查询结果中直接映射。
- 链式注入:前一个 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
# 这就是动态变量注入的典型场景
3.5 实战建议:模板管理的最佳实践
最后,分享几个我在企业项目中沉淀下来的经验:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 简单变量替换 | PromptTemplate | 轻量、易维护 |
| 需要示例引导 | FewShotPromptTemplate | 格式稳定、输出可控 |
| 多步骤流程 | 链式注入 + 校验 | 避免空值传播 |
| 模板需要频繁修改 | 外部文件 + PromptTemplate | 非技术人员也能改 |
嗯,这里要注意一点:不要为了用模板而用模板。如果你的 Prompt 只有一句话、一个变量,f-string 完全够用。但一旦涉及多变量、多场景、多人协作,PromptTemplate 的价值就体现出来了。
说白了,工具是为人服务的。选哪个,取决于你的团队规模和项目复杂度。