
个人主页 for_ever_love__ 欢迎各位大佬莅临其他栏目: 大模型开发从0到1 其他栏目: iOS项目总结大全 其他栏目: 我想学python了 其他栏目: iOS UI 文章目录LangChain 消息体系深挖System / Human / AI / Tool 到底怎么配合一、四种消息各有各的规矩1.1 SystemMessage 为什么必须放第一1.2 AIMessage 不只是文本1.3 ToolMessage 必须带 id二、content 不只是字符串三、用模板生成消息列表3.1 MessagesPlaceholder多轮对话的关键四、消息列表的管理4.1 追加用 add_messages4.2 裁剪消息会无限增长4.3 消息必须有 id 才能被追踪五、调试把消息打印成人能看的样子六、常见坑七、小结LangChain 消息体系深挖System / Human / AI / Tool 到底怎么配合很多人写 LangChain 只用过ChatPromptTemplate.from_template(...)把消息当成字符串的容器。但一旦要做多轮对话、工具调用、多模态这个认知就不够用了。这篇把消息类型、content 结构、以及消息列表怎么管讲透。一、四种消息各有各的规矩fromlangchain_core.messagesimport(SystemMessage,HumanMessage,AIMessage,ToolMessage,)messages[SystemMessage(你是一个严谨的技术文档助手只回答有依据的内容。),HumanMessage(这个函数是干什么的),AIMessage(它用于……),]类型谁产生的作用注意SystemMessage你定人设、定规则必须在第一条模型对开头最敏感HumanMessage用户用户的输入多轮时交替出现AIMessage模型模型的回复可能带tool_callsToolMessage你的代码工具执行结果必须带tool_call_id1.1 SystemMessage 为什么必须放第一这是实测有效的经验模型对上下文开头和结尾的信息最敏感中间部分容易被忽略就是常说的 “lost in the middle”。所以# ✅ 规则在最前最新输入在最后[System(规则),Human(q1),AI(a1),Human(q2),AI(a2),Human(最新问题)]# ❌ 规则夹在中间容易被忽略[Human(q1),System(规则),AI(a1),...]如果你的规则很长还有个技巧——在最后一条消息里再复述一遍关键约束HumanMessage(f{question}\n\n记住只依据上下文回答没有就说不知道)1.2 AIMessage 不只是文本aiAIMessage(content,# 内容为空tool_calls[{name:get_weather,args:{city:北京},id:call_abc123,}],)当模型决定调工具时content常常是空字符串意图全在tool_calls里。所以判断模型说了什么不能只看contentdefdescribe(msg):ifmsg.tool_calls:returnf想调工具:{[c[name]forcinmsg.tool_calls]}returnmsg.content1.3 ToolMessage 必须带 idToolMessage(content北京晴26℃,tool_call_idcall_abc123,# 必须和 AIMessage.tool_calls[0][id] 一致)id 对不上模型就不知道这个结果对应哪个调用行为会变得非常奇怪比如重复调用、或者无视结果。这是新手最常踩的坑之一。二、content 不只是字符串这是 0.3 版本后非常重要、但很多人不知道的变化消息的content可以是一个内容块列表用来传多模态内容。fromlangchain_core.messagesimportHumanMessage msgHumanMessage(content[{type:text,text:这张图里画的是什么},{type:image_url,image_url:{url:https://example.com/chart.png}},])结构长这样[{type:text,text:...},{type:image_url,image_url:{url:...}},{type:audio,audio:{...}},# 部分模型支持]带来的实际问题你写的代码里如果假设msg.content是字符串碰到多模态消息就会炸。安全写法defto_text(content):ifisinstance(content,str):returncontent# 只取文本块return.join(b.get(text,)forbincontentifb.get(type)text)三、用模板生成消息列表硬编码消息对象太笨实际都用模板fromlangchain_core.promptsimportChatPromptTemplate promptChatPromptTemplate.from_messages([(system,你是{role}回答要{language}。),(human,{question}),])msgsprompt.format_messages(role运维专家,language简洁,question磁盘满了怎么办)几种简写形式写法等价(system, ...)SystemMessagePromptTemplate(human, ...)HumanMessagePromptTemplate(ai, ...)AIMessagePromptTemplate(placeholder, {history})插入一整个消息列表3.1 MessagesPlaceholder多轮对话的关键fromlangchain_core.promptsimportMessagesPlaceholder promptChatPromptTemplate.from_messages([(system,你是客服助手。),MessagesPlaceholder(history),# ← 这里会展开成一串消息(human,{question}),])prompt.format_messages(history[HumanMessage(我叫小明),AIMessage(你好小明),],question我叫什么,)注意MessagesPlaceholder(history)的变量名必须和传入的 key 一致而且它接收的是消息列表不是字符串。传字符串会报错。给历史设默认值避免没历史时报错MessagesPlaceholder(history,optionalTrue)# 没有 history 时自动省略四、消息列表的管理4.1 追加用 add_messages在 LangGraph 里消息列表的正确更新方式是返回增量靠 reducer 合并fromtypingimportAnnotatedfromlanggraph.graph.messageimportadd_messagesclassState(TypedDict):messages:Annotated[list,add_messages]defnode(state):return{messages:[AIMessage(你好)]}# 只返回新的旧的不用带4.2 裁剪消息会无限增长对话久了消息列表会撑爆上下文。三种处理方式① 只保留最近 N 条fromlangchain_core.messagesimporttrim_messages trimmertrim_messages(max_tokens2000,strategylast,# 保留最近的token_counterllm,# 用模型算 tokeninclude_systemTrue,# 系统消息永远保留allow_partialFalse,)trimmedtrimmer.invoke(messages)include_systemTrue很重要——别把人设裁掉了。② 摘要压缩fromlangchain_core.messagesimportSystemMessage,HumanMessage,RemoveMessageiflen(messages)20:summaryllm.invoke([...请总结以上对话...]).content# 删掉旧消息只留一条摘要new_messages[SystemMessage(f之前的对话摘要{summary})]messages[-4:]③ 删掉指定消息fromlanggraph.graph.messageimportRemoveMessagereturn{messages:[RemoveMessage(idsome_msg.id)]}4.3 消息必须有 id 才能被追踪msgAIMessage(你好,idmsg-001)自己构造消息时如果不给 idLangChain 会自动生成一个 UUID但你在代码里就找不到它了没法更新也没法删除。需要后续操作的消息显式给 id。五、调试把消息打印成人能看的样子defpretty_print(messages):forminmessages:icon{system:⚙️,human:,ai:,tool:}.get(m.type,?)bodyto_text(m.content)orifgetattr(m,tool_calls,None):bodyf [调工具:{, .join(c[name]forcinm.tool_calls)}]print(f{icon}{m.type}:{body[:120]})pretty_print(messages)发送消息前先打印一遍这能省掉大量模型为什么不按我说的做的排查时间。很多时候问题就是你以为发了 3 条其实发了 30 条或者system 根本没进去。六、常见坑现象原因解法模型不遵守 system 规则system 夹在中间被忽略放第一条末尾再复述工具结果模型看不到tool_call_id对不上检查 id 是否一一对应content拼接报错多模态消息 content 是列表用to_text()提取MessagesPlaceholder报错传了字符串必须传消息列表历史被裁没了裁剪没设include_system加上或保留最近几条消息越跑越多没做裁剪定期 trim 或摘要七、小结四种消息System放第一、Human、AI可能只有 tool_calls、Tool必须带 idcontent可能是列表多模态别假设它是字符串MessagesPlaceholder(history)插入整个消息列表optionalTrue可省略消息列表会无限增长必须裁剪trim_messagesinclude_systemTrue或摘要需要后续更新/删除的消息显式给 id发送前打印一遍消息列表是最有效的调试手段。下一篇讲模型参数——temperature、max_tokens 这些到底该怎么调。