@langchain/langgraph 0.0.12 → 0.0.13

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.
Files changed (134) hide show
  1. package/dist/channels/any_value.cjs +57 -0
  2. package/dist/channels/any_value.d.ts +16 -0
  3. package/dist/channels/any_value.js +53 -0
  4. package/dist/channels/base.cjs +19 -28
  5. package/dist/channels/base.d.ts +13 -19
  6. package/dist/channels/base.js +17 -24
  7. package/dist/channels/binop.cjs +4 -3
  8. package/dist/channels/binop.d.ts +1 -1
  9. package/dist/channels/binop.js +3 -2
  10. package/dist/channels/dynamic_barrier_value.cjs +88 -0
  11. package/dist/channels/dynamic_barrier_value.d.ts +26 -0
  12. package/dist/channels/dynamic_barrier_value.js +84 -0
  13. package/dist/channels/ephemeral_value.cjs +64 -0
  14. package/dist/channels/ephemeral_value.d.ts +14 -0
  15. package/dist/channels/ephemeral_value.js +60 -0
  16. package/dist/channels/index.cjs +1 -3
  17. package/dist/channels/index.d.ts +1 -1
  18. package/dist/channels/index.js +1 -1
  19. package/dist/channels/last_value.cjs +11 -5
  20. package/dist/channels/last_value.d.ts +5 -1
  21. package/dist/channels/last_value.js +9 -3
  22. package/dist/channels/named_barrier_value.cjs +71 -0
  23. package/dist/channels/named_barrier_value.d.ts +18 -0
  24. package/dist/channels/named_barrier_value.js +66 -0
  25. package/dist/channels/topic.cjs +5 -3
  26. package/dist/channels/topic.d.ts +3 -3
  27. package/dist/channels/topic.js +5 -3
  28. package/dist/checkpoint/base.cjs +30 -12
  29. package/dist/checkpoint/base.d.ts +39 -22
  30. package/dist/checkpoint/base.js +28 -11
  31. package/dist/checkpoint/id.cjs +40 -0
  32. package/dist/checkpoint/id.d.ts +2 -0
  33. package/dist/checkpoint/id.js +35 -0
  34. package/dist/checkpoint/index.cjs +2 -2
  35. package/dist/checkpoint/index.d.ts +2 -2
  36. package/dist/checkpoint/index.js +2 -2
  37. package/dist/checkpoint/memory.cjs +63 -49
  38. package/dist/checkpoint/memory.d.ts +7 -10
  39. package/dist/checkpoint/memory.js +62 -47
  40. package/dist/checkpoint/sqlite.cjs +170 -0
  41. package/dist/checkpoint/sqlite.d.ts +14 -0
  42. package/dist/checkpoint/sqlite.js +163 -0
  43. package/dist/constants.cjs +3 -1
  44. package/dist/constants.d.ts +2 -0
  45. package/dist/constants.js +2 -0
  46. package/dist/errors.cjs +31 -0
  47. package/dist/errors.d.ts +12 -0
  48. package/dist/errors.js +24 -0
  49. package/dist/graph/graph.cjs +235 -95
  50. package/dist/graph/graph.d.ts +52 -23
  51. package/dist/graph/graph.js +234 -96
  52. package/dist/graph/index.cjs +2 -2
  53. package/dist/graph/index.d.ts +2 -2
  54. package/dist/graph/index.js +2 -2
  55. package/dist/graph/message.cjs +4 -3
  56. package/dist/graph/message.d.ts +4 -1
  57. package/dist/graph/message.js +4 -3
  58. package/dist/graph/state.cjs +237 -102
  59. package/dist/graph/state.d.ts +41 -18
  60. package/dist/graph/state.js +238 -104
  61. package/dist/index.cjs +6 -2
  62. package/dist/index.d.ts +3 -2
  63. package/dist/index.js +2 -1
  64. package/dist/prebuilt/agent_executor.cjs +22 -36
  65. package/dist/prebuilt/agent_executor.d.ts +7 -10
  66. package/dist/prebuilt/agent_executor.js +23 -37
  67. package/dist/prebuilt/chat_agent_executor.cjs +13 -13
  68. package/dist/prebuilt/chat_agent_executor.d.ts +3 -1
  69. package/dist/prebuilt/chat_agent_executor.js +15 -15
  70. package/dist/prebuilt/index.cjs +4 -1
  71. package/dist/prebuilt/index.d.ts +1 -0
  72. package/dist/prebuilt/index.js +1 -0
  73. package/dist/prebuilt/tool_node.cjs +59 -0
  74. package/dist/prebuilt/tool_node.d.ts +17 -0
  75. package/dist/prebuilt/tool_node.js +54 -0
  76. package/dist/pregel/debug.cjs +6 -8
  77. package/dist/pregel/debug.d.ts +2 -2
  78. package/dist/pregel/debug.js +5 -7
  79. package/dist/pregel/index.cjs +406 -236
  80. package/dist/pregel/index.d.ts +77 -41
  81. package/dist/pregel/index.js +408 -241
  82. package/dist/pregel/io.cjs +117 -30
  83. package/dist/pregel/io.d.ts +11 -3
  84. package/dist/pregel/io.js +111 -28
  85. package/dist/pregel/read.cjs +126 -46
  86. package/dist/pregel/read.d.ts +27 -18
  87. package/dist/pregel/read.js +125 -45
  88. package/dist/pregel/types.cjs +2 -0
  89. package/dist/pregel/types.d.ts +32 -0
  90. package/dist/pregel/types.js +1 -0
  91. package/dist/pregel/validate.cjs +58 -51
  92. package/dist/pregel/validate.d.ts +14 -13
  93. package/dist/pregel/validate.js +56 -50
  94. package/dist/pregel/write.cjs +46 -30
  95. package/dist/pregel/write.d.ts +18 -8
  96. package/dist/pregel/write.js +45 -29
  97. package/dist/serde/base.cjs +2 -0
  98. package/dist/serde/base.d.ts +4 -0
  99. package/dist/serde/base.js +1 -0
  100. package/dist/setup/async_local_storage.cjs +2 -2
  101. package/dist/setup/async_local_storage.js +1 -1
  102. package/dist/tests/channels.test.d.ts +1 -0
  103. package/dist/tests/channels.test.js +151 -0
  104. package/dist/tests/chatbot.int.test.d.ts +1 -0
  105. package/dist/tests/chatbot.int.test.js +61 -0
  106. package/dist/tests/checkpoints.test.d.ts +1 -0
  107. package/dist/tests/checkpoints.test.js +190 -0
  108. package/dist/tests/graph.test.d.ts +1 -0
  109. package/dist/tests/graph.test.js +15 -0
  110. package/dist/tests/prebuilt.int.test.d.ts +1 -0
  111. package/dist/tests/prebuilt.int.test.js +101 -0
  112. package/dist/tests/prebuilt.test.d.ts +1 -0
  113. package/dist/tests/prebuilt.test.js +195 -0
  114. package/dist/tests/pregel.io.test.d.ts +1 -0
  115. package/dist/tests/pregel.io.test.js +332 -0
  116. package/dist/tests/pregel.read.test.d.ts +1 -0
  117. package/dist/tests/pregel.read.test.js +109 -0
  118. package/dist/tests/pregel.test.d.ts +1 -0
  119. package/dist/tests/pregel.test.js +1879 -0
  120. package/dist/tests/pregel.validate.test.d.ts +1 -0
  121. package/dist/tests/pregel.validate.test.js +198 -0
  122. package/dist/tests/pregel.write.test.d.ts +1 -0
  123. package/dist/tests/pregel.write.test.js +44 -0
  124. package/dist/tests/tracing.int.test.d.ts +1 -0
  125. package/dist/tests/tracing.int.test.js +449 -0
  126. package/dist/tests/utils.d.ts +22 -0
  127. package/dist/tests/utils.js +76 -0
  128. package/dist/utils.cjs +74 -0
  129. package/dist/utils.d.ts +18 -0
  130. package/dist/utils.js +70 -0
  131. package/package.json +11 -7
  132. package/dist/pregel/reserved.cjs +0 -6
  133. package/dist/pregel/reserved.d.ts +0 -3
  134. package/dist/pregel/reserved.js +0 -3
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,198 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { PregelNode } from "../pregel/read.js";
3
+ import { GraphValidationError, validateGraph } from "../pregel/validate.js";
4
+ import { LastValue } from "../channels/last_value.js";
5
+ describe("validateGraph", () => {
6
+ it("should throw an error if a node is named __interrupt__", () => {
7
+ // set up test
8
+ const nodes = {
9
+ __interrupt__: new PregelNode({
10
+ channels: [""],
11
+ triggers: [],
12
+ }),
13
+ };
14
+ const channels = {
15
+ "": new LastValue(),
16
+ };
17
+ // call method / assertions
18
+ expect(() => {
19
+ validateGraph({
20
+ nodes,
21
+ channels,
22
+ inputChannels: "",
23
+ outputChannels: "",
24
+ streamChannels: "",
25
+ interruptAfterNodes: [],
26
+ interruptBeforeNodes: [],
27
+ });
28
+ }).toThrow(GraphValidationError);
29
+ });
30
+ it("should throw an error if a node is not the correct type", () => {
31
+ // set up test
32
+ class PregelNodeSubclass extends PregelNode {
33
+ }
34
+ const nodes = {
35
+ channelName: new PregelNodeSubclass({
36
+ channels: [""],
37
+ triggers: [],
38
+ }),
39
+ };
40
+ const channels = {
41
+ "": new LastValue(),
42
+ };
43
+ // call method / assertions
44
+ expect(() => {
45
+ validateGraph({
46
+ nodes,
47
+ channels,
48
+ inputChannels: "",
49
+ outputChannels: "",
50
+ streamChannels: "",
51
+ interruptAfterNodes: [],
52
+ interruptBeforeNodes: [],
53
+ });
54
+ }).toThrow(GraphValidationError);
55
+ });
56
+ it("should throw an error if input channel is not subscribed to by any node", () => {
57
+ // set up test
58
+ const nodes = {
59
+ channelName1: new PregelNode({
60
+ channels: [""],
61
+ triggers: ["channelName2"],
62
+ }),
63
+ };
64
+ const channels = {
65
+ "": new LastValue(),
66
+ };
67
+ // call method / assertions
68
+ expect(() => {
69
+ validateGraph({
70
+ nodes,
71
+ channels,
72
+ // @ts-expect-error - testing invalid input
73
+ inputChannels: "channelName3",
74
+ outputChannels: "",
75
+ streamChannels: "",
76
+ interruptAfterNodes: [],
77
+ interruptBeforeNodes: [],
78
+ });
79
+ }).toThrow(GraphValidationError);
80
+ });
81
+ it("should throw an error if none of the input channels are subscribed to by any node", () => {
82
+ // set up test
83
+ const nodes = {
84
+ channelName1: new PregelNode({
85
+ channels: [""],
86
+ triggers: ["channelName2"],
87
+ }),
88
+ };
89
+ const channels = {
90
+ "": new LastValue(),
91
+ };
92
+ // call method / assertions
93
+ expect(() => {
94
+ validateGraph({
95
+ nodes,
96
+ channels,
97
+ // @ts-expect-error - testing invalid input
98
+ inputChannels: ["channelName3", "channelName4", "channelName5"],
99
+ outputChannels: "",
100
+ streamChannels: "",
101
+ interruptAfterNodes: [],
102
+ interruptBeforeNodes: [],
103
+ });
104
+ }).toThrow(GraphValidationError);
105
+ });
106
+ it("should throw an error if 'interrupt after' nodes not in nodes map", () => {
107
+ // set up test
108
+ const nodes = {
109
+ channelName1: new PregelNode({
110
+ channels: [""],
111
+ triggers: ["channelName2"],
112
+ }),
113
+ };
114
+ const channels = {
115
+ "": new LastValue(),
116
+ };
117
+ // call method / assertions
118
+ expect(() => {
119
+ validateGraph({
120
+ nodes,
121
+ channels,
122
+ inputChannels: [""],
123
+ outputChannels: "",
124
+ streamChannels: "",
125
+ interruptAfterNodes: ["channelName3"],
126
+ interruptBeforeNodes: [],
127
+ });
128
+ }).toThrow(GraphValidationError);
129
+ });
130
+ it("should throw an error if 'interrupt after' nodes not in nodes map", () => {
131
+ // set up test
132
+ const nodes = {
133
+ channelName1: new PregelNode({
134
+ channels: [""],
135
+ triggers: ["channelName2"],
136
+ }),
137
+ };
138
+ const channels = {
139
+ "": new LastValue(),
140
+ };
141
+ // call method / assertions
142
+ expect(() => {
143
+ validateGraph({
144
+ nodes,
145
+ channels,
146
+ inputChannels: [""],
147
+ outputChannels: "",
148
+ streamChannels: "",
149
+ interruptAfterNodes: [],
150
+ interruptBeforeNodes: ["channelName3"],
151
+ });
152
+ }).toThrow(GraphValidationError);
153
+ });
154
+ it("should thrown on missing channels", () => {
155
+ // set up test
156
+ const nodes = {
157
+ channelName1: new PregelNode({
158
+ channels: [""],
159
+ triggers: ["channelName2"],
160
+ }),
161
+ };
162
+ const channels1 = {
163
+ "": new LastValue(),
164
+ channelName2: new LastValue(),
165
+ };
166
+ // call method / assertions
167
+ expect(() => validateGraph({
168
+ nodes,
169
+ channels: channels1,
170
+ inputChannels: "channelName2",
171
+ // @ts-expect-error - testing invalid input
172
+ outputChannels: "channelName3",
173
+ interruptAfterNodes: [],
174
+ interruptBeforeNodes: [],
175
+ })).toThrow(GraphValidationError);
176
+ // call method / assertions
177
+ expect(() => validateGraph({
178
+ nodes,
179
+ channels: channels1,
180
+ // @ts-expect-error - testing invalid input
181
+ inputChannels: "channelName3",
182
+ outputChannels: "channelName2",
183
+ interruptAfterNodes: [],
184
+ interruptBeforeNodes: [],
185
+ })).toThrow(GraphValidationError);
186
+ // call method / assertions
187
+ expect(() => validateGraph({
188
+ nodes,
189
+ channels: channels1,
190
+ inputChannels: "channelName2",
191
+ outputChannels: "",
192
+ // @ts-expect-error - testing invalid input
193
+ streamChannels: "channelName4",
194
+ interruptAfterNodes: [],
195
+ interruptBeforeNodes: [],
196
+ })).toThrow(GraphValidationError);
197
+ });
198
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,44 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import { RunnablePassthrough } from "@langchain/core/runnables";
3
+ import { ChannelWrite, PASSTHROUGH } from "../pregel/write.js";
4
+ describe("ChannelWrite", () => {
5
+ describe("_getWriteValues", () => {
6
+ it("should return the expect object", async () => {
7
+ // set up test
8
+ const channelWrite = new ChannelWrite([
9
+ {
10
+ channel: "someChannel1",
11
+ value: 1,
12
+ skipNone: false,
13
+ },
14
+ {
15
+ channel: "someChannel2",
16
+ value: PASSTHROUGH,
17
+ skipNone: false,
18
+ mapper: new RunnablePassthrough(),
19
+ },
20
+ {
21
+ channel: "someChannel3",
22
+ value: null,
23
+ skipNone: true,
24
+ },
25
+ {
26
+ channel: "someChannel4",
27
+ value: PASSTHROUGH,
28
+ skipNone: false,
29
+ },
30
+ ]);
31
+ const input = 2;
32
+ const config = {};
33
+ // call method / assertions
34
+ const writeValues = await channelWrite._getWriteValues(input, config);
35
+ const expectedWriteValues = {
36
+ someChannel1: 1,
37
+ someChannel2: 2, // value is set to input value since PASSTHROUGH value was specified (with mapper)
38
+ // someChannel3 should be filtered out
39
+ someChannel4: 2, // value is set to input value since PASSTHROUGH value was specified
40
+ };
41
+ expect(writeValues).toEqual(expectedWriteValues);
42
+ });
43
+ });
44
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,449 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ /* eslint-disable no-process-env */
3
+ import { test } from "@jest/globals";
4
+ import { pull } from "langchain/hub";
5
+ import { ChatOpenAI } from "@langchain/openai";
6
+ import { TavilySearchResults } from "@langchain/community/tools/tavily_search";
7
+ import { ChatPromptTemplate, MessagesPlaceholder, } from "@langchain/core/prompts";
8
+ import { HumanMessage } from "@langchain/core/messages";
9
+ import { RunnableLambda } from "@langchain/core/runnables";
10
+ import { AgentExecutor, createOpenAIFunctionsAgent, createOpenAIToolsAgent, } from "langchain/agents";
11
+ import { JsonOutputFunctionsParser, JsonOutputToolsParser, } from "langchain/output_parsers";
12
+ import { createOpenAIFnRunnable } from "langchain/chains/openai_functions";
13
+ import { zodToJsonSchema } from "zod-to-json-schema";
14
+ import { z } from "zod";
15
+ import { StateGraph, END } from "../index.js";
16
+ import { ToolExecutor } from "../prebuilt/tool_executor.js";
17
+ import { createAgentExecutor } from "../prebuilt/agent_executor.js";
18
+ test.skip("Can invoke with tracing", async () => {
19
+ const tools = [new TavilySearchResults({ maxResults: 1 })];
20
+ // Get the prompt to use - you can modify this!
21
+ const prompt = await pull("hwchase17/openai-functions-agent");
22
+ // Choose the LLM that will drive the agent
23
+ const llm = new ChatOpenAI({
24
+ modelName: "gpt-4-1106-preview",
25
+ temperature: 0,
26
+ });
27
+ // Construct the OpenAI Functions agent
28
+ const agentRunnable = await createOpenAIFunctionsAgent({
29
+ llm,
30
+ tools,
31
+ prompt,
32
+ });
33
+ const toolExecutor = new ToolExecutor({
34
+ tools,
35
+ });
36
+ // Define logic that will be used to determine which conditional edge to go down
37
+ const shouldContinue = (data) => {
38
+ if (data.agentOutcome && "returnValues" in data.agentOutcome) {
39
+ return "end";
40
+ }
41
+ return "continue";
42
+ };
43
+ const runAgent = async (data) => {
44
+ const agentOutcome = await agentRunnable.invoke(data);
45
+ return {
46
+ agentOutcome,
47
+ };
48
+ };
49
+ const executeTools = async (data) => {
50
+ const agentAction = data.agentOutcome;
51
+ if (!agentAction || "returnValues" in agentAction) {
52
+ throw new Error("Agent has not been run yet");
53
+ }
54
+ const output = await toolExecutor.invoke(agentAction);
55
+ return {
56
+ steps: [{ action: agentAction, observation: JSON.stringify(output) }],
57
+ };
58
+ };
59
+ // Define a new graph
60
+ const workflow = new StateGraph({
61
+ channels: {
62
+ input: null,
63
+ steps: {
64
+ value: (x, y) => x.concat(y),
65
+ default: () => [],
66
+ },
67
+ agentOutcome: null,
68
+ },
69
+ })
70
+ // Define the two nodes we will cycle between
71
+ .addNode("agent", new RunnableLambda({ func: runAgent }))
72
+ .addNode("action", new RunnableLambda({ func: executeTools }))
73
+ // Set the entrypoint as `agent`
74
+ // This means that this node is the first one called
75
+ .setEntryPoint("agent")
76
+ // We now add a conditional edge
77
+ .addConditionalEdges(
78
+ // First, we define the start node. We use `agent`.
79
+ // This means these are the edges taken after the `agent` node is called.
80
+ "agent",
81
+ // Next, we pass in the function that will determine which node is called next.
82
+ shouldContinue,
83
+ // Finally we pass in a mapping.
84
+ // The keys are strings, and the values are other nodes.
85
+ // END is a special node marking that the graph should finish.
86
+ // What will happen is we will call `should_continue`, and then the output of that
87
+ // will be matched against the keys in this mapping.
88
+ // Based on which one it matches, that node will then be called.
89
+ {
90
+ // If `tools`, then we call the tool node.
91
+ continue: "action",
92
+ // Otherwise we finish.
93
+ end: END,
94
+ })
95
+ // We now add a normal edge from `tools` to `agent`.
96
+ // This means that after `tools` is called, `agent` node is called next.
97
+ .addEdge("action", "agent");
98
+ const app = workflow.compile();
99
+ const inputs = { input: "what is the weather in sf" };
100
+ for await (const s of await app.stream(inputs)) {
101
+ console.log(s);
102
+ console.log("----\n");
103
+ }
104
+ });
105
+ test.skip("Can nest an agent executor", async () => {
106
+ const tools = [new TavilySearchResults({ maxResults: 3 })];
107
+ const llm = new ChatOpenAI({
108
+ modelName: "gpt-4-1106-preview",
109
+ temperature: 0,
110
+ });
111
+ const systemPrompt = `You are a web researcher. You may use the Tavily search engine to search the web for important information.`;
112
+ // Each worker node will be given a name and some tools.
113
+ const prompt = ChatPromptTemplate.fromMessages([
114
+ ["system", systemPrompt],
115
+ new MessagesPlaceholder("messages"),
116
+ new MessagesPlaceholder("agent_scratchpad"),
117
+ ]);
118
+ const agent = await createOpenAIToolsAgent({ llm, tools, prompt });
119
+ const executor = new AgentExecutor({ agent, tools });
120
+ const researcherNode = async (state) => {
121
+ console.log("STATE", state);
122
+ const result = await executor.invoke(state);
123
+ return {
124
+ messages: [
125
+ new HumanMessage({ content: result.output, name: "researcher" }),
126
+ ],
127
+ };
128
+ };
129
+ // Define the routing function
130
+ const functionDef = {
131
+ name: "route",
132
+ description: "Select the next role.",
133
+ parameters: {
134
+ title: "routeSchema",
135
+ type: "object",
136
+ properties: {
137
+ next: {
138
+ title: "Next",
139
+ anyOf: [{ enum: ["FINISH", "researcher"] }],
140
+ },
141
+ },
142
+ required: ["next"],
143
+ },
144
+ };
145
+ const toolDef = {
146
+ type: "function",
147
+ function: functionDef,
148
+ };
149
+ const supervisorPrompt = ChatPromptTemplate.fromMessages([
150
+ ["system", systemPrompt],
151
+ new MessagesPlaceholder("messages"),
152
+ [
153
+ "system",
154
+ "Given the conversation above, who should act next? Or should we FINISH? Select one of: {options}",
155
+ ],
156
+ ]);
157
+ const formattedPrompt = await supervisorPrompt.partial({
158
+ options: ["FINISH", "researcher"].join(", "),
159
+ });
160
+ const supervisorChain = formattedPrompt
161
+ .pipe(llm.bind({
162
+ tools: [toolDef],
163
+ tool_choice: { type: "function", function: { name: "route" } },
164
+ }))
165
+ .pipe(new JsonOutputToolsParser())
166
+ // select the first one
167
+ .pipe((x) => x[0].args);
168
+ // 1. Create the graph
169
+ const workflow = new StateGraph({
170
+ channels: {
171
+ messages: {
172
+ value: (x, y) => x.concat(y),
173
+ default: () => [],
174
+ },
175
+ next: null,
176
+ },
177
+ })
178
+ // 2. Add the nodes; these will do the work
179
+ .addNode("researcher", researcherNode)
180
+ .addNode("supervisor", supervisorChain)
181
+ // 3. Define the edges. We will define both regular and conditional ones
182
+ // After a worker completes, report to supervisor
183
+ .addEdge("researcher", "supervisor")
184
+ // When the supervisor returns, route to the agent identified in the supervisor's output
185
+ .addConditionalEdges("supervisor", (x) => x.next, {
186
+ researcher: "researcher",
187
+ // Or end work if done
188
+ FINISH: END,
189
+ })
190
+ .setEntryPoint("supervisor");
191
+ const graph = workflow.compile();
192
+ const streamResults = graph.stream({
193
+ messages: [
194
+ new HumanMessage({
195
+ content: "Who is the current prime minister of malaysia?",
196
+ }),
197
+ ],
198
+ }, { tags: ["outer_tag"], recursionLimit: 100 });
199
+ for await (const output of await streamResults) {
200
+ if (!output?.__end__) {
201
+ console.log(output);
202
+ console.log("----");
203
+ }
204
+ }
205
+ });
206
+ test.skip("Can nest a graph within a graph", async () => {
207
+ const tools = [new TavilySearchResults({ maxResults: 3 })];
208
+ const llm = new ChatOpenAI({
209
+ modelName: "gpt-4-1106-preview",
210
+ temperature: 0,
211
+ });
212
+ const systemPrompt = `You are a web researcher. You may use the Tavily search engine to search the web for important information.`;
213
+ // Each worker node will be given a name and some tools.
214
+ const prompt = ChatPromptTemplate.fromMessages([
215
+ ["system", systemPrompt],
216
+ new MessagesPlaceholder("messages"),
217
+ new MessagesPlaceholder("agent_scratchpad"),
218
+ ]);
219
+ const agent = await createOpenAIToolsAgent({ llm, tools, prompt });
220
+ const executor = new AgentExecutor({ agent, tools });
221
+ const researcherNode = async (state) => {
222
+ const result = await executor.invoke(state);
223
+ return {
224
+ messages: [
225
+ new HumanMessage({ content: result.output, name: "researcher" }),
226
+ ],
227
+ };
228
+ };
229
+ // Define the routing function
230
+ const functionDef = {
231
+ name: "route",
232
+ description: "Select the next role.",
233
+ parameters: {
234
+ title: "routeSchema",
235
+ type: "object",
236
+ properties: {
237
+ next: {
238
+ title: "Next",
239
+ anyOf: [{ enum: ["FINISH", "researcher"] }],
240
+ },
241
+ },
242
+ required: ["next"],
243
+ },
244
+ };
245
+ const toolDef = {
246
+ type: "function",
247
+ function: functionDef,
248
+ };
249
+ const supervisorPrompt = ChatPromptTemplate.fromMessages([
250
+ ["system", systemPrompt],
251
+ new MessagesPlaceholder("messages"),
252
+ [
253
+ "system",
254
+ "Given the conversation above, who should act next? Or should we FINISH? Select one of: {options}",
255
+ ],
256
+ ]);
257
+ const formattedPrompt = await supervisorPrompt.partial({
258
+ options: ["FINISH", "researcher"].join(", "),
259
+ });
260
+ const supervisorChain = formattedPrompt
261
+ .pipe(llm.bind({
262
+ tools: [toolDef],
263
+ tool_choice: { type: "function", function: { name: "route" } },
264
+ }))
265
+ .pipe(new JsonOutputToolsParser())
266
+ // select the first one
267
+ .pipe((x) => x[0].args);
268
+ // 1. Create the graph
269
+ const workflow = new StateGraph({
270
+ channels: {
271
+ messages: {
272
+ value: (x, y) => x.concat(y),
273
+ default: () => [],
274
+ },
275
+ next: null,
276
+ },
277
+ })
278
+ // 2. Add the nodes; these will do the work
279
+ .addNode("researcher", researcherNode)
280
+ .addNode("supervisor", supervisorChain)
281
+ // 3. Define the edges. We will define both regular and conditional ones
282
+ // After a worker completes, report to supervisor
283
+ .addEdge("researcher", "supervisor")
284
+ // When the supervisor returns, route to the agent identified in the supervisor's output
285
+ .addConditionalEdges("supervisor", (x) => x.next, {
286
+ researcher: "researcher",
287
+ FINISH: END,
288
+ })
289
+ .setEntryPoint("supervisor");
290
+ const graph = workflow.compile();
291
+ const streamResults = graph.stream({
292
+ messages: [
293
+ new HumanMessage({
294
+ content: "Who is the current prime minister of malaysia?",
295
+ }),
296
+ ],
297
+ }, { tags: ["outer_tag"], recursionLimit: 100 });
298
+ for await (const output of await streamResults) {
299
+ if (!output?.__end__) {
300
+ console.log(output);
301
+ console.log("----");
302
+ }
303
+ }
304
+ });
305
+ test.skip("Should trace plan and execute flow", async () => {
306
+ const tools = [new TavilySearchResults({ maxResults: 3 })];
307
+ // Get the prompt to use - you can modify this!
308
+ const prompt = await pull("hwchase17/openai-functions-agent");
309
+ // Choose the LLM that will drive the agent
310
+ const llm = new ChatOpenAI({ modelName: "gpt-4-0125-preview" });
311
+ // Construct the OpenAI Functions agent
312
+ const agentRunnable = await createOpenAIFunctionsAgent({
313
+ llm,
314
+ tools,
315
+ prompt,
316
+ });
317
+ const agentExecutor = createAgentExecutor({
318
+ agentRunnable,
319
+ tools,
320
+ });
321
+ const plan = zodToJsonSchema(z.object({
322
+ steps: z
323
+ .array(z.string())
324
+ .describe("different steps to follow, should be in sorted order"),
325
+ }));
326
+ const planFunction = {
327
+ name: "plan",
328
+ description: "This tool is used to plan the steps to follow",
329
+ parameters: plan,
330
+ };
331
+ const plannerPrompt = ChatPromptTemplate.fromTemplate(`For the given objective, come up with a simple step by step plan. \
332
+ This plan should involve individual tasks, that if executed correctly will yield the correct answer. Do not add any superfluous steps. \
333
+ The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps.
334
+
335
+ {objective}`);
336
+ const model = new ChatOpenAI({
337
+ modelName: "gpt-4-0125-preview",
338
+ }).bind({
339
+ functions: [planFunction],
340
+ function_call: planFunction,
341
+ });
342
+ const parserSingle = new JsonOutputFunctionsParser({
343
+ argsOnly: true,
344
+ });
345
+ const planner = plannerPrompt.pipe(model).pipe(parserSingle);
346
+ const response = zodToJsonSchema(z.object({
347
+ response: z.string().describe("Response to user."),
348
+ }));
349
+ const responseFunction = {
350
+ name: "response",
351
+ description: "Response to user.",
352
+ parameters: response,
353
+ };
354
+ const replannerPrompt = ChatPromptTemplate.fromTemplate(`For the given objective, come up with a simple step by step plan.
355
+ This plan should involve individual tasks, that if executed correctly will yield the correct answer. Do not add any superfluous steps.
356
+ The result of the final step should be the final answer. Make sure that each step has all the information needed - do not skip steps.
357
+
358
+ Your objective was this:
359
+ {input}
360
+
361
+ Your original plan was this:
362
+ {plan}
363
+
364
+ You have currently done the follow steps:
365
+ {pastSteps}
366
+
367
+ Update your plan accordingly. If no more steps are needed and you can return to the user, then respond with that and use the 'response' function.
368
+ Otherwise, fill out the plan.
369
+ Only add steps to the plan that still NEED to be done. Do not return previously done steps as part of the plan.`);
370
+ const parser = new JsonOutputFunctionsParser();
371
+ const replanner = createOpenAIFnRunnable({
372
+ functions: [planFunction, responseFunction],
373
+ outputParser: parser,
374
+ llm: new ChatOpenAI({
375
+ modelName: "gpt-4-0125-preview",
376
+ }),
377
+ prompt: replannerPrompt,
378
+ });
379
+ async function executeStep(state) {
380
+ const task = state.input;
381
+ const agentResponse = await agentExecutor.invoke({ input: task });
382
+ return {
383
+ pastSteps: [task, agentResponse.agentOutcome.returnValues.output],
384
+ };
385
+ }
386
+ async function planStep(state) {
387
+ if (!state.input) {
388
+ throw new Error("No input.");
389
+ }
390
+ const plan = await planner.invoke({ objective: state.input });
391
+ return { plan: plan.steps };
392
+ }
393
+ async function replanStep(state) {
394
+ const output = await replanner.invoke({
395
+ input: state.input,
396
+ plan: state.plan ? state.plan.join("\n") : "",
397
+ pastSteps: state.pastSteps.join("\n"),
398
+ });
399
+ if (output.response !== undefined) {
400
+ return { response: output.response };
401
+ }
402
+ return { plan: output.steps };
403
+ }
404
+ function shouldEnd(state) {
405
+ if (state.response) {
406
+ return "true";
407
+ }
408
+ return "false";
409
+ }
410
+ const workflow = new StateGraph({
411
+ channels: {
412
+ input: null,
413
+ plan: null,
414
+ pastSteps: {
415
+ reducer: (x, y) => x.concat(y),
416
+ default: () => [],
417
+ },
418
+ response: null,
419
+ },
420
+ })
421
+ // Add the plan node
422
+ .addNode("planner", planStep)
423
+ // Add the execution step
424
+ .addNode("agent", executeStep)
425
+ // Add a replan node
426
+ .addNode("replan", replanStep)
427
+ .setEntryPoint("planner")
428
+ // From plan we go to agent
429
+ .addEdge("planner", "agent")
430
+ // From agent, we replan
431
+ .addEdge("agent", "replan")
432
+ .addConditionalEdges("replan",
433
+ // Next, we pass in the function that will determine which node is called next.
434
+ shouldEnd, {
435
+ true: END,
436
+ false: "planner",
437
+ });
438
+ // Finally, we compile it!
439
+ // This compiles it into a LangChain Runnable,
440
+ // meaning you can use it as you would any other runnable
441
+ const app = workflow.compile();
442
+ const config = { recursionLimit: 50 };
443
+ const inputs = {
444
+ input: "what is the hometown of the 2024 Australia open winner?",
445
+ };
446
+ for await (const event of await app.stream(inputs, config)) {
447
+ console.log(event);
448
+ }
449
+ });