From 808f22afecfb503ccf8b6df9dc97ea4f76c11284 Mon Sep 17 00:00:00 2001 From: satoshi toyama Date: Tue, 17 Dec 2024 11:44:23 +0900 Subject: [PATCH 1/2] fix(graph): Handle ghost connections in graph flow derivation Add protection against invalid node references in connections and update graph version to 20241217 - Skip connections that reference non-existent source/target nodes - Add test case to verify ghost connection handling - Update graph version to 20241217 with migration path - Update initialization and version checking logic Fixes potential errors when processing broken graph connections --- .../p/[agentId]/canary/lib/graph.test.ts | 26 ++++++++++++++++--- .../p/[agentId]/canary/lib/graph.ts | 19 +++++++++++++- .../p/[agentId]/canary/lib/utils.ts | 2 +- app/(playground)/p/[agentId]/canary/types.ts | 6 +++-- 4 files changed, 46 insertions(+), 7 deletions(-) diff --git a/app/(playground)/p/[agentId]/canary/lib/graph.test.ts b/app/(playground)/p/[agentId]/canary/lib/graph.test.ts index 6407aa74..56f6b018 100644 --- a/app/(playground)/p/[agentId]/canary/lib/graph.test.ts +++ b/app/(playground)/p/[agentId]/canary/lib/graph.test.ts @@ -73,8 +73,15 @@ const graph: Graph = { name: "Goodday", position: { x: 135, y: 360 }, selected: false, - type: "variable", - content: { type: "text", text: "Today is very good day" }, + type: "action", + content: { + type: "textGeneration", + llm: "anthropic:claude-3-5-sonnet-latest", + temperature: 0.7, + topP: 1, + instruction: "Good day", + sources: [], + }, }, { id: "nd_guzyxfacpt5db2n9lkjify3z", @@ -104,7 +111,7 @@ const graph: Graph = { { id: "cnnc_x8dy20365eqk9h033a800a5a", sourceNodeId: "nd_h8h4uhp7kov9v7pj1yyofen8", - sourceNodeType: "variable", + sourceNodeType: "action", targetNodeId: "nd_ffz8hv1isj4w3r4s23a6klkz", targetNodeType: "action", targetNodeHandleId: "ndh_n3xuz7ao5dyfusfukagbi3l7", @@ -117,6 +124,15 @@ const graph: Graph = { targetNodeHandleId: "ndh_xjlzyp1yq7vd1ih43rxuo8l9", targetNodeType: "action", }, + // Ghost connection(targetNodeId is not in nodes) + { + id: "cnnc_ghost_connection", + sourceNodeId: "nd_ffz8hv1isj4w3r4s23a6klkz", + sourceNodeType: "action", + targetNodeId: "nd_fake_node", + targetNodeHandleId: "ndh_fake_node_handle", + targetNodeType: "action", + }, ], artifacts: [], version: "2024-12-09", @@ -133,6 +149,10 @@ describe("deriveFlows", () => { expect(flows[0].nodes.length).toBe(2); expect(flows[1].nodes.length).toBe(3); }); + test("ignore ghost connectors", () => { + console.log(flows[1].jobs[2].steps); + expect(flows[1].jobs[2].steps.length).toBe(1); + }); }); describe("isLatestVersion", () => { diff --git a/app/(playground)/p/[agentId]/canary/lib/graph.ts b/app/(playground)/p/[agentId]/canary/lib/graph.ts index d00fd842..edc9b966 100644 --- a/app/(playground)/p/[agentId]/canary/lib/graph.ts +++ b/app/(playground)/p/[agentId]/canary/lib/graph.ts @@ -5,6 +5,7 @@ import type { Flow, Graph, Job, + LatestGraphVersion, Node, NodeId, Step, @@ -15,8 +16,15 @@ export function deriveFlows(graph: Graph): Flow[] { const processedNodes = new Set(); const flows: Flow[] = []; const connectionMap = new Map>(); + const nodeIds = new Set(graph.nodes.map((node) => node.id)); for (const connection of graph.connections) { + if ( + !nodeIds.has(connection.sourceNodeId) || + !nodeIds.has(connection.targetNodeId) + ) { + continue; + } if (!connectionMap.has(connection.sourceNodeId)) { connectionMap.set(connection.sourceNodeId, new Set()); } @@ -268,7 +276,8 @@ export function deriveFlows(graph: Graph): Flow[] { } export function isLatestVersion(graph: Graph): boolean { - return graph.version === "20241213"; + const latestGraphVersion = "20241217" satisfies LatestGraphVersion; + return graph.version === latestGraphVersion; } export function migrateGraph(graph: Graph): Graph { @@ -320,5 +329,13 @@ export function migrateGraph(graph: Graph): Graph { }; } + if (newGraph.version === "20241213") { + newGraph = { + ...newGraph, + flows: deriveFlows(newGraph), + version: "20241217", + }; + } + return newGraph; } diff --git a/app/(playground)/p/[agentId]/canary/lib/utils.ts b/app/(playground)/p/[agentId]/canary/lib/utils.ts index dcbdcae2..0bcf1391 100644 --- a/app/(playground)/p/[agentId]/canary/lib/utils.ts +++ b/app/(playground)/p/[agentId]/canary/lib/utils.ts @@ -174,7 +174,7 @@ export function initGraph(): Graph { nodes: [], connections: [], artifacts: [], - version: "20241213" satisfies LatestGraphVersion, + version: "20241217" satisfies LatestGraphVersion, flows: [], executionIndexes: [], }; diff --git a/app/(playground)/p/[agentId]/canary/types.ts b/app/(playground)/p/[agentId]/canary/types.ts index 9a9d941c..209b6833 100644 --- a/app/(playground)/p/[agentId]/canary/types.ts +++ b/app/(playground)/p/[agentId]/canary/types.ts @@ -176,8 +176,10 @@ type GraphVersion = | "2024-12-10" | "2024-12-11" | "20241212" - | "20241213"; -export type LatestGraphVersion = "20241213"; + | "20241213" + | "20241217"; + +export type LatestGraphVersion = "20241217"; export interface Graph { id: GraphId; nodes: Node[]; From ed8de345e12c3abfdab329a98c8dc1cdc45e5ef8 Mon Sep 17 00:00:00 2001 From: satoshi toyama Date: Tue, 17 Dec 2024 12:00:30 +0900 Subject: [PATCH 2/2] pass tests --- app/(playground)/p/[agentId]/canary/lib/graph.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/(playground)/p/[agentId]/canary/lib/graph.test.ts b/app/(playground)/p/[agentId]/canary/lib/graph.test.ts index 56f6b018..ca097819 100644 --- a/app/(playground)/p/[agentId]/canary/lib/graph.test.ts +++ b/app/(playground)/p/[agentId]/canary/lib/graph.test.ts @@ -157,7 +157,7 @@ describe("deriveFlows", () => { describe("isLatestVersion", () => { test("latest version", () => { - expect(isLatestVersion({ version: "20241213" } as Graph)).toBe(true); + expect(isLatestVersion({ version: "20241217" } as Graph)).toBe(true); }); test("old version", () => { expect(isLatestVersion({} as Graph)).toBe(false); @@ -203,7 +203,7 @@ describe("migrateGraph", () => { ], artifacts: [], } as unknown as Graph); - expect(after.version).toBe("20241213"); + expect(after.version).toBe("20241217"); expect(after.nodes[0].content.type).toBe("files"); }); });