别再傻傻拼接字符串了,掌握消息结构,让你的模型真正"懂事"!!!

聊聊背景

上篇文章我们搞定了 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 按场景锁死——这套组合拳打好了,你的模型就"懂事"了。

哪位小伙伴还有问题的话,评论区聊。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐