目录
一、核心组件概述
二、Messages:聊天模型中的消息
1. LangChain 中的消息类型
2. AIMessage 中有哪些内容
三、多轮对话与历史消息
1. 没有历史消息时
2. 手动携带历史消息
3. 历史消息为什么需要管理
4. 使用 trim_messages 裁剪消息
四、PromptTemplate:字符串提示词模板
1. 模板的核心结构
2. 模板为什么重要
五、ChatPromptTemplate:聊天消息模板
六、MessagesPlaceholder:消息占位符
七、Few-Shot:少样本提示
1. Few-Shot 示例
2. Few-Shot 的适用场景
3. Few-Shot 的注意点
八、Few-Shot 示例选择器
1. 语义相似度选择
2. MMR:最大边际相关性
九、总结
LangChain 的核心并不只是“调用模型”。
它更重要的价值,是将消息、提示词、模型、解析器、检索器等能力标准化,让开发者可以像搭积木一样构建 LLM 应用。
一、核心组件概述
LangChain 中有很多核心组件,例如:
- Messages;
- Prompt Templates;
- Output Parsers;
- Document Loaders;
- Text Splitters;
- Embeddings;
- Vector Stores;
- Retrievers。
这些组件看起来很多,但本质上都围绕一个目标:
让大模型的输入更规范 让大模型的输出更稳定 让大模型能够使用外部知识 让整个调用流程更容易组合和维护本篇先学习前三个基础组件:
消息 Messages ↓ 提示词模板 PromptTemplate ↓ 少样本提示 Few-Shot Prompting二、Messages:聊天模型中的消息
聊天模型并不是简单接收一段字符串,而是接收一组消息。每条消息通常包括:
- 角色 Role;
- 内容 Content;
- 元数据 Metadata;
- 工具调用信息 Tool Calls;
- Token 使用信息 Usage Metadata。
常见角色如下:
| 角色 | 作用 |
|---|---|
| system | 设置模型身份、规则、上下文 |
| user | 用户输入 |
| assistant | 模型回答 |
| tool | 工具执行结果 |
1. LangChain 中的消息类型
LangChain 对不同模型厂商的消息格式进行了统一封装。
| 类名 | 说明 |
|---|---|
SystemMessage | 系统消息 |
HumanMessage | 用户消息 |
AIMessage | 模型回复 |
ToolMessage | 工具执行结果 |
AIMessageChunk | 流式输出块 |
示例:
from langchain_core.messages import ( SystemMessage, HumanMessage, AIMessage ) messages = [ SystemMessage(content="你是一名专业的 Java 后端工程师。"), HumanMessage(content="什么是 IOC?"), AIMessage(content="IOC 是控制反转,是 Spring 的核心思想之一。"), HumanMessage(content="那 DI 又是什么?") ]将消息交给模型:
from langchain_openai import ChatOpenAI model = ChatOpenAI(model="gpt-4o-mini") result = model.invoke(messages) print(result.content)2.AIMessage中有哪些内容
模型调用后返回的通常是AIMessage。其中常用属性包括:
print(result.content) print(result.response_metadata) print(result.usage_metadata) print(result.tool_calls)含义如下:
| 属性 | 含义 |
|---|---|
content | 模型回答内容 |
response_metadata | 响应信息,如模型名、请求 ID、结束原因 |
usage_metadata | Token 使用量 |
tool_calls | 模型请求调用的工具 |
如果模型没有调用工具,tool_calls通常为空列表。
三、多轮对话与历史消息
很多人第一次接触 LLM 时都会问:
模型能记住我上一句话吗?
严格来说,模型并不会自动保存会话记忆。每次调用模型时,模型只能看到本次请求发送给它的内容。
1. 没有历史消息时
from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage model = ChatOpenAI(model="gpt-4o-mini") result1 = model.invoke([ HumanMessage(content="你好,我叫小明") ]) print(result1.content) result2 = model.invoke([ HumanMessage(content="我叫什么名字?") ]) print(result2.content)第二次调用中,模型没有收到“小明”这条信息,所以无法回答。
2. 手动携带历史消息
from langchain_core.messages import ( HumanMessage, AIMessage ) messages = [ HumanMessage(content="你好,我叫小明"), AIMessage(content="你好,小明,很高兴认识你。"), HumanMessage(content="我叫什么名字?") ] result = model.invoke(messages) print(result.content)这样模型才能根据历史消息回答:
你叫小明。3. 历史消息为什么需要管理
多轮对话越多,消息列表会越长。
系统消息 + 第一轮用户问题 + 第一轮模型回答 + 第二轮用户问题 + 第二轮模型回答 + ...问题在于:
- 上下文窗口有限;
- Token 成本会持续增加;
- 太多旧消息会干扰当前任务;
- 模型可能无法聚焦真正重要的信息。
因此,历史消息管理通常包括:
- 保留最近几轮对话;
- 删除无关消息;
- 对旧对话做摘要;
- 按 Token 数量裁剪;
- 将会话状态存入 Redis、数据库或 LangGraph。
4. 使用trim_messages裁剪消息
from langchain_core.messages import ( HumanMessage, AIMessage, SystemMessage, trim_messages ) messages = [ SystemMessage(content="你是一个技术助手。"), HumanMessage(content="你好,我叫小明。"), AIMessage(content="你好,小明。"), HumanMessage(content="我喜欢学习 Python。"), AIMessage(content="Python 很适合入门编程。"), HumanMessage(content="我叫什么名字?") ] trimmer = trim_messages( max_tokens=100, strategy="last", token_counter=model, include_system=True ) trimmed_messages = trimmer.invoke(messages) result = model.invoke(trimmed_messages) print(result.content)这里的核心思想是:
保留系统消息和较新的对话内容,适当裁剪过早的历史消息。
四、PromptTemplate:字符串提示词模板
如果项目中需要频繁生成类似提示词,手动拼接字符串会很麻烦。
city = "西安" prompt = f"请介绍一下{city}的历史,并列出三个著名景点。"如果换一个城市,就要重新拼接。LangChain 提供了PromptTemplate。
from langchain_core.prompts import PromptTemplate prompt_template = PromptTemplate.from_template( "请使用{language}介绍{city}的历史,并列出三个著名景点。" ) prompt_value = prompt_template.invoke({ "language": "中文", "city": "西安" }) print(prompt_value.text)输出:
请使用中文介绍西安的历史,并列出三个著名景点。1. 模板的核心结构
PromptTemplate( template="请介绍{city}的历史", input_variables=["city"] )template:固定文本和变量占位符;input_variables:模板需要传入的变量名称。
2. 模板为什么重要
提示词模板的价值包括:
- 提高复用性;
- 统一 Prompt 格式;
- 将业务数据与提示词逻辑分离;
- 更方便版本管理和维护;
- 更容易构建复杂 LCEL 链。
五、ChatPromptTemplate:聊天消息模板
对于聊天模型,更常见的是ChatPromptTemplate。它可以一次性定义系统消息、用户消息、AI 消息等结构。
from langchain_core.prompts import ChatPromptTemplate prompt_template = ChatPromptTemplate.from_messages([ ("system", "你是一名{role}。请使用简洁、专业的语言回答。"), ("human", "{question}") ]) prompt_value = prompt_template.invoke({ "role": "Python 开发工程师", "question": "什么是生成器?" }) messages = prompt_value.to_messages() print(messages)输出结果会包含:
SystemMessage(...) HumanMessage(...)接下来可以将模板直接接入模型:
from langchain_openai import ChatOpenAI from langchain_core.output_parsers import StrOutputParser model = ChatOpenAI(model="gpt-4o-mini") chain = prompt_template | model | StrOutputParser() result = chain.invoke({ "role": "Python 开发工程师", "question": "什么是生成器?" }) print(result)这就是 LCEL 的一个典型链式写法。
六、MessagesPlaceholder:消息占位符
有时 Prompt 中不只是插入一个字符串,而是需要动态插入多条消息。例如聊天历史:
系统消息 + 历史对话 + 当前用户问题这时可以使用MessagesPlaceholder。
from langchain_core.prompts import ( ChatPromptTemplate, MessagesPlaceholder ) from langchain_core.messages import ( HumanMessage, AIMessage ) prompt_template = ChatPromptTemplate.from_messages([ ("system", "你是一名耐心的编程助手。"), MessagesPlaceholder(variable_name="history"), ("human", "{question}") ]) history = [ HumanMessage(content="什么是 Redis?"), AIMessage(content="Redis 是一个高性能内存数据库。") ] prompt_value = prompt_template.invoke({ "history": history, "question": "Redis 可以做缓存吗?" }) for message in prompt_value.to_messages(): print(message)最终结构大致如下:
SystemMessage:你是一名耐心的编程助手。 HumanMessage:什么是 Redis? AIMessage:Redis 是一个高性能内存数据库。 HumanMessage:Redis 可以做缓存吗?消息占位符常用于:
- 多轮对话历史;
- RAG 检索结果;
- Few-Shot 示例;
- 工具调用结果;
- 动态注入系统上下文。
七、Few-Shot:少样本提示
Few-Shot Prompting,即少样本提示。它的思想是:
不只是告诉模型“做什么”,还给模型几个“正确示例”,让模型模仿示例完成任务。
例如,我们希望模型判断评论情绪。直接提问:
判断下面评论的情绪:物流很慢,但是商品质量很好。模型可能会回答“中性”“积极”或“混合”,结果不一定稳定。如果先给它一些示例:
输入:商品很好,物流很快 输出:积极 输入:质量太差,再也不会买了 输出:消极 输入:物流很慢,但是商品质量很好 输出:模型会更容易理解我们的分类规则和输出格式。
1. Few-Shot 示例
from langchain_core.prompts import ( ChatPromptTemplate, FewShotChatMessagePromptTemplate ) examples = [ { "input": "商品很好,物流很快", "output": "积极" }, { "input": "质量太差,再也不会买了", "output": "消极" }, { "input": "整体一般,没有特别惊喜", "output": "中性" } ] example_prompt = ChatPromptTemplate.from_messages([ ("human", "{input}"), ("ai", "{output}") ]) few_shot_prompt = FewShotChatMessagePromptTemplate( examples=examples, example_prompt=example_prompt ) final_prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个商品评论情绪分类助手,只返回:积极、消极或中性。"), few_shot_prompt, ("human", "{input}") ])调用:
model = ChatOpenAI(model="gpt-4o-mini") chain = final_prompt | model | StrOutputParser() result = chain.invoke({ "input": "价格比较贵,但做工不错。" }) print(result)2. Few-Shot 的适用场景
Few-Shot 很适合:
- 文本分类;
- 信息抽取;
- 格式化输出;
- 特定写作风格;
- 特定业务规则;
- 多步骤推理任务;
- 难以通过一句话描述清楚的任务。
3. Few-Shot 的注意点
Few-Shot 并不是示例越多越好。
示例过多会导致:
- Token 消耗上升;
- 调用成本提高;
- 上下文窗口被占用;
- 模型被无关示例干扰。
一个高质量示例应该具备:
- 与真实输入相似;
- 格式清晰;
- 覆盖典型情况;
- 包含边界情况;
- 不包含互相矛盾的规则。
八、Few-Shot 示例选择器
实际项目中,示例可能有几百条甚至几千条,不可能全部放进 Prompt。因此需要根据当前问题,挑选最适合的几个示例。
常见策略包括:
| 选择方式 | 思路 |
|---|---|
| 固定示例 | 每次使用相同的几个示例 |
| 按长度选择 | 根据上下文剩余空间选择示例 |
| 语义相似度选择 | 选择与当前问题语义最接近的示例 |
| MMR 选择 | 兼顾相关性和多样性 |
| N-Gram 重叠 | 根据词语重叠程度选择示例 |
1. 语义相似度选择
例如有以下示例:
示例 1:如何学习 Python? 示例 2:Python 入门应该看什么书? 示例 3:Java 项目如何部署?如果当前问题是:
Python 初学者如何开始学习?那么应该优先选择示例 1 和示例 2。
语义相似度选择的核心是:
当前问题向量 ↓ 与示例库中的向量计算相似度 ↓ 挑选最相近的 K 个示例2. MMR:最大边际相关性
如果只根据相似度选择,可能得到三条内容非常相近的示例。
MMR 的作用是:
既选择和当前问题相关的示例 又避免多个示例内容过于重复它在 RAG 和 Few-Shot 中都很常见。
九、总结
本文学习了 LangChain 的前三个核心组件。
Messages:聊天模型输入和输出的标准单位;PromptTemplate:构建可复用的字符串提示词;ChatPromptTemplate:构建多角色消息模板;MessagesPlaceholder:动态插入历史消息或上下文;Few-Shot:通过示例约束模型行为;- 示例选择器:从大量示例中动态挑选最相关的内容。
下一篇将继续学习输出解析、RAG、文档加载、文本分割、向量化和检索器。