从模板语法、ChatPromptTemplate、MessagesPlaceholder 到源码级执行链路一、为什么 Prompt 不能一直写在代码里很多人刚接触大模型应用时第一反应是不就是把一句话发给模型吗比如“你是一个客服助手请回答用户问题”。这在 Demo 里可以跑通但一旦进入真实项目问题马上出现。智能客服要拼接用户问题、历史对话、知识库资料、系统规则股票分析助手要拼接行情、公告、资金流、风险提示智能营销助手要拼接人群、活动目标、历史案例、输出格式。所有内容如果都靠字符串拼接后面维护会非常痛苦。问题项目里会发生什么后果散落在代码里每个接口、每个 Agent 都写一份 Prompt改一次要全项目搜索变量靠手动拼context、question、history 容易传错模型回答不稳定输出格式不统一一会儿文本一会儿 JSON一会儿表格后端很难解析没有版本管理Prompt 改坏后无法快速回滚线上问题难复盘没有评测体系只能凭感觉判断效果好坏迭代方向不清楚所以Prompt Template 的核心价值不是“把一句话写得更漂亮”而是把大模型请求变成一个结构化、可维护、可复现的工程对象。二、通俗理解Prompt Template 就像合同模板最简单的比喻Prompt Template 就像一份合同模板。合同里有固定条款也有变量位置比如甲方、乙方、金额、日期。每次签合同时不会重新写一份合同而是把变量填进去。大模型应用也是一样。固定部分是角色、任务、规则、输出格式动态部分是用户问题、知识库资料、历史对话、工具结果。Prompt Template 做的事情就是把这些东西按照固定结构组装起来。# 一个最简单的 PromptTemplatefrom langchain_core.prompts import PromptTemplateprompt PromptTemplate.from_template(你是一个技术文章助手。请用通俗易懂的方式解释{topic}要求1. 先给结论2. 再举例子3. 最后总结)final_prompt prompt.format(topicLangChain Prompt Template)print(final_prompt)这段代码里{topic} 就是变量。业务系统只需要传入不同 topic就能复用同一套提示词结构。三、PromptTemplate 和 ChatPromptTemplate 有什么区别LangChain 里最常见的两个 Prompt 组件是 PromptTemplate 和 ChatPromptTemplate。两者都叫模板但适用场景不同。组件输入结构输出结果适合场景PromptTemplate一个字符串模板字符串 Prompt简单生成、翻译、摘要、分类ChatPromptTemplate一组带角色的消息模板ChatPromptValueChat Model、多轮对话、Agent、RAG现在企业项目里更常用 ChatPromptTemplate因为主流模型基本都是 Chat Model。它不是简单拼一大段字符串而是按 System、Human、AI、Tool 等角色组织消息。from langchain_core.prompts import ChatPromptTemplateprompt ChatPromptTemplate.from_messages([(system, 你是一个严谨的技术文章作者回答要通俗、准确。),(human, 请解释{question})])prompt_value prompt.invoke({question: Prompt Template 是什么})print(prompt_value.messages)四、源码地图Prompt 相关类怎么分层看 LangChain 源码时不要一开始就钻进所有细节。先抓住类之间的关系。大体可以分成三层字符串模板、消息模板、聊天模板。源码中PromptTemplate 是字符串模板ChatPromptTemplate 是聊天模板MessagesPlaceholder 是历史消息占位符。它们最后都会服务于一个目标把业务输入格式化为模型可以接收的 PromptValue 或 Message 列表。类名源码职责你可以怎么理解PromptTemplate保存 template、input_variables、template_format普通字符串模板StringPromptTemplate抽象字符串模板的 format / format_prompt 行为字符串模板基类BaseStringMessagePromptTemplate把字符串模板包装成某种消息字符串转角色消息ChatPromptTemplate维护一组 message templates聊天消息模板容器MessagesPlaceholder接收一组已有 messages 并插入模板历史对话插槽ChatPromptValue保存最终 messages即将交给 ChatModel 的请求对象五、PromptTemplate.from_template 做了什么PromptTemplate 由一个字符串模板组成它接受一组参数用来生成发送给语言模型的 Prompt模板格式支持 f-string、jinja2、mustache其中官方更推荐 f-string因为 jinja2 模板如果来自不可信来源会有安全风险。按源码逻辑整理成伪代码大概是这样# 这是按源码逻辑整理的伪代码不是逐行复制class PromptTemplate:classmethoddef from_template(cls, template, template_formatf-string, partial_variablesNone, **kwargs):# 1. 从模板字符串里解析变量名input_variables get_template_variables(template, template_format)# 2. 如果某些变量已经被 partial 固定就不再要求外部传入partial_variables partial_variables or {}input_variables [var for var in input_variablesif var not in partial_variables]# 3. 创建 PromptTemplate 对象return cls(input_variablesinput_variables,templatetemplate,template_formattemplate_format,partial_variablespartial_variables,**kwargs)这段逻辑背后的意义很重要LangChain 不只是保存了一段字符串它会提前分析这段字符串里有哪些变量。这样在运行时少传一个参数框架就能提前报错而不是把残缺 Prompt 发给模型。六、partial_variables把固定变量提前塞进去partial_variables 可以理解成“预填字段”。比如你的系统里语言、公司名、输出风格是固定的就不用每次调用都传。from langchain_core.prompts import PromptTemplateprompt PromptTemplate.from_template(你是 {company} 的客服助手。请用 {style} 的风格回答用户问题{question},partial_variables{company: 热闻岛,style: 简洁、准确、不要夸大})# 调用时只需要传 questionprint(prompt.format(question会员怎么开通))工程建议项目里可以把固定规则放到 partial_variables 或配置中心里把每次请求都变化的内容作为动态变量传入。这样 Prompt 更清晰也更容易做版本管理。七、ChatPromptTemplate.from_messages 做了什么ChatPromptTemplate 更适合现代 Chat Model。官方 API 参考说明from_messages 可以接收多种消息表示方式包括 BaseMessagePromptTemplate、BaseMessage、(message type, template) 二元组、(message class, template) 二元组或者一个字符串。源码里会把这些不同写法统一转换成标准消息模板。按源码逻辑整理成伪代码大概是这样# 这是按源码逻辑整理的伪代码不是逐行复制class ChatPromptTemplate:def __init__(self, messages, template_formatf-string, **kwargs):# 1. 统一消息格式messages_ [convert_to_message_template(message, template_format)for message in messages]# 2. 自动推断输入变量input_vars set()optional_vars set()partial_vars {}for message in messages_:if is_optional_messages_placeholder(message):partial_vars[message.variable_name] []optional_vars.add(message.variable_name)elif is_message_prompt_template(message):input_vars.update(message.input_variables)# 3. 保存到模板对象super().__init__(messagesmessages_,input_variablessorted(input_vars),optional_variablessorted(optional_vars),partial_variablespartial_vars,**kwargs)这就是为什么你写下面这种形式时LangChain 能自动知道 question 是必填变量history 可以作为历史消息插入。from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderprompt ChatPromptTemplate.from_messages([(system, 你是一个专业、严谨的 AI 助手。),MessagesPlaceholder(history, optionalTrue),(human, 用户问题{question})])八、MessagesPlaceholder多轮对话不要硬拼字符串多轮对话里很多人会把历史聊天记录拼成一大段字符串塞进 Prompt。这样做能跑但有两个问题第一角色信息容易丢第二工具消息、AI 消息、用户消息混在一起模型很难准确理解上下文。MessagesPlaceholder 的作用就是把一组已经存在的 messages 原样插入到 ChatPromptTemplate 中。源码中它会检查传入值是不是列表并把 tuple 等 message-like 对象转换成标准 BaseMessage还可以通过 n_messages 限制只保留最近几条。from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholderprompt ChatPromptTemplate.from_messages([(system, 你是一个股票分析助手回答必须提示风险。),MessagesPlaceholder(history, n_messages4),(human, 请结合上面的持仓信息分析{question})])prompt_value prompt.invoke({history: [(human, 我持有三安光电),(ai, 已记录你可以继续补充成本和仓位),(human, 成本 17.3仓位 12200 股),],question: 明天要不要减仓})九、invoke 到 format_messages 的执行链路在 LangChain 里PromptTemplate 和 ChatPromptTemplate 都可以被当成 Runnable 使用所以你经常会看到 prompt.invoke(...)。但 prompt.invoke 并不会调用大模型它只是把输入变量格式化成 PromptValue。后面再通过管道交给模型。BaseChatPromptTemplate 的源码逻辑可以这样理解format_prompt 会调用 format_messages然后把消息列表包装成 ChatPromptValue。# 这是按源码逻辑整理的伪代码不是逐行复制class BaseChatPromptTemplate:def format(self, **kwargs):return self.format_prompt(**kwargs).to_string()def format_prompt(self, **kwargs):messages self.format_messages(**kwargs)return ChatPromptValue(messagesmessages)def format_messages(self, **kwargs):# 具体由 ChatPromptTemplate 实现raise NotImplementedError所以完整流程是业务输入进来PromptTemplate 负责格式化生成 PromptValueChatModel 读取 PromptValue 里的 messages再发给模型供应商。十、和 LCEL 组合Prompt 可以像积木一样接模型LangChain 的 Prompt 不是孤立组件它可以和模型、输出解析器组成一条链。你可以把它理解成 Java 里的流水线先构造请求再调用服务最后解析响应。from langchain.chat_models import init_chat_modelfrom langchain_core.prompts import ChatPromptTemplatemodel init_chat_model(openai:gpt-5.5)prompt ChatPromptTemplate.from_messages([(system, 你是一个技术文章作者回答要通俗。),(human, 请解释{question})])chain prompt | modelresult chain.invoke({question: 为什么 Prompt 要模板化})print(result.content)这里的管道写法可以读成先用 prompt 把变量组装成 messages再把 messages 交给 model。Prompt 和 Model 解耦以后后面切模型、改 Prompt、加解析器都更容易。十一、实战案例一智能客服 Prompt Template下面是一个客服系统里更接近真实项目的 Prompt。它不是只告诉模型“你是客服”而是明确角色、边界、知识库、输出格式和兜底策略。from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholdercustomer_service_prompt ChatPromptTemplate.from_messages([(system, 你是平台客服助手。回答规则1. 必须优先依据【知识库资料】回答2. 不确定时不要编造提示用户转人工3. 涉及退款、改手机号、资金操作时只能说明流程不能直接承诺结果4. 输出要简洁分步骤说明),MessagesPlaceholder(history, optionalTrue),(human, 【用户问题】{question}【知识库资料】{context})])这个模板里system 是固定规则history 是多轮对话question 是用户当前问题context 是 RAG 检索出来的资料。这样设计以后客服回答会更稳定也更容易排查问题。十二、实战案例三RAG 问答 Prompt TemplateRAG 的关键不是让模型凭记忆回答而是让模型基于检索到的资料回答。因此 Prompt 里要明确告诉模型资料里没有就说不知道。rag_prompt ChatPromptTemplate.from_messages([(system, 你是一个知识库问答助手。请严格基于给定资料回答。如果资料中没有答案请回答“资料中没有找到明确信息”。不要编造来源不要补充无依据内容。),(human, 【检索资料】{context}【问题】{question})])这个模板简单但非常重要。它给模型加了一个“证据边界”只根据 context 回答。后续配合 Retriever、Rerank、引用来源才能组成真正可用的企业知识库问答系统。十三、企业级 Prompt 应该怎么管理在真实公司里Prompt 不能只放在 Python 文件里。因为 Prompt 本身会不断迭代可能需要灰度、回滚、AB 测试、效果评测和问题复盘。能力具体做法为什么重要版本管理每次 Prompt 变更记录 version线上出错可以回滚配置中心Prompt 放数据库或配置平台不用每次发版才能修改变量规范统一 question/context/history/tools 等命名减少调用错误日志追踪记录 Prompt 版本、输入变量、模型输出方便定位问题离线评测用固定测试集跑回归防止改好一个场景改坏另一个场景安全策略高危操作加边界和人工确认避免模型越权十四、常见坑为什么你的 Prompt Template 会报错LangChain 官方错误说明里提到INVALID_PROMPT_INPUT 通常和缺失变量、变量格式不正确、MessagesPlaceholder 传参错误、花括号转义有关。项目里最常见的是下面几个。坑错误写法正确理解少传变量模板里有 {question}调用时没传input_variables 必须都传齐花括号没转义想输出 JSON 却直接写 { }f-string 中普通大括号要写成 {{ 和 }}history 传字符串MessagesPlaceholder(history) 传了普通字符串应该传 message list 或 message-like 对象滥用 jinja2从用户输入加载 jinja2 模板不可信模板不要用 jinja2Prompt 太长把所有历史和文档都塞进去应该做裁剪、摘要、Rerank十五、源码级总结Prompt Template 的本质学完这一章你应该把 Prompt Template 理解成一个“模型请求构造器”。它不是魔法不是咒语而是大模型应用里的工程边界。你看到的表面源码里的真实动作写一个模板字符串解析变量名保存 template_format传入变量校验 input_variables 是否完整调用 prompt.invoke格式化成 PromptValue使用 ChatPromptTemplate生成一组 BaseMessage使用 MessagesPlaceholder把历史 messages 插入固定位置接到模型后面PromptValue 进入 ChatModel一句话记住Prompt Template 的核心不是“写一句更聪明的话”而是把角色、任务、上下文、变量和输出格式变成可维护的工程接口。下一章预告下一章讲 Structured Output如何让模型稳定输出 JSON。它会接在 Prompt Template 后面解决另一个工程难题模型回答再好如果输出格式不稳定后端也很难真正接入业务系统。内容来源Prompt Template提示词如何从“玄学”变成工程能力功能变化与行业影响解析_热闻岛