@langchain/langgraph 0.0.11 → 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.
- package/dist/channels/any_value.cjs +57 -0
- package/dist/channels/any_value.d.ts +16 -0
- package/dist/channels/any_value.js +53 -0
- package/dist/channels/base.cjs +19 -28
- package/dist/channels/base.d.ts +13 -19
- package/dist/channels/base.js +17 -24
- package/dist/channels/binop.cjs +4 -3
- package/dist/channels/binop.d.ts +1 -1
- package/dist/channels/binop.js +3 -2
- package/dist/channels/dynamic_barrier_value.cjs +88 -0
- package/dist/channels/dynamic_barrier_value.d.ts +26 -0
- package/dist/channels/dynamic_barrier_value.js +84 -0
- package/dist/channels/ephemeral_value.cjs +64 -0
- package/dist/channels/ephemeral_value.d.ts +14 -0
- package/dist/channels/ephemeral_value.js +60 -0
- package/dist/channels/index.cjs +1 -3
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/index.js +1 -1
- package/dist/channels/last_value.cjs +11 -5
- package/dist/channels/last_value.d.ts +5 -1
- package/dist/channels/last_value.js +9 -3
- package/dist/channels/named_barrier_value.cjs +71 -0
- package/dist/channels/named_barrier_value.d.ts +18 -0
- package/dist/channels/named_barrier_value.js +66 -0
- package/dist/channels/topic.cjs +5 -3
- package/dist/channels/topic.d.ts +3 -3
- package/dist/channels/topic.js +5 -3
- package/dist/checkpoint/base.cjs +30 -12
- package/dist/checkpoint/base.d.ts +39 -22
- package/dist/checkpoint/base.js +28 -11
- package/dist/checkpoint/id.cjs +40 -0
- package/dist/checkpoint/id.d.ts +2 -0
- package/dist/checkpoint/id.js +35 -0
- package/dist/checkpoint/index.cjs +2 -2
- package/dist/checkpoint/index.d.ts +2 -2
- package/dist/checkpoint/index.js +2 -2
- package/dist/checkpoint/memory.cjs +63 -49
- package/dist/checkpoint/memory.d.ts +7 -10
- package/dist/checkpoint/memory.js +62 -47
- package/dist/checkpoint/sqlite.cjs +170 -0
- package/dist/checkpoint/sqlite.d.ts +14 -0
- package/dist/checkpoint/sqlite.js +163 -0
- package/dist/constants.cjs +3 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/errors.cjs +31 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +24 -0
- package/dist/graph/graph.cjs +234 -96
- package/dist/graph/graph.d.ts +52 -23
- package/dist/graph/graph.js +233 -97
- package/dist/graph/index.cjs +2 -2
- package/dist/graph/index.d.ts +2 -2
- package/dist/graph/index.js +2 -2
- package/dist/graph/message.cjs +4 -3
- package/dist/graph/message.d.ts +4 -1
- package/dist/graph/message.js +4 -3
- package/dist/graph/state.cjs +237 -102
- package/dist/graph/state.d.ts +41 -18
- package/dist/graph/state.js +238 -104
- package/dist/index.cjs +6 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/prebuilt/agent_executor.cjs +22 -36
- package/dist/prebuilt/agent_executor.d.ts +7 -10
- package/dist/prebuilt/agent_executor.js +23 -37
- package/dist/prebuilt/chat_agent_executor.cjs +13 -13
- package/dist/prebuilt/chat_agent_executor.d.ts +3 -1
- package/dist/prebuilt/chat_agent_executor.js +15 -15
- package/dist/prebuilt/index.cjs +4 -1
- package/dist/prebuilt/index.d.ts +1 -0
- package/dist/prebuilt/index.js +1 -0
- package/dist/prebuilt/tool_node.cjs +59 -0
- package/dist/prebuilt/tool_node.d.ts +17 -0
- package/dist/prebuilt/tool_node.js +54 -0
- package/dist/pregel/debug.cjs +6 -8
- package/dist/pregel/debug.d.ts +2 -2
- package/dist/pregel/debug.js +5 -7
- package/dist/pregel/index.cjs +406 -236
- package/dist/pregel/index.d.ts +77 -41
- package/dist/pregel/index.js +408 -241
- package/dist/pregel/io.cjs +117 -30
- package/dist/pregel/io.d.ts +11 -3
- package/dist/pregel/io.js +111 -28
- package/dist/pregel/read.cjs +126 -46
- package/dist/pregel/read.d.ts +27 -18
- package/dist/pregel/read.js +125 -45
- package/dist/pregel/types.cjs +2 -0
- package/dist/pregel/types.d.ts +32 -0
- package/dist/pregel/types.js +1 -0
- package/dist/pregel/validate.cjs +58 -51
- package/dist/pregel/validate.d.ts +14 -13
- package/dist/pregel/validate.js +56 -50
- package/dist/pregel/write.cjs +46 -30
- package/dist/pregel/write.d.ts +18 -8
- package/dist/pregel/write.js +45 -29
- package/dist/serde/base.cjs +2 -0
- package/dist/serde/base.d.ts +4 -0
- package/dist/serde/base.js +1 -0
- package/dist/setup/async_local_storage.cjs +2 -2
- package/dist/setup/async_local_storage.js +1 -1
- package/dist/tests/channels.test.d.ts +1 -0
- package/dist/tests/channels.test.js +151 -0
- package/dist/tests/chatbot.int.test.d.ts +1 -0
- package/dist/tests/chatbot.int.test.js +61 -0
- package/dist/tests/checkpoints.test.d.ts +1 -0
- package/dist/tests/checkpoints.test.js +190 -0
- package/dist/tests/graph.test.d.ts +1 -0
- package/dist/tests/graph.test.js +15 -0
- package/dist/tests/prebuilt.int.test.d.ts +1 -0
- package/dist/tests/prebuilt.int.test.js +101 -0
- package/dist/tests/prebuilt.test.d.ts +1 -0
- package/dist/tests/prebuilt.test.js +195 -0
- package/dist/tests/pregel.io.test.d.ts +1 -0
- package/dist/tests/pregel.io.test.js +332 -0
- package/dist/tests/pregel.read.test.d.ts +1 -0
- package/dist/tests/pregel.read.test.js +109 -0
- package/dist/tests/pregel.test.d.ts +1 -0
- package/dist/tests/pregel.test.js +1879 -0
- package/dist/tests/pregel.validate.test.d.ts +1 -0
- package/dist/tests/pregel.validate.test.js +198 -0
- package/dist/tests/pregel.write.test.d.ts +1 -0
- package/dist/tests/pregel.write.test.js +44 -0
- package/dist/tests/tracing.int.test.d.ts +1 -0
- package/dist/tests/tracing.int.test.js +449 -0
- package/dist/tests/utils.d.ts +22 -0
- package/dist/tests/utils.js +76 -0
- package/dist/utils.cjs +74 -0
- package/dist/utils.d.ts +18 -0
- package/dist/utils.js +70 -0
- package/package.json +12 -8
- package/dist/pregel/reserved.cjs +0 -6
- package/dist/pregel/reserved.d.ts +0 -3
- 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
|
+
});
|