)
《LangChain RAG 全链路学习笔记》系列 · 第 3 篇共 13 篇作者AVA环境Python 3.11 LangChain 1.x⚠️看之前先注意一件事网上大量 LangChain 教程还是 0.3.x这一章的导入路径全变了——from langchain.prompts import ...照抄会直接ModuleNotFoundError: No module named langchain。本系列代码全部在 1.x 上实测跑通新旧路径都标了。这一章解决什么问题第 2 章解决的是「怎么跟模型说话」—— 拼一个消息列表丢进去。但真写起来马上会撞上两个问题每次都得手写整段 prompt只有一两个词在变剩下全是复制粘贴想改文案就得改代码—— 调个语气、加个角色设定都要动.py文件这一章把提示词从写死的字符串升级成带变量的模板对象。说白了这是「手搓 prompt」到「prompt 工程」的分界线。核心结论一两个类一条分界线 ⭐PromptTemplateChatPromptTemplate产出一整段文本一串消息各自带 role适合补全式任务、单轮指令对话式任务主流构造PromptTemplate.from_template(...)ChatPromptTemplate.from_messages([...])invoke()返回StringPromptValueChatPromptValue取文本有.text没有.text得用.to_string()一句话PromptTemplate产出一段话ChatPromptTemplate产出一段对话。而且它和上一章的模型抽象是同一组对应关系LLM ←→ PromptTemplate 一段文本进 ChatModel ←→ ChatPromptTemplate 一串消息进看明白这张表本章一半的坑就提前躲掉了。核心结论二from_messages里能放四种东西 ⭐⭐这是本章最容易记混的地方 —— 同一个列表里允许四种长得完全不一样的写法ChatPromptTemplate.from_messages([(system,你是{product}的客服),# ① 元组 —— 最常用SystemMessagePromptTemplate.from_template(...),# ② 显式的角色模板类MessagesPlaceholder(chat_history),# ③ 消息占位符第 7 章的伏笔few_shot_prompt,# ④ 少样本模板本身就是个零件(human,{input}),])写法长什么样什么时候用①元组(system, 文本)日常首选短平快②角色模板类SystemMessagePromptTemplate.from_template(...)需要在外面单独持有这个对象时③MessagesPlaceholderMessagesPlaceholder(名字)要往中间插一整段历史消息时第 7 章④少样本模板一个FewShotChatMessagePromptTemplate对象教模型一个新规则时⚠️坑没有BaseMessagePromptTemplate.from_template这个类方法。要么用三个具体类System/Human/AIMessagePromptTemplate要么用元组。核心结论三本章的四种技巧技巧解决什么问题关键类 / 方法少样本 FewShot模型不会的新规则给几个例子就能学会FewShotPromptTemplate字符串版FewShotChatMessagePromptTemplate消息版外部加载load_prompt提示词不想写死在代码里改文案不用改代码load_prompt(xx.json)预填partial有些变量是固定的不想每次传.partial(...)/partial_variables{...}模板组合 Compose大提示词由几个小模板拼成号 / 手动流水线少样本那段演示特别值得说05→06/07是一组「反例 → 正解」问模型2 9 是什么——是个编造出来的运算符模型只能瞎猜。给它 3 个例子2 9 11、4 3 7…之后它就能推出是加法。这就是 FewShot 的价值用例子「教会」模型一个规则而不用微调。最小可运行代码fromlangchain_core.promptsimportChatPromptTemplate# ⭐ 注意路径fromlangchain_openaiimportChatOpenAIfromdotenvimportload_dotenv load_dotenv()modelChatOpenAI(modeldeepseek-chat,base_urlhttps://api.deepseek.com/v1,temperature0.7,)prompt_templateChatPromptTemplate.from_messages([(system,你是个乐于助人的助手),(human,给我们讲一个有关{topic}的笑话),])promptprompt_template.invoke({topic:猫})print(prompt.to_string())# 看渲染后的文本print(prompt.to_messages())# 看消息列表responsemodel.invoke(prompt)# ChatPromptValue 可以直接喂给模型print(response.content)两个必须记住的约定invoke()得到的是PromptValue对象不是字符串—— 但要喂给模型时直接给就行不用转。想看内容StringPromptValue用.textChatPromptValue用.to_string()。⚠️ 我踩到的坑1.ChatPromptValue没有.textpromptchat_template.invoke({topic:猫})print(prompt.text)# ❌ AttributeError: ChatPromptValue object has no attribute textprint(prompt.to_string())# ✅PromptTemplate那边有.textChatPromptTemplate这边没有 ——因为对话不是一段文本是一串消息压根没有单一文本可以给。典型的「同一个家族方法不通用」。靠猜必踩。2..to_string()会把角色结构拍扁 ⚠️ 这条最隐蔽sprompt_template.invoke({input:...}).to_string()model.invoke(s)# ❌ system 角色失效了.to_string()把整串消息拍成一段纯文本。这一拍system/human的角色边界就没了—— 模型看到的只是一大段字你的角色设定等于白写。✅正确做法把ChatPromptValue对象直接交给模型别转字符串。这个坑阴在它不报错。代码跑得好好的只是模型不听话了 —— 你会以为是模型的问题去调 temperature、换模型方向全错。3.FewShotPromptTemplate的suffix是必填的prefix可以省suffix不行。少一个直接ValidationError: 1 validation error for FewShotPromptTemplate suffix Field required道理其实简单prefix是开头的引导语可省suffix是例子后面接什么比如Q: {input}\nA:。没有它例子摆完就断了模型不知道该干什么。4.load_prompt的路径是相对「当前工作目录」的load_prompt(03-prompt/08-prompt.json,encodingutf-8)路径相对的是你从哪运行脚本不是脚本自己所在的目录 → 课程这个例子必须在langchain-coding根目录下运行encodingutf-8不能省—— Windows 默认 GBKjson 里有中文会直接UnicodeDecodeError5.MessagesPlaceholder的名字必须和invoke的 key 完全一致名字对不上 →KeyError。而且值必须是消息对象列表不是字符串列表。想让它可选得显式写optionalTrue。⚠️ 1.x 和 0.3.x 的导入路径差异# 网上 0.3.x 教程fromlangchain.promptsimportPromptTemplatefromlangchain.prompts.chatimportChatPromptTemplate# 本机 1.x 实际fromlangchain_core.promptsimportPromptTemplate,ChatPromptTemplate# ⭐还有一个更狠的PipelinePromptTemplate在 1.x被整个删掉了。课程源码里的14-ComposePrompt.py就是这个 —— 取消注释就ModuleNotFoundError不取消就NameError怎么都是错的。它其实是个存档文件。替代思路是手动流水线让子模板先各自渲染成字符串塞回字典再喂给总模板。补充 ①我一开始以为「模板」就是字符串拼接我最开始只是以为模板就是一个用户提示词一个系统提示词没有考虑到这个部分预填外部加载啥的补充 ②我在这章实际遇到的报错分不清那个模板提示词返回的类型是 char 还是 string导致后面的.text出现了错误补充 ③那个「」实验给我的启发模型本身是啥都不知道的他只是拥有非常强大的学习能力甚至可以举一反三。✅ 自测三问ChatPromptTemplate(...).invoke({...})返回什么能直接print吗.text能用吗返回ChatPromptValue在langchain_core.prompt_values里装的字段是messages跟text没关系。⚠️ 我第一次答的是StringPromptValue—— 那是PromptTemplate的返回值答反了。这俩的爹都是PromptValue但方法不通用。能直接print吗能但打出来的是一坨 repr ——messages[SystemMessage(content你是老师, additional_kwargs{}, ...), HumanMessage(...)]。想要文本得用.to_string()结果是System: 你是老师\nHuman: 11。.text能用吗不能直接AttributeError: ChatPromptValue object has no attribute text。⭐ 记住这条分界线StringPromptValue有.textChatPromptValue没有—— 这就是本章第一个坑。partial_variables和.partial()有什么区别partial的值可以传函数吗我先答错了一版把「构造时预填」和「外部加载 JSON」当成了一回事其实它们没关系。真实区别只有「什么时候填」partial_variables{...}→构造时填写在PromptTemplate(...)里面.partial(...)→事后填并且返回新对象原模板不变可以链式接着写两个都能传函数能力完全一样实测PromptTemplate(...,partial_variables{date:_now})# ✅ 直接传 Python 函数# 实测存进去的就是函数本身不是字符串# partial_variables {date: function _now at 0x000001BF59E10540}⚠️「只能放常量、放不了函数」说的不是partial_variables是「外部加载 JSON」。JSON 里压根没有函数这种类型所以load_prompt(xx.json)里的partial_variables只能存死值。这是 JSON 的限制不是partial_variables的限制。PipelinePromptTemplate在 1.x 没了用什么思路顶上FewShotPromptTemplate能不能不写suffix① 用「手动流水线」顶上—— 把PipelinePromptTemplate内部做的事自己走一遍让子模板先各自invoke().to_string()渲染成字符串塞回同一个字典再喂给总模板。forname,prompt_templateininput_prompts:my_input[name]prompt_template.invoke(my_input).to_string()my_outputfull_prompt_template.invoke(my_input)②suffix不能省——prefix可省suffix是必填的缺了直接ValidationError: 1 validation error for FewShotPromptTemplate suffix Field required道理很简单prefix是开头的引导语可省suffix是例子后面接什么比如Q: {input}\nA:。没有它例子摆完就断了模型不知道要干什么。本系列共 13 篇下一篇04 示例选择器 —— 例子太多塞不下怎么自动挑几个最像的笔记同步更新在 GitHubgithub.com/MaDai66/langchain-rag-notes代码基于 LangChain 1.x 实测有问题欢迎评论区交流。