在现代 LangChain 体系中,消息(Messages)不只是聊天记录,而是驱动模型、工具、状态和中间件的核心数据结构。很多初学者会把一切输入都写成一个超长字符串,这在简单任务中还能工作,但一旦系统开始出现多轮对话、工具调用、检索上下文、流式输出和状态恢复,字符串拼接就会迅速失控。消息模型的价值在于:把上下文拆成有角色、有顺序、有来源的结构化单元。
本章会从消息的概念、消息类型、模板化组织、历史裁剪、工具消息、流式消息、多模态消息到工程实践,系统说明为什么现代 LangChain 项目几乎都应该围绕 Messages 来建模。
传统 LLM 的输入形式往往像这样:
prompt = "你是一个助手,请总结下面这篇文章,并给出 3 条建议:..." result = llm.invoke(prompt)
这种方式的问题是:
现代聊天模型更推荐这样组织输入:
from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage model = ChatOpenAI(model="gpt-4o-mini", temperature=0) messages = [ SystemMessage(content="你是资深项目顾问,回答要专业、谨慎、结构化。"), HumanMessage(content="请总结这份项目周报的风险点,并给出缓解建议。") ] response = model.invoke(messages) print(response.content)
消息列表的好处在于:
一个真实的企业助手在回答前,往往要装配很多上下文:
如果没有消息抽象,这些内容会混成一个难以维护的超级 prompt;如果用消息来表达,则每一段信息都可以被单独查看、替换、裁剪和审计。
根据 LangChain 官方文档,消息对象通常由三部分组成:
SystemMessage 用于定义模型的高层行为边界,例如:
from langchain_core.messages import SystemMessage system_msg = SystemMessage( content=""" 你是企业法务助手。 1. 只能基于提供的合同条款与制度解释。 2. 若证据不足,必须明确说明“不确定”。 3. 不要生成最终法律意见书,只给出审阅建议。 """ )
SystemMessage 的设计原则:
HumanMessage 表示用户或外部调用方输入的本轮请求。它通常承载:
from langchain_core.messages import HumanMessage human_msg = HumanMessage( content="请根据以下会议纪要,提取待办事项,并按优先级排序。" )
一个常见经验是:
AIMessage 表示模型历史输出。它不仅能承载普通文本,也能承载:
response = model.invoke(messages) print(type(response)) print(response.content) print(response.usage_metadata)
在工程上,AIMessage 有两个重要用途:
当模型发起工具调用后,工具执行结果通常通过 ToolMessage 回传给模型。它是 Agent 与外部世界交互的关键桥梁。
from langchain_core.messages import AIMessage, ToolMessage ai_message = AIMessage( content="", tool_calls=[{ "name": "get_weather", "args": {"city": "上海"}, "id": "call_001", "type": "tool_call" }] ) tool_message = ToolMessage( content="上海今日多云,26 摄氏度,空气湿度 72%。", tool_call_id="call_001", name="get_weather" )
ToolMessage 的关键要求:
from langchain_openai import ChatOpenAI from langchain_core.messages import SystemMessage, HumanMessage model = ChatOpenAI(model="gpt-4o-mini", temperature=0) messages = [ SystemMessage(content="你是一名面向初学者的 Python 讲师。"), HumanMessage(content="请解释什么是装饰器,并给一个最简单示例。") ] response = model.invoke(messages) print(response.content)
LangChain 也支持直接用字典形式表示消息,便于与前端或第三方接口打通。
result = model.invoke([ {"role": "system", "content": "你是简历优化助手。"}, {"role": "user", "content": "帮我把这段项目经历改得更专业。"} ]) print(result.content)
这种格式适合:
如果直接手写消息列表,随着变量变多会越来越难维护。所以 LangChain 常配合 `ChatPromptTemplate` 来生成消息。
from langchain_core.prompts import ChatPromptTemplate prompt = ChatPromptTemplate.from_messages([ ("system", "你是公司的培训讲师,擅长把复杂概念讲简单。"), ("human", "请用类比法解释 {topic},并给出一个工作场景案例。") ]) messages = prompt.invoke({"topic": "工具调用"}) for msg in messages.messages: print(type(msg).__name__, msg.content)
from langchain_openai import ChatOpenAI model = ChatOpenAI(model="gpt-4o-mini", temperature=0.2) chain = prompt | model result = chain.invoke({"topic": "RAG"}) print(result.content)
这种写法的价值在于:
对话系统中最常见的需求就是把已有消息历史插入模板。
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, AIMessage prompt = ChatPromptTemplate.from_messages([ ("system", "你是企业知识助手。资料不足时必须明确说明。"), MessagesPlaceholder(variable_name="history"), ("human", "{question}") ]) history = [ HumanMessage(content="差旅报销多久到账?"), AIMessage(content="通常在审批完成后的 3 个工作日内到账。") ] messages = prompt.invoke({ "history": history, "question": "那发票抬头写错了怎么办?" })
这样做的好处是:
保留所有历史会带来:
因此,一个好的会话系统通常会把历史拆成三层:
from langchain_core.messages import SystemMessage def trim_messages(messages, keep_last=6): system_messages = [m for m in messages if isinstance(m, SystemMessage)] others = [m for m in messages if not isinstance(m, SystemMessage)] return system_messages[:1] + others[-keep_last:]
这个版本很简单,但已经体现出一个重要原则:
在企业应用中,更推荐“按 token 预算”而不是“按条数”裁剪:
def pack_context(system_msg, summary, recent_messages, budget_tokens=6000): """示意函数:按预算拼接上下文。""" packed = [system_msg] if summary: packed.append(summary) current_tokens = estimate_tokens(packed) for msg in reversed(recent_messages): msg_tokens = estimate_tokens([msg]) if current_tokens + msg_tokens > budget_tokens: break packed.insert(1, msg) current_tokens += msg_tokens return packed
这里的 `estimate_tokens()` 可以由你使用模型官方 tokenizer 或统一估算逻辑实现。
当你把工具绑定到模型后,模型在需要时会返回带 `tool_calls` 的 AIMessage,而不是直接文本答案。
from langchain_openai import ChatOpenAI from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市天气。""" return f"{city} 今日晴,27 摄氏度。" model = ChatOpenAI(model="gpt-4o-mini", temperature=0) model_with_tools = model.bind_tools([get_weather]) response = model_with_tools.invoke("北京今天的天气怎么样?") print(response.tool_calls)
你会看到类似这样的工具调用结构:
理解 ToolMessage 的最佳方式,是手动跑一遍完整闭环。
from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, ToolMessage from langchain_core.tools import tool @tool def get_weather(city: str) -> str: """查询指定城市天气。""" return f"{city} 今日小雨,建议带伞。" model = ChatOpenAI(model="gpt-4o-mini", temperature=0).bind_tools([get_weather]) messages = [HumanMessage(content="杭州今天适合骑车上班吗?请先查天气。")] ai_msg = model.invoke(messages) messages.append(ai_msg) for tool_call in ai_msg.tool_calls: if tool_call["name"] == "get_weather": result = get_weather.invoke(tool_call["args"]) messages.append(ToolMessage( content=result, tool_call_id=tool_call["id"], name=tool_call["name"] )) final_answer = model.invoke(messages) print(final_answer.content)
这段代码能帮助你理解:
除了 `content`,消息对象还可能包含很多工程上非常重要的信息:
response = model.invoke([HumanMessage(content="请用一句话解释什么是消息对象")]) print("id:", response.id) print("usage:", response.usage_metadata) print("response_metadata:", response.response_metadata)
这些字段很适合用于:
LangChain 官方消息体系支持文本、图片、文件等更复杂的内容块。即便你的项目当前只用文本,也建议建立一个认知:消息不一定只是字符串。
from langchain_core.messages import HumanMessage message = HumanMessage(content=[ {"type": "text", "text": "请识别这张发票中的金额和日期。"}, {"type": "image_url", "image_url": {"url": "https://example.com/invoice.png"}} ])
在流式输出时,你收到的往往不是完整 AIMessage,而是一个个 `AIMessageChunk`。
from langchain_openai import ChatOpenAI model = ChatOpenAI(model="gpt-4o-mini", streaming=True) full = None for chunk in model.stream("用三句话解释为什么要做消息分层"): print(chunk.text, end="") full = chunk if full is None else full + chunk print(" ---") print(full.content)
这意味着:
下面给出一个小型示例:它支持系统规则、历史消息插入和本轮问答。
from langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder from langchain_core.messages import HumanMessage, AIMessage faq_prompt = ChatPromptTemplate.from_messages([ ("system", """ 你是公司内部 FAQ 助手。 1. 只回答行政制度、报销流程、IT 支持类问题。 2. 若历史中已经给出答案,应保持前后一致。 3. 不确定时请明确说明,并建议用户联系对应部门。 """), MessagesPlaceholder("history"), ("human", "{question}") ]) history = [ HumanMessage(content="电脑密码过期后怎么办?"), AIMessage(content="请通过统一身份平台重置密码,若失败联系 IT 服务台。") ] model = ChatOpenAI(model="gpt-4o-mini", temperature=0) chain = faq_prompt | model result = chain.invoke({ "history": history, "question": "如果统一身份平台打不开呢?" }) print(result.content)
这个示例虽然简单,但已经体现了消息体系的 4 个优点:
当回答质量不稳定时,优先排查消息层:
1. 使用 `ChatPromptTemplate` 和 `MessagesPlaceholder` 实现一个支持多轮追问的 FAQ 助手。
2. 编写一个消息裁剪器,要求始终保留第一条 SystemMessage 和最近 8 条消息。
3. 手动模拟一次工具调用闭环:创建 AIMessage、执行工具、构造 ToolMessage,再继续调用模型。
4. 设计一个多模态报销助手的消息结构,说明哪些信息应放在图片内容、OCR 文本、结构化字段和 ToolMessage 中。
5. 为你的项目设计一份“消息日志格式”,至少包含 message_id、role、token、trace_id 和 created_at 字段。