Location within the framework Hive-agent-framework/serializer.
Serialization is a process of converting complex data structures or objects into a format that can be easily stored, transmitted, and reconstructed later. Serialization is a difficult task, and JavaScript does not provide a magic tool to serialize and deserialize an arbitrary input. That is why we made such one.
import { Serializer } from "Hive-agent-framework/serializer/serializer";
const original = new Date("2024-01-01T00:00:00.000Z");
const serialized = Serializer.serialize(original);
const deserialized = Serializer.deserialize(serialized);
console.info(deserialized instanceof Date); // true
console.info(original.toISOString() === deserialized.toISOString()); // true
Source: examples/serialization/base.ts
[!NOTE]
Serializer knows how to serialize/deserialize the most well-known JavaScript data structures. Continue reading to see how to register your own.
Being Serializable
Most parts of the framework implement the internal Serializable class, which exposes the following methods.
createSnapshot (returns an object that "snapshots" the current state)
loadSnapshot (applies the provided snapshot to the current instance)
fromSerialized (static, creates the new instance from the given serialized input)
fromSnapshot (static, creates the new instance from the given snapshot)
See the direct usage on the following memory example.
import { TokenMemory } from "Hive-agent-framework/memory/tokenMemory";
import { OllamaChatLLM } from "Hive-agent-framework/adapters/ollama/chat";
import { BaseMessage } from "Hive-agent-framework/llms/primitives/message";
const llm = new OllamaChatLLM();
const memory = new TokenMemory({ llm });
await memory.addMany([
BaseMessage.of({
role: "user",
text: "What is your name?",
}),
]);
const serialized = memory.serialize();
const deserialized = TokenMemory.fromSerialized(serialized);
await deserialized.add(
BaseMessage.of({
role: "assistant",
text: "Hive",
}),
);
Source: examples/serialization/memory.ts
Serializing unknowns
If you want to serialize a class that the Serializer does not know, it throws the SerializerError error. However, you can tell the Serializer how to work with your class by registering it as a serializable.
import { Serializer } from "Hive-agent-framework/serializer/serializer";
class MyClass {
constructor(public readonly name: string) {}
}
Serializer.register(MyClass, {
// Defines how to transform a class to a plain object (snapshot)
toPlain: (instance) => ({ name: instance.name }),
// Defines how to transform a plain object (snapshot) a class instance
fromPlain: (snapshot) => new MyClass(snapshot.name),
// optional handlers to support lazy initiation (handling circular dependencies)
createEmpty: () => new MyClass(""),
updateInstance: (instance, update) => {
Object.assign(instance, update);
},
});
const instance = new MyClass("Hive");
const serialized = Serializer.serialize(instance);
const deserialized = Serializer.deserialize<MyClass>(serialized);
console.info(instance);
console.info(deserialized);
Source: examples/serialization/customExternal.ts
or you can extend the Serializable class.
import { Serializable } from "Hive-agent-framework/internals/serializable";
class MyClass extends Serializable {
constructor(public readonly name: string) {
super();
}
static {
// register class to the global serializer register
this.register();
}
createSnapshot(): unknown {
return {
name: this.name,
};
}
loadSnapshot(snapshot: ReturnType<typeof this.createSnapshot>) {
Object.assign(this, snapshot);
}
}
const instance = new MyClass("Hive");
const serialized = instance.serialize();
const deserialized = MyClass.fromSerialized(serialized);
console.info(instance);
console.info(deserialized);
Source: examples/serialization/customInternal.ts
[!TIP]
Most framework components are Serializable.
Context matters
import { UnconstrainedMemory } from "Hive-agent-framework/memory/unconstrainedMemory";
import { BaseMessage } from "Hive-agent-framework/llms/primitives/message";
// String containing serialized `UnconstrainedMemory` instance with one message in it.
const serialized = `{"__version":"0.0.0","__root":{"__serializer":true,"__class":"Object","__ref":"5","__value":{"target":"UnconstrainedMemory","snapshot":{"__serializer":true,"__class":"Object","__ref":"4","__value":{"messages":{"__serializer":true,"__class":"Array","__ref":"1","__value":[{"__serializer":true,"__class":"BaseMessage","__ref":"2","__value":{"role":"user","text":"Serialization is amazing, isn't?","meta":{"__serializer":true,"__class":"Undefined","__ref":"3"}}}]}}}}}}`;
// If `BaseMessage` was not imported the serialization would fail because the `BaseMessage` had no chance to register itself.
const memory = UnconstrainedMemory.fromSerialized(serialized, {
// this part can be omitted if all classes used in the serialized string are imported (and have `static` register block) or at least one initiated
extraClasses: [BaseMessage],
});
console.info(memory.messages);
Source: examples/serialization/context.ts
[!IMPORTANT]
Ensuring that all classes are registered in advance can be annoying, but there's a good reason for that. If we imported all the classes for you, that would significantly increase your application's size and bootstrapping time + you would have to install all peer dependencies that you may not even need.