@langgraph-js/pure-graph 1.4.2 → 1.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,177 +0,0 @@
1
- import { EventEmitter } from 'eventemitter3';
2
- import { JsonPlusSerializer } from './JsonPlusSerializer.js';
3
- /**
4
- * 基础流队列类
5
- * Base stream queue class
6
- */
7
- export class BaseStreamQueue extends EventEmitter {
8
- id;
9
- compressMessages;
10
- /** 序列化器实例 / Serializer instance */
11
- serializer = new JsonPlusSerializer();
12
- /**
13
- * 构造函数
14
- * Constructor
15
- * @param compressMessages 是否压缩消息 / Whether to compress messages
16
- */
17
- constructor(id, compressMessages = true) {
18
- super();
19
- this.id = id;
20
- this.compressMessages = compressMessages;
21
- }
22
- /**
23
- * 编码数据为 Uint8Array
24
- * Encode data to Uint8Array
25
- * @param message 要编码的消息 / Message to encode
26
- * @returns 编码后的 Uint8Array / Encoded Uint8Array
27
- */
28
- async encodeData(message) {
29
- const [_, serializedMessage] = await this.serializer.dumpsTyped(message);
30
- return serializedMessage;
31
- }
32
- /**
33
- * 解码数据为 EventMessage
34
- * Decode data to EventMessage
35
- * @param serializedMessage 要解码的消息 / Message to decode
36
- * @returns 解码后的 EventMessage / Decoded EventMessage
37
- */
38
- async decodeData(serializedMessage) {
39
- const message = (await this.serializer.loadsTyped('json', serializedMessage));
40
- return message;
41
- }
42
- }
43
- /**
44
- * StreamQueue 管理器,通过 id 管理多个队列实例
45
- * StreamQueue manager, manages multiple queue instances by id
46
- */
47
- export class StreamQueueManager {
48
- /** 存储队列实例的 Map / Map storing queue instances */
49
- queues = new Map();
50
- /** 默认是否压缩消息 / Default compress messages setting */
51
- defaultCompressMessages;
52
- /** 队列构造函数 / Queue constructor */
53
- queueConstructor;
54
- /**
55
- * 构造函数
56
- * Constructor
57
- * @param queueConstructor 队列构造函数 / Queue constructor
58
- * @param options 配置选项 / Configuration options
59
- */
60
- constructor(queueConstructor, options = {}) {
61
- this.defaultCompressMessages = options.defaultCompressMessages ?? true;
62
- this.queueConstructor = queueConstructor;
63
- }
64
- /**
65
- * 创建指定 id 的队列
66
- * Create queue with specified id
67
- * @param id 队列 ID / Queue ID
68
- * @param compressMessages 是否压缩消息 / Whether to compress messages
69
- * @returns 创建的队列实例 / Created queue instance
70
- */
71
- createQueue(id, compressMessages) {
72
- const compress = compressMessages ?? this.defaultCompressMessages;
73
- this.queues.set(id, new this.queueConstructor(id));
74
- return this.queues.get(id);
75
- }
76
- /**
77
- * 获取或创建指定 id 的队列
78
- * Get or create queue with specified id
79
- * @param id 队列 ID / Queue ID
80
- * @param compressMessages 是否压缩消息,默认为构造函数中的默认值 / Whether to compress messages, defaults to constructor default
81
- * @returns StreamQueue 实例 / StreamQueue instance
82
- */
83
- getQueue(id) {
84
- const queue = this.queues.get(id);
85
- if (!queue) {
86
- throw new Error(`Queue with id '${id}' does not exist`);
87
- }
88
- return queue;
89
- }
90
- /**
91
- * 取消指定 id 的队列
92
- * Cancel queue with specified id
93
- * @param id 队列 ID / Queue ID
94
- */
95
- cancelQueue(id) {
96
- const queue = this.queues.get(id);
97
- if (queue) {
98
- queue.cancel();
99
- this.removeQueue(id);
100
- }
101
- }
102
- /**
103
- * 向指定 id 的队列推送数据
104
- * Push data to queue with specified id
105
- * @param id 队列 ID / Queue ID
106
- * @param item 要推送的数据项 / Item to push
107
- * @param compressMessages 是否压缩消息,默认为构造函数中的默认值 / Whether to compress messages, defaults to constructor default
108
- */
109
- async pushToQueue(id, item, compressMessages) {
110
- const queue = this.getQueue(id);
111
- await queue.push(item);
112
- }
113
- /**
114
- * 获取指定 id 队列中的所有数据
115
- * Get all data from queue with specified id
116
- * @param id 队列 ID / Queue ID
117
- * @returns 队列中的所有数据 / All data in the queue
118
- */
119
- async getQueueData(id) {
120
- const queue = this.queues.get(id);
121
- if (!queue) {
122
- throw new Error(`Queue with id '${id}' does not exist`);
123
- }
124
- return await queue.getAll();
125
- }
126
- /**
127
- * 清空指定 id 的队列
128
- * Clear queue with specified id
129
- * @param id 队列 ID / Queue ID
130
- */
131
- clearQueue(id) {
132
- const queue = this.queues.get(id);
133
- if (queue) {
134
- queue.clear();
135
- }
136
- }
137
- /**
138
- * 删除指定 id 的队列
139
- * Remove queue with specified id
140
- * @param id 队列 ID / Queue ID
141
- * @returns 是否成功删除 / Whether successfully deleted
142
- */
143
- removeQueue(id) {
144
- setTimeout(() => {
145
- return this.queues.delete(id);
146
- }, 500);
147
- }
148
- /**
149
- * 获取所有队列的 ID
150
- * Get all queue IDs
151
- * @returns 所有队列 ID 的数组 / Array of all queue IDs
152
- */
153
- getAllQueueIds() {
154
- return Array.from(this.queues.keys());
155
- }
156
- /**
157
- * 获取所有队列及其数据的快照
158
- * Get snapshot of all queues and their data
159
- * @returns 包含所有队列数据的结果对象 / Result object containing all queue data
160
- */
161
- async getAllQueuesData() {
162
- const result = {};
163
- for (const [id, queue] of this.queues) {
164
- result[id] = await queue.getAll();
165
- }
166
- return result;
167
- }
168
- /**
169
- * 清空所有队列
170
- * Clear all queues
171
- */
172
- clearAllQueues() {
173
- for (const queue of this.queues.values()) {
174
- queue.clear();
175
- }
176
- }
177
- }
@@ -1,64 +0,0 @@
1
- import { StreamQueueManager } from '../queue/stream_queue';
2
- import { MemorySaver } from './memory/checkpoint';
3
- import { MemoryStreamQueue } from './memory/queue';
4
- import { MemoryThreadsManager } from './memory/threads';
5
- import { SQLiteThreadsManager } from './sqlite/threads';
6
- // 所有的适配实现,都请写到这里,通过环境变量进行判断使用哪种方式进行适配
7
- export const createCheckPointer = async () => {
8
- if ((process.env.REDIS_URL && process.env.CHECKPOINT_TYPE === 'redis') ||
9
- process.env.CHECKPOINT_TYPE === 'shallow/redis') {
10
- if (process.env.CHECKPOINT_TYPE === 'redis') {
11
- console.debug('LG | Using redis as checkpoint');
12
- const { RedisSaver } = await import('@langchain/langgraph-checkpoint-redis');
13
- return await RedisSaver.fromUrl(process.env.REDIS_URL, {
14
- defaultTTL: 60, // TTL in minutes
15
- refreshOnRead: true,
16
- });
17
- }
18
- if (process.env.CHECKPOINT_TYPE === 'shallow/redis') {
19
- console.debug('LG | Using shallow redis as checkpoint');
20
- const { ShallowRedisSaver } = await import('@langchain/langgraph-checkpoint-redis/shallow');
21
- return await ShallowRedisSaver.fromUrl(process.env.REDIS_URL);
22
- }
23
- }
24
- if (process.env.DATABASE_URL) {
25
- console.debug('LG | Using postgres as checkpoint');
26
- const { createPGCheckpoint } = await import('./pg/checkpoint');
27
- return createPGCheckpoint();
28
- }
29
- if (process.env.SQLITE_DATABASE_URI) {
30
- console.debug('LG | Using sqlite as checkpoint');
31
- const { SqliteSaver } = await import('./sqlite/checkpoint');
32
- const db = SqliteSaver.fromConnString(process.env.SQLITE_DATABASE_URI);
33
- return db;
34
- }
35
- return new MemorySaver();
36
- };
37
- export const createMessageQueue = async () => {
38
- let q;
39
- if (process.env.REDIS_URL) {
40
- console.debug('LG | Using redis as stream queue');
41
- const { RedisStreamQueue } = await import('./redis/queue');
42
- q = RedisStreamQueue;
43
- }
44
- else {
45
- q = MemoryStreamQueue;
46
- }
47
- return new StreamQueueManager(q);
48
- };
49
- export const createThreadManager = async (config) => {
50
- if (process.env.DATABASE_URL && config.checkpointer) {
51
- const { PostgresThreadsManager } = await import('./pg/threads');
52
- const threadsManager = new PostgresThreadsManager(config.checkpointer);
53
- if (process.env.DATABASE_INIT === 'true') {
54
- await threadsManager.setup();
55
- }
56
- return threadsManager;
57
- }
58
- if (process.env.SQLITE_DATABASE_URI && config.checkpointer) {
59
- const threadsManager = new SQLiteThreadsManager(config.checkpointer);
60
- await threadsManager.setup();
61
- return threadsManager;
62
- }
63
- return new MemoryThreadsManager();
64
- };
@@ -1,2 +0,0 @@
1
- import { MemorySaver } from '@langchain/langgraph-checkpoint';
2
- export { MemorySaver };
@@ -1,81 +0,0 @@
1
- import { CancelEventMessage } from '../../queue/event_message.js';
2
- import { BaseStreamQueue } from '../../queue/stream_queue.js';
3
- /** 内存实现的消息队列,用于存储消息 */
4
- export class MemoryStreamQueue extends BaseStreamQueue {
5
- data = [];
6
- async push(item) {
7
- const data = this.compressMessages ? (await this.encodeData(item)) : item;
8
- this.data.push(data);
9
- this.emit('dataChange', data);
10
- }
11
- onDataChange(listener) {
12
- this.on('dataChange', async (item) => {
13
- listener(this.compressMessages ? (await this.decodeData(item)) : item);
14
- });
15
- return () => this.off('dataChange', listener);
16
- }
17
- /**
18
- * 异步生成器:支持 for await...of 方式消费队列数据
19
- */
20
- async *onDataReceive() {
21
- let queue = [];
22
- let pendingResolve = null;
23
- let isStreamEnded = false;
24
- const handleData = async (item) => {
25
- const data = this.compressMessages ? (await this.decodeData(item)) : item;
26
- queue.push(data);
27
- // 检查是否为流结束或错误信号
28
- if (data.event === '__stream_end__' ||
29
- data.event === '__stream_error__' ||
30
- data.event === '__stream_cancel__') {
31
- setTimeout(() => {
32
- isStreamEnded = true;
33
- if (pendingResolve) {
34
- pendingResolve();
35
- pendingResolve = null;
36
- }
37
- }, 300);
38
- if (data.event === '__stream_cancel__') {
39
- this.cancel();
40
- }
41
- }
42
- if (pendingResolve) {
43
- pendingResolve();
44
- pendingResolve = null;
45
- }
46
- };
47
- // todo 这个框架的事件监听的数据返回顺序有误
48
- this.on('dataChange', handleData);
49
- try {
50
- while (!isStreamEnded) {
51
- if (queue.length > 0) {
52
- for (const item of queue) {
53
- yield item;
54
- }
55
- queue = [];
56
- }
57
- else {
58
- await new Promise((resolve) => {
59
- pendingResolve = resolve;
60
- });
61
- }
62
- }
63
- }
64
- finally {
65
- this.off('dataChange', handleData);
66
- }
67
- }
68
- async getAll() {
69
- return this.compressMessages
70
- ? (await Promise.all(this.data.map((i) => this.decodeData(i))))
71
- : this.data;
72
- }
73
- clear() {
74
- this.data = [];
75
- }
76
- cancelSignal = new AbortController();
77
- cancel() {
78
- this.push(new CancelEventMessage());
79
- this.cancelSignal.abort('user cancel this run');
80
- }
81
- }
@@ -1,145 +0,0 @@
1
- import { getGraph } from '../../utils/getGraph.js';
2
- import { serialiseAsDict } from '../../graph/stream.js';
3
- export class MemoryThreadsManager {
4
- threads = [];
5
- async setup() {
6
- return;
7
- }
8
- async create(payload) {
9
- const threadId = payload?.threadId || crypto.randomUUID();
10
- if (payload?.ifExists === 'raise' && this.threads.some((t) => t.thread_id === threadId)) {
11
- throw new Error(`Thread with ID ${threadId} already exists.`);
12
- }
13
- const thread = {
14
- thread_id: threadId,
15
- created_at: new Date().toISOString(),
16
- updated_at: new Date().toISOString(),
17
- metadata: payload?.metadata || {},
18
- status: 'idle',
19
- values: null,
20
- interrupts: {},
21
- };
22
- this.threads.push(thread);
23
- return thread;
24
- }
25
- async search(query) {
26
- let filteredThreads = [...this.threads];
27
- if (query?.status) {
28
- filteredThreads = filteredThreads.filter((t) => t.status === query.status);
29
- }
30
- if (query?.metadata) {
31
- for (const key in query.metadata) {
32
- if (Object.prototype.hasOwnProperty.call(query.metadata, key)) {
33
- filteredThreads = filteredThreads.filter((t) => t.metadata && t.metadata[key] === query.metadata?.[key]);
34
- }
35
- }
36
- }
37
- if (query?.sortBy) {
38
- filteredThreads.sort((a, b) => {
39
- let aValue;
40
- let bValue;
41
- switch (query.sortBy) {
42
- case 'created_at':
43
- aValue = new Date(a.created_at).getTime();
44
- bValue = new Date(b.created_at).getTime();
45
- break;
46
- case 'updated_at':
47
- aValue = new Date(a.updated_at).getTime();
48
- bValue = new Date(b.updated_at).getTime();
49
- break;
50
- default:
51
- return 0;
52
- }
53
- if (query.sortOrder === 'desc') {
54
- return bValue - aValue;
55
- }
56
- else {
57
- return aValue - bValue;
58
- }
59
- });
60
- }
61
- const offset = query?.offset || 0;
62
- const limit = query?.limit || filteredThreads.length;
63
- return filteredThreads.slice(offset, offset + limit);
64
- }
65
- async get(threadId) {
66
- const thread = this.threads.find((t) => t.thread_id === threadId);
67
- if (!thread) {
68
- throw new Error(`Thread with ID ${threadId} not found.`);
69
- }
70
- return thread;
71
- }
72
- async set(threadId, thread) {
73
- const index = this.threads.findIndex((t) => t.thread_id === threadId);
74
- if (index === -1) {
75
- throw new Error(`Thread with ID ${threadId} not found.`);
76
- }
77
- this.threads[index] = { ...this.threads[index], ...thread };
78
- }
79
- async delete(threadId) {
80
- const initialLength = this.threads.length;
81
- this.threads = this.threads.filter((t) => t.thread_id !== threadId);
82
- if (this.threads.length === initialLength) {
83
- throw new Error(`Thread with ID ${threadId} not found.`);
84
- }
85
- }
86
- async updateState(threadId, thread) {
87
- const index = this.threads.findIndex((t) => t.thread_id === threadId);
88
- if (index === -1) {
89
- throw new Error(`Thread with ID ${threadId} not found.`);
90
- }
91
- const targetThread = this.threads[index];
92
- if (targetThread.status === 'busy') {
93
- throw new Error(`Thread with ID ${threadId} is busy, can't update state.`);
94
- }
95
- this.threads[index] = { ...targetThread, values: thread.values };
96
- if (!targetThread.metadata?.graph_id) {
97
- throw new Error(`Thread with ID ${threadId} has no graph_id.`);
98
- }
99
- const graphId = targetThread.metadata?.graph_id;
100
- const config = {
101
- configurable: {
102
- thread_id: threadId,
103
- graph_id: graphId,
104
- },
105
- };
106
- const graph = await getGraph(graphId, config);
107
- const nextConfig = await graph.updateState(config, thread.values);
108
- const graphState = await graph.getState(config);
109
- await this.set(threadId, { values: JSON.parse(serialiseAsDict(graphState.values)) });
110
- return nextConfig;
111
- }
112
- runs = [];
113
- async createRun(threadId, assistantId, payload) {
114
- const runId = crypto.randomUUID();
115
- const run = {
116
- run_id: runId,
117
- thread_id: threadId,
118
- assistant_id: assistantId,
119
- created_at: new Date().toISOString(),
120
- updated_at: new Date().toISOString(),
121
- status: 'pending',
122
- metadata: payload?.metadata ?? {},
123
- multitask_strategy: 'reject',
124
- };
125
- this.runs.push(run);
126
- return run;
127
- }
128
- async listRuns(threadId, options) {
129
- let filteredRuns = [...this.runs];
130
- if (options?.status) {
131
- filteredRuns = filteredRuns.filter((r) => r.status === options.status);
132
- }
133
- if (options?.limit) {
134
- filteredRuns = filteredRuns.slice(options.offset || 0, (options.offset || 0) + options.limit);
135
- }
136
- return filteredRuns;
137
- }
138
- async updateRun(runId, run) {
139
- const index = this.runs.findIndex((r) => r.run_id === runId);
140
- if (index === -1) {
141
- throw new Error(`Run with ID ${runId} not found.`);
142
- }
143
- this.runs[index] = { ...this.runs[index], ...run };
144
- }
145
- }
@@ -1,9 +0,0 @@
1
- import { PostgresSaver } from '@langchain/langgraph-checkpoint-postgres';
2
- export const createPGCheckpoint = async () => {
3
- const checkpointer = PostgresSaver.fromConnString(process.env.DATABASE_URL);
4
- if (process.env.DATABASE_INIT === 'true') {
5
- console.debug('LG | Initializing postgres checkpoint');
6
- await checkpointer.setup();
7
- }
8
- return checkpointer;
9
- };