Skip to content

Commit

Permalink
Allow subgraphs to access modules
Browse files Browse the repository at this point in the history
- **Remove spurious logging and unused code.**
- **Update inspectable subgraphs on edit.**
- **Store parent on `InspectableGraph` to allow modules to run in
subgraphs.**
- **Try plumbing outerGraph.**
- **Change `GraphLoader.load` to return `GraphLoaderResult`.**
- **Introduce `GraphToRun` and make `runGraph` take it instead of
`GraphDescriptor`.**
- **Start passing `GraphLoaderResult` pretty much everywhere.**
- **Switch `GraphLoader` to return supergraph and subGraphId.**
- **Correctly resolve `outerGraph` in `NodeInvoker`.**
- **docs(changeset): Allow subgraphs to access modules**

Progress on #3777.
  • Loading branch information
dglazkov authored Nov 15, 2024
1 parent 6b5ead3 commit 18dace0
Show file tree
Hide file tree
Showing 46 changed files with 388 additions and 238 deletions.
11 changes: 11 additions & 0 deletions .changeset/fresh-crabs-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@google-labs/node-nursery-web": minor
"@google-labs/breadboard-cli": minor
"@breadboard-ai/visual-editor": minor
"@google-labs/breadboard": minor
"@breadboard-ai/build-code": minor
"@google-labs/core-kit": minor
"@breadboard-ai/build": minor
---

Allow subgraphs to access modules
3 changes: 1 addition & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions packages/board-server/src/api-view/api-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,11 +272,11 @@ export class ApiExplorer extends LitElement {
const loader = createLoader([]);
const base = new URL(window.location.href);
const graph = await loader.load(url, { base });
if (!graph) {
if (!graph.success) {
// TODO: Better error handling, maybe a toast?
throw new Error(`Unable to load graph: ${url}`);
}
const runner = graph;
const runner = graph.graph;
const { title, description } = runner;

if (title) {
Expand Down
2 changes: 1 addition & 1 deletion packages/breadboard-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
"dependencies": {
"@breadboard-ai/build": "0.10.5",
"@breadboard-ai/import": "0.1.12",
"@breadboard-ai/visual-editor": "^1.22.1",
"@breadboard-ai/visual-editor": "1.22.1",
"@google-labs/breadboard": "^0.29.0",
"@google-labs/core-kit": "^0.16.0",
"@google-labs/template-kit": "^0.3.14",
Expand Down
4 changes: 2 additions & 2 deletions packages/breadboard-cli/src/commands/lib/loaders/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { pathToFileURL } from "url";

export class JSONLoader extends Loader {
async load(filePath: string): Promise<GraphDescriptor | null> {
const graph = await createLoader().load(filePath, {
const loadResult = await createLoader().load(filePath, {
base: new URL(pathToFileURL(process.cwd()).toString()),
});
return graph;
return loadResult.success ? loadResult.graph : null;
}
}
11 changes: 7 additions & 4 deletions packages/breadboard-cli/src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@ async function runBoard(
? new VerboseLoggingProbe(async (data) => console.log(data))
: undefined;

for await (const stop of runGraph(board, {
kits,
probe,
})) {
for await (const stop of runGraph(
{ graph: board },
{
kits,
probe,
}
)) {
if (stop.type === "input") {
const nodeInputs = stop.inputArguments;
// we won't mutate the inputs.
Expand Down
61 changes: 34 additions & 27 deletions packages/breadboard/src/capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { baseURLFromContext } from "./loader/loader.js";
import { GraphLoaderResult } from "./loader/types.js";
import {
BreadboardCapability,
GraphDescriptor,
Expand Down Expand Up @@ -61,41 +62,45 @@ const isGraphDescriptor = (
export const graphDescriptorFromCapability = async (
capability: BreadboardCapability,
context?: NodeHandlerContext
) => {
): Promise<GraphLoaderResult> => {
if (isGraphDescriptorCapability(capability)) {
// If all we got is a GraphDescriptor, build a runnable board from it.
// TODO: Use JSON schema to validate rather than this hack.
return capability.board;
return { success: true, graph: capability.board };
} else if (isResolvedURLBoardCapability(capability)) {
if (!context?.loader) {
throw new Error(
`The "board" Capability is a URL, but no loader was supplied.`
);
return {
success: false,
error: `The "board" Capability is a URL, but no loader was supplied.`,
};
}
const graph = await context.loader.load(capability.url, context);
if (!graph) {
throw new Error(
`Unable to load "board" Capability with the URL of ${capability.url}.`
);
const loaderResult = await context.loader.load(capability.url, context);
if (!loaderResult.success) {
return {
success: false,
error: `Unable to load "board" Capability with the URL of ${capability.url}: ${loaderResult.error}`,
};
}
return graph;
return loaderResult;
} else if (isUnresolvedPathBoardCapability(capability)) {
if (!context?.loader) {
throw new Error(
`The "board" Capability is a URL, but no loader was supplied.`
);
}
const graph = await context.loader.load(capability.path, context);
if (!graph) {
throw new Error(
`Unable to load "board" Capability with the path of ${capability.path}.`
);
const loaderResult = await context.loader.load(capability.path, context);
if (!loaderResult.success) {
return {
success: false,
error: `Unable to load "board" Capability with the path of ${capability.path}: ${loaderResult.error}`,
};
}
return graph;
return loaderResult;
}
throw new Error(
`Unsupported type of "board" Capability. Perhaps the supplied board isn't actually a GraphDescriptor?`
);
return {
success: false,
error: `Unsupported type of "board" Capability. Perhaps the supplied board isn't actually a GraphDescriptor?`,
};
};

// TODO: Maybe this is just a GraphLoader API? Instead of taking a `string`,
Expand All @@ -104,19 +109,21 @@ export const graphDescriptorFromCapability = async (
export const getGraphDescriptor = async (
board: unknown,
context?: NodeHandlerContext
): Promise<GraphDescriptor | undefined> => {
if (!board) return undefined;
): Promise<GraphLoaderResult> => {
if (!board) return { success: false, error: "No board provided" };

if (typeof board === "string") {
const graph = await context?.loader?.load(board, context);
if (!graph) throw new Error(`Unable to load graph from "${board}"`);
return graph;
const loaderResult = await context?.loader?.load(board, context);
if (!loaderResult?.success || !loaderResult.graph) {
throw new Error(`Unable to load graph from "${board}"`);
}
return loaderResult;
} else if (isBreadboardCapability(board)) {
return graphDescriptorFromCapability(board, context);
} else if (isGraphDescriptor(board)) {
return board;
return { success: true, graph: board };
}
return undefined;
return { success: false, error: "Unable to get GraphDescriptor" };
};

const resolvePath = (
Expand Down
15 changes: 10 additions & 5 deletions packages/breadboard/src/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { inspect } from "./inspector/index.js";
import { SENTINEL_BASE_URL } from "./loader/loader.js";
import { resolveGraph, SENTINEL_BASE_URL } from "./loader/loader.js";
import { invokeGraph } from "./run/invoke-graph.js";
import type {
GraphDescriptor,
Expand Down Expand Up @@ -161,10 +161,15 @@ async function getGraphHandlerInternal(
if (!loader) {
throw new Error(`Cannot load graph for type "${type}" without a loader.`);
}
const graph = await loader.load(type, context);
if (!graph) {
throw new Error(`Cannot load graph for type "${type}"`);
const loadResult = await loader.load(type, context);
if (!loadResult.success) {
throw new Error(
`Cannot load graph for type "${type}": ${loadResult.error}`
);
}

const graph = resolveGraph(loadResult);

return {
invoke: async (inputs, context) => {
const base = context.board?.url && new URL(context.board?.url);
Expand All @@ -175,7 +180,7 @@ async function getGraphHandlerInternal(
}
: { ...context };

return await invokeGraph(graph, inputs, invocationContext);
return await invokeGraph(loadResult, inputs, invocationContext);
},
describe: async (inputs, _inputSchema, _outputSchema, context) => {
if (!context) {
Expand Down
25 changes: 12 additions & 13 deletions packages/breadboard/src/harness/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import { asyncGen, runGraph } from "../index.js";
import { createLoader } from "../loader/index.js";
import { LastNode } from "../remote/types.js";
import { timestamp } from "../timestamp.js";
import {
BreadboardRunResult,
ErrorObject,
GraphDescriptor,
Kit,
} from "../types.js";
import { BreadboardRunResult, ErrorObject, GraphToRun, Kit } from "../types.js";
import { Diagnostics } from "./diagnostics.js";
import { extractError } from "./error.js";
import { HarnessRunResult, RunConfig } from "./types.js";
Expand Down Expand Up @@ -106,19 +101,23 @@ const maybeSaveResult = (result: BreadboardRunResult, last?: LastNode) => {
return last;
};

const load = async (config: RunConfig): Promise<GraphDescriptor> => {
const load = async (config: RunConfig): Promise<GraphToRun> => {
const base = baseURL(config);
const loader = config.loader || createLoader();
const graph = await loader.load(config.url, { base });
if (!graph) {
throw new Error(`Unable to load graph from "${config.url}"`);
const loadResult = await loader.load(config.url, { base });
if (!loadResult.success) {
throw new Error(
`Unable to load graph from "${config.url}": ${loadResult.error}`
);
}
return graph;
return loadResult;
};

export async function* runLocally(config: RunConfig, kits: Kit[]) {
yield* asyncGen<HarnessRunResult>(async (next) => {
const runner = config.runner || (await load(config));
const graphToRun: GraphToRun = config.runner
? { graph: config.runner }
: await load(config);
const loader = config.loader || createLoader();
const store = config.store || createDefaultDataStore();
const { base, signal, inputs, state, start, stopAfter } = config;
Expand All @@ -133,7 +132,7 @@ export async function* runLocally(config: RunConfig, kits: Kit[]) {
})
: undefined;

for await (const data of runGraph(runner, {
for await (const data of runGraph(graphToRun, {
probe,
kits,
loader,
Expand Down
10 changes: 6 additions & 4 deletions packages/breadboard/src/harness/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,13 @@ export const serve = async (config: ServeConfig | Promise<ServeConfig>) => {
const url = await getBoardURL(config, factory);
const base = baseURL(config);
const loader = createLoader();
const graph = await loader.load(url, { base });
if (!graph) {
throw new Error(`Unable to load graph from "${config.url}"`);
const loadResult = await loader.load(url, { base });
if (!loadResult.success) {
throw new Error(
`Unable to load graph from "${config.url}": ${loadResult.error}`
);
}
return server.serve(graph, !!config.diagnostics, { kits });
return server.serve(loadResult.graph, !!config.diagnostics, { kits });
};

/**
Expand Down
29 changes: 18 additions & 11 deletions packages/breadboard/src/inspector/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
} from "./schemas.js";
import {
InspectableEdge,
InspectableGraph,
InspectableGraphOptions,
InspectableGraphWithStore,
InspectableKit,
Expand All @@ -62,7 +63,7 @@ export const inspectableGraph = (
graph: GraphDescriptor,
options?: InspectableGraphOptions
): InspectableGraphWithStore => {
return new Graph(graph, options);
return new Graph(graph, null, options);
};

const maybeURL = (url?: string): URL | undefined => {
Expand Down Expand Up @@ -90,12 +91,18 @@ class Graph implements InspectableGraphWithStore {
#options: InspectableGraphOptions;

#graph: GraphDescriptor;
#parent: InspectableGraph | null;
#cache: MutableGraph;
#graphs: InspectableSubgraphs | null = null;

#imperativeMain: ModuleIdentifier | undefined;

constructor(graph: GraphDescriptor, options?: InspectableGraphOptions) {
constructor(
graph: GraphDescriptor,
parent: InspectableGraph | null,
options?: InspectableGraphOptions
) {
this.#parent = parent;
if (isImperativeGraph(graph)) {
const { main } = graph;
this.#graph = toDeclarativeGraph(graph);
Expand Down Expand Up @@ -227,7 +234,7 @@ class Graph implements InspectableGraphWithStore {
}
const loader = this.#options.loader || createLoader();
const context: NodeDescriberContext = {
outerGraph: this.#graph,
outerGraph: this.#parent?.raw() || this.#graph,
loader,
kits,
sandbox: this.#options.sandbox,
Expand Down Expand Up @@ -435,16 +442,15 @@ class Graph implements InspectableGraphWithStore {
const base = this.#url;

// try loading the describer graph.
const describerGraph = await loader.load(customDescriber, {
const loadResult = await loader.load(customDescriber, {
base,
board: this.#graph,
outerGraph: this.#graph,
});
if (!describerGraph) {
console.warn(
`Could not load custom describer graph ${customDescriber}`
);
return { success: false };
if (!loadResult.success) {
const error = `Could not load custom describer graph ${customDescriber}: ${loadResult.error}`;
console.warn(error);
return loadResult;
}
const { inputSchema: $inputSchema, outputSchema: $outputSchema } =
await this.#describeWithStaticAnalysis();
Expand All @@ -454,7 +460,7 @@ class Graph implements InspectableGraphWithStore {
// delete $outputSchema.properties?.inputSchema;
// delete $outputSchema.properties?.outputSchema;
const result = (await invokeGraph(
describerGraph,
loadResult,
{ ...inputs, $inputSchema, $outputSchema },
{
base,
Expand Down Expand Up @@ -534,6 +540,7 @@ class Graph implements InspectableGraphWithStore {

this.#cache.describe.clear(visualOnly, affectedNodes);
this.#graph = graph;
this.#graphs = null;
}

resetGraph(graph: GraphDescriptor): void {
Expand All @@ -556,7 +563,7 @@ class Graph implements InspectableGraphWithStore {
if (!subgraphs) return {};
return Object.fromEntries(
Object.entries(subgraphs).map(([id, descriptor]) => {
return [id, new Graph(descriptor, this.#options)];
return [id, new Graph(descriptor, this, this.#options)];
})
);
}
Expand Down
Loading

0 comments on commit 18dace0

Please sign in to comment.