Skip to content

Latest commit

 

History

History
202 lines (146 loc) · 5.5 KB

emitter.md

File metadata and controls

202 lines (146 loc) · 5.5 KB

Emitter (Observability)

Location within the framework bee-agent-framework/emitter.

An emitter is a core functionality of the framework that allows you to see what is happening under the hood.

Standalone usage

The following examples demonstrate how Emitter concept works.

Basic Usage

import { Emitter, EventMeta } from "bee-agent-framework/emitter/emitter";

// Get the root emitter or create your own
const root = Emitter.root;

root.match("*.*", async (data: unknown, event: EventMeta) => {
  console.log(`Received event '${event.path}' with data ${JSON.stringify(data)}`);
});

await root.emit("start", { id: 123 });
await root.emit("end", { id: 123 });

Source: examples/emitter/base.ts

Note

You can create your own emitter by initiating the Emitter class, but typically it's better to use or fork the root one (as can be seen in the following examples).

Advanced

import { Emitter, EventMeta, Callback } from "bee-agent-framework/emitter/emitter";

// Define events in advanced
interface Events {
  start: Callback<{ id: number }>;
  update: Callback<{ id: number; data: string }>;
}

// Create emitter with a type support
const emitter = Emitter.root.child<Events>({
  namespace: ["bee", "demo"],
  creator: {}, // typically a class
  context: {}, // custom data (propagates to the event's context property)
  groupId: undefined, // optional id for grouping common events (propagates to the event's groupId property)
  trace: undefined, // data related to identity what emitted what and which context (internally used by framework's components)
});

// Listen for "start" event
emitter.on("start", async (data, event: EventMeta) => {
  console.log(`Received ${event.name} event with id "${data.id}"`);
});

// Listen for "update" event
emitter.on("update", async (data, event: EventMeta) => {
  console.log(`Received ${event.name}' with id "${data.id}" and data ${data.data}`);
});

await emitter.emit("start", { id: 123 });
await emitter.emit("update", { id: 123, data: "Hello Bee!" });

Source: examples/emitter/advanced.ts

Note

Because we created the Emitter instance directly emitted events will not be propagated to the root which may or may not be desired. The piping concept is explained later on.

Event Matching

import { Callback, Emitter } from "bee-agent-framework/emitter/emitter";
import { BaseLLM } from "bee-agent-framework/llms/base";

interface Events {
  update: Callback<{ data: string }>;
}

const emitter = new Emitter<Events>({
  namespace: ["app"],
});

// Match events by a concrete name (strictly typed)
emitter.on("update", async (data, event) => {});

// Match all events emitted directly on the instance (not nested)
emitter.match("*", async (data, event) => {});

// Match all events (included nested)
emitter.match("*.*", async (data, event) => {});

// Match events by providing a filter function
emitter.match(
  (event) => event.creator instanceof BaseLLM,
  async (data, event) => {},
);

// Match events by regex
emitter.match(/watsonx/, async (data, event) => {});

Source: examples/emitter/matchers.ts

Event Piping

import { Emitter, EventMeta } from "bee-agent-framework/emitter/emitter";

const first = new Emitter({
  namespace: ["app"],
});

first.match("*.*", (data: unknown, event: EventMeta) => {
  console.log(
    `'first' has retrieved the following event ${event.path}, isDirect: ${event.source === first}`,
  );
});

const second = new Emitter({
  namespace: ["app", "llm"],
});
second.match("*.*", (data: unknown, event: EventMeta) => {
  console.log(
    `'second' has retrieved the following event '${event.path}', isDirect: ${event.source === second}`,
  );
});

// Propagate all events from the 'second' emitter to the 'first' emitter
const unpipe = second.pipe(first);

await first.emit("a", {});
await second.emit("b", {});

console.log("Unpipe");
unpipe();

await first.emit("c", {});
await second.emit("d", {});

Source: examples/emitter/piping.ts

Framework Usage

Typically, you consume out-of-the-box modules that use the Emitter concept on your behalf.

Agent usage

import { BeeAgent } from "bee-agent-framework/agents/bee/agent";
import { UnconstrainedMemory } from "bee-agent-framework/memory/unconstrainedMemory";
import { OllamaChatLLM } from "bee-agent-framework/adapters/ollama/chat";

const agent = new BeeAgent({
  llm: new OllamaChatLLM(),
  memory: new UnconstrainedMemory(),
  tools: [],
});

// Matching events on the instance level
agent.emitter.match("*.*", (data, event) => {});

await agent
  .run({
    prompt: "Hello agent!",
  })
  .observe((emitter) => {
    // Matching events on the execution (run) level
    emitter.match("*.*", (data, event) => {
      console.info(`RUN LOG: received event '${event.path}'`);
    });
  });

Source: examples/emitter/agentMatchers.ts

Important

The observe method is also supported on Tools and LLMs.

Tip

The more complex agentic example can be found here.

Tip

To verify if a given class instance has one, check for the presence of the emitter property.