大模型对话的核心秘密:System/Human/AI 三种消息,你真的用对了吗?(二)
别再傻傻拼接字符串了,掌握消息结构,让你的模型真正"懂事"!!!
聊聊背景
上篇文章我们搞定了 DeepSeek 的接入,能跑通了。但有个问题不知道你发现没有——那种直接把字符串怼给模型的方式,在实际项目中根本撑不住。
为啥?因为真实场景里,你得告诉模型"你是谁",用户会连续提问,模型得记住上下文。
消息结构就是来解决这些问题的。今天不聊虚的,直接说三件事:
-
SystemMessage 到底有多重要(很多人在这一步就翻车了)
-
多轮对话怎么让模型"记住"前面聊了什么
-
历史消息怎么管才不会把钱包烧穿
一、SystemMessage:被大多数人低估的"定海神针"
先说个我在项目中踩过的坑。
早期做客服机器人的时候,我直接在 HumanMessage 里写"你是个客服,你要耐心"。然后模型确实像客服了,但偶尔会蹦出一句"我是AI助手,不是真的客服"——直接穿帮。
问题出在哪?SystemMessage 和 HumanMessage 的优先级不一样。

SystemMessage 是开发者和模型之间的"内部约定",在对话最开头下发,优先级最高。你在 HumanMessage 里临时交代身份,模型可能当你是"用户的要求"而不是"系统的指令",选择性服从。
看段代码就明白了:
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(content="""你是资深Java工程师,回答必须带代码示例,语气专业"""),
HumanMessage(content="Spring Boot 3.0 有什么新特性?")
]
SystemMessage 里定的是人格和底线:
-
你是谁(角色身份)
-
你能做什么(能力边界)
-
输出长什么样(格式要求)
-
什么不能碰(禁止事项)
这个设定一旦定了,贯穿整场对话。如果模型不听话,先检查 SystemMessage 写没写对。
二、多轮对话:说白了就是"把历史塞回去"
很多新手问我:模型怎么记住前面说了啥?
答案很简单:每次都把完整的对话历史再发给它。
模型本身没有记忆,你每次调用它都是一次独立的计算。想让"记住",就得把前面的 HumanMessage 和 AIMessage 都带上。
from langchain_core.messages import HumanMessage, AIMessage
# 第一轮
messages = [
SystemMessage(content="你是健身教练"),
HumanMessage(content="初学者怎么练胸肌?")
]
resp1 = model.invoke(messages)
# 第二轮 - 把第一轮的对话也带上
messages.append(AIMessage(content=resp1.content))
messages.append(HumanMessage(content="一周练几次合适?"))
resp2 = model.invoke(messages) # 这时候模型知道你是"练胸肌的初学者"
看到没?消息列表一直在累积。第二轮调用时,模型"看到"了第一轮的所有内容。
这就是多轮对话的本质——不是什么黑科技,就是列表的追加和复用。
三、temperature:一条参数定生死
这个参数我得单独拎出来说,因为太多人乱调了。
temperature 控制的是随机性,取值范围 0-2:
| 数值 | 效果 | 什么时候用 |
|---|---|---|
| 0 | 每次回答一模一样 | 代码生成、数据提取、数学计算、订单查询 |
| 0.1-0.4 | 逻辑稳定,偶尔换词 | 客服、知识库问答、文档解析(90%场景就落在这) |
| 0.5-0.8 | 有点创意了 | 文案、总结、日常聊天 |
| ≥1.0 | 放飞自我,开始瞎编 | 故事创作、诗歌、头脑风暴 |
线上项目的血泪教训:但凡涉及到查询、计算、提取,temperature 一律往 0 靠。
我之前见过有人用 temperature=0.8 做订单状态查询,结果模型编出来一个根本不存在的物流信息——用户直接投诉。从那以后我养成了习惯:所有涉及数据准确性的任务,temperature 闭眼给 0。
四、历史消息管理:别让你的对话上下文"撑死"
上面那个例子里,消息列表一直 append,看起来挺方便对吧?
但你想过没有,如果用户聊了 50 轮,就是 100 条消息。每次调用都把 100 条历史全传过去,后果是什么:
-
钱在烧:按 token 计费,历史越长越贵
-
速度变慢:传输加推理,时间线性增长
-
可能直接报错:超出模型上下文窗口限制(比如 DeepSeek 是 64K)
解决方案其实就一行代码——切片截断:
MAX_HISTORY = 6 # 只保留最近6条
history_messages = messages[-MAX_HISTORY:] if len(messages) > MAX_HISTORY else messages
6 条消息 ≈ 最近 3 轮对话(Human + AI 算一轮),够用了。
有个细节值得注意:SystemMessage 不要截掉。可以单独拿出来,和历史消息拼接:
system_msg = messages[0] # 第一条通常是 System
history = messages[1:] # 后面的才是对话记录
# 截断历史,但 System 保留
limited = [system_msg] + history[-MAX_HISTORY:]
五、下一章预告
今天聊的是最基础的消息管理。下一章会升级到 Prompt 模板工程化,重点讲:
-
ChatPromptTemplate 怎么用(比手动拼接优雅十倍)
-
MessagesPlaceholder 如何在模板里预留历史插槽
-
三个贴近业务的完整案例(商品文案、学习计划、客服回复)
到时候你会发现,原来 Prompt 可以像搭积木一样结构化地组织。
一句话总结
SystemMessage 管身份,HumanMessage 管提问,AIMessage 管回复,历史消息用切片管住长度,temperature 按场景锁死——这套组合拳打好了,你的模型就"懂事"了。
哪位小伙伴还有问题的话,评论区聊。
更多推荐




所有评论(0)