@minded-ai/mindedjs 1.0.28 → 1.0.30
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/agent.d.ts +1 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +1 -1
- package/dist/agent.js.map +1 -1
- package/dist/checkpointer/checkpointSaverFactory.d.ts +1 -0
- package/dist/checkpointer/checkpointSaverFactory.d.ts.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/edges/createDirectEdge.d.ts +1 -0
- package/dist/edges/createDirectEdge.d.ts.map +1 -0
- package/dist/edges/createLogicalRouter.d.ts +1 -0
- package/dist/edges/createLogicalRouter.d.ts.map +1 -0
- package/dist/edges/createPromptRouter.d.ts +1 -0
- package/dist/edges/createPromptRouter.d.ts.map +1 -0
- package/dist/edges/edgeFactory.d.ts +1 -0
- package/dist/edges/edgeFactory.d.ts.map +1 -0
- package/dist/events/AgentEvents.d.ts +1 -0
- package/dist/events/AgentEvents.d.ts.map +1 -0
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/llm/createLlmInstance.d.ts +1 -0
- package/dist/llm/createLlmInstance.d.ts.map +1 -0
- package/dist/nodes/addHumanInTheLoopNode.d.ts +1 -0
- package/dist/nodes/addHumanInTheLoopNode.d.ts.map +1 -0
- package/dist/nodes/addPromptNode.d.ts +1 -0
- package/dist/nodes/addPromptNode.d.ts.map +1 -0
- package/dist/nodes/addToolNode.d.ts +1 -0
- package/dist/nodes/addToolNode.d.ts.map +1 -0
- package/dist/nodes/addTriggerNode.d.ts +1 -0
- package/dist/nodes/addTriggerNode.d.ts.map +1 -0
- package/dist/nodes/nodeFactory.d.ts +1 -0
- package/dist/nodes/nodeFactory.d.ts.map +1 -0
- package/dist/platform/config.d.ts +1 -0
- package/dist/platform/config.d.ts.map +1 -0
- package/dist/platform/mindedChatOpenAI.d.ts +1 -0
- package/dist/platform/mindedChatOpenAI.d.ts.map +1 -0
- package/dist/platform/mindedCheckpointSaver.d.ts +1 -0
- package/dist/platform/mindedCheckpointSaver.d.ts.map +1 -0
- package/dist/platform/mindedConnection.d.ts +1 -0
- package/dist/platform/mindedConnection.d.ts.map +1 -0
- package/dist/platform/mindedConnectionTypes.d.ts +1 -0
- package/dist/platform/mindedConnectionTypes.d.ts.map +1 -0
- package/dist/triggers/triggerTypeToDefaultMessage.d.ts +1 -0
- package/dist/triggers/triggerTypeToDefaultMessage.d.ts.map +1 -0
- package/dist/types/Agent.types.d.ts +1 -0
- package/dist/types/Agent.types.d.ts.map +1 -0
- package/dist/types/Flows.types.d.ts +1 -0
- package/dist/types/Flows.types.d.ts.map +1 -0
- package/dist/types/LLM.types.d.ts +1 -0
- package/dist/types/LLM.types.d.ts.map +1 -0
- package/dist/types/LangGraph.types.d.ts +1 -0
- package/dist/types/LangGraph.types.d.ts.map +1 -0
- package/dist/types/Tools.types.d.ts +1 -0
- package/dist/types/Tools.types.d.ts.map +1 -0
- package/package.json +5 -1
- package/src/agent.ts +1 -1
- package/.github/workflows/CI.yml +0 -34
- package/.prettierrc +0 -8
- package/docs/.gitbook/assets/image.png +0 -0
- package/docs/README.md +0 -51
- package/docs/SUMMARY.md +0 -23
- package/docs/api-reference/.nojekyll +0 -1
- package/docs/api-reference/assets/hierarchy.js +0 -1
- package/docs/api-reference/assets/highlight.css +0 -120
- package/docs/api-reference/assets/icons.js +0 -18
- package/docs/api-reference/assets/icons.svg +0 -1
- package/docs/api-reference/assets/main.js +0 -60
- package/docs/api-reference/assets/navigation.js +0 -1
- package/docs/api-reference/assets/search.js +0 -1
- package/docs/api-reference/assets/style.css +0 -1640
- package/docs/api-reference/classes/index.Agent.html +0 -4
- package/docs/api-reference/enums/index.EdgeType.html +0 -4
- package/docs/api-reference/enums/index.NodeType.html +0 -6
- package/docs/api-reference/enums/index.TriggerType.html +0 -4
- package/docs/api-reference/enums/index.events.html +0 -3
- package/docs/api-reference/hierarchy.html +0 -1
- package/docs/api-reference/index.html +0 -310
- package/docs/api-reference/interfaces/index.AppToolNode.html +0 -5
- package/docs/api-reference/interfaces/index.AppTriggerNode.html +0 -6
- package/docs/api-reference/interfaces/index.Flow.html +0 -4
- package/docs/api-reference/interfaces/index.JunctionNode.html +0 -4
- package/docs/api-reference/interfaces/index.LogicalConditionEdge.html +0 -5
- package/docs/api-reference/interfaces/index.ManualTriggerNode.html +0 -5
- package/docs/api-reference/interfaces/index.PromptConditionEdge.html +0 -5
- package/docs/api-reference/interfaces/index.PromptNode.html +0 -6
- package/docs/api-reference/interfaces/index.StepForwardEdge.html +0 -4
- package/docs/api-reference/interfaces/index.Tool.html +0 -6
- package/docs/api-reference/interfaces/index.ToolNode.html +0 -5
- package/docs/api-reference/modules/index-1.html +0 -1
- package/docs/api-reference/modules/index.html +0 -1
- package/docs/api-reference/modules.html +0 -1
- package/docs/api-reference/types/index.Edge.html +0 -1
- package/docs/api-reference/types/index.Node.html +0 -1
- package/docs/api-reference/types/index.TriggerNode.html +0 -1
- package/docs/core-concepts/edges.md +0 -299
- package/docs/core-concepts/events.md +0 -165
- package/docs/core-concepts/flows.md +0 -74
- package/docs/core-concepts/memory-types.md +0 -208
- package/docs/core-concepts/nodes.md +0 -257
- package/docs/core-concepts/tools.md +0 -225
- package/docs/core-concepts/triggers.md +0 -87
- package/docs/examples/order-refund-flow.md +0 -560
- package/docs/getting-started/environment-configuration.md +0 -117
- package/docs/getting-started/installation.md +0 -40
- package/docs/getting-started/quick-start.md +0 -264
- package/docs-structure.md +0 -144
- package/eslint.config.js +0 -68
- package/examples/orderRefundAgent/flows/orderRefundFlow.yaml +0 -26
- package/examples/orderRefundAgent/minded.json +0 -10
- package/examples/orderRefundAgent/orderRefundAgent.ts +0 -64
- package/examples/orderRefundAgent/schema.ts +0 -7
- package/examples/orderRefundAgent/tools/escalateConversation.ts +0 -28
- package/examples/orderRefundAgent/tools/index.ts +0 -4
- package/examples/orderRefundAgent/tools/refundOrder.ts +0 -27
- package/test/can-stay-on-node/can-stay-on-node.test.ts +0 -154
- package/test/can-stay-on-node/flows/test-flow.yaml +0 -25
- package/test/cannot-stay-on-node/cannot-stay-on-node.test.ts +0 -209
- package/test/cannot-stay-on-node/flows/test-flow.yaml +0 -34
- package/test/checkpoint-saver/minded-checkpoint-saver-list.test.ts +0 -140
- package/test/checkpoint-saver/minded-checkpoint-saver.test.ts +0 -100
- package/test/edge-priority/edge-priority.test.ts +0 -182
- package/test/edge-priority/flows/all-three-edges/test-flow.yaml +0 -35
- package/test/edge-priority/flows/logical-prompt-edges/test-flow.yaml +0 -26
- package/test/edge-priority/flows/stepforward-logical-edges/test-flow.yaml +0 -25
- package/test/edge-priority/flows/stepforward-prompt-edges/test-flow.yaml +0 -25
- package/test/human-in-the-loop-node/flows/test-flow.yaml +0 -23
- package/test/human-in-the-loop-node/human-in-the-loop-node.test.ts +0 -94
- package/test/logical-edges/flows/logical-edge-test-flow.yaml +0 -24
- package/test/logical-edges/logical-edges.test.ts +0 -68
- package/test/no-human-in-the-loop-node/flows/test-flow.yaml +0 -23
- package/test/no-human-in-the-loop-node/no-human-in-the-loop-node.test.ts +0 -81
- package/test/prompt-edges/flows/test-flow.yaml +0 -24
- package/test/prompt-edges/prompt-edges.test.ts +0 -92
- package/test/prompt-node/flows/test-flow.yaml +0 -24
- package/test/prompt-node/prompt-node.test.ts +0 -88
- package/test/setup.ts +0 -5
- package/test/tool-node/flows/test-flow.yaml +0 -14
- package/test/tool-node/tool-node.test.ts +0 -68
- package/test/trigger/flows/test-flow.yaml +0 -7
- package/test/trigger/trigger.test.ts +0 -185
- package/tsconfig.json +0 -17
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { Agent } from '../../src/agent';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { AgentEvents } from '../../src/events/AgentEvents';
|
|
7
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
8
|
-
|
|
9
|
-
const memorySchema = z.object({
|
|
10
|
-
success: z.boolean(),
|
|
11
|
-
stayCount: z.number().optional(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('Can Stay On Node', function () {
|
|
15
|
-
this.timeout(10000); // Set timeout to 10 seconds for all tests in this suite
|
|
16
|
-
|
|
17
|
-
let agent: Agent;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
const flowsPath = path.join(__dirname, 'flows');
|
|
21
|
-
const toolsPath = path.join(__dirname, 'tools');
|
|
22
|
-
agent = new Agent({
|
|
23
|
-
memorySchema,
|
|
24
|
-
config: {
|
|
25
|
-
tools: [toolsPath],
|
|
26
|
-
flows: [flowsPath],
|
|
27
|
-
llm: {
|
|
28
|
-
name: 'ChatOpenAI',
|
|
29
|
-
properties: {
|
|
30
|
-
model: 'gpt-4o-mini',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
tools: [
|
|
35
|
-
{
|
|
36
|
-
name: 'successTool',
|
|
37
|
-
description: 'Success Tool that marks completion',
|
|
38
|
-
input: z.object({
|
|
39
|
-
name: z.string(),
|
|
40
|
-
}),
|
|
41
|
-
execute: async () => ({
|
|
42
|
-
memory: {
|
|
43
|
-
success: true,
|
|
44
|
-
},
|
|
45
|
-
}),
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// Add trigger event handler
|
|
51
|
-
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
|
|
52
|
-
if (triggerName === 'Trigger') {
|
|
53
|
-
return {
|
|
54
|
-
memory: {},
|
|
55
|
-
messages: [new HumanMessage(triggerBody.message)],
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should stay on the same node multiple times when instructed to stay, then continue', async function () {
|
|
62
|
-
this.timeout(20000); // Set timeout to 40 seconds for this test
|
|
63
|
-
|
|
64
|
-
const sessionId = uuidv4();
|
|
65
|
-
|
|
66
|
-
// First invocation - triggers the flow and reaches the human-in-the-loop pause
|
|
67
|
-
const result1 = await agent.invoke({
|
|
68
|
-
triggerBody: {
|
|
69
|
-
message: 'Initial input for processing',
|
|
70
|
-
},
|
|
71
|
-
triggerName: 'Trigger',
|
|
72
|
-
sessionId: sessionId,
|
|
73
|
-
appName: 'test',
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
// Should pause at human-in-the-loop, not reach success tool yet
|
|
77
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
78
|
-
expect(result1.messages).to.have.length.greaterThan(0);
|
|
79
|
-
|
|
80
|
-
// Second invocation - provide "stay" instruction
|
|
81
|
-
const result2 = await agent.invoke({
|
|
82
|
-
triggerBody: {
|
|
83
|
-
message: 'stay',
|
|
84
|
-
},
|
|
85
|
-
triggerName: 'Trigger',
|
|
86
|
-
sessionId: sessionId,
|
|
87
|
-
appName: 'test',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Should stay on the node, pause again, not reach success tool
|
|
91
|
-
expect(result2.memory.success).to.not.equal(true);
|
|
92
|
-
expect(result2.messages).to.have.length.greaterThan(0);
|
|
93
|
-
|
|
94
|
-
// Third invocation - provide another "stay" instruction
|
|
95
|
-
const result3 = await agent.invoke({
|
|
96
|
-
triggerBody: {
|
|
97
|
-
message: 'stay',
|
|
98
|
-
},
|
|
99
|
-
triggerName: 'Trigger',
|
|
100
|
-
sessionId: sessionId,
|
|
101
|
-
appName: 'test',
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Should still stay on the node, pause again
|
|
105
|
-
expect(result3.memory.success).to.not.equal(true);
|
|
106
|
-
expect(result3.messages).to.have.length.greaterThan(0);
|
|
107
|
-
|
|
108
|
-
// Fourth invocation - provide "continue" instruction
|
|
109
|
-
const result4 = await agent.invoke({
|
|
110
|
-
triggerBody: {
|
|
111
|
-
message: 'continue',
|
|
112
|
-
},
|
|
113
|
-
triggerName: 'Trigger',
|
|
114
|
-
sessionId: sessionId,
|
|
115
|
-
appName: 'test',
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
// Should now move to success tool and complete
|
|
119
|
-
expect(result4.memory.success).to.equal(true);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it('should move to next node immediately when instructed to continue', async function () {
|
|
123
|
-
this.timeout(20000); // Set timeout to 20 seconds for this test
|
|
124
|
-
|
|
125
|
-
const sessionId = uuidv4();
|
|
126
|
-
|
|
127
|
-
// First invocation - triggers the flow and reaches the human-in-the-loop pause
|
|
128
|
-
const result1 = await agent.invoke({
|
|
129
|
-
triggerBody: {
|
|
130
|
-
message: 'Initial input for processing',
|
|
131
|
-
},
|
|
132
|
-
triggerName: 'Trigger',
|
|
133
|
-
sessionId: sessionId,
|
|
134
|
-
appName: 'test',
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Should pause at human-in-the-loop, not reach success tool yet
|
|
138
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
139
|
-
expect(result1.messages).to.have.length.greaterThan(0);
|
|
140
|
-
|
|
141
|
-
// Second invocation - provide "continue" instruction
|
|
142
|
-
const result2 = await agent.invoke({
|
|
143
|
-
triggerBody: {
|
|
144
|
-
message: 'continue',
|
|
145
|
-
},
|
|
146
|
-
triggerName: 'Trigger',
|
|
147
|
-
sessionId: sessionId,
|
|
148
|
-
appName: 'test',
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Should now move to success tool and complete
|
|
152
|
-
expect(result2.memory.success).to.equal(true);
|
|
153
|
-
});
|
|
154
|
-
});
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
name: Can Stay On Node Test Flow
|
|
2
|
-
nodes:
|
|
3
|
-
- type: 'trigger'
|
|
4
|
-
triggerType: 'manual'
|
|
5
|
-
name: 'Trigger'
|
|
6
|
-
|
|
7
|
-
- type: 'promptNode'
|
|
8
|
-
name: 'DecisionNode'
|
|
9
|
-
prompt: 'I have processed your input. Would you like me to continue to the next step, or should I stay on this node for further processing? Please respond with "continue" to move forward or "stay" to remain here for additional analysis.'
|
|
10
|
-
canStayOnNode: true
|
|
11
|
-
humanInTheLoop: true
|
|
12
|
-
|
|
13
|
-
- type: 'tool'
|
|
14
|
-
name: 'Success'
|
|
15
|
-
toolName: 'successTool'
|
|
16
|
-
|
|
17
|
-
edges:
|
|
18
|
-
- source: 'Trigger'
|
|
19
|
-
target: 'DecisionNode'
|
|
20
|
-
type: 'stepForward'
|
|
21
|
-
|
|
22
|
-
- source: 'DecisionNode'
|
|
23
|
-
target: 'Success'
|
|
24
|
-
type: 'promptCondition'
|
|
25
|
-
prompt: 'move to success node if the user wants to continue or if processing is complete'
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { Agent } from '../../src/agent';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { AgentEvents } from '../../src/events/AgentEvents';
|
|
7
|
-
import { HumanMessage } from '@langchain/core/messages';
|
|
8
|
-
|
|
9
|
-
const memorySchema = z.object({
|
|
10
|
-
success: z.boolean(),
|
|
11
|
-
alternative: z.boolean(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('Cannot Stay On Node', function () {
|
|
15
|
-
this.timeout(10000); // Set timeout to 10 seconds for all tests in this suite
|
|
16
|
-
|
|
17
|
-
let agent: Agent;
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
const flowsPath = path.join(__dirname, 'flows');
|
|
21
|
-
const toolsPath = path.join(__dirname, 'tools');
|
|
22
|
-
agent = new Agent({
|
|
23
|
-
memorySchema,
|
|
24
|
-
config: {
|
|
25
|
-
flows: [flowsPath],
|
|
26
|
-
tools: [toolsPath],
|
|
27
|
-
llm: {
|
|
28
|
-
name: 'ChatOpenAI',
|
|
29
|
-
properties: {
|
|
30
|
-
model: 'gpt-4o-mini',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
tools: [
|
|
35
|
-
{
|
|
36
|
-
name: 'successTool',
|
|
37
|
-
description: 'Success Tool that marks completion',
|
|
38
|
-
input: z.object({
|
|
39
|
-
name: z.string(),
|
|
40
|
-
}),
|
|
41
|
-
execute: async () => ({
|
|
42
|
-
memory: {
|
|
43
|
-
success: true,
|
|
44
|
-
alternative: false,
|
|
45
|
-
},
|
|
46
|
-
}),
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
name: 'alternativeTool',
|
|
50
|
-
description: 'Alternative Tool that marks alternative completion',
|
|
51
|
-
input: z.object({
|
|
52
|
-
name: z.string(),
|
|
53
|
-
}),
|
|
54
|
-
execute: async () => ({
|
|
55
|
-
memory: {
|
|
56
|
-
success: false,
|
|
57
|
-
alternative: true,
|
|
58
|
-
},
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// Add trigger event handler
|
|
65
|
-
agent.on(AgentEvents.TRIGGER_EVENT, async ({ triggerName, triggerBody }) => {
|
|
66
|
-
if (triggerName === 'Trigger') {
|
|
67
|
-
return {
|
|
68
|
-
memory: {},
|
|
69
|
-
messages: [new HumanMessage(triggerBody.message)],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should NOT stay on node when explicitly requested to stay (canStayOnNode is false)', async function () {
|
|
76
|
-
this.timeout(20000);
|
|
77
|
-
|
|
78
|
-
const sessionId = uuidv4();
|
|
79
|
-
|
|
80
|
-
// First invocation - triggers the flow and reaches the human-in-the-loop pause
|
|
81
|
-
const result1 = await agent.invoke({
|
|
82
|
-
triggerBody: {
|
|
83
|
-
message: 'Initial input for processing',
|
|
84
|
-
},
|
|
85
|
-
triggerName: 'Trigger',
|
|
86
|
-
sessionId: sessionId,
|
|
87
|
-
appName: 'test',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Should pause at human-in-the-loop, not reach any tool yet
|
|
91
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
92
|
-
expect(result1.memory.alternative).to.not.equal(true);
|
|
93
|
-
expect(result1.messages).to.have.length.greaterThan(0);
|
|
94
|
-
|
|
95
|
-
// Second invocation - explicitly request to stay on the node
|
|
96
|
-
const result2 = await agent.invoke({
|
|
97
|
-
triggerBody: {
|
|
98
|
-
message: 'You must stay at the node! Do not move to the next step. Stay here and continue processing on this same node.',
|
|
99
|
-
},
|
|
100
|
-
triggerName: 'Trigger',
|
|
101
|
-
sessionId: sessionId,
|
|
102
|
-
appName: 'test',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// Should NOT stay on the node - should move to one of the target nodes
|
|
106
|
-
// Since canStayOnNode is false, the router has no self-referencing edge option
|
|
107
|
-
expect(result2.memory.success || result2.memory.alternative).to.equal(true);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('should move to success node when user requests main workflow', async function () {
|
|
111
|
-
this.timeout(20000);
|
|
112
|
-
|
|
113
|
-
const sessionId = uuidv4();
|
|
114
|
-
|
|
115
|
-
// First invocation - initial trigger
|
|
116
|
-
const result1 = await agent.invoke({
|
|
117
|
-
triggerBody: {
|
|
118
|
-
message: 'Start processing',
|
|
119
|
-
},
|
|
120
|
-
triggerName: 'Trigger',
|
|
121
|
-
sessionId: sessionId,
|
|
122
|
-
appName: 'test',
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
126
|
-
expect(result1.memory.alternative).to.not.equal(true);
|
|
127
|
-
|
|
128
|
-
// Second invocation - request main workflow
|
|
129
|
-
const result2 = await agent.invoke({
|
|
130
|
-
triggerBody: {
|
|
131
|
-
message: 'Proceed with the main workflow',
|
|
132
|
-
},
|
|
133
|
-
triggerName: 'Trigger',
|
|
134
|
-
sessionId: sessionId,
|
|
135
|
-
appName: 'test',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// Should move to success node
|
|
139
|
-
expect(result2.memory.success).to.equal(true);
|
|
140
|
-
expect(result2.memory.alternative).to.equal(false);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should move to alternative node when user requests alternative approach', async function () {
|
|
144
|
-
this.timeout(20000);
|
|
145
|
-
|
|
146
|
-
const sessionId = uuidv4();
|
|
147
|
-
|
|
148
|
-
// First invocation - initial trigger
|
|
149
|
-
const result1 = await agent.invoke({
|
|
150
|
-
triggerBody: {
|
|
151
|
-
message: 'Begin workflow',
|
|
152
|
-
},
|
|
153
|
-
triggerName: 'Trigger',
|
|
154
|
-
sessionId: sessionId,
|
|
155
|
-
appName: 'test',
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
159
|
-
expect(result1.memory.alternative).to.not.equal(true);
|
|
160
|
-
|
|
161
|
-
// Second invocation - request alternative approach
|
|
162
|
-
const result2 = await agent.invoke({
|
|
163
|
-
triggerBody: {
|
|
164
|
-
message: 'I want an alternative approach instead',
|
|
165
|
-
},
|
|
166
|
-
triggerName: 'Trigger',
|
|
167
|
-
sessionId: sessionId,
|
|
168
|
-
appName: 'test',
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// Should move to alternative node
|
|
172
|
-
expect(result2.memory.success).to.equal(false);
|
|
173
|
-
expect(result2.memory.alternative).to.equal(true);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it('should force progression even with strong stay requests', async function () {
|
|
177
|
-
this.timeout(20000);
|
|
178
|
-
|
|
179
|
-
const sessionId = uuidv4();
|
|
180
|
-
|
|
181
|
-
// First invocation - initial trigger
|
|
182
|
-
const result1 = await agent.invoke({
|
|
183
|
-
triggerBody: {
|
|
184
|
-
message: 'Process this data',
|
|
185
|
-
},
|
|
186
|
-
triggerName: 'Trigger',
|
|
187
|
-
sessionId: sessionId,
|
|
188
|
-
appName: 'test',
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
expect(result1.memory.success).to.not.equal(true);
|
|
192
|
-
expect(result1.memory.alternative).to.not.equal(true);
|
|
193
|
-
|
|
194
|
-
// Second invocation - very strong request to stay
|
|
195
|
-
const result2 = await agent.invoke({
|
|
196
|
-
triggerBody: {
|
|
197
|
-
message:
|
|
198
|
-
'STAY! Do not proceed! You must remain on this current node and not move forward. Keep processing here. Do not go to any other node.',
|
|
199
|
-
},
|
|
200
|
-
triggerName: 'Trigger',
|
|
201
|
-
sessionId: sessionId,
|
|
202
|
-
appName: 'test',
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
// Should still be forced to move to one of the available target nodes
|
|
206
|
-
// because there's no self-referencing edge (canStayOnNode is false)
|
|
207
|
-
expect(result2.memory.success || result2.memory.alternative).to.equal(true);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Cannot Stay On Node Test Flow
|
|
2
|
-
nodes:
|
|
3
|
-
- type: 'trigger'
|
|
4
|
-
triggerType: 'manual'
|
|
5
|
-
name: 'Trigger'
|
|
6
|
-
|
|
7
|
-
- type: 'promptNode'
|
|
8
|
-
name: 'ProcessingNode'
|
|
9
|
-
prompt: 'I have processed your input. Please provide your feedback or additional instructions.'
|
|
10
|
-
humanInTheLoop: true
|
|
11
|
-
# Note: canStayOnNode is NOT set to true
|
|
12
|
-
|
|
13
|
-
- type: 'tool'
|
|
14
|
-
name: 'Success'
|
|
15
|
-
toolName: 'successTool'
|
|
16
|
-
|
|
17
|
-
- type: 'tool'
|
|
18
|
-
name: 'Alternative'
|
|
19
|
-
toolName: 'alternativeTool'
|
|
20
|
-
|
|
21
|
-
edges:
|
|
22
|
-
- source: 'Trigger'
|
|
23
|
-
target: 'ProcessingNode'
|
|
24
|
-
type: 'stepForward'
|
|
25
|
-
|
|
26
|
-
- source: 'ProcessingNode'
|
|
27
|
-
target: 'Success'
|
|
28
|
-
type: 'promptCondition'
|
|
29
|
-
prompt: 'move to success node if the user wants to proceed with the main workflow'
|
|
30
|
-
|
|
31
|
-
- source: 'ProcessingNode'
|
|
32
|
-
target: 'Alternative'
|
|
33
|
-
type: 'promptCondition'
|
|
34
|
-
prompt: 'move to alternative node if the user wants an alternative approach'
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
3
|
-
import * as sinon from 'sinon';
|
|
4
|
-
import { MindedCheckpointSaver } from '../../src/platform/mindedCheckpointSaver';
|
|
5
|
-
import { Checkpoint, CheckpointTuple, CheckpointMetadata, emptyCheckpoint, CheckpointListOptions } from '@langchain/langgraph-checkpoint';
|
|
6
|
-
import { RunnableConfig } from '@langchain/core/runnables';
|
|
7
|
-
import { HumanMessage, AIMessage, SystemMessage } from '@langchain/core/messages';
|
|
8
|
-
import { MindedConnection } from '../../src/platform/mindedConnection';
|
|
9
|
-
import { MindedConnectionSocketMessageType } from '../../src/platform/mindedConnectionTypes';
|
|
10
|
-
|
|
11
|
-
describe('MindedCheckpointSaver - List Method', function () {
|
|
12
|
-
let checkpointSaver: MindedCheckpointSaver;
|
|
13
|
-
let mindedConnection: MindedConnection;
|
|
14
|
-
let awaitEmitStub: sinon.SinonStub;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
mindedConnection = new MindedConnection();
|
|
18
|
-
checkpointSaver = new MindedCheckpointSaver(mindedConnection);
|
|
19
|
-
awaitEmitStub = sinon.stub(mindedConnection, 'awaitEmit');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
sinon.restore();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('list', () => {
|
|
27
|
-
it('should deserialize multiple checkpoints with different message types', async () => {
|
|
28
|
-
// Create original checkpoints with proper class instances
|
|
29
|
-
const checkpoint1: Checkpoint = {
|
|
30
|
-
...emptyCheckpoint(),
|
|
31
|
-
id: 'checkpoint-1',
|
|
32
|
-
channel_values: {
|
|
33
|
-
messages: [new HumanMessage('First message')],
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const checkpoint2: Checkpoint = {
|
|
38
|
-
...emptyCheckpoint(),
|
|
39
|
-
id: 'checkpoint-2',
|
|
40
|
-
channel_values: {
|
|
41
|
-
messages: [new SystemMessage('System prompt'), new AIMessage('AI response')],
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const metadata1: CheckpointMetadata = {
|
|
46
|
-
source: 'input',
|
|
47
|
-
step: 1,
|
|
48
|
-
writes: {},
|
|
49
|
-
parents: {},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
const metadata2: CheckpointMetadata = {
|
|
53
|
-
source: 'loop',
|
|
54
|
-
step: 2,
|
|
55
|
-
writes: {},
|
|
56
|
-
parents: {},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// Simulate JSON serialization (what happens during HTTP transport)
|
|
60
|
-
const jsonCheckpoint1 = JSON.parse(JSON.stringify(checkpoint1));
|
|
61
|
-
const jsonCheckpoint2 = JSON.parse(JSON.stringify(checkpoint2));
|
|
62
|
-
const jsonMetadata1 = JSON.parse(JSON.stringify(metadata1));
|
|
63
|
-
const jsonMetadata2 = JSON.parse(JSON.stringify(metadata2));
|
|
64
|
-
|
|
65
|
-
const mockServerResponse = {
|
|
66
|
-
checkpoints: [
|
|
67
|
-
{
|
|
68
|
-
config: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } },
|
|
69
|
-
checkpoint: jsonCheckpoint1,
|
|
70
|
-
metadata: jsonMetadata1,
|
|
71
|
-
parentConfig: undefined,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
config: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-2' } },
|
|
75
|
-
checkpoint: jsonCheckpoint2,
|
|
76
|
-
metadata: jsonMetadata2,
|
|
77
|
-
parentConfig: { configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } },
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Mock the awaitEmit response
|
|
83
|
-
awaitEmitStub.resolves(mockServerResponse);
|
|
84
|
-
|
|
85
|
-
const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
|
|
86
|
-
const options: CheckpointListOptions = { limit: 10 };
|
|
87
|
-
|
|
88
|
-
const results: CheckpointTuple[] = [];
|
|
89
|
-
for await (const tuple of checkpointSaver.list(config, options)) {
|
|
90
|
-
results.push(tuple);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Verify the awaitEmit was called correctly
|
|
94
|
-
expect(awaitEmitStub.calledOnce).to.equal(true);
|
|
95
|
-
expect(awaitEmitStub.firstCall.args[0]).to.equal(MindedConnectionSocketMessageType.CHECKPOINT_LIST);
|
|
96
|
-
expect(awaitEmitStub.firstCall.args[1]).to.deep.equal({
|
|
97
|
-
type: MindedConnectionSocketMessageType.CHECKPOINT_LIST,
|
|
98
|
-
config,
|
|
99
|
-
options,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Verify we got the correct number of results
|
|
103
|
-
expect(results).to.have.length(2);
|
|
104
|
-
|
|
105
|
-
// Verify first checkpoint - JSON objects should be restored to proper classes
|
|
106
|
-
const result1 = results[0];
|
|
107
|
-
expect(result1.checkpoint.id).to.equal('checkpoint-1');
|
|
108
|
-
expect(result1.metadata?.source).to.equal('input');
|
|
109
|
-
const messages1 = result1.checkpoint.channel_values.messages as any[];
|
|
110
|
-
expect(messages1).to.have.length(1);
|
|
111
|
-
expect(messages1[0]).to.be.instanceOf(HumanMessage);
|
|
112
|
-
expect(messages1[0].content).to.equal('First message');
|
|
113
|
-
|
|
114
|
-
// Verify second checkpoint - JSON objects should be restored to proper classes
|
|
115
|
-
const result2 = results[1];
|
|
116
|
-
expect(result2.checkpoint.id).to.equal('checkpoint-2');
|
|
117
|
-
expect(result2.metadata?.source).to.equal('loop');
|
|
118
|
-
expect(result2.parentConfig).to.deep.equal({ configurable: { thread_id: 'test-thread', checkpoint_id: 'checkpoint-1' } });
|
|
119
|
-
const messages2 = result2.checkpoint.channel_values.messages as any[];
|
|
120
|
-
expect(messages2).to.have.length(2);
|
|
121
|
-
expect(messages2[0]).to.be.instanceOf(SystemMessage);
|
|
122
|
-
expect(messages2[1]).to.be.instanceOf(AIMessage);
|
|
123
|
-
expect(messages2[0].content).to.equal('System prompt');
|
|
124
|
-
expect(messages2[1].content).to.equal('AI response');
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should handle empty checkpoint list', async () => {
|
|
128
|
-
awaitEmitStub.resolves({ checkpoints: [] });
|
|
129
|
-
|
|
130
|
-
const config: RunnableConfig = { configurable: { thread_id: 'empty-thread' } };
|
|
131
|
-
|
|
132
|
-
const results: CheckpointTuple[] = [];
|
|
133
|
-
for await (const tuple of checkpointSaver.list(config)) {
|
|
134
|
-
results.push(tuple);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
expect(results).to.have.length(0);
|
|
138
|
-
});
|
|
139
|
-
});
|
|
140
|
-
});
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { expect } from 'chai';
|
|
2
|
-
import { describe, it, beforeEach, afterEach } from 'mocha';
|
|
3
|
-
import * as sinon from 'sinon';
|
|
4
|
-
import { MindedCheckpointSaver } from '../../src/platform/mindedCheckpointSaver';
|
|
5
|
-
import { Checkpoint, CheckpointMetadata, emptyCheckpoint } from '@langchain/langgraph-checkpoint';
|
|
6
|
-
import { RunnableConfig } from '@langchain/core/runnables';
|
|
7
|
-
import { HumanMessage, AIMessage } from '@langchain/core/messages';
|
|
8
|
-
import { MindedConnection } from '../../src/platform/mindedConnection';
|
|
9
|
-
import { MindedConnectionSocketMessageType } from '../../src/platform/mindedConnectionTypes';
|
|
10
|
-
|
|
11
|
-
describe('MindedCheckpointSaver', function () {
|
|
12
|
-
let checkpointSaver: MindedCheckpointSaver;
|
|
13
|
-
let mindedConnection: MindedConnection;
|
|
14
|
-
let awaitEmitStub: sinon.SinonStub;
|
|
15
|
-
|
|
16
|
-
beforeEach(() => {
|
|
17
|
-
mindedConnection = new MindedConnection();
|
|
18
|
-
checkpointSaver = new MindedCheckpointSaver(mindedConnection);
|
|
19
|
-
awaitEmitStub = sinon.stub(mindedConnection, 'awaitEmit');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
afterEach(() => {
|
|
23
|
-
sinon.restore();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
describe('getTuple', () => {
|
|
27
|
-
it('should deserialize checkpoint and metadata correctly', async () => {
|
|
28
|
-
// First, let's create proper objects and see what the serializer produces
|
|
29
|
-
const originalHumanMessage = new HumanMessage('Hello');
|
|
30
|
-
const originalAIMessage = new AIMessage('Hi there!');
|
|
31
|
-
|
|
32
|
-
// Create a checkpoint with these messages
|
|
33
|
-
const originalCheckpoint: Checkpoint = {
|
|
34
|
-
...emptyCheckpoint(),
|
|
35
|
-
id: 'test-checkpoint-id',
|
|
36
|
-
channel_values: {
|
|
37
|
-
messages: [originalHumanMessage, originalAIMessage],
|
|
38
|
-
},
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const originalMetadata: CheckpointMetadata = {
|
|
42
|
-
source: 'input',
|
|
43
|
-
step: 1,
|
|
44
|
-
writes: {},
|
|
45
|
-
parents: {},
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
// Simulate what happens when these objects go through JSON serialization (HTTP transport)
|
|
49
|
-
// This is what the server would actually return - plain JSON objects without class info
|
|
50
|
-
const jsonSerializedCheckpoint = JSON.parse(JSON.stringify(originalCheckpoint));
|
|
51
|
-
const jsonSerializedMetadata = JSON.parse(JSON.stringify(originalMetadata));
|
|
52
|
-
|
|
53
|
-
const mockServerResponse = {
|
|
54
|
-
tuple: {
|
|
55
|
-
config: { configurable: { thread_id: 'test-thread' } },
|
|
56
|
-
checkpoint: jsonSerializedCheckpoint,
|
|
57
|
-
metadata: jsonSerializedMetadata,
|
|
58
|
-
parentConfig: undefined,
|
|
59
|
-
pendingWrites: [],
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
// Mock the awaitEmit response
|
|
64
|
-
awaitEmitStub.resolves(mockServerResponse);
|
|
65
|
-
|
|
66
|
-
const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
|
|
67
|
-
const result = await checkpointSaver.getTuple(config);
|
|
68
|
-
|
|
69
|
-
// Verify the awaitEmit was called correctly
|
|
70
|
-
expect(awaitEmitStub.calledOnce).to.equal(true);
|
|
71
|
-
expect(awaitEmitStub.firstCall.args[0]).to.equal(MindedConnectionSocketMessageType.CHECKPOINT_GET_TUPLE);
|
|
72
|
-
expect(awaitEmitStub.firstCall.args[1]).to.deep.equal({
|
|
73
|
-
type: MindedConnectionSocketMessageType.CHECKPOINT_GET_TUPLE,
|
|
74
|
-
config,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// Verify the result structure
|
|
78
|
-
expect(result).to.not.equal(undefined);
|
|
79
|
-
expect(result!.config).to.deep.equal(mockServerResponse.tuple.config);
|
|
80
|
-
expect(result!.checkpoint.id).to.equal('test-checkpoint-id');
|
|
81
|
-
|
|
82
|
-
// This is the key test - the JSON objects should be restored to proper classes
|
|
83
|
-
const messages = result!.checkpoint.channel_values.messages as any[];
|
|
84
|
-
expect(messages).to.have.length(2);
|
|
85
|
-
expect(messages[0]).to.be.instanceOf(HumanMessage);
|
|
86
|
-
expect(messages[1]).to.be.instanceOf(AIMessage);
|
|
87
|
-
expect(messages[0].content).to.equal('Hello');
|
|
88
|
-
expect(messages[1].content).to.equal('Hi there!');
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
it('should return undefined when server returns no tuple', async () => {
|
|
92
|
-
awaitEmitStub.resolves({ tuple: undefined });
|
|
93
|
-
|
|
94
|
-
const config: RunnableConfig = { configurable: { thread_id: 'test-thread' } };
|
|
95
|
-
const result = await checkpointSaver.getTuple(config);
|
|
96
|
-
|
|
97
|
-
expect(result).to.equal(undefined);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
});
|