狂野飙车6鸿蒙版
471.84M · 2025-10-31
在构建复杂的AI应用时,我们经常需要处理自定义的对象或复杂的数据类型。然而,标准的序列化机制可能无法直接处理这些特殊类型的数据。LangGraph提供了灵活的方式来处理这种情况,本文将介绍一种简单可靠的方法来实现自定义序列化。
标准的序列化机制(如JSON序列化)只能处理基本的数据类型(如字符串、数字、列表、字典等)。但在实际应用中,我们经常需要处理:
当这些复杂数据类型需要被保存到Checkpoint中时,我们就需要实现自定义的序列化和反序列化逻辑。
我们将创建一个处理自定义Person类的应用,展示如何实现自定义序列化:
Person类让我们先来看完整的代码实现:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
LangGraph Checkpoint 示例4: 自定义序列化
这个示例展示了如何使用自定义的序列化逻辑来处理复杂数据类型。
应用功能:创建一个处理自定义对象的图,并实现序列化和反序列化方法。
"""
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import InMemorySaver
print("======= Checkpoint示例4: 自定义序列化 =======")
# 定义一个自定义类
class Person:
def __init__(self, name: str, age: int, hobbies: list):
self.name = name
self.age = age
self.hobbies = hobbies
def __repr__(self):
return f"Person(name='{self.name}', age={self.age}, hobbies={self.hobbies})"
# 添加方法将对象转换为可序列化的格式
def to_dict(self):
return {
"_type": "Person",
"name": self.name,
"age": self.age,
"hobbies": self.hobbies
}
# 静态方法从字典中恢复对象
@staticmethod
def from_dict(data):
return Person(data["name"], data["age"], data["hobbies"])
# 定义状态类型(使用可序列化的数据结构)
class PersonState(TypedDict):
person_data: dict # 存储序列化后的Person数据
updated: bool
# 定义节点函数 - 包含序列化和反序列化逻辑
def update_person(state: PersonState) -> PersonState:
"""更新Person对象的信息"""
# 从序列化数据中恢复Person对象
person_data = state["person_data"]
person = Person.from_dict(person_data)
# 更新年龄和爱好
person.age += 1
if "阅读" not in person.hobbies:
person.hobbies.append("阅读")
# 将更新后的对象序列化为字典
updated_person_data = person.to_dict()
return {"person_data": updated_person_data, "updated": True}
# 创建StateGraph
person_graph = StateGraph(PersonState)
# 添加节点
person_graph.add_node("update", update_person)
# 设置入口点和出口点
person_graph.add_edge(START, "update")
person_graph.add_edge("update", END)
# 使用标准的InMemorySaver
checkpointer = InMemorySaver()
# 编译图并添加checkpointer
compiled_graph = person_graph.compile(checkpointer=checkpointer)
# 创建会话配置
session_config = {"configurable": {"thread_id": "person-session"}}
# 创建一个Person对象并序列化
initial_person = Person(name="张三", age=30, hobbies=["编程", "跑步"])
initial_person_data = initial_person.to_dict()
print(f"初始Person对象: {initial_person}")
# 执行第一次调用 - 传入序列化后的数据
result1 = compiled_graph.invoke({"person_data": initial_person_data, "updated": False}, config=session_config)
# 从结果中恢复Person对象以显示
result1_person = Person.from_dict(result1["person_data"])
print(f"第一次调用后: {result1_person}")
# 执行第二次调用,应该使用保存的状态
result2 = compiled_graph.invoke({}, config=session_config)
result2_person = Person.from_dict(result2["person_data"])
print(f"第二次调用后: {result2_person}")
# 执行第三次调用
result3 = compiled_graph.invoke({}, config=session_config)
result3_person = Person.from_dict(result3["person_data"])
print(f"第三次调用后: {result3_person}")
# 验证状态是否正确保存
print("======= 验证状态保存 =======")
saved_state = compiled_graph.get_state(session_config)
if saved_state:
print(f"保存的状态数据类型: {type(saved_state.values['person_data']).__name__}")
print(f"保存的状态数据: {saved_state.values['person_data']}")
# 从保存的状态中恢复Person对象
restored_person = Person.from_dict(saved_state.values['person_data'])
print(f"从保存状态恢复的对象: {restored_person}")
else:
print("未能获取到保存的状态")
执行结果如下
======= Checkpoint示例4: 自定义序列化 =======
初始Person对象: Person(name='张三', age=30, hobbies=['编程', '跑步'])
第一次调用后: Person(name='张三', age=31, hobbies=['编程', '跑步', '阅读'])
第二次调用后: Person(name='张三', age=32, hobbies=['编程', '跑步', '阅读'])
第三次调用后: Person(name='张三', age=33, hobbies=['编程', '跑步', '阅读'])
======= 验证状态保存 =======
保存的状态数据类型: dict
保存的状态数据: {'_type': 'Person', 'name': '张三', 'age': 33, 'hobbies': ['编程', '跑步', '阅读']}
从保存状态恢复的对象: Person(name='张三', age=33, hobbies=['编程', '跑步', '阅读'])
class Person:
def __init__(self, name: str, age: int, hobbies: list):
self.name = name
self.age = age
self.hobbies = hobbies
def __repr__(self):
return f"Person(name='{self.name}', age={self.age}, hobbies={self.hobbies})"
def to_dict(self):
return {
"_type": "Person",
"name": self.name,
"age": self.age,
"hobbies": self.hobbies
}
@staticmethod
def from_dict(data):
return Person(data["name"], data["age"], data["hobbies"])
我们创建了一个简单的Person类,并为其实现了两个关键方法:
to_dict():将对象转换为可序列化的字典格式,包含一个特殊的_type字段用于标识对象类型from_dict():静态方法,从字典中恢复Person对象这两个方法是实现自定义序列化和反序列化的基础,遵循了"先让它工作"的原则,保持简单直接。
class PersonState(TypedDict):
person_data: dict # 存储序列化后的Person数据
updated: bool
关键设计点:我们在状态中存储的是序列化后的字典数据(person_data),而不是直接存储Person对象。这是一种更简单可靠的方法,避免了与LangGraph内部序列化机制的兼容性问题。
def update_person(state: PersonState) -> PersonState:
"""更新Person对象的信息"""
# 从序列化数据中恢复Person对象
person_data = state["person_data"]
person = Person.from_dict(person_data)
# 业务逻辑:更新年龄和爱好
person.age += 1
if "阅读" not in person.hobbies:
person.hobbies.append("阅读")
# 将更新后的对象序列化为字典
updated_person_data = person.to_dict()
return {"person_data": updated_person_data, "updated": True}
这个节点函数展示了核心的序列化处理模式:
person_data字典反序列化为Person对象Person对象进行业务操作(更新年龄和爱好)Person对象序列化为字典这种方法的优点是简单直接,不需要修改或继承LangGraph的内部类。
# 使用标准的InMemorySaver
checkpointer = InMemorySaver()
# 编译图并添加checkpointer
compiled_graph = person_graph.compile(checkpointer=checkpointer)
我们使用了标准的InMemorySaver,不需要创建自定义的Checkpoint saver类。这符合"先让它工作"的原则,避免了不必要的复杂性。
# 创建一个Person对象并序列化
initial_person = Person(name="张三", age=30, hobbies=["编程", "跑步"])
initial_person_data = initial_person.to_dict()
# 执行第一次调用 - 传入序列化后的数据
result1 = compiled_graph.invoke({"person_data": initial_person_data, "updated": False}, config=session_config)
# 从结果中恢复Person对象以显示
result1_person = Person.from_dict(result1["person_data"])
在调用图时,我们:
Person对象序列化为字典invoke方法Person对象以便显示执行上述代码,你会看到类似以下的输出:
======= Checkpoint示例4: 自定义序列化 =======
初始Person对象: Person(name='张三', age=30, hobbies=['编程', '跑步'])
第一次调用后: Person(name='张三', age=31, hobbies=['编程', '跑步', '阅读'])
第二次调用后: Person(name='张三', age=32, hobbies=['编程', '跑步', '阅读'])
第三次调用后: Person(name='张三', age=33, hobbies=['编程', '跑步', '阅读'])
======= 验证状态保存 =======
保存的状态数据类型: dict
保存的状态数据: {'_type': 'Person', 'name': '张三', 'age': 33, 'hobbies': ['编程', '跑步', '阅读']}
从保存状态恢复的对象: Person(name='张三', age=33, hobbies=['编程', '跑步', '阅读'])
我们可以看到:
Person对象的状态在多次调用之间被正确地保存和恢复Person对象Person对象自定义序列化在很多实际应用场景中都非常有用:
在本文中,我们学习了一种简单可靠的方法来实现LangGraph中的自定义序列化:
to_dict()和from_dict()方法这种方法遵循了"MAKE IT WORK FIRST"的原则,先实现核心功能,再考虑优化和扩展。它简单、可靠,适用于大多数自定义数据类型的场景。
记住,在实际应用中,保持代码简单直接比过度设计更重要。先让它工作,然后再根据实际需求进行优化和改进。