@breadboard-ai/build
Advanced tools
Comparing version
# Changelog | ||
## 0.4.0 | ||
### Minor Changes | ||
- de524a4: Change the describe function to return only the names of ports instead of full JSON schema, and be stricter about when it is required/optional/forbidden based on the port configurations. | ||
### Patch Changes | ||
- de524a4: Improved type-safety and type descriptions relating to node definitions. | ||
- de524a4: Add support for reflective nodes, where the inputs provided at instantiation automatically reflect to outputs. | ||
- de524a4: Add `assertOutput` method, for getting an output port in cases where it is not possible at compile-time to know what output ports will exist. | ||
- de524a4: Add support for polymorphic nodes with dynamic output ports. | ||
- Updated dependencies [c3cb25f] | ||
- Updated dependencies [ae79e4a] | ||
- Updated dependencies [72c5c6b] | ||
- Updated dependencies [dd810dd] | ||
- Updated dependencies [c5ba396] | ||
- Updated dependencies [7bafa40] | ||
- Updated dependencies [2932f4b] | ||
- Updated dependencies [51159c4] | ||
- Updated dependencies [6f9ba52] | ||
- @google-labs/breadboard@0.17.0 | ||
## 0.3.1 | ||
@@ -4,0 +27,0 @@ |
@@ -13,3 +13,4 @@ /** | ||
export { placeholder } from "./internal/board/placeholder.js"; | ||
export { serialize, type SerializableBoard, } from "./internal/board/serialize.js"; | ||
export { serialize } from "./internal/board/serialize.js"; | ||
export type { SerializableBoard, } from "./internal/common/serializable.js"; | ||
export { defineNodeType } from "./internal/define/define.js"; | ||
@@ -16,0 +17,0 @@ export type { NodeFactoryFromDefinition } from "./internal/define/node-factory.js"; |
@@ -9,3 +9,3 @@ /** | ||
export { placeholder } from "./internal/board/placeholder.js"; | ||
export { serialize, } from "./internal/board/serialize.js"; | ||
export { serialize } from "./internal/board/serialize.js"; | ||
export { defineNodeType } from "./internal/define/define.js"; | ||
@@ -12,0 +12,0 @@ export { anyOf } from "./internal/type-system/any-of.js"; |
@@ -7,2 +7,3 @@ /** | ||
import { InputPort, type OutputPortReference, type ValuesOrOutputPorts } from "../common/port.js"; | ||
import type { SerializableBoard } from "../common/serializable.js"; | ||
import type { JsonSerializable } from "../type-system/type.js"; | ||
@@ -48,3 +49,3 @@ import type { GenericSpecialInput } from "./input.js"; | ||
type BoardInstantiateFunction<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> = (values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>) => BoardInstance<IPORTS, OPORTS>; | ||
declare class BoardInstance<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> { | ||
declare class BoardInstance<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> implements SerializableBoard { | ||
#private; | ||
@@ -51,0 +52,0 @@ readonly inputs: IPORTS; |
@@ -7,6 +7,3 @@ /** | ||
import type { GraphDescriptor } from "@google-labs/breadboard"; | ||
import { OutputPortGetter } from "../common/port.js"; | ||
import { type BreadboardType, type JsonSerializable } from "../type-system/type.js"; | ||
import type { GenericSpecialInput } from "./input.js"; | ||
import { type Placeholder } from "./placeholder.js"; | ||
import type { SerializableBoard } from "../common/serializable.js"; | ||
/** | ||
@@ -17,24 +14,1 @@ * Serialize a Breadboard board to Breadboard Graph Language (BGL) so that it | ||
export declare function serialize(board: SerializableBoard): GraphDescriptor; | ||
export interface SerializableBoard { | ||
inputs: Record<string, SerializableInputPort | GenericSpecialInput>; | ||
outputs: Record<string, SerializableOutputPortReference>; | ||
} | ||
interface SerializableNode { | ||
type: string; | ||
inputs: Record<string, SerializableInputPort>; | ||
} | ||
interface SerializableInputPort { | ||
name: string; | ||
type: BreadboardType; | ||
node: SerializableNode; | ||
value?: JsonSerializable | SerializableOutputPortReference | GenericSpecialInput | Placeholder<JsonSerializable>; | ||
} | ||
interface SerializableOutputPort { | ||
name: string; | ||
type: BreadboardType; | ||
node: SerializableNode; | ||
} | ||
interface SerializableOutputPortReference { | ||
[OutputPortGetter]: SerializableOutputPort; | ||
} | ||
export {}; |
@@ -7,3 +7,3 @@ /** | ||
import { OutputPortGetter } from "../common/port.js"; | ||
import { toJSONSchema, } from "../type-system/type.js"; | ||
import { toJSONSchema } from "../type-system/type.js"; | ||
import { isPlaceholder } from "./placeholder.js"; | ||
@@ -10,0 +10,0 @@ /** |
@@ -9,3 +9,3 @@ /** | ||
import type { BreadboardType, ConvertBreadboardType, JsonSerializable } from "../type-system/type.js"; | ||
import type { GenericBreadboardNodeInstance } from "./instance.js"; | ||
import type { SerializableInputPort, SerializableNode, SerializableOutputPort } from "./serializable.js"; | ||
export type PortConfig = StaticPortConfig | DynamicPortConfig; | ||
@@ -68,9 +68,9 @@ /** | ||
*/ | ||
export declare class InputPort<T extends JsonSerializable> { | ||
export declare class InputPort<T extends JsonSerializable> implements SerializableInputPort { | ||
#private; | ||
readonly type: BreadboardType; | ||
readonly name: string; | ||
readonly node: GenericBreadboardNodeInstance; | ||
readonly node: SerializableNode; | ||
readonly value?: ValueOrOutputPort<T>; | ||
constructor(type: BreadboardType, name: string, node: GenericBreadboardNodeInstance, value: ValueOrOutputPort<T>); | ||
constructor(type: BreadboardType, name: string, node: SerializableNode, value: ValueOrOutputPort<T>); | ||
} | ||
@@ -80,3 +80,3 @@ /** | ||
*/ | ||
export declare class OutputPort<T extends JsonSerializable> implements OutputPortReference<T> { | ||
export declare class OutputPort<T extends JsonSerializable> implements OutputPortReference<T>, SerializableOutputPort { | ||
#private; | ||
@@ -86,4 +86,4 @@ readonly [OutputPortGetter]: this; | ||
readonly name: string; | ||
readonly node: GenericBreadboardNodeInstance; | ||
constructor(type: BreadboardType, name: string, node: GenericBreadboardNodeInstance); | ||
readonly node: SerializableNode; | ||
constructor(type: BreadboardType, name: string, node: SerializableNode); | ||
} | ||
@@ -90,0 +90,0 @@ export interface OutputPortReference<T extends JsonSerializable> { |
@@ -30,2 +30,12 @@ /** | ||
type PermuteUnion<U, T = U> = [U] extends [never] ? [] : T extends unknown ? [T, ...PermuteUnion<Exclude<U, T>>] : never; | ||
/** | ||
* A hack that encourages TypeScript to expand a type when choosing how to | ||
* display it. Useful for utility types that we don't want to expose directly to | ||
* users. | ||
* | ||
* https://github.com/microsoft/TypeScript/issues/47980#issuecomment-1049304607 | ||
*/ | ||
export type Expand<T> = T extends unknown ? { | ||
[K in keyof T]: T[K]; | ||
} : never; | ||
export {}; |
@@ -6,6 +6,6 @@ /** | ||
*/ | ||
import type { PortConfigMap } from "../common/port.js"; | ||
import type { CountUnion } from "../common/type-util.js"; | ||
import { type MonomorphicDefinition, type MonomorphicInvokeFunction } from "./definition-monomorphic.js"; | ||
import { type PolymorphicDefinition, type PolymorphicDescribeFunction, type PolymorphicInvokeFunction } from "./definition-polymorphic.js"; | ||
import type { CountUnion, Expand } from "../common/type-util.js"; | ||
import type { ConvertBreadboardType, JsonSerializable } from "../type-system/type.js"; | ||
import type { DynamicInputPortConfig, DynamicOutputPortConfig, InputPortConfig, OutputPortConfig, PortConfig, StaticInputPortConfig, StaticOutputPortConfig } from "./config.js"; | ||
import { type Definition } from "./definition.js"; | ||
/** | ||
@@ -36,3 +36,3 @@ * Define a new Breadboard node type. | ||
* description: "The reversed string", | ||
* // (Optional) Allow the node itself to act as a shortcut for | ||
* // (Optional) Allow the node itself to act`a shortcut for | ||
* // this output port when wiring up this node in a board. | ||
@@ -105,22 +105,84 @@ * primary: true | ||
*/ | ||
export declare function defineNodeType<INPUTS extends PortConfigMap, OUTPUTS extends PortConfigMap>(params: { | ||
export declare function defineNodeType<I extends Record<string, InputPortConfig>, O extends Record<string, OutputPortConfig>, F extends LooseInvokeFn<I>, D extends LooseDescribeFn>(params: { | ||
name: string; | ||
inputs: INPUTS; | ||
outputs: ForbidMultiplePrimaries<OUTPUTS>; | ||
invoke: IsPolymorphic<INPUTS> extends true ? PolymorphicInvokeFunction<OmitDynamicPortConfig<INPUTS>, ExtractDynamicPortConfig<INPUTS>, OUTPUTS> : MonomorphicInvokeFunction<INPUTS, OUTPUTS>; | ||
describe?: IsPolymorphic<INPUTS> extends true ? PolymorphicDescribeFunction<OmitDynamicPortConfig<INPUTS>, ExtractDynamicPortConfig<INPUTS>> : never; | ||
}): NodeDefinition<INPUTS, OUTPUTS>; | ||
type ExtractDynamicPortConfig<SHAPE extends PortConfigMap> = SHAPE["*"]; | ||
type OmitDynamicPortConfig<SHAPE extends PortConfigMap> = Omit<SHAPE, "*">; | ||
type NodeDefinition<ISHAPE extends PortConfigMap, OSHAPE extends PortConfigMap> = IsPolymorphic<ISHAPE> extends true ? PolymorphicDefinition<OmitDynamicPortConfig<ISHAPE>, ExtractDynamicPortConfig<ISHAPE>, OSHAPE> : MonomorphicDefinition<ISHAPE, OSHAPE>; | ||
type IsPolymorphic<ISHAPE extends PortConfigMap> = ISHAPE["*"] extends object ? true : false; | ||
type ForbidMultiplePrimaries<M extends PortConfigMap> = HasMultiplePrimaries<M> extends true ? { | ||
[K in keyof M]: Omit<M[K], "primary"> & { | ||
primary: false; | ||
inputs: I; | ||
outputs: O; | ||
invoke: F; | ||
describe?: D; | ||
} & { | ||
inputs: StrictInputs<I>; | ||
outputs: StrictOutputs<O>; | ||
invoke: StrictInvokeFn<I, O, F>; | ||
} & StrictDescribeFn<I, O>): Definition<Expand<GetStaticTypes<I>>, Expand<GetStaticTypes<O>>, GetDynamicTypes<I>, GetDynamicTypes<O>, GetReflective<O>, GetPrimary<I>, GetPrimary<O>>; | ||
type StrictInputs<I extends Record<string, InputPortConfig>> = { | ||
[K in keyof I]: K extends "*" ? StrictMatch<I[K], DynamicInputPortConfig> : StrictMatch<I[K], StaticInputPortConfig>; | ||
} & ForbidMultiplePrimaries<I>; | ||
type StrictOutputs<O extends Record<string, OutputPortConfig>> = { | ||
[K in keyof O]: K extends "*" ? StrictMatch<O[K], DynamicOutputPortConfig> : StrictMatch<O[K], StaticOutputPortConfig>; | ||
} & ForbidMultiplePrimaries<O>; | ||
/** | ||
* Check that ACTUAL is assignable to EXPECTED and that there are no excess | ||
* properties. | ||
*/ | ||
type StrictMatch<ACTUAL, EXPECTED> = { | ||
[K in keyof ACTUAL]: K extends keyof EXPECTED ? EXPECTED[K] : never; | ||
}; | ||
type ForbidMultiplePrimaries<C extends Record<string, PortConfig>> = CountUnion<PrimaryPortNames<C>> extends 0 | 1 ? C : { | ||
[K in keyof C]: Omit<C[K], "primary"> & { | ||
primary: never; | ||
}; | ||
} : M; | ||
type HasMultiplePrimaries<M extends PortConfigMap> = CountUnion<PrimaryPortNames<M>> extends 0 | 1 ? false : true; | ||
type PrimaryPortNames<M extends PortConfigMap> = { | ||
[K in keyof M]: M[K]["primary"] extends true ? K : never; | ||
}[keyof M]; | ||
}; | ||
type PrimaryPortNames<C extends Record<string, PortConfig>> = { | ||
[K in keyof C]: C[K] extends StaticInputPortConfig | StaticOutputPortConfig ? C[K]["primary"] extends true ? K : never : never; | ||
}[keyof C]; | ||
type LooseInvokeFn<I extends Record<string, InputPortConfig>> = Expand<(staticParams: Expand<StaticInvokeParams<I>>, dynamicParams: Expand<DynamicInvokeParams<I>>) => { | ||
[K: string]: JsonSerializable; | ||
} | Promise<{ | ||
[K: string]: JsonSerializable; | ||
}>>; | ||
type StrictInvokeFn<I extends Record<string, InputPortConfig>, O extends Record<string, OutputPortConfig>, F extends LooseInvokeFn<I>> = (staticInputs: Expand<StaticInvokeParams<I>>, dynamicInputs: Expand<DynamicInvokeParams<I>>) => StrictInvokeFnReturn<I, O, F> | Promise<StrictInvokeFnReturn<I, O, F>>; | ||
type StrictInvokeFnReturn<I extends Record<string, InputPortConfig>, O extends Record<string, OutputPortConfig>, F extends LooseInvokeFn<I>> = { | ||
[K in keyof Omit<O, "*">]: Convert<O[K]>; | ||
} & { | ||
[K in keyof ReturnType<F extends (...args: unknown[]) => unknown ? F : never>]: K extends keyof O ? Convert<O[K]> : O["*"] extends DynamicOutputPortConfig ? Convert<O["*"]> : never; | ||
}; | ||
type StaticInvokeParams<I extends Record<string, InputPortConfig>> = { | ||
[K in keyof Omit<I, "*">]: Convert<I[K]>; | ||
}; | ||
type DynamicInvokeParams<I extends Record<string, InputPortConfig>> = I["*"] extends DynamicInputPortConfig ? { | ||
[K: string]: Convert<I["*"]>; | ||
} : {}; | ||
type GetStaticTypes<C extends Record<string, PortConfig>> = { | ||
[K in Exclude<keyof C, "*">]: K extends "*" ? never : Convert<C[K]>; | ||
}; | ||
type GetDynamicTypes<C extends Record<string, PortConfig>> = C["*"] extends PortConfig ? Convert<C["*"]> : undefined; | ||
type GetPrimary<C extends Record<string, PortConfig>> = { | ||
[K in keyof Omit<C, "*">]: C[K] extends StaticInputPortConfig | StaticOutputPortConfig ? C[K]["primary"] extends true ? K : undefined : undefined; | ||
}[keyof Omit<C, "*">]; | ||
type GetReflective<O extends Record<string, OutputPortConfig>> = O["*"] extends DynamicOutputPortConfig ? O["*"]["reflective"] extends true ? true : false : false; | ||
type Convert<C extends PortConfig> = ConvertBreadboardType<C["type"]>; | ||
type LooseDescribeFn = Function; | ||
type StrictDescribeFn<I extends Record<string, InputPortConfig>, O extends Record<string, OutputPortConfig>> = I["*"] extends DynamicInputPortConfig ? O["*"] extends DynamicOutputPortConfig ? O["*"]["reflective"] extends true ? { | ||
describe?: (staticInputs: Expand<StaticInvokeParams<I>>, dynamicInputs: Expand<DynamicInvokeParams<I>>) => { | ||
inputs: string[]; | ||
outputs?: never; | ||
}; | ||
} : { | ||
describe: (staticInputs: Expand<StaticInvokeParams<I>>, dynamicInputs: Expand<DynamicInvokeParams<I>>) => { | ||
inputs?: string[]; | ||
outputs: string[]; | ||
}; | ||
} : { | ||
describe?: (staticInputs: Expand<StaticInvokeParams<I>>, dynamicInputs: Expand<DynamicInvokeParams<I>>) => { | ||
inputs: string[]; | ||
outputs?: never; | ||
}; | ||
} : O["*"] extends DynamicOutputPortConfig ? { | ||
describe: (staticInputs: Expand<StaticInvokeParams<I>>, dynamicInputs: Expand<DynamicInvokeParams<I>>) => { | ||
inputs?: never; | ||
outputs: string[]; | ||
}; | ||
} : { | ||
describe?: never; | ||
}; | ||
export {}; |
@@ -0,1 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/ban-types */ | ||
/** | ||
@@ -6,4 +7,3 @@ * @license | ||
*/ | ||
import { defineMonomorphicNodeType, } from "./definition-monomorphic.js"; | ||
import { definePolymorphicNodeType, } from "./definition-polymorphic.js"; | ||
import { DefinitionImpl } from "./definition.js"; | ||
/** | ||
@@ -34,3 +34,3 @@ * Define a new Breadboard node type. | ||
* description: "The reversed string", | ||
* // (Optional) Allow the node itself to act as a shortcut for | ||
* // (Optional) Allow the node itself to act`a shortcut for | ||
* // this output port when wiring up this node in a board. | ||
@@ -104,28 +104,30 @@ * primary: true | ||
export function defineNodeType(params) { | ||
const { name, inputs, outputs, invoke, describe } = params; | ||
validateOutputs(outputs); | ||
const def = isPolymorphic(inputs, invoke) | ||
? definePolymorphicNodeType(name ?? "TODO_UNNAMED_POLY", omitDynamicPort(inputs), | ||
// TODO(aomarks) Remove ! | ||
inputs["*"], outputs, | ||
// TODO(aomarks) Remove cast | ||
invoke, describe) | ||
: defineMonomorphicNodeType(name ?? "TODO_UNNAMED_MONO", inputs, outputs, invoke); | ||
return def; | ||
} | ||
function validateOutputs(outputs) { | ||
const primaryPortNames = Object.entries(outputs) | ||
.filter(([, config]) => config.primary === true) | ||
.map(([key]) => key); | ||
if (primaryPortNames.length > 1) { | ||
throw new Error("Node definition has more than one primary output port: " + | ||
primaryPortNames.join(", ")); | ||
if (!params.name) { | ||
throw new Error("params.name is required"); | ||
} | ||
if (!params.inputs) { | ||
throw new Error("params.inputs is required"); | ||
} | ||
if (!params.outputs) { | ||
throw new Error("params.outputs is required"); | ||
} | ||
if (!params.invoke) { | ||
throw new Error("params.invoke is required"); | ||
} | ||
const impl = new DefinitionImpl(params.name, omitDynamic(params.inputs), omitDynamic(params.outputs), params.inputs["*"], params.outputs["*"], primary(params.inputs), primary(params.outputs), params.invoke, params.describe); | ||
return Object.assign(impl.instantiate.bind(impl), { | ||
invoke: impl.invoke.bind(impl), | ||
describe: impl.describe.bind(impl), | ||
}); | ||
} | ||
function isPolymorphic(inputs, invoke) { | ||
return inputs["*"] !== undefined; | ||
function omitDynamic(configs) { | ||
return Object.fromEntries(Object.entries(configs).filter(([name]) => name !== "*")); | ||
} | ||
function omitDynamicPort(shape) { | ||
return Object.fromEntries(Object.entries(shape).filter(([name]) => name !== "*")); | ||
function primary(configs) { | ||
const primaries = Object.entries(configs).filter(([, config]) => "primary" in config && config.primary); | ||
if (primaries.length > 1) { | ||
throw new Error("Too many primaries"); | ||
} | ||
return primaries[0]?.[0]; | ||
} | ||
//# sourceMappingURL=define.js.map |
@@ -8,2 +8,6 @@ /** | ||
import type { PortConfigMap } from "../common/port.js"; | ||
export declare function portConfigMapToJSONSchema(config: PortConfigMap): JSONSchema4; | ||
export declare function portConfigMapToJSONSchema(config: PortConfigMap): JSONSchema4 & { | ||
properties: { | ||
[k: string]: JSONSchema4; | ||
}; | ||
}; |
@@ -10,3 +10,5 @@ /** | ||
type: "object", | ||
properties: Object.fromEntries([...Object.entries(config)].map(([name, { description, type }]) => { | ||
properties: Object.fromEntries(Object.entries(config) | ||
.sort(([nameA], [nameB]) => nameA.localeCompare(nameB)) | ||
.map(([name, { description, type }]) => { | ||
const schema = toJSONSchema(type); | ||
@@ -19,5 +21,5 @@ schema.title = name; | ||
})), | ||
required: [...Object.keys(config)], | ||
required: Object.keys(config).sort(), | ||
}; | ||
} | ||
//# sourceMappingURL=json-schema.js.map |
@@ -7,5 +7,5 @@ /** | ||
import type { NewNodeFactory } from "@google-labs/breadboard"; | ||
import type { ConvertBreadboardType } from "../type-system/type.js"; | ||
import type { MonomorphicDefinition } from "./definition-monomorphic.js"; | ||
import type { PolymorphicDefinition } from "./definition-polymorphic.js"; | ||
import type { Definition } from "./definition.js"; | ||
import type { JsonSerializable } from "../type-system/type.js"; | ||
import type { Expand } from "../common/type-util.js"; | ||
/** | ||
@@ -16,11 +16,6 @@ * `NodeFactoryFromDefinition` takes a {@link NodeDefinition} type (as returned | ||
*/ | ||
export type NodeFactoryFromDefinition<DEF extends // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
MonomorphicDefinition<any, any> | PolymorphicDefinition<any, any, any>> = DEF extends MonomorphicDefinition<infer ISHAPE, infer OSHAPE> ? NewNodeFactory<{ | ||
[PORT in keyof ISHAPE]: ConvertBreadboardType<ISHAPE[PORT]["type"]>; | ||
}, { | ||
[PORT in keyof OSHAPE]: ConvertBreadboardType<OSHAPE[PORT]["type"]>; | ||
}> : DEF extends PolymorphicDefinition<infer ISHAPE, any, infer OSHAPE> ? NewNodeFactory<{ | ||
[PORT in keyof Omit<ISHAPE, "*">]: ConvertBreadboardType<ISHAPE[PORT]["type"]>; | ||
} & Record<string, unknown>, { | ||
[PORT in keyof Omit<OSHAPE, "*">]: ConvertBreadboardType<OSHAPE[PORT]["type"]>; | ||
} & Record<string, unknown>> : never; | ||
export type NodeFactoryFromDefinition<D extends Definition<any, any, any, any, any, any, any>> = D extends Definition<infer SI, infer SO, infer DI, infer DO, any, any, any> ? NewNodeFactory<Expand<SI & (DI extends JsonSerializable ? { | ||
[K: string]: DI; | ||
} : object)>, Expand<SO & (DO extends JsonSerializable ? { | ||
[K: string]: DO; | ||
} : object)>> : never; |
@@ -15,3 +15,3 @@ /** | ||
*/ | ||
export type BasicBreadboardType = "string" | "number" | "boolean" | "unknown"; | ||
export type BasicBreadboardType = "string" | "number" | "boolean" | "null" | "unknown"; | ||
/** | ||
@@ -34,3 +34,3 @@ * All Breadboard values must be JSON serializable, and this is the set of | ||
*/ | ||
export type ConvertBreadboardType<BT extends BreadboardType> = BT extends "string" ? string : BT extends "number" ? number : BT extends "boolean" ? boolean : BT extends "unknown" ? JsonSerializable : BT extends AdvancedBreadboardType<infer TT> ? TT : never; | ||
export type ConvertBreadboardType<BT extends BreadboardType> = BT extends "string" ? string : BT extends "number" ? number : BT extends "boolean" ? boolean : BT extends "null" ? null : BT extends "unknown" ? JsonSerializable : BT extends AdvancedBreadboardType<infer TT> ? TT : never; | ||
/** | ||
@@ -37,0 +37,0 @@ * Convert a {@link BreadboardType} to JSON Schema. |
@@ -16,3 +16,4 @@ /** | ||
case "number": | ||
case "boolean": { | ||
case "boolean": | ||
case "null": { | ||
return { type }; | ||
@@ -19,0 +20,0 @@ } |
{ | ||
"name": "@breadboard-ai/build", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "JavaScript library for building boards and defining new node types for the Breadboard AI prototyping library", | ||
@@ -107,11 +107,11 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@google-labs/breadboard": "^0.16.0", | ||
"@google-labs/breadboard": "^0.17.0", | ||
"@types/json-schema": "^7.0.15" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^20.11.30", | ||
"@types/node": "^20.12.7", | ||
"eslint": "^8.57.0", | ||
"typescript": "^5.3.3", | ||
"typescript": "^5.4.5", | ||
"wireit": "^0.14.4" | ||
} | ||
} |
146
README.md
@@ -56,5 +56,5 @@ # @breadboard-ai/build | ||
- `description`: (Recommended) A brief description of the port, which will be displayed in the | ||
Breadboard visual editor and in other places where introspection/debugging is | ||
performed. | ||
- `description`: (Recommended) A brief description of the port, which will be | ||
displayed in the Breadboard visual editor and in other places where | ||
introspection/debugging is performed. | ||
@@ -91,3 +91,3 @@ - `primary`: (Optional) Enables a syntactic sugar feature for an output port to | ||
```ts | ||
import { defineNodeType, anyOf } from "@breadboard-ai/build"; | ||
import { defineNodeType } from "@breadboard-ai/build"; | ||
@@ -102,3 +102,3 @@ export const templater = defineNodeType({ | ||
"*": { | ||
type: anyOf("string", "number"), | ||
type: "string", | ||
description: "Values to fill into template's {{placeholders}}.", | ||
@@ -113,20 +113,8 @@ }, | ||
}, | ||
describe: ({ template }) => { | ||
return { | ||
inputs: Object.fromEntries( | ||
extractPlaceholders(template ?? "").map((name) => [ | ||
name, | ||
{ | ||
type: anyOf("string", "number"), | ||
description: `A value for the ${name} placeholder`, | ||
}, | ||
]) | ||
), | ||
}; | ||
}, | ||
invoke: ({ template }, placeholders) => { | ||
return { | ||
result: substituteTemplatePlaceholders(template, placeholders), | ||
}; | ||
}, | ||
describe: ({ template }) => ({ | ||
inputs: extractPlaceholders(template ?? ""), | ||
}), | ||
invoke: ({ template }, placeholders) => ({ | ||
result: substituteTemplatePlaceholders(template, placeholders), | ||
}), | ||
}); | ||
@@ -140,25 +128,82 @@ ``` | ||
### polymorphic `invoke` | ||
The `invoke` function for polymorphic nodes is very similar to [invoke for | ||
monomorphic nodes](#invoke), except that an additional second parameter is | ||
passed to the function which contains the input values for the dynamic values. | ||
### `reflective` | ||
Setting `reflective: true` on the `*` output configuration tells Breadboard that | ||
all dynamically created input ports will have a corresponding output port. | ||
### `describe` | ||
The `describe` function determines which dynamic input and output ports should | ||
be opened for a polymorphic node at runtime. | ||
The `describe` function allows you to tell Breadboard which input and output | ||
ports are valid and open at runtime based on some particular set of input | ||
values. | ||
This function should return an object with either or both of `inputs` and | ||
`outputs`, containing all _additional_ dynamic ports that should be opened, | ||
given some set of input values. | ||
A `describe` function will be passed a set of values (in the same way as | ||
`invoke`), and should return an object containing either or both of `inputs` and | ||
`outputs`, each an array of strings, the names for the input and/or output ports | ||
that should be dynamically opened. | ||
Note that the fixed inputs and outputs (e.g. `template` and `result` in the | ||
example above) need _not_ be returned by the `describe` function, since those | ||
are automatically generated from the static port configuration. | ||
For example, in `templater` above, the `describe` function parses the static | ||
`template` input and opens a port for each of the template's placeholders. | ||
Also note that monomorphic node definitions need not implement a `describe` | ||
function _at all_, since its input and output ports are completely determined | ||
from the static configuration. | ||
In most situations a `describe` function is not required, because Breadboard can | ||
use the port configuration and instantiation values to determine the ports | ||
automatically. See the following table to check whether you should provide a | ||
`describe` function, and if so whether it should return `inputs`, `outputs`, or | ||
both. (Note that _Dynamic_ means there is a special `*` port configuration, and | ||
_Static_ means there is not.) | ||
### polymorphic `invoke` | ||
| Inputs | Outputs | Describe Inputs | Describe Outputs | | ||
| ------- | ---------- | --------------- | ---------------- | | ||
| Static | Static | N/A | N/A | | ||
| Dynamic | Static | Optional | N/A | | ||
| Static | Dynamic | N/A | **Required** | | ||
| Dynamic | Dynamic | Optional | **Required** | | ||
| Dynamic | Reflective | Optional | N/A | | ||
The `invoke` function for polymorphic nodes is very similar to [invoke for | ||
monomorphic nodes](#invoke), except that an additional second parameter is | ||
passed to the function which contains the input values for the dynamic values. | ||
### `assertOutput` | ||
When a node has dynamic outputs, but is not `reflective`, it is not possible at | ||
compile time for Breadboard to know what the valid output ports of a node are. | ||
In this case, use the `assertOutput` method to get an output port with a given | ||
name. Note that there is no guarantee this port will exist at runtime, so a | ||
runtime error could occur. | ||
(Note that the following example is highly contrived. It is better to find a | ||
way to use fully static or reflective nodes whenever possible to avoid the use | ||
of `assertOutput`). | ||
```ts | ||
import { defineNodeType, array } from "@breadboard-ai/build"; | ||
const weirdStringLength = defineNodeType({ | ||
name: "weirdStringLength", | ||
inputs: { | ||
strings: { type: array("string") }, | ||
}, | ||
outputs: { | ||
"*": { type: "number" }, | ||
}, | ||
describe: ({ strings }) => ({ | ||
outputs: strings, | ||
}), | ||
invoke: ({ strings }) => | ||
Object.fromEntries(strings.map((name) => [name, name.length])), | ||
}); | ||
const lengths = weirdStringLength({ strings: ["foo", "bar"] }); | ||
// All 3 of these variables will have type OutputPort<number> and can be wired | ||
// up to other nodes and boards as normal, but only `foo` and `bar` will | ||
// *actually* be valid at runtime. | ||
const foo = lengths.assertOutput("foo"); | ||
const bar = lengths.assertOutput("bar"); | ||
const baz = lengths.assertOutput("baz"); // Oops! | ||
``` | ||
## Adding nodes to Kits | ||
@@ -305,11 +350,15 @@ | ||
- `"boolean"` | ||
- `"null"` | ||
### Utility types | ||
- `array(<type>)`: A function which generates a JSON Schema `array` and its | ||
corresponding TypeScript `Array<...>` type. | ||
- `object({ prop1: <type1>, prop2: <type2>, ... })`: A function which generates a | ||
JSON Schema `object` and its corresponding TypeScript `{...}` type. | ||
- `anyOf(<type1>, <type2>, ...)`: A function which generates a JSON Schema | ||
`anyOf` and its corresponding TypeScript union (`type1 | type2`). | ||
- `object({ prop1: <type1>, prop2: <type2> })`: A function which generates a | ||
JSON Schema `object` and its corresponding TypeScript `{...}` type. | ||
### Unsafe type escape hatch | ||
@@ -337,19 +386,10 @@ | ||
1. Polymorphic nodes with dynamic _outputs_ are not yet supported. | ||
2. The `context` object is not yet passed to `invoke`, so certain low-level | ||
1. The `context` object is not yet passed to `invoke`, so certain low-level | ||
operations are not yet possible. | ||
3. `describe` is only passed values for fixed ports, not dynamic ones. | ||
4. There is not currently a type check for excess properties on the return type | ||
of monomorphic invoke. That is, while TypeScript will enforce that all | ||
configured output ports have a value, it will not yet complain if an output | ||
is returned that does not match a configured output port. | ||
5. There is no way to specify a description for a board's output (probably an | ||
2. There is no way to specify a description for a board's output (probably an | ||
`output` function, similar to `input`, is the solution there). | ||
6. You cannot yet embed boards into other boards (this will work by | ||
3. You cannot yet embed boards into other boards (this will work by | ||
instantiating a board object just like a regular node, but during | ||
serialization an `invoke` node will be created in its place.) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
182260
7.37%63
5%1852
9.33%389
11.46%+ Added
- Removed