LangGraph--基础学习(memory和持久化)
LangGraph 有一个内置的持久层,通过checkpointers实现。当你用一个checkpointers编译图时,checkpointers在每一个超步保存一个图状态的检查点
。这些检查点被保存到一个线程
中,可以在图形执行后访问。因为线程
允许在执行后访问图的状态,所以包括人在回路、内存、时间旅行和容错在内的几个强大功能都是可能的。请参阅此操作指南 ,了解如何在图形中添加和使用检查指针的端到端示例。下面,我们将更详细地讨论这些概念。
Threads¶ 线程 ¶
线程是分配给checkpointers保存的每个检查点的唯一 ID 或线程标识符 。当使用检查指针调用图时,您必须指定 thread_id
作为配置的可配置
部分的一部分:
{"configurable": {"thread_id": "1"}}
Checkpoints¶ 检查点
检查点是在每个超级步骤中保存的图形状态的快照,由具有以下关键属性的 StateSnapshot
对象表示:
config
:与此检查点关联的配置。metadata
:与此检查点关联的元数据。values
:此时状态通道的值。- next 图中接下来要执行的节点名称的元组。
tasks
:PregelTask
对象的元组,包含有关接下来要执行的任务的信息。如果以前尝试过该步骤,则它将包含错误信息。如果图从节点内动态中断,则任务将包含与中断关联的附加数据。
至于 Checkpoints的其他使用方法大家可以参考官方文档,我这边因为还没有深入用到就先不介绍了,后续介绍memory
memory
记忆是一种认知功能,允许人们存储,检索和使用信息来理解他们的现在和未来。想想和一个忘记你告诉他们的一切的同事一起工作的挫折感,需要不断重复!随着人工智能代理承担涉及大量用户交互的更复杂的任务,为它们配备内存对于效率和用户满意度同样至关重要。有了记忆,代理可以从反馈中学习并适应用户的偏好。本指南涵盖了基于回忆范围的两种记忆类型:
短期记忆 ,或线程范围记忆,可以在任何时候从与用户的单个会话线程内调用。LangGraph 将短期记忆作为智能体状态的一部分进行管理。使用检查指针将状态持久化到数据库,以便线程可以随时恢复。当调用图形或完成一个步骤时,短期记忆会更新,并且在每个步骤开始时读取 State。例子如下:
聊天记忆:
import os
from dotenv import load_dotenv# 加载.env文件中的环境变量
load_dotenv()
from typing import Annotated,List,Literal
from typing_extensions import TypedDict
from langchain_deepseek import ChatDeepSeek
from langchain_tavily import TavilySearch
from langchain_core.messages import BaseMessage,SystemMessage,AIMessage,HumanMessage,ToolMessage
from typing_extensions import TypedDict
from langgraph.graph import StateGraph,START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode, tools_condition
from langchain_core.tools import tool
from langgraph.checkpoint.memory import MemorySaver
from langgraph.types import Command, interrupt
from pydantic import BaseModelllm = ChatDeepSeek(model="deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"))from langgraph.checkpoint.memory import MemorySaver
memory = MemorySaver()from langchain_tavily import TavilySearch
class State(TypedDict):messages:Annotated[list, add_messages] # 增加不覆盖graph_builder = StateGraph(State)from langchain_community.tools.tavily_search import TavilySearchResults
from langgraph.prebuilt import ToolNode,tools_condition
tool = TavilySearchResults(max_results=2)
tool_node =ToolNode(tools=[tool])
tools =[tool]
llm_with_tools = llm.bind_tools(tools)from langchain_core.messages import BaseMessage,SystemMessage,AIMessage,HumanMessage,ToolMessage
def chatbot(state:State):message_content = str(state["messages"])message = [SystemMessage(content="你是一个聊天机器人"),HumanMessage(content=message_content)]return {"messages":[llm_with_tools.invoke(message)]}graph_builder.add_node("chatbot",chatbot)
graph_builder.add_node("tools",tool_node)
graph_builder.add_conditional_edges("chatbot", tools_condition)graph_builder.add_edge("tools","chatbot")
graph_builder.add_edge(START,"chatbot")
graph_builder.add_edge("chatbot",END)graph = graph_builder.compile(checkpointer=memory)# 打印图结构
print(graph.get_graph().draw_mermaid())
graph_png = graph.get_graph().draw_mermaid_png()
with open("PersistenceMemory.png", "wb") as f:f.write(graph_png)
config = {"configurable":{"thread_id":1}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = graph.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你好,我叫zsf
================================== Ai Message ==================================你好,zsf!看来我们又见面了,很高兴再次和你聊天!有什么我可以帮你的吗? 😊
================================ Human Message =================================你是谁
================================== Ai Message ==================================你好,我是一个智能聊天助手,可以帮助你解答问题、提供信息或者陪你聊天!你可以叫我助手或者任何你喜欢的名字。有什么我可以帮你的吗? 😊
goodbye
config = {"configurable":{"thread_id":3}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = graph.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你还记得我叫什么名字吗?
================================== Ai Message ==================================目前我无法记住或存储个人信息,包括您的名字。但如果您愿意告诉我,我可以在这段对话中称呼您!
goodbye
config = {"configurable":{"thread_id":1}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = graph.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你还记得我的名字吗?
================================== Ai Message ==================================当然记得!你叫 **zsf**,对吧?有什么需要我帮忙的吗? 😊
goodbye
可以发现统一线程他是可以记住的,不同线程无法互相关联,
聊天总结和信息删除
该代码使用了前面的数据路由模式,实现对话聊天数据的总结和删除,以此控制token量降低成本,具体参考代码,具体模式参考前面的博客
llm_summery = ChatDeepSeek(model="deepseek-chat",api_key=os.getenv("DEEPSEEK_API_KEY"))from typing import Literal
def route_tools(state:State)->Literal["tools","summarize_conversation","__end__"]:messages = state["messages"]print(f"len(messages) is {len(messages)}")if isinstance(state, list):ai_message = state[-1]elif messages:=state.get("messages",[]):ai_message = messages[-1]else:raise ValueError(f"no messages found in input state to tool_edge:{state}")if hasattr(ai_message,"tool_calls") and len(ai_message.tool_calls)>0:return "tools"elif len(messages)>6:return "summarize_conversation"return "__end__"from langchain_core.messages import RemoveMessagedef summarize_conversation(state:State):summary = state.get("summary","")if summary: # 如果存在summ则让大模型进行总结提取,并进行覆盖summary_message=(f"This is summary of the conversation to date:{summary}\n\n""extend the summary by taking into account the new message above:")else:summary_message = "Create a summay of the conversation above:"print(state["messages"])messages = state["messages"]+[HumanMessage(content=summary_message)]response = llm_summery.invoke(messages)print(response)delete_messages = [RemoveMessage(id=m.id) for m in state["messages"]][:-1] # 删除 除最新一条消息外的所有消息return {"summary":response.content,"messages":delete_messages}builder = StateGraph(State)builder.add_node("summarize_conversation",summarize_conversation)
builder.add_node("chatbot",chatbot)
builder.add_node("tools",tool_node)builder.add_edge(START, "chatbot")
builder.add_edge("tools","chatbot")
builder.add_conditional_edges("chatbot",route_tools,{"tools":"tools", "__end__":"__end__","summarize_conversation":"summarize_conversation"})
builder.add_edge("summarize_conversation",END)g = builder.compile(checkpointer=memory)
print(g.get_graph().draw_mermaid())
graph_png = g.get_graph().draw_mermaid_png()
with open("SummeryMemory.png", "wb") as f:f.write(graph_png)
config = {"configurable":{"thread_id":101}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = g.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你好,我是zsf
len(messages) is 2
================================== Ai Message ==================================你好,zsf!很高兴认识你!有什么我可以帮助你的吗?
================================ Human Message =================================你还记得我的名字吗
len(messages) is 4
================================== Ai Message ==================================当然记得!你的名字是 **zsf**。有什么需要我帮忙的吗?
================================ Human Message =================================苏超最近比赛最新进展
len(messages) is 6
================================== Ai Message ==================================
Tool Calls:tavily_search_results_json (call_0_acb022ab-9802-474a-93fb-c557e44593a0)Call ID: call_0_acb022ab-9802-474a-93fb-c557e44593a0Args:query: 苏超 最近比赛 最新进展
================================= Tool Message =================================
Name: tavily_search_results_json[{"title": "苏超最新积分榜公布 - 紫牛新闻", "url": "https://www.yzwb.net/news/ty/202506/t20250614_221828.html", "content": "[](https://www.yzwb.net/)\n\n[首页](https://www.yzwb.net/ \"首页\")>[新闻中心](https://www.yzwb.net/news/ \"新闻中心\")>[体育](https://www.yzwb.net/news/ty/ \"体育\")\n\n苏超最新积分榜公布\n\n来源: 江苏省城市足球联赛\n\n2025-06-14 20:44:00\n\n\n\n**城市联赛第4轮第一比赛日战报**\n\n6月14日\n\n苏超第四轮\n\n南京队客场1:1战平淮安队\n\n泰州队客场1:1战平扬州队 [...] * [连云港东海:共享“指尖”盛宴 挖出百亿产业](https://www.yzwb.net/news/yw/202506/t20250614_221826.html \"连云港东海:共享“指尖”盛宴 挖出百亿产业\")\n* [无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕](https://www.yzwb.net/news/yw/202506/t20250614_221823.html \"无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕\")\n* [江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”](https://www.yzwb.net/news/yw/202506/t20250614_221822.html \"江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”\") [...] * [盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录](https://www.yzwb.net/news/ty/202506/t20250614_221813.html \"盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录\")\n* [奥林匹克精神主题展览在北京奥运博物馆开幕](https://www.yzwb.net/news/ty/202506/t20250614_221785.html \"奥林匹克精神主题展览在北京奥运博物馆开幕\")\n* [中国男排强势逆袭荷兰,收官战或冲击更高排名](https://www.yzwb.net/news/ty/202506/t20250614_221748.html \"中国男排强势逆袭荷兰,收官战或冲击更高排名\")\n* [世俱杯前瞻:世界足坛开启崭新时代](https://www.yzwb.net/news/ty/202506/t20250614_221745.html \"世俱杯前瞻:世界足坛开启崭新时代\")", "score": 0.6549173}, {"title": "省外观众破万,“苏超”人气爆棚 - 新华网江苏频道", "url": "http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html", "content": "“好幸运,抢到了常州VS南京的票!”6月18日中午,福建球迷陈先生擦拭着额头的汗水,“咔嚓”一声兴奋地截下购票成功的手机页面。这场于6月21日在常州举行的“苏超”第五轮揭幕战,单场观众达36712人,省外观众突破万人,均刷新此前联赛相关纪录。常州赛区组委会表示,本场比赛预约购票人数达到76万人次,并专门设置外省观众专享通道,18日当天开票即“秒空”。6月21日,在宿迁举行的第四轮收官战同样火爆,“比Labubu都难抢!”来自河南驻马店的球迷武鑫远通过手机App足足刷了两天才搞定,种草“苏超”后,他不但自己每场都想看,还邀请亲朋好友7月6日来看宿迁主场对战连云港的比赛。", "score": 0.5163278}]
len(messages) is 8
================================== Ai Message ==================================以下是关于苏超(江苏省城市足球联赛)最近比赛的最新进展:1. **苏超第四轮比赛结果**:- 南京队客场1:1战平淮安队。- 泰州队客场1:1战平扬州队。- 更多比赛详情可以参考[紫牛新闻的报道](https://www.yzwb.net/news/ty/202506/t20250614_221828.html)。2. **第五轮比赛人气爆棚**:- 常州VS南京的比赛吸引了大量观众,单场观众达36712人,省外观众突破万人,刷新了联赛纪录。- 购票情况非常火爆,预约人数达到76万人次,开票即“秒空”。- 详情请见[新华网江苏频道的报道](http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html)。如果你对某场比赛或球队有更具体的兴趣,可以告诉我,我会帮你进一步查找信息!
[HumanMessage(content='你好,我是zsf', additional_kwargs={}, response_metadata={}, id='1322e1ed-7cd4-4d4f-a941-d1d1971179ca'), AIMessage(content='你好,zsf!很高兴认识你!有什么我可以帮助你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 192, 'total_tokens': 207, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 64}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '4a3dd295-7c53-42d6-a8c7-643751dce2f4', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--768b4715-af0f-4252-a7aa-851c220e6f87-0', usage_metadata={'input_tokens': 192, 'output_tokens': 15, 'total_tokens': 207, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}), HumanMessage(content='你还记得我的名字吗', additional_kwargs={}, response_metadata={}, id='278d8bad-5c63-4986-9893-510ee69ad32c'), AIMessage(content='当然记得!你的名字是 **zsf**。有什么需要我帮忙的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 17, 'prompt_tokens': 517, 'total_tokens': 534, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 389}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '30cbb540-dbb4-49bb-9d1e-ce25bddc4831', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--96952010-1460-4817-b196-41d5596cf239-0', usage_metadata={'input_tokens': 517, 'output_tokens': 17, 'total_tokens': 534, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}), HumanMessage(content='苏超最近比赛最新进展', additional_kwargs={}, response_metadata={}, id='fe993fe8-fa05-4f81-9598-e3bdc920a734'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_0_acb022ab-9802-474a-93fb-c557e44593a0', 'function': {'arguments': '{"query":"苏超 最近比赛 最新进展"}', 'name': 'tavily_search_results_json'}, 'type': 'function', 'index': 0}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 29, 'prompt_tokens': 838, 'total_tokens': 867, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 512}, 'prompt_cache_hit_tokens': 512, 'prompt_cache_miss_tokens': 326}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'aec61cdf-58cd-4f33-a1df-22ac13b6997b', 'service_tier': None, 'finish_reason': 'tool_calls', 'logprobs': None}, id='run--2bf4c365-ead2-403e-9e39-84a507b4e223-0', tool_calls=[{'name': 'tavily_search_results_json', 'args': {'query': '苏超 最近比赛 最新进展'}, 'id': 'call_0_acb022ab-9802-474a-93fb-c557e44593a0', 'type': 'tool_call'}], usage_metadata={'input_tokens': 838, 'output_tokens': 29, 'total_tokens': 867, 'input_token_details': {'cache_read': 512}, 'output_token_details': {}}), ToolMessage(content='[{"title": "苏超最新积分榜公布 - 紫牛新闻", "url": "https://www.yzwb.net/news/ty/202506/t20250614_221828.html", "content": "[](https://www.yzwb.net/)\\n\\n[首页](https://www.yzwb.net/ \\"首页\\")>[新闻中心](https://www.yzwb.net/news/ \\"新闻中心\\")>[体育](https://www.yzwb.net/news/ty/ \\"体育\\")\\n\\n苏超最新积分榜公布\\n\\n来源: 江苏省城市足球联赛\\n\\n2025-06-14 20:44:00\\n\\n\\n\\n**城市联赛第4轮第一比赛日战报**\\n\\n6月14日\\n\\n苏超第四轮\\n\\n南京队客场1:1战平淮安队\\n\\n泰州队客场1:1战平扬州队 [...] * [连云港东海:共享“指尖”盛宴 挖出百亿产业](https://www.yzwb.net/news/yw/202506/t20250614_221826.html \\"连云港东海:共享“指尖”盛宴 挖出百亿产业\\")\\n* [无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕](https://www.yzwb.net/news/yw/202506/t20250614_221823.html \\"无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕\\")\\n* [江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”](https://www.yzwb.net/news/yw/202506/t20250614_221822.html \\"江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”\\") [...] * [盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录](https://www.yzwb.net/news/ty/202506/t20250614_221813.html \\"盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录\\")\\n* [奥林匹克精神主题展览在北京奥运博物馆开幕](https://www.yzwb.net/news/ty/202506/t20250614_221785.html \\"奥林匹克精神主题展览在北京奥运博物馆开幕\\")\\n* [中国男排强势逆袭荷兰,收官战或冲击更高排名](https://www.yzwb.net/news/ty/202506/t20250614_221748.html \\"中国男排强势逆袭荷兰,收官战或冲击更高排名\\")\\n* [世俱杯前瞻:世界足坛开启崭新时代](https://www.yzwb.net/news/ty/202506/t20250614_221745.html \\"世俱杯前瞻:世界足坛开启崭新时代\\")", "score": 0.6549173}, {"title": "省外观众破万,“苏超”人气爆棚 - 新华网江苏频道", "url": "http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html", "content": "“好幸运,抢到了常州VS南京的票!”6月18日中午,福建球迷陈先生擦拭着额头的汗水,“咔嚓”一声兴奋地截下购票成功的手机页面。这场于6月21日在常州举行的“苏超”第五轮揭幕战,单场观众达36712人,省外观众突破万人,均刷新此前联赛相关纪录。常州赛区组委会表示,本场比赛预约购票人数达到76万人次,并专门设置外省观众专享通道,18日当天开票即“秒空”。6月21日,在宿迁举行的第四轮收官战同样火爆,“比Labubu都难抢!”来自河南驻马店的球迷武鑫远通过手机App足足刷了两天才搞定,种草“苏超”后,他不但自己每场都想看,还邀请亲朋好友7月6日来看宿迁主场对战连云港的比赛。", "score": 0.5163278}]', name='tavily_search_results_json', id='33912dfe-b018-45a7-90b9-0a2d1aaa35e4', tool_call_id='call_0_acb022ab-9802-474a-93fb-c557e44593a0', artifact={'query': '苏超 最近比赛 最新进展', 'follow_up_questions': None, 'answer': None, 'images': [], 'results': [{'url': 'https://www.yzwb.net/news/ty/202506/t20250614_221828.html', 'title': '苏超最新积分榜公布 - 紫牛新闻', 'content': '[](https://www.yzwb.net/)\n\n[首页](https://www.yzwb.net/ "首页")>[新闻中心](https://www.yzwb.net/news/ "新闻中心")>[体育](https://www.yzwb.net/news/ty/ "体育")\n\n苏超最新积分榜公布\n\n来源: 江苏省城市足球联赛\n\n2025-06-14 20:44:00\n\n\n\n**城市联赛第4轮第一比赛日战报**\n\n6月14日\n\n苏超第四轮\n\n南京队客场1:1战平淮安队\n\n泰州队客场1:1战平扬州队 [...] * [连云港东海:共享“指尖”盛宴 挖出百亿产业](https://www.yzwb.net/news/yw/202506/t20250614_221826.html "连云港东海:共享“指尖”盛宴 挖出百亿产业")\n* [无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕](https://www.yzwb.net/news/yw/202506/t20250614_221823.html "无锡:数字驱动“跨境出海”新热潮,长三角跨境电商交易会成果丰硕")\n* [江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”](https://www.yzwb.net/news/yw/202506/t20250614_221822.html "江苏扬州:酒店预订量达到9成,景区预约量创新高!文体旅融合“苏超”再“得分”") [...] * [盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录](https://www.yzwb.net/news/ty/202506/t20250614_221813.html "盛李豪/王子菲混合团体赛摘银,资格赛打破世界纪录")\n* [奥林匹克精神主题展览在北京奥运博物馆开幕](https://www.yzwb.net/news/ty/202506/t20250614_221785.html "奥林匹克精神主题展览在北京奥运博物馆开幕")\n* [中国男排强势逆袭荷兰,收官战或冲击更高排名](https://www.yzwb.net/news/ty/202506/t20250614_221748.html "中国男排强势逆袭荷兰,收官战或冲击更高排名")\n* [世俱杯前瞻:世界足坛开启崭新时代](https://www.yzwb.net/news/ty/202506/t20250614_221745.html "世俱杯前瞻:世界足坛开启崭新时代")', 'score': 0.6549173, 'raw_content': None}, {'url': 'http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html', 'title': '省外观众破万,“苏超”人气爆棚 - 新华网江苏频道', 'content': '“好幸运,抢到了常州VS南京的票!”6月18日中午,福建球迷陈先生擦拭着额头的汗水,“咔嚓”一声兴奋地截下购票成功的手机页面。这场于6月21日在常州举行的“苏超”第五轮揭幕战,单场观众达36712人,省外观众突破万人,均刷新此前联赛相关纪录。常州赛区组委会表示,本场比赛预约购票人数达到76万人次,并专门设置外省观众专享通道,18日当天开票即“秒空”。6月21日,在宿迁举行的第四轮收官战同样火爆,“比Labubu都难抢!”来自河南驻马店的球迷武鑫远通过手机App足足刷了两天才搞定,种草“苏超”后,他不但自己每场都想看,还邀请亲朋好友7月6日来看宿迁主场对战连云港的比赛。', 'score': 0.5163278, 'raw_content': None}], 'response_time': 1.42}), AIMessage(content='以下是关于苏超(江苏省城市足球联赛)最近比赛的最新进展:\n\n1. **苏超第四轮比赛结果**:\n - 南京队客场1:1战平淮安队。\n - 泰州队客场1:1战平扬州队。\n - 更多比赛详情可以参考[紫牛新闻的报道](https://www.yzwb.net/news/ty/202506/t20250614_221828.html)。\n\n2. **第五轮比赛人气爆棚**:\n - 常州VS南京的比赛吸引了大量观众,单场观众达36712人,省外观众突破万人,刷新了联赛纪录。\n - 购票情况非常火爆,预约人数达到76万人次,开票即“秒空”。\n - 详情请见[新华网江苏频道的报道](http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html)。\n\n如果你对某场比赛或球队有更具体的兴趣,可以告诉我,我会帮你进一步查找信息!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 228, 'prompt_tokens': 3290, 'total_tokens': 3518, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 832}, 'prompt_cache_hit_tokens': 832, 'prompt_cache_miss_tokens': 2458}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '8f01efd3-5b87-402a-9c06-963cda44958e', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--f0bcd595-0a35-485a-9524-2d5b41358741-0', usage_metadata={'input_tokens': 3290, 'output_tokens': 228, 'total_tokens': 3518, 'input_token_details': {'cache_read': 832}, 'output_token_details': {}})]
content="### **Conversation Summary** \n\n1. **Introduction**: \n - User **zsf** greeted the assistant, which responded warmly and asked how it could help. \n\n2. **Memory Check**: \n - The assistant confirmed it remembered **zsf**'s name when asked. \n\n3. **Latest Updates on Jiangsu Super League (苏超)**: \n - **Recent Match Results (Round 4)**: \n - Nanjing 1-1 Huai'an \n - Taizhou 1-1 Yangzhou \n - **Popularity Surge (Round 5)**: \n - The **Changzhou vs. Nanjing** match set records with **36,712 attendees**, including over **10,000 out-of-province fans**. \n - Tickets sold out instantly, with **760,000 pre-registrations**. \n - Sources: [紫牛新闻](https://www.yzwb.net/news/ty/202506/t20250614_221828.html) | [新华网](http://www.js.xinhuanet.com/20250622/4cfb6520329e4ed0a7c0e19c2f26fa25/c.html) \n\n4. **Closing Offer**: \n - The assistant invited further questions about specific matches or teams. \n\n### **Key Points**: \n- The conversation covered greetings, memory confirmation, and sports updates. \n- The **Jiangsu Super League** is gaining significant attention, especially for its high attendance and fan engagement. \n\nLet me know if you'd like additional details!" additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 328, 'prompt_tokens': 1307, 'total_tokens': 1635, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 1307}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'df3df4e0-b316-4297-8365-e9e76c179a41', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--33a599f3-7d2a-4f3c-b9b6-17fc2efd71b3-0' usage_metadata={'input_tokens': 1307, 'output_tokens': 328, 'total_tokens': 1635, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}
中间有总结输出:
content="### **Conversation Summary** \n\n1. **Introduction**: \n - User **zsf** greeted the assistant, which responded warmly and asked how it could help. \n\n2. **Memory Check**: \n - The assistant confirmed it remembered **zsf**'s name when asked. \n\n3. **Latest Updates on Jiangsu Super League (苏超)**: \n - **Recent Match Results (Round 4)**: \n - Nanjing 1-1 Huai'an \n - Taizhou 1-1 Yangzhou \n - **Popularity Surge (Round 5)**: \n - The **Changzhou vs. Nanjing** match set records with **36,712 attendees**, including over **10,000 out-of-province fans**. \n - Tickets sold out instantly, with **760,000 pre-registrations**. \n - Sources: [紫牛新闻](https://www.yzwb.net/news/ty/202506/t20250614_221828.html)
也回到了3
================================ Human Message =================================我喜欢学习Stem
len(messages) is 3
================================== Ai Message ==================================STEM(科学、技术、工程和数学)是非常有趣且充满挑战的领域!你对STEM中的哪个具体方向最感兴趣呢?比如:1. **科学(Science)**:生物学、化学、物理学等。
2. **技术(Technology)**:编程、人工智能、数据分析等。
3. **工程(Engineering)**:机械工程、电子工程、土木工程等。
4. **数学(Mathematics)**:代数、几何、统计学等。如果你有具体的问题或想了解某个主题的更多信息,可以告诉我,我会尽力帮助你!
================================ Human Message =================================我喜欢学习
len(messages) is 5
手动删除messages
使用最上面的第一的graph进行测试:
config = {"configurable":{"thread_id":5}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = graph.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你好, 我是zsf
================================== Ai Message ==================================你好,zsf!很高兴认识你!有什么我可以帮你的吗?
================================ Human Message =================================你还记的我是谁吗?
================================== Ai Message ==================================你好,zsf!当然记得你,我们刚刚才聊过呢!有什么需要我帮忙的吗?
goodbye
messages = graph.get_state(config).values["messages"]
messages
[HumanMessage(content='你好, 我是zsf', additional_kwargs={}, response_metadata={}, id='79608b16-1e92-48f0-8832-0139030bd1ef'),AIMessage(content='你好,zsf!很高兴认识你!有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 192, 'total_tokens': 207, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 64}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'a82c559c-850c-4b0b-a5f8-cbfb666b515a', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--3d80d266-c250-44c0-8589-f8f2c1ce4d4e-0', usage_metadata={'input_tokens': 192, 'output_tokens': 15, 'total_tokens': 207, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}),HumanMessage(content='你还记的我是谁吗?', additional_kwargs={}, response_metadata={}, id='c0e70217-d8e8-40f9-b305-192f705a2e94'),AIMessage(content='你好,zsf!当然记得你,我们刚刚才聊过呢!有什么需要我帮忙的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 523, 'total_tokens': 545, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 395}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '1cff9dac-501b-4a74-845f-0c146b1eea71', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--3817e930-7cc3-426e-9e55-1dc5983bcbfd-0', usage_metadata={'input_tokens': 523, 'output_tokens': 22, 'total_tokens': 545, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]
len(messages)
4
messages[0].id
'79608b16-1e92-48f0-8832-0139030bd1ef'
from langchain_core.messages import RemoveMessage
graph.update_state(config=config, values={"messages":RemoveMessage("79608b16-1e92-48f0-8832-0139030bd1ef")})
{'configurable': {'thread_id': '5','checkpoint_ns': '','checkpoint_id': '1f04f44f-302e-6201-8005-6c605b089f75'}}
messages = graph.get_state(config).values["messages"]
len(messages)
3
messages
[AIMessage(content='你好,zsf!很高兴认识你!有什么我可以帮你的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 192, 'total_tokens': 207, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 64}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': 'a82c559c-850c-4b0b-a5f8-cbfb666b515a', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--3d80d266-c250-44c0-8589-f8f2c1ce4d4e-0', usage_metadata={'input_tokens': 192, 'output_tokens': 15, 'total_tokens': 207, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}}),HumanMessage(content='你还记的我是谁吗?', additional_kwargs={}, response_metadata={}, id='c0e70217-d8e8-40f9-b305-192f705a2e94'),AIMessage(content='你好,zsf!当然记得你,我们刚刚才聊过呢!有什么需要我帮忙的吗?', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 22, 'prompt_tokens': 523, 'total_tokens': 545, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 128}, 'prompt_cache_hit_tokens': 128, 'prompt_cache_miss_tokens': 395}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_8802369eaa_prod0425fp8', 'id': '1cff9dac-501b-4a74-845f-0c146b1eea71', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None}, id='run--3817e930-7cc3-426e-9e55-1dc5983bcbfd-0', usage_metadata={'input_tokens': 523, 'output_tokens': 22, 'total_tokens': 545, 'input_token_details': {'cache_read': 128}, 'output_token_details': {}})]
从上面可以发现我们删除了对应的节点
自动删除messages
其他的定义都和第一个一样增加以下代码:
def delete_messages(state:State):messages = state["messages"]if(len(messages)>3):return {"messages":[RemoveMessage(id=m.id) for m in messages][:-3]} # 只保留最新的三个信息
from typing import Literal
def should_continue(state:State)->Literal["action","delete_messages"]:""" 返回到下个节点去执行 """last_message = state["messages"][-1]if not last_message.tool_calls:return "delete_messages"return "action"
g_builder = StateGraph(State)
g_builder.add_node("action",tool_node)
g_builder.add_node("delete_messages", delete_messages)
g_builder.add_node("chatbot",chatbot)g_builder.add_edge(START, "chatbot")
g_builder.add_conditional_edges("chatbot",should_continue,{"action":"action","delete_messages":"delete_messages"})
g_builder.add_edge("action","chatbot")
g_builder.add_edge("delete_messages",END)b = g_builder.compile(checkpointer=memory)
print(b.get_graph().draw_mermaid())
# data =b.get_graph().draw_mermaid_png()
# with open("autodelete.png", "wb") as f:
# f.write(data)
config = {"configurable":{"thread_id":8}}
while True:user_input = input("请输入:")# print(user_input)if user_input.lower() in ["q"]:print("goodbye")breakevents = b.stream({"messages":[("user",user_input)]},config=config, stream_mode="values")for event in events:event["messages"][-1].pretty_print()
================================ Human Message =================================你好,我是zsf
================================== Ai Message ==================================你好,zsf!很高兴认识你!有什么可以帮你的吗?
================================ Human Message =================================你还记得我是谁?
================================== Ai Message ==================================你好,zsf!当然记得你,我们刚刚才聊过呢!有什么需要我帮忙的吗?
================================== Ai Message ==================================你好,zsf!当然记得你,我们刚刚才聊过呢!有什么需要我帮忙的吗?
================================ Human Message =================================3+5等于几
================================== Ai Message ==================================3 + 5 等于 8!有什么其他问题需要帮忙吗?
================================== Ai Message ==================================3 + 5 等于 8!有什么其他问题需要帮忙吗?
goodbye
messages = b.get_state(config).values["messages"]
len(messages)
3
所以本节主要学习就删除和提取信息,控制调用大模型的token,控制成本,中间可看到大量用到了路由模式,所以后续还是要多理解前面几个模式