在上一篇文章中,我们完成了 LangChain 环境的搭建,并成功实现了对 DeepSeek 模型的第一次调用。当时我们使用的是最简单的方式:直接向模型传递一个字符串。

虽然这种方式可以让模型运转,但在构建真实的 AI 应用(如智能客服、Chatbot)时,它显得捉襟见肘。我们往往需要更精细地控制模型的行为:

  • 设定角色: 告诉 AI“你是谁”,是严谨的律师还是幽默的导游。

  • 制定规则: 规定哪些能说,哪些不能说,回答的格式是什么。

  • 维护上下文: 让 AI 记得几分钟前用户说了什么,实现真正的“对话”。

要实现这些功能,我们就必须深入理解 LangChain 的消息结构(Message Structure)。本篇文章将带你掌握模型调用中最核心的三个消息角色,学习如何控制模型性格,并手把手教你实现一个带历史记忆的命令行对话助手。

1. 为什么字符串输入还不够?

在 REST API 的原生调用中,我们通常需要构建复杂的 JSON 对象。LangChain 对此进行了抽象,引入了消息(Messages)的概念。

使用消息结构而非纯文本,核心优势在于语义化控制状态管理。模型需要知道哪部分内容是开发者的指令,哪部分是用户的提问,哪部分是它自己之前的回答。

2. 核心消息角色详解

在 LangChain(以及大多数主流大模型 API)中,对话被抽象为一个消息列表。最常用的有三类消息:

角色 (Role)LangChain 类描述
SystemSystemMessage系统消息。 由开发者预设,用于定义 AI 的身份、行为准则、输出格式、语气等。它是对话的基调,优先级最高。
HumanHumanMessage用户消息。 真实的终端用户输入的提问或需求。
AIAIMessageAI 回复。 模型生成的文本内容。在多轮对话中,需要将其捕获并重新塞回消息列表。

我们可以从 langchain_core.messages 导入这些类:

Python

from langchain_core.messages import AIMessage, HumanMessage, SystemMessage

2.1 SystemMessage:铸造 AI 的灵魂

SystemMessage 是你给大模型下达的“底层指令”。它通常在对话的最开始发送一次,并在整个会话中持续生效(取决于模型的上下文窗口和注意力机制)。对应的 OpenAI API 角色是 role: "system"

常见用途:

  • 限定角色: “你是一名专业的 Python 架构师。”

  • 限定语气: “你的回答必须幽默、风趣,多用表情包。”

  • 限定规则: “如果用户询问价格,请引导其联系销售,不要直接报价。”

  • 限定输出: “所有回答必须是标准的 JSON 格式。”

代码示例:

Python

# 设定一个傲娇的 AI 助手
messages = [
    SystemMessage(content="你是一个傲娇的机器人助手,虽然会回答用户问题,但语气要显得不情愿。"),
    HumanMessage(content="帮我解释一下什么是量子力学。"),
]
# 模型可能会回复:哼,真麻烦,连这个都不懂...(然后开始解释)

提示: 在使用初期,无需构建极其复杂的 System Prompt。规则过多反而可能导致模型执行不稳定或产生冲突。

2.2 HumanMessage:传递用户意图

HumanMessage 代表用户的输入。在实际项目中,它可能来自命令行 (input())、Web 前端的输入框、App 的聊天界面或者 API 请求参数。

Python

user_input = input(">> 用户:")
human_message = HumanMessage(content=user_input)

2.3 AIMessage:捕获模型的产出

AIMessage 是调用 model.invoke() 后返回的对象。它包含了模型生成的文本。

单轮对话中,我们通常只关心 AIMessage.content。 但在多轮对话中,整个 AIMessage 对象(或至少其内容)必须被保存下来,并在下一次发起请求时,连同新的 HumanMessage 一起发送给模型。

多轮对话原理解析图:

如图所示,多轮对话本质上是不断滚雪球式地将历史消息列表发送给模型。模型本身是不具备记忆功能的(Stateless),记忆是由程序员通过维护这个列表来实现的。

3. 实战案例:构建带角色的命令行客服

光说不练假把式。我们结合 SystemMessagetemperature 参数,来实现一个真实的客服场景。

3.1 引入模型参数:Temperature

在调用模型时,temperature(温度)是一个非常关键的参数。它控制着模型输出的随机性和创造性。

  • Temperature = 0: 完全确定性。相同输入永远返回相同输出。适合:代码生成、数学计算、知识库问答(RAG)。

  • Temperature ≈ 0.1 - 0.4: 严谨、逻辑稳定、极少幻觉。适合:商务客服、金融报表解析。

  • Temperature ≈ 0.7 - 1.0: 平衡创意与逻辑。适合:通用聊天、邮件撰写、文案摘要。

  • Temperature > 1.0: 脑洞大开、容易瞎编。适合:写故事、写诗、创意激荡。

对于客服场景,我们需要稳定、礼貌,严禁瞎编,因此应设置较低的温度。

3.2 实战:基础客服助手

3.2.1:单次对话助手(01_customer_service.py)

Python

import os

from dotenv import load_dotenv
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage, HumanMessage

load_dotenv()

model = init_chat_model(
    model='Pro/zai-org/GLM-5',
    model_provider='openai',
    api_key=os.getenv('SILICONFLOW_API_KEY'),
    base_url=os.getenv('SILICONFLOW_BASE_URL'),
    temperature=0.5
)

messages = [
    SystemMessage(content="你是一个傲娇的机器人助手,虽然会回答用户问题,但语气要显得不情愿。"),
    HumanMessage(content="帮我解释一下什么是量子力学。"),
]

response = model.invoke(messages)
print(response.content)

运行测试: 观察模型是否使用了机器人语气,以及当你询问“什么是量子力学”时,它是否会根据 System 规则回复你。下面是我的运行结果。

3.2.2:多轮对话助手(02-chat_with_history)

Python

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(
    model='Pro/zai-org/GLM-5.1',
    model_provider='openai',
    api_key=os.getenv('SILICONFLOW_API_KEY'),
    base_url=os.getenv('SILICONFLOW_BASE_URL'),
    temperature=0.5
)

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))

运行测试: 观察模型是否使用了机器人语气,以及当你询问问题时,它是否会连续回答,并且根据 System 规则回复你。下面是我的运行结果。

4. 上下文管理优化:限制历史消息数量

上面的代码虽然实现了多轮对话,但存在一个致命缺陷:消息列表会无限增长

随着对话的进行:

  1. 成本飙升: 大模型的计费通常基于 Token 数量(输入+输出)。历史越长,每次请求的 Token 数越多。

  2. 速度变慢: 模型处理长上下文需要更长的时间。

  3. 甚至报错: 每个模型都有上下文窗口限制(e.g., 8K, 32K, 128K Token)。一旦超过限制,请求就会失败。

  4. 干扰严重: 过久的、无关的历史消息可能会干扰模型对当前问题的判断。

因此,在实际工程中,我们必须进行上下文管理。最简单直接的方法是:只保留最近 N 轮对话

4.1 实战:保留历史的助手 

我们可以利用 Python list 的切片功能轻松实现这一点。通常一轮对话包含一门 HumanMessage 和一个 AIMessage,如果我们想保留最近 3 轮对话,就需要保留最近 6 条消息。

同时,SystemMessage 通常不能被切掉,它必须始终处于列表的第一位。

Python

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(
    model='Pro/zai-org/GLM-5.1',
    model_provider='openai',
    api_key=os.getenv('SILICONFLOW_API_KEY'),
    base_url=os.getenv('SILICONFLOW_BASE_URL'),
    temperature=0.5
)

system=SystemMessage(
        content="你是一个快递客服,回答问题要有礼貌,假如对方没有提供单号,请让其先提供快递单号"
    )

history=[]

while True:
    question=input("用户:")
    if question=="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))

运行测试: 观察对话history长度计算,实际我们长度只记录的六条进行回复。下面是我的运行结果。

5. 展望:更好的多轮对话写法

本篇文章中,我们手动维护了一个 list,并手动进行 append 和切片。这是理解原理的最佳方式。

但在 LangChain 的高级用法中,通常不建议这样手动操作。下一章在讲解 ChatPromptTemplate(提示词模板)时,我们会介绍更优雅的写法,例如使用 MessagesPlaceholder 在模板中预留历史消息的位置:

Python

# 示意代码,下一章详述
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的地理学家。"),
    MessagesPlaceholder(variable_name="chat_history"), # 这里用来动态插入历史消息
    ("human", "{question}"),
])

配合 LangChain 的 RunnableWithMessageHistory(将在内存管理章节讲解),可以实现完全自动化的历史消息维护和修剪,无需手动写 Python code 去管理 list。

6. 总结

掌握消息结构是迈向 LangChain 高级开发的第一步。请务必牢记以下几点:

  1. 三剑客: SystemMessage 定人设,HumanMessage 传意图,AIMessage 捕产出。

  2. 多轮对话本质: 是状态的维护,是不断把完整的历史消息(雪球)发送给模型。

  3. Temperature: 决定模型严谨程度的关键按钮。客服用低温,创意用高温。

  4. 上下文管理: 现实项目中必须限制历史长度,兼顾成本、速度和模型注意力。

如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注。在下一篇文章中,我们将深入探讨 LangChain 的另一个核心概念:PromptTemplate(提示词模板),教你如何像写代码一样解耦和管理复杂的提示词。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐