在构建智能助手、客服机器人或 Agent
时,我们需要的是多轮对话能力 :模型需要记住之前的对话内容,理解上下文,并且能够扮演特定的角色。 这就需要用到 LangChain
的另一大核心组件:Chat Models(聊天模型)。本文将带你深入理解聊天消息的结构,并实战调用阿里云的通义千问聊天模型。
💬 聊天模型 vs 普通语言模型
为什么有了 LLM 还需要 Chat Model?区别在于输入数据的结构 和对上下文的处理:
| 特性 | LLM (如 Tongyi) |
Chat Model (如 ChatTongyi) |
|---|---|---|
| 输入格式 | 纯字符串 (str) |
消息列表 (List[BaseMessage]) |
| 上下文管理 | 无状态。每次调用都是独立的,模型不知道之前聊过什么。 | 有状态。通过传入历史消息列表,模型能"记住"之前的对话。 |
| 角色区分 | 无法区分说话人。 | 明确区分 系统指令 、用户提问 、AI 回答。 |
| 适用场景 | 文本补全、分类、抽取、单次问答。 | 聊天机器人、角色扮演、复杂推理、Agent 交互。 |
💡 核心差异 :Chat Model 不仅仅是生成文本,它是在模拟一场对话。
📨 聊天消息的三种类型
在 LangChain 中,构建对话需要用到三种核心的消息类型,它们对应了 OpenAI 协议中的标准角色:
1. SystemMessage (系统消息)
- 作用 :设定模型的人设、背景和约束。这是对话的"总指挥"。
- 示例:"你是一个专业的 Python 程序员"、"请用幽默的语气回答"、"只返回 JSON 格式"。
- 位置:通常放在消息列表的最前面。
2. HumanMessage (人类消息)
- 作用 :代表用户的输入。
- 示例:"写一首诗"、"解释一下量子力学"、"帮我改这段代码"。
3. AIMessage (AI 消息)
- 作用 :代表模型之前的回复。
- 用途:在多轮对话中,我们需要把模型上一次的回答也传回去,这样模型才知道"上一句是我说的",从而保持逻辑连贯。
🎭 实战案例:扮演边塞诗人
我们将创建一个具有"盛唐气象"的边塞诗人 AI。它不仅会写诗,还能记住自己上一首诗的风格,并创作新的作品。
1. 代码实现(标准写法)
python
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
# 1. 初始化聊天模型
model = ChatTongyi(model_name="qwen-max")
# 2. 准备消息列表 (构建上下文) - 标准面向对象写法
messages = [
# 【系统指令】设定人设
SystemMessage(content="你是一个边塞诗人。你的诗歌风格豪迈、苍凉,常描写大漠、孤城、战争与思乡。"),
# 【第一轮对话】用户请求
HumanMessage(content="写一首唐诗"),
# 【第一轮对话】AI 的回复 (模拟历史记忆)
AIMessage(content="黄沙百战穿金甲,不破楼兰誓不还。孤城遥望玉门关,万里征人未得闲。"),
# 【第二轮对话】用户基于上一轮的回复提出新要求
HumanMessage(content="按照你上一首回复的格式再写一首唐诗"),
]
print("🤖 正在请求边塞诗人创作...\n")
print("✅ 新作如下:\n")
try:
# 3. 调用 stream 方法进行流式输出
res = model.stream(input=messages)
# 4. 遍历并打印结果
for chunk in res:
print(chunk.content, end="", flush=True)
print("\n\n✨ 创作完毕!")
except Exception as e:
print(f"\n❌ 调用出错:{e}")
2. 代码深度解析
A. 引入正确的类
我们必须从 chat_models 模块导入 ChatTongyi,并从 messages 模块导入三种消息类型。
B. 构建上下文 (messages 列表)
这是本案例的精髓。
- 我们先告诉模型:"你是边塞诗人" (
SystemMessage)。 - 然后用户说:"写一首" (
HumanMessage)。 - 接着,我们把模型之前的回答(或者我们预设的一个回答)放回去 (
AIMessage)。这一步至关重要,因为它让模型知道了"上一首诗长什么样"。 - 最后,用户说:"按这个格式再写一首" (
HumanMessage)。
如果没有中间那个 AIMessage,模型就不知道"上一首"指的是哪一首,可能会随机发挥,导致风格不统一。
C. 流式输出 (stream)
与 LLM 一样,Chat Model 也支持 stream 方法。
input=messages:传入整个对话历史列表。chunk.content:在遍历生成器时,每个chunk是一个消息对象,我们需要访问它的.content属性来获取具体的文本字符串。
⚡ 进阶技巧:消息列表的"简写形式"
虽然使用 SystemMessage、HumanMessage 等类是最规范、类型安全 的写法(IDE 会有更好的提示),但在快速原型开发或脚本编写时,LangChain 还支持一种极简的元组写法。
你可以直接使用 (角色, 内容) 的元组来代替复杂的类实例化。
🆚 写法对比
标准写法(推荐用于生产环境):
python
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage
messages = [
SystemMessage(content="你是一个边塞诗人。"),
HumanMessage(content="写一首唐诗。"),
AIMessage(content="锄禾日当午..."),
HumanMessage(content="再写一首。")
]
简写写法(推荐用于快速测试/脚本):
python
# 直接使用元组 (role, content)
# role 可以是: "system", "human" (或 "user"), "ai" (或 "assistant")
messages = [
("system", "你是一个边塞诗人。"),
("human", "写一首唐诗。"),
("ai", "锄禾日当午,汗滴禾下土。谁知盘中餐,粒粒皆辛苦。"),
("human", "按照你上一个回复的格式,再写一首唐诗。")
]
💡 注意:
- 这种简写形式在底层会被 LangChain 自动转换为对应的消息对象。
- 角色名称支持别名:
"human"等同于"user","ai"等同于"assistant"。- 建议:在正式的项目代码中,为了代码的可读性和类型检查,依然推荐使用标准的类写法;但在 Jupyter Notebook 调试或写 Demo 时,简写形式能极大减少代码量。
🧠 进阶思考:为什么要手动构造 AIMessage?
在上面的代码中,我们手动写死了 AIMessage 的内容。在实际的聊天机器人开发中,这个过程是自动的:
- 用户发送消息 -> 程序将
HumanMessage加入列表。 - 调用
model.stream(messages)。 - 程序接收流式响应,并在前端展示。
- 关键步骤 :程序将完整的 AI 回复保存为
AIMessage,并追加 到messages列表中。 - 下一次用户再发消息时,这个列表就包含了完整的历史记录。
这就是多轮对话记忆的实现原理。
🚀 总结
本节课我们完成了从"文本生成"到"智能对话"的跨越:
- 组件升级 :从
LLM切换到ChatModel(ChatTongyi)。 - 数据结构 :掌握了
SystemMessage(人设)、HumanMessage(用户)、AIMessage(AI) 三种消息类型。 - 两种写法 :学会了标准的类实例化写法 和便捷的元组简写写法。
- 上下文构建:学会了通过组装消息列表来传递历史对话信息,实现多轮交互。
- 流式体验:结合了流式输出,让对话更加流畅自然。