LangChain 实战第 2 章:搞懂消息结构,写出会“记住对话“的客服助手
LangChain 实战第 2 章:搞懂消息结构,写出会"记住对话"的客服助手
📌 这是系列第 2 篇。上一篇我们已经搭好 Python 环境、配好 DeepSeek、跑通了第一次模型调用。这一篇往上走一步:学会用消息结构和模型聊天,并做出一个能连续对话的命令行客服。
一、本章目标
学完这一章,你应该能够:
- 理解模型调用里
role(角色)是什么 - 用system消息控制"助手是谁"
- 用human消息把用户问题传给模型
- 把ai回复存起来,形成多轮对话
- 设置最常用的模型参数
temperature - 亲手实现一个带上下文的命令行客服助手
二、为什么不能只传字符串?
上一章我们是这样调用的:
response = model.invoke("请用一句话介绍 LangChain 的作用")
这样能跑,但在真实项目里不够灵活。因为实际场景我们经常要同时告诉模型:
- 你是什么角色(客服?讲师?翻译?)
- 回答要遵守什么规则(语气、长度、格式)
- 用户现在问了什么
- 前面已经聊过什么
把这些信息全塞进一个字符串里,既难维护又容易出错。更好的办法,是把它们拆成结构化的消息传给模型。
三、三种消息角色:system / human / ai
大模型对话里有三类最常出现的消息:
| 角色 | 作用 |
|---|---|
system | 设置模型的角色、规则和回答风格 |
human | 用户的输入 |
ai | 模型的回复 |
- SystemMessage(系统消息):开发者设定——AI 是谁、能干啥、不能干啥、用什么语气、输出什么格式。
- HumanMessage(用户消息):普通人的提问、需求、问题。
- AIMessage(AI 回复):大模型返回的内容。
在 LangChain 里,这三个类是这样导入的:
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage
拼一组消息也很直观:
messages = [ SystemMessage(content="你是一名客服助手,回答要礼貌、简洁。"), HumanMessage(content="我的订单什么时候发货?"), ]
💡 记住这个组合:system 定规则,human 提问题,ai 记回答。后面所有对话都建立在它之上。
四、SystemMessage:给 AI 定身份和规矩
SystemMessage相当于在对话最开头,给 AI 立好"永久人设":
- 在一轮对话最前面发给模型
- 优先级高于用户的所有提问(
HumanMessage) - 整轮对话全程生效
它对应 OpenAI 接口里的role: "system"。常见用途有:
- 限定角色(你是电商客服 / Python 讲师)
- 限定语气(礼貌、专业、口语化)
- 限定回答长度
- 限定输出格式(JSON、分点、不超过 N 字)
- 限定不能回答的范围
示例:
SystemMessage(content="你是一名 Python 讲师,回答要适合初学者。")
⚠️ 新手建议:第一阶段的 system 规则别写太长太复杂。规则一多,模型反而可能执行不稳定。先把"角色 + 一句要求"写清楚就够用了。
五、HumanMessage:把用户的话传进去
HumanMessage表示用户输入,也就是"用户说了什么"。
HumanMessage(content="我想查询订单物流")
真实项目里,用户输入通常来自这些地方:
- 命令行输入
- Web 页面的输入框
- App 的聊天框
- 后端 API 的请求参数
本章先用命令行输入来演示,最容易看懂原理。
六、AIMessage:模型的回复,也是下一轮的"记忆"
AIMessage表示模型回复。它不只是"拿到答案就完事"——在多轮对话里,必须把模型回复也加进历史消息,否则模型根本不知道之前聊过什么。
messages.append(AIMessage(content=response.content))
加上这一行后,下一次调用模型时,它就能"看到"自己上一次的回答,对话才连得起来。
七、案例一:一句话定好客服人设
用system消息控制模型角色。创建01_customer_service.py:
import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.messages import HumanMessage, SystemMessage load_dotenv() model = init_chat_model( model="deepseek-v4-flash", model_provider="openai", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url=os.getenv("DEEPSEEK_BASE_URL"), temperature=0.5, ) messages = [ SystemMessage( content=( "你是一名电商客服助手。" "回答要礼貌、简洁。" "如果用户没有提供订单号,就提醒用户提供订单号。" ) ), HumanMessage(content="我的快递怎么还没到?"), ] response = model.invoke(messages) print(response.content)
运行:
python 01_customer_service.py
重点观察两点:模型是否用了客服语气?是否提醒你提供订单号?
八、模型参数 temperature:控制"稳重"还是"脑洞"
temperature是最常用、也最该先搞懂的模型参数。它控制输出的随机性、创造性、幻觉程度,取值范围0 ~ 2:
- 越接近0:输出固定、严谨、重复度高,几乎不瞎编;
- 越接近2:脑洞大、自由发挥,容易编造不存在的内容。
| 取值区间 | 特点 | 适合场景 |
|---|---|---|
= 0 | 完全确定性,同输入同输出 | 代码生成、RAG 知识库问答、数据提取、订单查询、数学计算 |
0.1 ~ 0.4 | 轻微随机,逻辑稳、极少幻觉 | 客服问答、RAG、文档解析、结构化提取、规范代码 |
0.5 ~ 0.8 | 创意与逻辑平衡 | 文案、总结、普通聊天 |
≥ 1.0 | 高创造性,易虚构 | 写故事、诗歌、创意文案 |
设置方式就是把temperature传给init_chat_model:
model = init_chat_model( model="deepseek-v4-flash", model_provider="openai", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url=os.getenv("DEEPSEEK_BASE_URL"), temperature=0.3, )
💡 经验值:问答 / 客服 / 知识库把
temperature调低(0.1~0.4);文案 / 创意 / 改写可以调高(0.7~1.0)。第一阶段先把temperature用熟就够了,别一上来追一堆参数。
九、案例二:能连续对话的命令行客服
上面案例每次都只问一句话。真实客服要能"接着聊"。核心思路:把每一轮的用户提问和模型回复都存进messages列表。
创建02_chat_with_history.py:
import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.messages import SystemMessage, HumanMessage, AIMessage load_dotenv() model = init_chat_model( base_url=os.getenv("DEEPSEEK_BASE_URL"), api_key=os.getenv("DEEPSEEK_API_KEY"), model="deepseek-v4-flash", temperature=0.5, model_provider="openai" ) messages = [ SystemMessage(content="你是一个快递客服,回答问题要有礼貌,假如对方没有提供单号,请让其先提供快递单号") ] while True: question = input("用户:") if question == "exit": break messages.append(HumanMessage(content=question)) response = model.invoke(messages) print("客服的回答:" + response.content) messages.append(AIMessage(content=response.content))
运行:
python 02_chat_with_history.py
可以这样测试:
用户:我要查物流 用户:订单号是 A10086 用户:那我可以申请退款吗?
因为程序把历史消息都保留了,模型能看到前面的对话,所以第三问它才知道你在说"刚才那个订单"。
十、多轮对话的坑:历史不能无限堆
保存历史消息很有用,但不能无限制地一直存。原因很现实:
- 消息越多,每次请求花的钱越多
- 上下文太长,模型处理速度变慢
- 超过模型上下文上限会直接报错
- 太早的消息可能干扰当前回答
本章先用"保存全部历史"这种最简单的方式。后面会专门讲怎么控制上下文长度。
十一、案例三:只保留最近几轮
为了避免历史无限增长,可以只保留最近几条消息。创建03_chat_with_limited_history.py:
import os from dotenv import load_dotenv from langchain.chat_models import init_chat_model from langchain_core.messages import SystemMessage, HumanMessage, AIMessage load_dotenv() model = init_chat_model( base_url=os.getenv("DEEPSEEK_BASE_URL"), api_key=os.getenv("DEEPSEEK_API_KEY"), model="deepseek-v4-flash", temperature=0.5, model_provider="openai" ) system = SystemMessage(content="你是一个快递客服,回答问题要有礼貌,假如对方没有提供单号,请让其先提供快递单号") history = [] while True: question = input("用户:") if question.lower() == "exit": break history.append(HumanMessage(content=question)) print("history 的长度是:" + str(len(history))) messages = [system] + history[-6:] response = model.invoke(messages) print("客服的回答:" + response.content) history.append(AIMessage(content=response.content))
关键在这一行:
messages = [system] + history[-6:]
history[-6:]表示只取最后 6 条历史消息。因为一轮对话通常包含"用户消息 + AI 回复"两条,所以 6 条 ≈ 最近 3 轮对话。
📘 顺手复习一下 list 切片(后面会经常用):
# lst[start:end:step],索引从 0 开始 a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] print(a[:4]) # [0, 1, 2, 3] print(a[3:]) # [3, 4, 5, 6, 7, 8, 9] print(a[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] 倒序 print(a[::2]) # [0, 2, 4, 6, 8] 隔一个取 print(a[-1]) # 9 最后一位 print(a[-6:]) # [4, 5, 6, 7, 8, 9] 最后六个
十二、进阶预告:用模板更优雅地插历史
本章我们是手动拼消息的:
messages = [system_message] + history[-6:]
这种写法直观,适合先理解原理。等学到ChatPromptTemplate,可以用MessagesPlaceholder在模板里预留历史消息的位置,更省心:
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder prompt_template = ChatPromptTemplate.from_messages( [ ("system", "你是一名电商客服助手,回答要礼貌、简洁。"), MessagesPlaceholder(variable_name="history"), ("human", "{question}"), ] )
其中:
history用来接收以前的HumanMessage和AIMessagequestion表示用户当前的问题
本章只要知道"有这种更方便的写法"就行,完整用法下一章讲 Prompt 模板时展开。
十三、本章重点(速记)
- 模型调用既可以传字符串,也可以传消息列表
SystemMessage设置角色和规则HumanMessage表示用户输入AIMessage表示模型回复- 多轮对话必须保存历史消息
- 历史消息不能无限增长(用
history[-6:]截断) temperature影响回答的稳定性和创造性
十四、常见问题
Q1:为什么模型没严格按 system 规则回答?大模型不是传统if/else程序。system 规则能提高"按要求回答"的概率,但不保证 100% 执行。如果要求很重要,后续可以配合:更清晰的 Prompt、结构化输出、程序校验、后处理逻辑来兜底。
Q2:为什么多轮对话越来越慢?因为每次请求都把历史消息一起发给了模型。历史越长,请求内容越多、越慢。解决办法就是限制历史数量,例如history[-6:]。
