@langchain/langgraph 0.0.26 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,16 @@
1
1
  import { BaseChannel } from "./index.js";
2
- export type BinaryOperator<Value> = (a: Value, b: Value) => Value;
2
+ export type BinaryOperator<ValueType, UpdateType> = (a: ValueType, b: UpdateType) => ValueType;
3
3
  /**
4
4
  * Stores the result of applying a binary operator to the current value and each new value.
5
5
  */
6
- export declare class BinaryOperatorAggregate<Value> extends BaseChannel<Value, Value, Value> {
6
+ export declare class BinaryOperatorAggregate<ValueType, UpdateType = ValueType> extends BaseChannel<ValueType, UpdateType, ValueType> {
7
7
  lc_graph_name: string;
8
- value: Value | undefined;
9
- operator: BinaryOperator<Value>;
10
- initialValueFactory?: () => Value;
11
- constructor(operator: BinaryOperator<Value>, initialValueFactory?: () => Value);
12
- fromCheckpoint(checkpoint?: Value): this;
13
- update(values: Value[]): void;
14
- get(): Value;
15
- checkpoint(): Value;
8
+ value: ValueType | undefined;
9
+ operator: BinaryOperator<ValueType, UpdateType>;
10
+ initialValueFactory?: () => ValueType;
11
+ constructor(operator: BinaryOperator<ValueType, UpdateType>, initialValueFactory?: () => ValueType);
12
+ fromCheckpoint(checkpoint?: ValueType): this;
13
+ update(values: UpdateType[]): void;
14
+ get(): ValueType;
15
+ checkpoint(): ValueType;
16
16
  }
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TAG_HIDDEN = exports.INTERRUPT = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = void 0;
3
+ exports.TASKS = exports.TAG_HIDDEN = exports.INTERRUPT = exports.CONFIG_KEY_READ = exports.CONFIG_KEY_SEND = void 0;
4
4
  exports.CONFIG_KEY_SEND = "__pregel_send";
5
5
  exports.CONFIG_KEY_READ = "__pregel_read";
6
6
  exports.INTERRUPT = "__interrupt__";
7
7
  exports.TAG_HIDDEN = "langsmith:hidden";
8
+ exports.TASKS = "__pregel_tasks";
@@ -2,3 +2,4 @@ export declare const CONFIG_KEY_SEND = "__pregel_send";
2
2
  export declare const CONFIG_KEY_READ = "__pregel_read";
3
3
  export declare const INTERRUPT = "__interrupt__";
4
4
  export declare const TAG_HIDDEN = "langsmith:hidden";
5
+ export declare const TASKS = "__pregel_tasks";
package/dist/constants.js CHANGED
@@ -2,3 +2,4 @@ export const CONFIG_KEY_SEND = "__pregel_send";
2
2
  export const CONFIG_KEY_READ = "__pregel_read";
3
3
  export const INTERRUPT = "__interrupt__";
4
4
  export const TAG_HIDDEN = "langsmith:hidden";
5
+ export const TASKS = "__pregel_tasks";
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CompiledGraph = exports.Graph = exports.Branch = exports.END = exports.START = void 0;
4
4
  /* eslint-disable @typescript-eslint/no-use-before-define */
5
5
  const runnables_1 = require("@langchain/core/runnables");
6
+ const graph_1 = require("@langchain/core/runnables/graph");
7
+ const zod_1 = require("zod");
6
8
  const read_js_1 = require("../pregel/read.cjs");
7
9
  const index_js_1 = require("../pregel/index.cjs");
8
10
  const ephemeral_value_js_1 = require("../channels/ephemeral_value.cjs");
@@ -316,5 +318,85 @@ class CompiledGraph extends index_js_1.Pregel {
316
318
  }
317
319
  }
318
320
  }
321
+ /**
322
+ * Returns a drawable representation of the computation graph.
323
+ */
324
+ getGraph(config) {
325
+ const xray = config?.xray;
326
+ const graph = new graph_1.Graph();
327
+ const startNodes = {
328
+ [exports.START]: graph.addNode({
329
+ schema: zod_1.z.any(),
330
+ }, exports.START),
331
+ };
332
+ const endNodes = {
333
+ [exports.END]: graph.addNode({
334
+ schema: zod_1.z.any(),
335
+ }, exports.END),
336
+ };
337
+ for (const [key, node] of Object.entries(this.builder.nodes)) {
338
+ if (config?.xray) {
339
+ const subgraph = isCompiledGraph(node)
340
+ ? node.getGraph({
341
+ ...config,
342
+ xray: typeof xray === "number" && xray > 0 ? xray - 1 : xray,
343
+ })
344
+ : node.getGraph(config);
345
+ subgraph.trimFirstNode();
346
+ subgraph.trimLastNode();
347
+ if (Object.keys(subgraph.nodes).length > 1) {
348
+ const [newEndNode, newStartNode] = graph.extend(subgraph, key);
349
+ if (newEndNode !== undefined) {
350
+ endNodes[key] = newEndNode;
351
+ }
352
+ if (newStartNode !== undefined) {
353
+ startNodes[key] = newStartNode;
354
+ }
355
+ }
356
+ else {
357
+ const newNode = graph.addNode(node, key);
358
+ startNodes[key] = newNode;
359
+ endNodes[key] = newNode;
360
+ }
361
+ }
362
+ else {
363
+ const newNode = graph.addNode(node, key);
364
+ startNodes[key] = newNode;
365
+ endNodes[key] = newNode;
366
+ }
367
+ }
368
+ for (const [start, end] of this.builder.allEdges) {
369
+ graph.addEdge(startNodes[start], endNodes[end]);
370
+ }
371
+ for (const [start, branches] of Object.entries(this.builder.branches)) {
372
+ const defaultEnds = {
373
+ ...Object.fromEntries(Object.keys(this.builder.nodes)
374
+ .filter((k) => k !== start)
375
+ .map((k) => [k, k])),
376
+ [exports.END]: exports.END,
377
+ };
378
+ for (const branch of Object.values(branches)) {
379
+ let ends;
380
+ if (branch.ends !== undefined) {
381
+ ends = branch.ends;
382
+ }
383
+ else {
384
+ ends = defaultEnds;
385
+ }
386
+ for (const [label, end] of Object.entries(ends)) {
387
+ graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : undefined, true);
388
+ }
389
+ }
390
+ }
391
+ return graph;
392
+ }
319
393
  }
320
394
  exports.CompiledGraph = CompiledGraph;
395
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
396
+ function isCompiledGraph(x) {
397
+ return (
398
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
399
+ typeof x.attachNode === "function" &&
400
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
401
+ typeof x.attachEdge === "function");
402
+ }
@@ -1,4 +1,5 @@
1
1
  import { Runnable, RunnableConfig, RunnableLike } from "@langchain/core/runnables";
2
+ import { Graph as RunnableGraph } from "@langchain/core/runnables/graph";
2
3
  import { PregelNode } from "../pregel/read.js";
3
4
  import { Pregel, PregelInterface } from "../pregel/index.js";
4
5
  import { BaseCheckpointSaver } from "../checkpoint/base.js";
@@ -56,4 +57,10 @@ export declare class CompiledGraph<N extends string, RunInput = any, RunOutput =
56
57
  attachNode(key: N, node: Runnable<RunInput, RunOutput>): void;
57
58
  attachEdge(start: N | typeof START, end: N | typeof END): void;
58
59
  attachBranch(start: N | typeof START, name: string, branch: Branch<RunInput, N>): void;
60
+ /**
61
+ * Returns a drawable representation of the computation graph.
62
+ */
63
+ getGraph(config?: RunnableConfig & {
64
+ xray?: boolean | number;
65
+ }): RunnableGraph;
59
66
  }
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
2
2
  import { _coerceToRunnable, } from "@langchain/core/runnables";
3
+ import { Graph as RunnableGraph, } from "@langchain/core/runnables/graph";
4
+ import { z } from "zod";
3
5
  import { PregelNode } from "../pregel/read.js";
4
6
  import { Channel, Pregel } from "../pregel/index.js";
5
7
  import { EphemeralValue } from "../channels/ephemeral_value.js";
@@ -311,4 +313,84 @@ export class CompiledGraph extends Pregel {
311
313
  }
312
314
  }
313
315
  }
316
+ /**
317
+ * Returns a drawable representation of the computation graph.
318
+ */
319
+ getGraph(config) {
320
+ const xray = config?.xray;
321
+ const graph = new RunnableGraph();
322
+ const startNodes = {
323
+ [START]: graph.addNode({
324
+ schema: z.any(),
325
+ }, START),
326
+ };
327
+ const endNodes = {
328
+ [END]: graph.addNode({
329
+ schema: z.any(),
330
+ }, END),
331
+ };
332
+ for (const [key, node] of Object.entries(this.builder.nodes)) {
333
+ if (config?.xray) {
334
+ const subgraph = isCompiledGraph(node)
335
+ ? node.getGraph({
336
+ ...config,
337
+ xray: typeof xray === "number" && xray > 0 ? xray - 1 : xray,
338
+ })
339
+ : node.getGraph(config);
340
+ subgraph.trimFirstNode();
341
+ subgraph.trimLastNode();
342
+ if (Object.keys(subgraph.nodes).length > 1) {
343
+ const [newEndNode, newStartNode] = graph.extend(subgraph, key);
344
+ if (newEndNode !== undefined) {
345
+ endNodes[key] = newEndNode;
346
+ }
347
+ if (newStartNode !== undefined) {
348
+ startNodes[key] = newStartNode;
349
+ }
350
+ }
351
+ else {
352
+ const newNode = graph.addNode(node, key);
353
+ startNodes[key] = newNode;
354
+ endNodes[key] = newNode;
355
+ }
356
+ }
357
+ else {
358
+ const newNode = graph.addNode(node, key);
359
+ startNodes[key] = newNode;
360
+ endNodes[key] = newNode;
361
+ }
362
+ }
363
+ for (const [start, end] of this.builder.allEdges) {
364
+ graph.addEdge(startNodes[start], endNodes[end]);
365
+ }
366
+ for (const [start, branches] of Object.entries(this.builder.branches)) {
367
+ const defaultEnds = {
368
+ ...Object.fromEntries(Object.keys(this.builder.nodes)
369
+ .filter((k) => k !== start)
370
+ .map((k) => [k, k])),
371
+ [END]: END,
372
+ };
373
+ for (const branch of Object.values(branches)) {
374
+ let ends;
375
+ if (branch.ends !== undefined) {
376
+ ends = branch.ends;
377
+ }
378
+ else {
379
+ ends = defaultEnds;
380
+ }
381
+ for (const [label, end] of Object.entries(ends)) {
382
+ graph.addEdge(startNodes[start], endNodes[end], label !== end ? label : undefined, true);
383
+ }
384
+ }
385
+ }
386
+ return graph;
387
+ }
388
+ }
389
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
390
+ function isCompiledGraph(x) {
391
+ return (
392
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
393
+ typeof x.attachNode === "function" &&
394
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
395
+ typeof x.attachEdge === "function");
314
396
  }
@@ -4,18 +4,18 @@ import { BinaryOperator } from "../channels/binop.js";
4
4
  import { END, CompiledGraph, Graph, START, Branch } from "./graph.js";
5
5
  import { BaseCheckpointSaver } from "../checkpoint/base.js";
6
6
  import { All } from "../pregel/types.js";
7
- type SingleReducer<T> = {
8
- reducer: BinaryOperator<T>;
9
- default?: () => T;
7
+ type SingleReducer<ValueType, UpdateType = ValueType> = {
8
+ reducer: BinaryOperator<ValueType, UpdateType>;
9
+ default?: () => ValueType;
10
10
  } | {
11
11
  /**
12
12
  * @deprecated Use `reducer` instead
13
13
  */
14
- value: BinaryOperator<T>;
15
- default?: () => T;
14
+ value: BinaryOperator<ValueType, UpdateType>;
15
+ default?: () => ValueType;
16
16
  } | null;
17
17
  export type ChannelReducers<Channels extends object> = {
18
- [K in keyof Channels]: SingleReducer<Channels[K]>;
18
+ [K in keyof Channels]: SingleReducer<Channels[K], any>;
19
19
  };
20
20
  export interface StateGraphArgs<Channels extends object | unknown> {
21
21
  channels: Channels extends object ? Channels extends unknown[] ? ChannelReducers<{
@@ -24,7 +24,7 @@ export interface StateGraphArgs<Channels extends object | unknown> {
24
24
  __root__: Channels;
25
25
  }>;
26
26
  }
27
- export declare class StateGraph<State extends object | unknown, Update extends object | unknown = Partial<State>, N extends string = typeof START> extends Graph<N, State, Update> {
27
+ export declare class StateGraph<State extends object | unknown, Update extends object | unknown = Partial<Record<keyof State, any>>, N extends string = typeof START> extends Graph<N, State, Update> {
28
28
  channels: Record<string, BaseChannel>;
29
29
  waitingEdges: Set<[N[], N]>;
30
30
  constructor(fields: StateGraphArgs<State>);
@@ -16,5 +16,5 @@ export interface AgentExecutorState {
16
16
  export declare function createAgentExecutor({ agentRunnable, tools, }: {
17
17
  agentRunnable: Runnable;
18
18
  tools: Array<Tool> | ToolExecutor;
19
- }): import("../graph/state.js").CompiledStateGraph<AgentExecutorState, Partial<AgentExecutorState>, "__start__" | "agent" | "action">;
19
+ }): import("../graph/state.js").CompiledStateGraph<AgentExecutorState, Partial<Record<keyof AgentExecutorState, any>>, "__start__" | "agent" | "action">;
20
20
  export {};
@@ -45,6 +45,13 @@ class ToolExecutor extends runnables_1.RunnableBinding {
45
45
  return acc;
46
46
  }, {});
47
47
  }
48
+ /**
49
+ * Execute a tool invocation
50
+ *
51
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
52
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
53
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
54
+ */
48
55
  async _execute(toolInvocation, config) {
49
56
  if (!(toolInvocation.tool in this.toolMap)) {
50
57
  return this.invalidToolMsgTemplate
@@ -22,6 +22,13 @@ export declare class ToolExecutor extends RunnableBinding<ToolExecutorInputType,
22
22
  toolMap: Record<string, StructuredTool>;
23
23
  invalidToolMsgTemplate: string;
24
24
  constructor(fields: ToolExecutorArgs);
25
- _execute(toolInvocation: ToolInvocationInterface, config?: RunnableConfig): Promise<string>;
25
+ /**
26
+ * Execute a tool invocation
27
+ *
28
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
29
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
30
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
31
+ */
32
+ _execute(toolInvocation: ToolInvocationInterface, config?: RunnableConfig): Promise<ToolExecutorOutputType>;
26
33
  }
27
34
  export {};
@@ -42,6 +42,13 @@ export class ToolExecutor extends RunnableBinding {
42
42
  return acc;
43
43
  }, {});
44
44
  }
45
+ /**
46
+ * Execute a tool invocation
47
+ *
48
+ * @param {ToolInvocationInterface} toolInvocation The tool to invoke and the input to pass to it.
49
+ * @param {RunnableConfig | undefined} config Optional configuration to pass to the tool when invoked.
50
+ * @returns Either the result of the tool invocation (`string` or `ToolMessage`, set by the `ToolOutput` generic) or a string error message.
51
+ */
45
52
  async _execute(toolInvocation, config) {
46
53
  if (!(toolInvocation.tool in this.toolMap)) {
47
54
  return this.invalidToolMsgTemplate
@@ -33,12 +33,17 @@ class ToolNode extends utils_js_1.RunnableCallable {
33
33
  if (tool === undefined) {
34
34
  throw new Error(`Tool ${call.name} not found.`);
35
35
  }
36
- const output = await tool.invoke(call.args, config);
37
- return new messages_1.ToolMessage({
38
- name: tool.name,
39
- content: typeof output === "string" ? output : JSON.stringify(output),
40
- tool_call_id: call.id,
41
- });
36
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
37
+ if ((0, messages_1.isBaseMessage)(output) && output._getType() === "tool") {
38
+ return output;
39
+ }
40
+ else {
41
+ return new messages_1.ToolMessage({
42
+ name: tool.name,
43
+ content: typeof output === "string" ? output : JSON.stringify(output),
44
+ tool_call_id: call.id,
45
+ });
46
+ }
42
47
  }) ?? []);
43
48
  return Array.isArray(input) ? outputs : { messages: outputs };
44
49
  }
@@ -1,4 +1,4 @@
1
- import { ToolMessage } from "@langchain/core/messages";
1
+ import { ToolMessage, isBaseMessage, } from "@langchain/core/messages";
2
2
  import { RunnableCallable } from "../utils.js";
3
3
  import { END } from "../graph/graph.js";
4
4
  export class ToolNode extends RunnableCallable {
@@ -30,12 +30,17 @@ export class ToolNode extends RunnableCallable {
30
30
  if (tool === undefined) {
31
31
  throw new Error(`Tool ${call.name} not found.`);
32
32
  }
33
- const output = await tool.invoke(call.args, config);
34
- return new ToolMessage({
35
- name: tool.name,
36
- content: typeof output === "string" ? output : JSON.stringify(output),
37
- tool_call_id: call.id,
38
- });
33
+ const output = await tool.invoke({ ...call, type: "tool_call" }, config);
34
+ if (isBaseMessage(output) && output._getType() === "tool") {
35
+ return output;
36
+ }
37
+ else {
38
+ return new ToolMessage({
39
+ name: tool.name,
40
+ content: typeof output === "string" ? output : JSON.stringify(output),
41
+ tool_call_id: call.id,
42
+ });
43
+ }
39
44
  }) ?? []);
40
45
  return Array.isArray(input) ? outputs : { messages: outputs };
41
46
  }
@@ -442,10 +442,17 @@ class Pregel extends runnables_1.Runnable {
442
442
  }
443
443
  const tasksWithConfig = nextTasks.map(
444
444
  // eslint-disable-next-line no-loop-func
445
- (task) => [
445
+ (task, i) => [
446
446
  task.proc,
447
447
  task.input,
448
- (0, runnables_1.patchConfig)(restConfig, {
448
+ (0, runnables_1.patchConfig)((0, runnables_1.mergeConfigs)(restConfig, processes[task.name].config, {
449
+ metadata: {
450
+ langgraph_step: step,
451
+ langgraph_node: task.name,
452
+ langgraph_triggers: [constants_js_1.TASKS],
453
+ langgraph_task_idx: i,
454
+ },
455
+ }), {
449
456
  callbacks: runManager?.getChild(`graph:step:${step}`),
450
457
  runName: task.name,
451
458
  configurable: {
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable no-param-reassign */
2
- import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, patchConfig, } from "@langchain/core/runnables";
2
+ import { Runnable, RunnableSequence, _coerceToRunnable, ensureConfig, mergeConfigs, patchConfig, } from "@langchain/core/runnables";
3
3
  import { IterableReadableStream } from "@langchain/core/utils/stream";
4
4
  import { createCheckpoint, emptyChannels, } from "../channels/base.js";
5
5
  import { copyCheckpoint, emptyCheckpoint, getChannelVersion, getVersionSeen, } from "../checkpoint/base.js";
@@ -7,7 +7,7 @@ import { PregelNode } from "./read.js";
7
7
  import { validateGraph, validateKeys } from "./validate.js";
8
8
  import { mapInput, mapOutputUpdates, mapOutputValues, readChannel, readChannels, single, } from "./io.js";
9
9
  import { ChannelWrite, PASSTHROUGH } from "./write.js";
10
- import { CONFIG_KEY_READ, CONFIG_KEY_SEND, INTERRUPT, TAG_HIDDEN, } from "../constants.js";
10
+ import { CONFIG_KEY_READ, CONFIG_KEY_SEND, INTERRUPT, TAG_HIDDEN, TASKS, } from "../constants.js";
11
11
  import { EmptyChannelError, GraphRecursionError, GraphValueError, InvalidUpdateError, } from "../errors.js";
12
12
  const DEFAULT_LOOP_LIMIT = 25;
13
13
  function isString(value) {
@@ -438,10 +438,17 @@ export class Pregel extends Runnable {
438
438
  }
439
439
  const tasksWithConfig = nextTasks.map(
440
440
  // eslint-disable-next-line no-loop-func
441
- (task) => [
441
+ (task, i) => [
442
442
  task.proc,
443
443
  task.input,
444
- patchConfig(restConfig, {
444
+ patchConfig(mergeConfigs(restConfig, processes[task.name].config, {
445
+ metadata: {
446
+ langgraph_step: step,
447
+ langgraph_node: task.name,
448
+ langgraph_triggers: [TASKS],
449
+ langgraph_task_idx: i,
450
+ },
451
+ }), {
445
452
  callbacks: runManager?.getChild(`graph:step:${step}`),
446
453
  runName: task.name,
447
454
  configurable: {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { test, expect } from "@jest/globals";
2
+ import { createReactAgent } from "../prebuilt/index.js";
3
+ import { FakeSearchTool, FakeToolCallingChatModel } from "./utils.js";
4
+ test("prebuilt agent", async () => {
5
+ // Define the tools for the agent to use
6
+ const tools = [new FakeSearchTool()];
7
+ const model = new FakeToolCallingChatModel({});
8
+ const app = createReactAgent({ llm: model, tools });
9
+ const graph = app.getGraph();
10
+ const mermaid = graph.drawMermaid();
11
+ expect(mermaid).toEqual(`%%{init: {'flowchart': {'curve': 'linear'}}}%%
12
+ graph TD;
13
+ \t__start__[__start__]:::startclass;
14
+ \t__end__[__end__]:::endclass;
15
+ \tagent([agent]):::otherclass;
16
+ \ttools([tools]):::otherclass;
17
+ \t__start__ --> agent;
18
+ \ttools --> agent;
19
+ \tagent -. continue .-> tools;
20
+ \tagent -.-> __end__;
21
+ \tclassDef startclass fill:#ffdfba;
22
+ \tclassDef endclass fill:#baffc9;
23
+ \tclassDef otherclass fill:#fad7de;
24
+ `);
25
+ });
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect } from "@jest/globals";
2
2
  import { StateGraph } from "../graph/state.js";
3
+ import { END, START } from "../web.js";
3
4
  describe("State", () => {
4
5
  it("should validate a new node key correctly ", () => {
5
6
  const stateGraph = new StateGraph({
@@ -12,4 +13,21 @@ describe("State", () => {
12
13
  stateGraph.addNode("newNodeKey", (_) => ({}));
13
14
  }).not.toThrow();
14
15
  });
16
+ it("should allow reducers with different argument types", async () => {
17
+ const stateGraph = new StateGraph({
18
+ channels: {
19
+ testval: {
20
+ reducer: (left, right) => right ? left.concat([right.toString()]) : left,
21
+ },
22
+ },
23
+ });
24
+ const graph = stateGraph
25
+ .addNode("testnode", (_) => ({ testval: "hi!" }))
26
+ .addEdge(START, "testnode")
27
+ .addEdge("testnode", END)
28
+ .compile();
29
+ expect(await graph.invoke({ testval: ["hello"] })).toEqual({
30
+ testval: ["hello", "hi!"],
31
+ });
32
+ });
15
33
  });