Prompt提示词工程化:用LangChain把提示词从字符串变成资产

引言:如果你项目的提示词还散落在十几个Python文件里,改个措辞要全局搜索替换,这篇文章就是写给你的。

一、提示词的"野蛮生长"时代

半年前,我接手了一个电商AI客服项目。代码仓库不大,业务逻辑也不复杂,但我花了整整两天才理清那些提示词都藏在哪儿。

有的在views.py里,直接写在接口函数里:

python 复制代码
prompt = f"你是一个专业客服,请用中文回答用户问题:{user_input}"

有的在utils.py里,套了三层字符串拼接:

python 复制代码
base = "你是一位" + role + ",请用" + style + "风格回答"
content = base + "\n问题:" + question

还有的在配置文件的JSON里,用换行符\n硬编码了一整段系统提示,读起来像天书。

最崩溃的是产品经理。她想调整一下客服的说话语气,从"专业严肃"改成"亲切活泼"。我以为就改一行代码,结果发现同样的系统提示在五个文件里各有一份副本,而且措辞还略有不同------有的是"你是一位专业客服",有的是"你是一名专业客服助手",有的末尾还加了一句"请保持礼貌"。

那一刻我意识到:提示词不是代码,但我们的管理方式,连代码都不如。

代码好歹有DRY原则(Don't Repeat Yourself),有模块化管理,有版本控制。但提示词呢?它们像野草一样散落在项目的各个角落,没有结构,没有复用,没有版本管理。改一处,漏三处;上线前,谁也不知道模型到底接收的是哪一版提示词。

问题出在哪儿?

我们把提示词当成了"字符串",而不是"资产"。

字符串是一次性的、内联的、与代码耦合的。资产是可复用的、可配置的、可独立迭代管理的。LangChain的PromptTemplate体系,本质上就是一套把提示词从"字符串"升级为"资产"的工程化方案。

二、PromptTemplate的三种形态:从简单到复杂

LangChain把提示词模板抽象成了三种核心形态,覆盖了从简单问答到复杂对话的绝大多数场景。理解这三种形态,你就掌握了Prompt工程化的骨架。

形态一:简单模板------变量的"占位符"

这是最基础、也最常用的形态。它的核心思想很简单:把提示词中会变动的部分抽成变量,固定的部分写成模板。

直接上代码:

python 复制代码
from langchain_core.prompts import PromptTemplate

# 定义模板
template = PromptTemplate.from_template(
    "你是一位{role},请用{style}风格回答以下问题:\n\n问题:{question}"
)

# 填充变量,生成最终提示词
prompt = template.invoke({
    "role": "电商AI客服",
    "style": "亲切活泼",
    "question": "您好, 有什么需要我帮您解答的?"
})

from_template会自动扫描字符串中的{变量名},生成对应的输入变量列表。你不需要手动维护一个input_variables数组,减少了漏写变量的低级错误。

这种形态适合提示词结构固定、只有少量变量需要动态替换的场景。比如生成邮件、翻译文本、格式化输出等。

但简单模板有一个明显的局限:它生成的是纯文本字符串。而现代的大模型(尤其是GPT-4、Claude、DeepSeek等对话模型)普遍采用"消息列表"的接口格式------system消息设定角色,user消息承载输入,assistant消息代表历史回复。简单模板无法直接生成这种结构化消息。

这就需要第二种形态。

形态二:聊天消息模板------对话的"结构化骨架"

ChatPromptTemplate是LangChain中最强大的提示词工具,也是我在生产环境中最常用的组件。它不生成字符串,而是生成一条结构化的消息列表,直接对接现代对话模型的消息接口。

它的核心用法是from_messages,通过元组列表来定义消息的"角色+内容":

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位{role},服务原则是:{principle}"),
    ("human", "{user_input}"),
])

每一个元组的第一位是消息角色(systemhumanai),第二位是内容模板。变量用{name}占位,运行时自动填充。

真正让ChatPromptTemplate发挥威力的是,它能把"系统角色+历史对话+用户输入"这三层结构,清清楚楚地组织在一起。

看一个完整的客服机器人示例:

python 复制代码
from langchain_core.prompts import ChatPromptTemplate

# 定义三层结构的消息模板
prompt = ChatPromptTemplate.from_messages([
    # 第一层:系统角色,定义AI的身份和行为边界
    ("system", "你是一位电商AI客服助手,名叫小智。\n"
               "服务原则:\n"
               "1. 回答简洁,控制在100字以内;\n"
               "2. 涉及投诉时,先安抚情绪,再给出解决方案;\n"
               "3. 不知道的问题,如实告知用户会转接人工,不编造信息。\n"
               "当前日期:{current_date}"),

    # 第二层:历史对话(占位符,运行时注入)
    ("placeholder", "{chat_history}"),

    # 第三层:用户当前输入
    ("human", "{user_input}"),
])

这段代码的优雅之处在于:提示词的结构一目了然 。系统提示、历史对话、用户输入,三层分离,互不干扰。系统提示里可以写很长的行为约束,历史对话通过placeholder动态注入,用户输入始终在最后。

运行时,你只需要传入变量:

python 复制代码
messages = prompt.invoke({
    "current_date": "2026-03-09",
     "chat_history": [
        ("human", "我昨天买的手机什么时候到?"),
        ("ai", "您好,您的订单预计明天送达,快递单号已短信通知您。")
    ],
    "user_input": "能帮我改个地址吗?"
})

LangChain会自动把chat_history展开成human/ai交替的消息对,拼进最终的消息列表里。你不需要自己写循环拼接字符串,也不用担心消息格式是否符合模型要求。

这就是工程化的第一步:把提示词从"字符串拼接"变成"结构化配置"。

形态三:Few-Shot示例------让模型"照葫芦画瓢"

有时候,单纯告诉模型"你是什么角色"还不够。你需要给它看几个"标准答案",让它模仿特定的回答风格或格式。这就是Few-Shot(少样本)提示。

LangChain提供了FewShotChatMessagePromptTemplate,专门用于在对话模板中插入示例。

假设我们要做一个"情绪分析客服",需要模型把用户的情绪分类为"愤怒/焦虑/满意/咨询",并给出对应的安抚策略。直接描述规则,模型可能理解不到位。不如给它看几个例子:

python 复制代码
from langchain_core.prompts import (
    ChatPromptTemplate, 
    FewShotChatMessagePromptTemplate
)

# 第一步:定义单条示例的格式
example_prompt = ChatPromptTemplate.from_messages([
    ("human", "用户反馈:{feedback}"),
    ("ai", "情绪:{emotion}\n策略:{strategy}")
])

# 第二步:准备示例数据
examples = [
    {
        "feedback": "你们的发货速度也太慢了,三天了还没动静!",
        "emotion": "愤怒",
        "strategy": "先道歉,说明原因,给出补偿方案(如优惠券)。"
    },
    {
        "feedback": "这个产品的保修期是多久?",
        "emotion": "咨询",
        "strategy": "直接回答保修政策,无需安抚。"
    },
    {
        "feedback": "上次的问题还没解决,你们到底行不行?",
        "emotion": "焦虑",
        "strategy": "承认问题,给出明确的解决时间表,承诺跟进。"
    },
]

# 第三步:组装Few-Shot模板
few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_prompt=example_prompt,
    examples=examples,
)

# 第四步:拼入最终模板
final_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一位情绪分析专家,根据用户反馈判断情绪并给出应对策略。"),
    few_shot_prompt,  # 插入示例
    ("human", "用户反馈:{feedback}"),
])

运行后,LangChain会自动把三条示例渲染成human/ai交替的消息,放在系统提示之后、真实用户输入之前。模型看到"照葫芦画瓢"的素材,输出质量会稳定很多。

但Few-Shot有一个隐形的坑:示例越多,提示词越长,Token消耗越贵,上下文越容易溢出。

如果你准备了50条示例,每次请求都全量塞进去,那成本和时间都吃不消。这时候就需要第四种武器------动态示例选择。

三、ExampleSelector:解决"提示词太长"的智能剪刀

ExampleSelector是LangChain提示词工程化中最被低估的组件。它的作用很简单:根据输入的特征,从示例库中动态挑选最相关的几条,而不是每次都全量加载。

LangChain内置了几种选择策略,最实用的是两种:

策略一:按长度裁剪(LengthBasedExampleSelector)

这个选择器的逻辑很直观:输入越长,选的示例越少;输入越短,可以多选几条。 它通过控制最终提示词的总长度,防止上下文溢出。

python 复制代码
from langchain_core.prompts import PromptTemplate
from langchain_core.prompts.few_shot import FewShotPromptTemplate
from langchain_core.example_selectors import LengthBasedExampleSelector

# 示例库
examples = [
    {"input": "happy", "output": "sad"},
    {"input": "tall", "output": "short"},
    {"input": "sunny", "output": "gloomy"},
    {"input": "windy", "output": "calm"},
    {"input": "高兴", "output": "悲伤"},
]

# 单条示例模板
example_prompt = PromptTemplate.from_template(
    "原词:{input}\n反义:{output}"
)

# 动态选择器:根据长度智能裁剪
example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=50,  # 控制示例部分的总长度上限
)

# 组装Few-Shot模板
dynamic_prompt = FewShotPromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
    prefix="给出每个输入词的反义词:",
    suffix="原词:{adjective}\n反义:",
    input_variables=["adjective"],
)

# 测试:短输入,示例全量加载
print(dynamic_prompt.invoke({"adjective": "big"}))

# 测试:长输入,示例自动裁剪
long_input = "big and huge and massive and large and gigantic..."
print(dynamic_prompt.invoke({"adjective": long_input}))

当输入很长时,选择器会自动减少示例数量,确保总长度不超过max_length。这就像一个智能剪刀,在不影响核心任务的前提下,帮你剪掉冗余的上下文。

策略二:按语义相似度匹配(SemanticSimilarityExampleSelector)

如果你的示例库很大(比如几百条客服问答对),按长度裁剪还不够精细。这时候可以用语义相似度选择:把输入和示例都转成向量,选最相关的K条。

python 复制代码
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma

# 示例库(客服场景)
examples = [
    {"question": "怎么退款?", "answer": "您好,请进入我的订单点击申请退款..."},
    {"question": "发货太慢了", "answer": "非常抱歉,由于大促期间订单量激增..."},
    {"question": "这个有保修吗?", "answer": "本店所有商品均享受一年质保..."},
    # ... 更多示例
]

# 使用向量相似度选择器
example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples=examples,
    embeddings=OpenAIEmbeddings(),
    vectorstore_cls=Chroma,
    k=2,  # 只选最相关的2条
)

当用户问"我想退货"时,选择器会自动匹配到"怎么退款?"这条示例;当用户问"质量有问题怎么办",它会匹配到保修或投诉相关的示例。既节省了Token,又提升了示例的针对性。

ExampleSelector的工程价值在于:它让Few-Shot提示从"静态配置"进化成了"动态策略"。 示例库可以独立维护、持续扩充,而提示词的长度始终可控。

四、可直接套用的客服机器人Prompt模板

说了这么多,最后附赠一个我在生产环境中实际使用的客服机器人Prompt模板。你可以直接复制,根据自己的业务微调变量即可。

python 复制代码
from langchain_core.prompts import (
    ChatPromptTemplate, 
    FewShotChatMessagePromptTemplate,
    MessagesPlaceholder
)
from langchain_core.example_selectors import LengthBasedExampleSelector
from langchain_core.prompts import PromptTemplate

# ========== 1. 定义Few-Shot示例(情绪识别+应对策略) ==========
examples = [
    {
        "feedback": "你们的发货速度也太慢了,三天了还没动静!",
        "emotion": "愤怒",
        "reply": "非常抱歉给您带来了不好的体验。由于大促期间订单量激增,仓库正在加班加点处理。我已为您备注优先发货,并补偿一张20元无门槛优惠券,请您查收。"
    },
    {
        "feedback": "这个产品的保修期是多久?",
        "emotion": "咨询",
        "reply": "您好,本店所有商品均享受一年质保。质保期内非人为损坏,可免费维修或更换。具体保修条款可在商品详情页查看。"
    },
    {
        "feedback": "上次的问题还没解决,你们到底行不行?",
        "emotion": "焦虑",
        "reply": "非常抱歉让您久等了。您的问题我们已升级至售后主管处理,预计24小时内给您明确答复。这是我的工号#9527,您可以随时追问进度。"
    },
    {
        "feedback": "东西收到了,质量很好,下次还会来买",
        "emotion": "满意",
        "reply": "感谢您的认可!我们会继续努力。关注店铺可领取会员专属折扣,期待您的再次光临。"
    },
]

# 单条示例格式
example_prompt = ChatPromptTemplate.from_messages([
    ("human", "用户反馈:{feedback}"),
    ("ai", "情绪识别:{emotion}\n回复:{reply}")
])

# 动态选择器(按长度裁剪,防止示例过多)
example_selector = LengthBasedExampleSelector(
    examples=examples,
    example_prompt=example_prompt,
    max_length=800,
)

few_shot_prompt = FewShotChatMessagePromptTemplate(
    example_selector=example_selector,
    example_prompt=example_prompt,
)

# ========== 2. 组装最终Prompt模板 ==========
customer_service_prompt = ChatPromptTemplate.from_messages([
    # 系统角色层
    ("system", "你是一位电商客服助手,名叫小智。\n\n"
               "【身份设定】\n"
               "- 你代表极客数码旗舰店为用户提供服务\n"
               "- 语气亲切专业,不使用机械化的敬语堆砌\n\n"
               "【回复原则】\n"
               "1. 优先参考示例中的应对策略,保持风格一致;\n"
               "2. 涉及退款/投诉时,必须先安抚情绪,再给出具体解决方案;\n"
               "3. 不知道的问题,如实告知这个问题我需要确认一下,请您稍等2分钟,绝不编造;\n"
               "4. 每次回复控制在120字以内,避免大段文字;\n"
               "5. 适当使用emoji增加亲和力,但每段回复不超过2个。\n\n"
               "【当前上下文】\n"
               "- 今日日期:{current_date}\n"
               "- 用户等级:{user_level}\n"
               "- 当前活动:{current_campaign}"),

    # Few-Shot示例层(动态选择)
    few_shot_prompt,

    # 历史对话层(自动注入)
    MessagesPlaceholder(variable_name="chat_history"),

    # 用户输入层
    ("human", "{user_input}"),
])

# ========== 3. 使用示例 ==========
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
chain = customer_service_prompt | llm

# 调用
response = chain.invoke({
    "current_date": "2026-03-09",
    "user_level": "VIP3",
    "current_campaign": "618年中大促",
    "chat_history": [],
    "user_input": "我昨天买的耳机,今天降价了50块,能退差价吗?"
})

print(response.content)

这个模板包含了Prompt工程化的所有核心要素:

  • 系统提示独立管理 :角色、原则、上下文变量全部集中在system消息里,修改时只需改这一处;
  • Few-Shot示例动态加载 :通过LengthBasedExampleSelector控制长度,示例库可以无限扩充;
  • 历史对话自动注入MessagesPlaceholder让多轮对话的管理变得干净;
  • 业务变量参数化:日期、用户等级、当前活动都是动态变量,不同场景传入不同值即可。

五、写在最后:提示词管理的三个段位

回顾我自己做AI项目的经历,提示词管理大概经历了三个段位:

青铜段位:硬编码字符串。提示词散落在代码各处,改一处漏三处,上线前心里没底。

白银段位:配置文件化。把提示词抽离到YAML或JSON里,至少实现了内容和代码的分离,但缺乏结构化管理,变量替换靠字符串拼接,容易出错。

黄金段位:模板资产化。用LangChain的PromptTemplate体系,把提示词变成可复用、可配置、可动态选择的结构化资产。系统提示、示例库、历史对话、用户输入,各归其位,互不干扰。

提示词是AI应用的"源代码",它的质量直接决定了模型的输出质量。 但很长一段时间里,我们对待提示词的态度,远不如对待真正的源代码那么认真。没有版本管理,没有模块化,没有单元测试,全凭工程师的个人习惯。

LangChain的PromptTemplate体系,本质上是在补这一课。它不提供魔法,只提供规范------而规范,正是工程化的起点。

如果你今天还在用f-string拼接提示词,不妨花半小时,把最核心的那段提示词改成ChatPromptTemplate.from_messages。当你看到代码里那整整齐齐的消息列表,看到变量和结构清晰分离,你会明白:提示词不是字符串,它是资产,值得被认真对待。

相关推荐
土星云SaturnCloud1 小时前
防爆边缘计算+工业视频智能分析:高危场景视觉安全闭环,落地架构与场景全解
服务器·人工智能·ai·边缘计算
周末也要写八哥1 小时前
开发者如何快速实现一个NLP模型?
人工智能·自然语言处理
大龄码农有梦想1 小时前
AI 智能体核心组件:Tool、MCP 与 Skills 的区别、标准与协同架构
人工智能·agent·智能体·ai工具·tool·mcp·skills
The Open Group1 小时前
AI智能体时代,如何构建数字化架构以实现持续成功
大数据·人工智能·架构
Elastic 中国社区官方博客1 小时前
将 Logstash Pipeline 从 Azure Event Hubs 迁移到 OTel Collector Kafka Receiver
大数据·数据库·人工智能·分布式·elasticsearch·搜索引擎·kafka
weixin_446260851 小时前
AI驱动的前沿前端技术栈深度解析:从模型能力到UI封装的完整生命周期
前端·人工智能·ui
坤岭1 小时前
企业级AI应用落地的三重架构与实战解析
人工智能·架构
新知图书1 小时前
会议音视频速读(使用千问)
人工智能·ai助手·千问·高效办公
Peter·Pan爱编程1 小时前
第十篇:Trae:字节跳动的国产 AI 原生 IDE 崛起与特色功能
ide·人工智能