Skip to content

Commit

Permalink
side wires (#3789)
Browse files Browse the repository at this point in the history
- **Introduce `side` behavior.**
- **Introduce `side` port list.**
- **Plumb side ports to `GraphNode`.**
- **Make no-input/no-output boards invokable.**
- **docs(changeset): Expose side wires in `InspectableGraph`.**

Progress on #3788
  • Loading branch information
dglazkov authored Nov 15, 2024
1 parent 18dace0 commit 32b50af
Show file tree
Hide file tree
Showing 18 changed files with 122 additions and 27 deletions.
9 changes: 9 additions & 0 deletions .changeset/green-wasps-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@breadboard-ai/visual-editor": minor
"@google-labs/breadboard": minor
"@google-labs/agent-kit": minor
"@breadboard-ai/shared-ui": minor
"@google-labs/core-kit": minor
---

Expose side wires in `InspectableGraph`.
1 change: 1 addition & 0 deletions packages/agent-kit/agent.kit.json
Original file line number Diff line number Diff line change
Expand Up @@ -10634,6 +10634,7 @@
"llm-content",
"module",
"ports-spec",
"side",
"stream",
"transient"
],
Expand Down
1 change: 1 addition & 0 deletions packages/agent-kit/boards/specialist.bgl.json
Original file line number Diff line number Diff line change
Expand Up @@ -3703,6 +3703,7 @@
"llm-content",
"module",
"ports-spec",
"side",
"stream",
"transient"
],
Expand Down
6 changes: 5 additions & 1 deletion packages/breadboard/src/inspector/bubbled-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,11 @@ export class BubbledInspectableNode implements InspectableNode {
bubbledValues
),
};
return { inputs, outputs };
const side: InspectablePortList = {
fixed: true,
ports: [],
};
return { inputs, outputs, side };
}
return this.#actual.ports(inputValues, outputValues || undefined);
}
Expand Down
20 changes: 15 additions & 5 deletions packages/breadboard/src/inspector/kits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
NodeHandlerObject,
} from "../types.js";
import { graphUrlLike } from "../utils/graph-url-like.js";
import { collectPortsForType } from "./ports.js";
import { collectPortsForType, filterSidePorts } from "./ports.js";
import { describeInput, describeOutput } from "./schemas.js";
import {
InspectableKit,
Expand Down Expand Up @@ -129,15 +129,21 @@ const portsFromHandler = async (
}
try {
const described = await handler.describe();
const inputs = {
fixed: described.inputSchema.additionalProperties === false,
ports: collectPortsForType(described.inputSchema, "input"),
};
const side = {
fixed: true,
ports: filterSidePorts(inputs),
};
return {
inputs: {
fixed: described.inputSchema.additionalProperties === false,
ports: collectPortsForType(described.inputSchema, "input"),
},
inputs,
outputs: {
fixed: described.outputSchema.additionalProperties === false,
ports: collectPortsForType(described.outputSchema, "output"),
},
side,
};
} catch (e) {
console.warn(`Error describing node type ${type}:`, e);
Expand Down Expand Up @@ -192,6 +198,10 @@ export const emptyPorts = (): InspectableNodePorts => ({
ports: [],
fixed: false,
},
side: {
ports: [],
fixed: true,
},
});

function shortUrlTitle(url: string) {
Expand Down
8 changes: 6 additions & 2 deletions packages/breadboard/src/inspector/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
NodeTypeIdentifier,
OutputValues,
} from "../types.js";
import { collectPorts } from "./ports.js";
import { collectPorts, filterSidePorts } from "./ports.js";
import { EdgeType } from "./schemas.js";
import {
InspectableEdge,
Expand Down Expand Up @@ -126,6 +126,10 @@ class Node implements InspectableNode {
this.#inputsAndConfig(inputValues, this.configuration())
),
};
const side: InspectablePortList = {
fixed: true,
ports: filterSidePorts(inputs),
};
const addErrorPort =
this.descriptor.type !== "input" && this.descriptor.type !== "output";
const outputs: InspectablePortList = {
Expand All @@ -139,7 +143,7 @@ class Node implements InspectableNode {
outputValues
),
};
return { inputs, outputs };
return { inputs, outputs, side };
}
}

Expand Down
49 changes: 48 additions & 1 deletion packages/breadboard/src/inspector/ports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CanConnectAnalysis,
InspectableEdge,
InspectablePort,
InspectablePortList,
InspectablePortType,
PortStatus,
} from "./types.js";
Expand Down Expand Up @@ -59,6 +60,7 @@ const BEHAVIOR_AFFECTS_TYPE_CHECKING: { [K in BehaviorSchema]: boolean } = {
image: true,
code: true,
module: true,
side: false,
};

export const collectPorts = (
Expand Down Expand Up @@ -106,6 +108,7 @@ export const collectPorts = (
const expected = schemaPortNames.includes(port) || star;
const required = requiredPortNames.includes(port);
const portSchema = schema.properties?.[port] || DEFAULT_SCHEMA;
const kind = computeKind(type, portSchema);
if (portSchema.behavior?.includes("deprecated") && !wired) return null;
return {
name: port,
Expand All @@ -128,12 +131,40 @@ export const collectPorts = (
),
schema: portSchema,
type: new PortType(portSchema),
kind: type === EdgeType.In ? "input" : "output",
kind,
} satisfies InspectablePort;
})
.filter(Boolean) as InspectablePort[];
};

function computeKind(
type: EdgeType,
portSchema: Schema
): "input" | "output" | "side" {
const behaviors: BehaviorSchema[] =
(portSchema.type === "array"
? behaviorFromArray(portSchema.items)
: portSchema.behavior) || [];
if (type === EdgeType.Out) return "output";
const match: Set<BehaviorSchema> = new Set(["config", "board", "side"]);
let count = 0;
behaviors.forEach((behavior) => {
if (match.has(behavior)) ++count;
});
if (count == 3) return "side";
return "input";
}

function behaviorFromArray(
items: Schema | Schema[] | undefined
): BehaviorSchema[] {
if (!items) return [];
if (Array.isArray(items)) {
return items.flatMap((item) => item.behavior || []) || [];
}
return items.behavior || [];
}

export class PortType implements InspectablePortType {
constructor(public schema: Schema) {}

Expand Down Expand Up @@ -213,3 +244,19 @@ export const collectPortsForType = (
} satisfies InspectablePort;
});
};

/**
* CAUTION: Side-effectey. Will remove side-wire ports from `inputs`.
*/
export function filterSidePorts(inputs: InspectablePortList) {
const sidePorts: InspectablePort[] = [];
const inputPorts = inputs.ports.filter((port) => {
if (port.kind === "side") {
sidePorts.push(port);
return false;
}
return true;
});
inputs.ports = inputPorts;
return sidePorts;
}
6 changes: 5 additions & 1 deletion packages/breadboard/src/inspector/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ export type InspectablePort = {
/**
* Is this an input or output port?
*/
kind: "input" | "output";
kind: "input" | "output" | "side";
};

export type InspectablePortType = {
Expand Down Expand Up @@ -507,6 +507,10 @@ export type InspectableNodePorts = {
* Returns the output ports of the node.
*/
outputs: InspectablePortList;
/**
* Return the side ports of the node.
*/
side: InspectablePortList;
};

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/breadboard/src/inspector/virtual-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ class VirtualNode implements InspectableNode {
ports: [],
fixed: true,
},
side: {
ports: [],
fixed: true,
},
};
}

Expand Down
9 changes: 1 addition & 8 deletions packages/breadboard/src/run/invoke-graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,9 @@ export async function invokeGraph(
const path = context.invocationPath || [];
const lifecycle = context.state?.lifecycle();

const adjustedContext = context.start
? {
...context,
inputs,
}
: context;

for await (const result of runGraph(
graphToRun,
adjustedContext,
{ ...context, inputs },
resumeFrom
)) {
if (result.type === "input") {
Expand Down
9 changes: 5 additions & 4 deletions packages/breadboard/src/run/node-invoker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ export class NodeInvoker {
}

#adjustInputs(result: TraversalResult) {
const { inputs, descriptor } = result;
if (!this.#start) return inputs;
if (this.#start !== descriptor.id) return inputs;
return { ...inputs, ...this.#initialInputs };
const { inputs, current } = result;
if (current.from === "$entry") {
return { ...inputs, ...this.#initialInputs };
}
return inputs;
}

async invokeNode(result: TraversalResult, invocationPath: number[]) {
Expand Down
7 changes: 6 additions & 1 deletion packages/breadboard/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ export type BehaviorSchema =
/**
* Indicates that the string is a Module.
*/
| "module";
| "module"
/**
* Indicates that this is a side wire
* See https://github.com/breadboard-ai/breadboard/issues/3788#issuecomment-2477813443
*/
| "side";

export type Schema = {
title?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/core-kit/src/nodes/invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const getRunnableBoard = async (
result = await loadGraphFromPath(path, context);
}
if (!result?.success) {
throw new Error("Unable to get board");
throw new Error(`Unable to get board: ${result?.error}`);
}
return { board: result, start, stopAfter, args };
}
Expand Down
10 changes: 10 additions & 0 deletions packages/shared-ui/src/elements/editor/graph-node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,16 @@ export class GraphNode extends PIXI.Container {
return { width: this.#width, height: this.#height };
}

set sidePorts(ports: InspectablePort[] | null) {
if (ports?.length) {
console.log("✨ got side ports", ports);
}
}

get sidePorts() {
return null;
}

set inPorts(ports: InspectablePort[] | null) {
this.#collapsedPortList.readOnly = this.readOnly;
this.#collapsedPortList.inPorts = ports;
Expand Down
1 change: 1 addition & 0 deletions packages/shared-ui/src/elements/editor/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,6 +1321,7 @@ export class Graph extends PIXI.Container {
graphNode.readOnly = this.readOnly;
graphNode.inPorts = portInfo.inputs.ports;
graphNode.outPorts = portInfo.outputs.ports;
graphNode.sidePorts = portInfo.side.ports;
graphNode.fixedInputs = portInfo.inputs.fixed;
graphNode.fixedOutputs = portInfo.outputs.fixed;
const info = this.#nodeInfo;
Expand Down
5 changes: 3 additions & 2 deletions packages/shared-ui/src/utils/mode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const filterConfigByMode = (
ports: ports.inputs.ports.filter(removeStarPort),
},
outputs,
side: ports.side,
};
}

Expand All @@ -55,7 +56,7 @@ export const filterConfigByMode = (
ports: inputPorts,
};

return { inputs, outputs: ports.outputs };
return { inputs, outputs: ports.outputs, side: ports.side };

function filterForConfigAware(port: InspectablePort) {
const hasConfig = port.schema.behavior?.includes("config");
Expand Down Expand Up @@ -90,5 +91,5 @@ export const filterPortsByMode = (
ports: ports.outputs.ports.filter(removeHardPort("$error")),
};

return { inputs, outputs };
return { inputs, outputs, side: ports.side };
};
1 change: 1 addition & 0 deletions packages/visual-editor/public/agent.kit.json
Original file line number Diff line number Diff line change
Expand Up @@ -10634,6 +10634,7 @@
"llm-content",
"module",
"ports-spec",
"side",
"stream",
"transient"
],
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
{ "path": "packages/shared-ui" },
{ "path": "packages/template-kit" },
{ "path": "packages/types" },
{ "path": "packages/unified-server" },
{ "path": "packages/visual-editor" },
{ "path": "packages/website" }
]
Expand Down

0 comments on commit 32b50af

Please sign in to comment.