Skip to content

Commit

Permalink
Introduce metadata for NodeHandler entries, teaching node types i…
Browse files Browse the repository at this point in the history
…n Kits to describe themselves.

- **Sort nodes in `InspectableKit` and add kit metadata.**
- **Plumb node type info to selector.**
- **Add `NodeHandlerMetadata`.**
- **Mark a few node types as deprecated.**
- **Deprecate a PaLM generate text.**
- **Implement `GraphDescriptorNodeHandler`.**
- **Teach KitManifest about node type metadata.**
- **Add support for metadata in `defineNodeType`.**
- **Teach Core kit about node handler metadata.**
- **Teach JSON Kit about node handler metadata.**
- **Deprecate nodes in Node Nursery Web.**
- **Teach PaLM Kit about node handler metadata.**
- **Teach Templates Kit about node handler metadata.**
- **docs(changeset): Introduce `metadata` for `NodeHandler` entries,
teaching node types in Kits to describe themselves.**

Fixes breadboard-ai#1459.
  • Loading branch information
dglazkov authored Apr 21, 2024
1 parent f3e87f1 commit 416aed2
Show file tree
Hide file tree
Showing 36 changed files with 321 additions and 121 deletions.
13 changes: 13 additions & 0 deletions .changeset/green-apes-act.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@google-labs/node-nursery-web": patch
"@google-labs/breadboard-ui": patch
"@google-labs/template-kit": patch
"@google-labs/breadboard": patch
"@google-labs/core-kit": patch
"@google-labs/json-kit": patch
"@google-labs/palm-kit": patch
"@google-labs/breadboard-schema": patch
"@breadboard-ai/build": patch
---

Introduce `metadata` for `NodeHandler` entries, teaching node types in Kits to describe themselves.
29 changes: 20 additions & 9 deletions packages/breadboard-ui/src/elements/editor/node-selector.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { GraphDescriptor, Kit, inspect } from "@google-labs/breadboard";
import {
GraphDescriptor,
Kit,
NodeHandlerMetadata,
inspect,
} from "@google-labs/breadboard";
import { LitElement, html, css, nothing } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { map } from "lit/directives/map.js";
Expand Down Expand Up @@ -230,7 +235,10 @@ export class NodeSelector extends LitElement {
});

const kits = graph.kits() || [];
const kitList = new Map<string, string[]>();
const kitList = new Map<
string,
{ id: string; metadata: NodeHandlerMetadata }[]
>();
kits.sort((kit1, kit2) =>
(kit1.descriptor.title || "") > (kit2.descriptor.title || "") ? 1 : -1
);
Expand All @@ -242,6 +250,7 @@ export class NodeSelector extends LitElement {

let kitNodes = kit.nodeTypes;
kitNodes = kit.nodeTypes.filter((node) => {
if (node.metadata().deprecated) return false;
if (!this.filter) {
return true;
}
Expand All @@ -255,7 +264,7 @@ export class NodeSelector extends LitElement {

kitList.set(
kit.descriptor.title,
kitNodes.map((node) => node.type())
kitNodes.map((node) => ({ id: node.type(), metadata: node.metadata() }))
);
}

Expand Down Expand Up @@ -296,27 +305,29 @@ export class NodeSelector extends LitElement {
/><label for="${kitId}"><span>${kitName}</span></label>
<div class="kit-contents">
<ul>
${map(kitContents, (kitItemName) => {
const kitItemId = kitItemName
${map(kitContents, (nodeTypeInfo) => {
const className = nodeTypeInfo.id
.toLocaleLowerCase()
.replace(/\W/, "-");
const id = nodeTypeInfo.id;
return html`<li
class=${classMap({
[kitItemId]: true,
[className]: true,
["kit-item"]: true,
})}
draggable="true"
title=${nodeTypeInfo.metadata.description || ""}
@dblclick=${() => {
this.dispatchEvent(new KitNodeChosenEvent(kitItemName));
this.dispatchEvent(new KitNodeChosenEvent(id));
}}
@dragstart=${(evt: DragEvent) => {
if (!evt.dataTransfer) {
return;
}
evt.dataTransfer.setData(DATA_TYPE, kitItemName);
evt.dataTransfer.setData(DATA_TYPE, id);
}}
>
<span>${kitItemName}</span>
<span>${id}</span>
</li>`;
})}
</ul>
Expand Down
26 changes: 20 additions & 6 deletions packages/breadboard/src/inspector/kits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
NodeHandlers,
NodeHandler,
NodeDescriberResult,
NodeHandlerMetadata,
} from "../types.js";
import { collectPortsForType } from "./ports.js";
import { describeInput, describeOutput } from "./schemas.js";
Expand All @@ -27,8 +28,15 @@ const createBuiltInKit = (): InspectableKit => {
url: "",
},
nodeTypes: [
new BuiltInNodeType("input", describeInput),
new BuiltInNodeType("output", describeOutput),
new BuiltInNodeType("input", describeInput, {
title: "Input",
description: "The input node. Use it to request inputs for your board.",
}),
new BuiltInNodeType("output", describeOutput, {
title: "Output",
description:
"The output node. Use it to provide outputs from your board.",
}),
],
};
};
Expand All @@ -51,9 +59,9 @@ export const collectKits = (kits: Kit[]): InspectableKit[] => {
};

const collectNodeTypes = (handlers: NodeHandlers): InspectableNodeType[] => {
return Object.entries(handlers).map(
([type, handler]) => new NodeType(type, handler)
);
return Object.entries(handlers)
.sort()
.map(([type, handler]) => new NodeType(type, handler));
};

class NodeType implements InspectableNodeType {
Expand All @@ -65,6 +73,10 @@ class NodeType implements InspectableNodeType {
this.#handler = handler;
}

metadata(): NodeHandlerMetadata {
return "metadata" in this.#handler ? this.#handler.metadata || {} : {};
}

type() {
return this.#type;
}
Expand Down Expand Up @@ -95,13 +107,15 @@ class NodeType implements InspectableNodeType {
class BuiltInNodeType extends NodeType {
constructor(
type: string,
describer: (options: NodeTypeDescriberOptions) => NodeDescriberResult
describer: (options: NodeTypeDescriberOptions) => NodeDescriberResult,
metadata: NodeHandlerMetadata
) {
super(type, {
invoke: async () => {},
describe: async () => {
return describer({});
},
metadata,
});
}
}
Expand Down
5 changes: 5 additions & 0 deletions packages/breadboard/src/inspector/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
NodeConfiguration,
NodeDescriberResult,
NodeDescriptor,
NodeHandlerMetadata,
NodeIdentifier,
NodeTypeIdentifier,
NodeValue,
Expand Down Expand Up @@ -388,6 +389,10 @@ export type InspectableKit = {
};

export type InspectableNodeType = {
/**
* Returns the metadata, associated with this node type.
*/
metadata(): NodeHandlerMetadata;
/**
* Returns the type of the node.
*/
Expand Down
71 changes: 39 additions & 32 deletions packages/breadboard/src/kits/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,58 @@

import { inspect } from "../inspector/index.js";
import { loadWithFetch } from "../loader/default.js";
import { createLoader } from "../loader/index.js";
import { BoardRunner } from "../runner.js";
import { GraphDescriptor, Kit, KitManifest, NodeHandler } from "../types.js";
import {
GraphDescriptor,
InputValues,
Kit,
KitManifest,
NodeHandlerContext,
NodeHandlerMetadata,
NodeHandlerObject,
} from "../types.js";
import { asRuntimeKit } from "./ctors.js";

type ManifestEntry = string | GraphDescriptor;

const getGraphDescriptor = async (
base: URL,
key: string,
entry: ManifestEntry
) => {
if (typeof entry === "string") {
const loader = createLoader();
const result = await loader.load(entry, { base });
if (result === null) {
throw new Error(`Unable to load graph descriptor from "${entry}"`);
}
return result;
} else if (entry.edges && entry.nodes) {
const setBaseURL = (base: URL, key: string, graph: GraphDescriptor) => {
if (graph.edges && graph.nodes) {
const url = new URL(base);
url.searchParams.set("graph", key);
return { ...entry, url: url.href };
return { ...graph, url: url.href };
} else {
throw new Error("Invalid graph descriptor");
}
};

class GraphDescriptorNodeHandler implements NodeHandlerObject {
#base: URL;
#type: string;
#graph: GraphDescriptor;
metadata: NodeHandlerMetadata;

constructor(base: URL, type: string, graph: GraphDescriptor) {
this.#base = base;
this.#type = type;
this.#graph = setBaseURL(base, type, graph);
this.describe = this.describe.bind(this);
this.invoke = this.invoke.bind(this);
const { title, description } = this.#graph;
this.metadata = { title, description };
}

async describe() {
return await inspect(this.#graph).describe();
}

async invoke(inputs: InputValues, context: NodeHandlerContext) {
const board = await BoardRunner.fromGraphDescriptor(this.#graph);
return await board.runOnce(inputs, context);
}
}

const createHandlersFromManifest = (base: URL, nodes: KitManifest["nodes"]) => {
return Object.fromEntries(
Object.entries(nodes).map(([key, value]) => {
return [
key,
{
describe: async () => {
const graph = await getGraphDescriptor(base, key, value);
return await inspect(graph).describe();
},
invoke: async (inputs, context) => {
const graph = await getGraphDescriptor(base, key, value);
const board = await BoardRunner.fromGraphDescriptor(graph);
return await board.runOnce(inputs, context);
},
} as NodeHandler,
];
return [key, new GraphDescriptorNodeHandler(base, key, value)];
})
);
};
Expand Down
28 changes: 22 additions & 6 deletions packages/breadboard/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,28 @@ export type NodeDescriberFunction = (
context?: NodeDescriberContext
) => Promise<NodeDescriberResult>;

export type NodeHandler =
| {
invoke: NodeHandlerFunction;
describe?: NodeDescriberFunction;
}
| NodeHandlerFunction;
export type NodeHandlerMetadata = {
/**
* Title of the node type.
*/
title?: string;
/**
* Description of the node type.
*/
description?: string;
/**
* Whether or not the node is deprecated.
*/
deprecated?: boolean;
};

export type NodeHandlerObject = {
invoke: NodeHandlerFunction;
describe?: NodeDescriberFunction;
metadata?: NodeHandlerMetadata;
};

export type NodeHandler = NodeHandlerObject | NodeHandlerFunction;

/**
* All known node handlers.
Expand Down
3 changes: 3 additions & 0 deletions packages/build/src/internal/define/define.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import type { NodeHandlerMetadata } from "@google-labs/breadboard";
import type { CountUnion, Expand } from "../common/type-util.js";
import type {
ConvertBreadboardType,
Expand Down Expand Up @@ -128,6 +129,7 @@ export function defineNodeType<
outputs: O;
invoke: F;
describe?: D;
metadata?: NodeHandlerMetadata;
} & {
// Then narrow down the types with further constraints. This 2-step
// approach lets us generate additional and more precise errors.
Expand Down Expand Up @@ -189,6 +191,7 @@ export function defineNodeType<
return Object.assign(impl.instantiate.bind(impl), {
invoke: impl.invoke.bind(impl),
describe: impl.describe.bind(impl),
metadata: params.metadata || {},
});
}

Expand Down
3 changes: 3 additions & 0 deletions packages/core-kit/src/nodes/append.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ export const appendDescriber: NodeDescriberFunction = async (
};

export default {
metadata: {
deprecated: true,
},
describe: appendDescriber,
invoke: async (inputs: InputValues): Promise<OutputValues> => {
const { accumulator, ...values } = inputs as AppendInputs;
Expand Down
34 changes: 22 additions & 12 deletions packages/core-kit/src/nodes/batch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { InputValues, NodeValue, OutputValues } from "@google-labs/breadboard";
import {
InputValues,
NodeHandlerObject,
NodeValue,
OutputValues,
} from "@google-labs/breadboard";

export type BatcherInputs = InputValues & {
/**
Expand All @@ -24,14 +29,19 @@ export type BatcherOutputs = InputValues & {
list: NodeValue[];
};

export default async (inputs: InputValues): Promise<OutputValues> => {
const { list, size } = inputs as BatcherInputs;
if (!list) throw new Error("Batcher requires `list` input");
if (!size) throw new Error("Batcher requires `size` input");
if (!list.length) return { list: [[]] };
const batches = [];
for (let i = 0; i < list.length; i += size) {
batches.push(list.slice(i, i + size));
}
return { list: batches };
};
export default {
metadata: {
deprecated: true,
},
invoke: async (inputs: InputValues): Promise<OutputValues> => {
const { list, size } = inputs as BatcherInputs;
if (!list) throw new Error("Batcher requires `list` input");
if (!size) throw new Error("Batcher requires `size` input");
if (!list.length) return { list: [[]] };
const batches = [];
for (let i = 0; i < list.length; i += size) {
batches.push(list.slice(i, i + size));
}
return { list: batches };
},
} satisfies NodeHandlerObject;
Loading

0 comments on commit 416aed2

Please sign in to comment.