国内优秀网站设计有口皆碑的域名备案加急
国内优秀网站设计,有口皆碑的域名备案加急,视觉设计基础,上海市干部公示参考文章#xff1a;黑马大模型RAG与Agent智能体实战教程LangChain提示词——43、Agent智能体——流式输出#xff08;agent.stream()、结果块chunk、tool calls、stream_mode#xff09; 文章目录LangChain Agent 流式输出#xff1a;如何只打印「新增消息」#xff1f;1…参考文章黑马大模型RAG与Agent智能体实战教程LangChain提示词——43、Agent智能体——流式输出agent.stream()、结果块chunk、tool calls、stream_mode文章目录LangChain Agent 流式输出如何只打印「新增消息」1. 背景为什么会有「重复消息」问题2. 思路把 messages 当成「日志数组」3. 核心代码只打印本次新增的消息4. 更好看的打印根据消息类型区分输出5. 这种写法有几个好处6. 结语把 Agent 想成「日志流」而不是「答案」LangChain Agent 流式输出如何只打印「新增消息」在用 LangChain 的 Agent 做流式输出时很多人会有一个共同疑问agent.stream(..., stream_modevalues)每个 chunk 都是“当前完整对话状态”那我怎样只打印本次新产生的消息而不是一遍遍重复之前的内容这篇博文就记录一下我在你的示例里用的一套小技巧用一个seen_len记录“已经看过多少条消息”然后每个 chunk 只处理“从上次之后新增的部分”。逻辑简单但非常好用。1. 背景为什么会有「重复消息」问题在 LangChain 里agent.stream可以指定不同的stream_mode。我们这里用的是agent.stream(input{messages:[...]},stream_modevalues,)stream_modevalues的特点每个chunk其实是一个「状态快照」里面有一个messages字段是一个列表包含到当前为止的所有消息系统提示、用户、Agent 思考、工具调用、工具结果等。这会带来一个问题假设当前一轮对话里顺序产生了 4 条消息用户提问HumanMessageAgent 决定调用工具AIMessagetool_calls工具get_price的结果ToolMessage工具get_info的结果ToolMessage在某个时刻的chunk里messages可能是这样[HumanMessage(...),AIMessage(...,tool_calls[get_price,get_info]),ToolMessage(...get_price 结果...),ToolMessage(...get_info 结果...),]如果你每次都只写latest_messagemessages[-1]pretty_print_latest_message(latest_message)那就只会看到最后一条消息比如get_info的结果中间那条get_price的ToolMessage在终端上就“悄悄消失了”。2. 思路把messages当成「日志数组」解决这个问题其实很直观把chunk[messages]当成一个只会越来越长的“日志数组”我们只需要记住一个数字上一次已经处理到第几条。于是就有了这套逻辑定义seen_len 0表示“目前为止我们已经打印过前seen_len条消息”每来一个新的chunk整个messages列表可能变长了新增的部分就是messages[seen_len:]处理完这些「新增消息」之后把seen_len更新为len(messages)。3. 核心代码只打印本次新增的消息下面是简化后的核心循环代码来自你的stream_agent_messages函数思想final_answer_parts:List[str][]chunk_count0seen_len0# 已经处理过的消息数量forchunkinagent.stream(input{messages:[{role:user,content:user_question}]},stream_modevalues,):chunk_count1messages:List[BaseMessage]chunk.get(messages,[])ifnotmessages:continue# 本次 chunk 相比上一次新增的所有消息new_messages:List[BaseMessage]messages[seen_len:]seen_lenlen(messages)ifnotnew_messages:continueformsginnew_messages:pretty_print_latest_message(msg,chunk_indexchunk_count)# 如果有自然语言内容就顺便拼到最终回答里contentgetattr(msg,content,None)ifisinstance(content,str)andcontent:final_answer_parts.append(content)关键点就两行new_messagesmessages[seen_len:]seen_lenlen(messages)第一次循环seen_len 0所以会处理messages[0:]也就是全部消息后续每次只会处理上次没见过的尾巴部分。4. 更好看的打印根据消息类型区分输出为了让终端输出更易读我们还写了一个小工具函数defpretty_print_latest_message(latest_message:BaseMessage,chunk_index:int)-None:contentgetattr(latest_message,content,None)tool_callsgetattr(latest_message,tool_calls,None)prefixf[chunk{chunk_index:02d}]msg_typetype(latest_message).__name__ifcontent:print(f{prefix}{msg_type}:{content})eliftool_calls:names:List[str][]fortcintool_calls:ifisinstance(tc,dict):names.append(str(tc.get(name)ortc.get(tool,unknown_tool)))else:names.append(str(getattr(tc,name,unknown_tool)))print(f{prefix}{msg_type}调用工具:{names})else:print(f{prefix}{msg_type}: (无 content/tool_calls可能是内部控制消息))这会带来类似下面的终端输出[chunk 01] HumanMessage: 传智教育股价多少并介绍一下 [chunk 02] AIMessage 调用工具: [get_price, get_info] [chunk 03] ToolMessage: 股票传智教育的价格是20元 [chunk 03] ToolMessage: 股票传智教育是一家A股上市公司专注于IT职业教育。 [chunk 04] AIMessage: 传智教育当前股价为20元是一家专注IT职业教育的A股上市公司……可以非常清楚地看到哪一步是人类提问哪一步是 Agent 决定调用哪些工具每个工具分别返回了什么最终 Agent 是如何整合工具结果给出自然语言回答的。5. 这种写法有几个好处完全不依赖内部实现细节我们只依赖一个事实messages只会“追加新元素”不会中途把老消息删掉。适用于任何带messages的流式 values 输出不管是 Agent、Chain只要stream(..., stream_modevalues)且返回中有messages都可以套这个模式。非常适合做「可视化思考过程」在教学 / Debug / Demo 时实时看到 Agent 的每一步思考和每次工具调用非常直观。方便后处理我们顺便把自然语言content拼成一个final_answer既有“过程日志”又有“最终结果”。6. 结语把 Agent 想成「日志流」而不是「答案」很多人第一次用 LangChain 的 Agent会习惯性只看最终答案invoke或者只看chunk[-1]。但其实更有趣的是把 Agent 当成一个“不断追加日志的系统”每条消息都是一片「思考碎片」借助stream_modevaluesseen_len的小技巧你可以把这些碎片在终端里“实时播放”出来。