Skip to content

Latest commit

 

History

History
173 lines (126 loc) · 5.98 KB

serialization.md

File metadata and controls

173 lines (126 loc) · 5.98 KB

Serialization

Tip

Location within the framework bee-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 "bee-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 "bee-agent-framework/memory/tokenMemory";
import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat";
import { BaseMessage } from "bee-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: "Bee",
  }),
);

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 "bee-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("Bee");
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 "bee-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("Bee");
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 "bee-agent-framework/memory/unconstrainedMemory";
import { BaseMessage } from "bee-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.