@axiom-lattice/gateway 2.1.3 → 2.1.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.
@@ -13,30 +13,34 @@ export class AgentManager {
13
13
  }
14
14
  callAgentInQueue(queue: {
15
15
  assistant_id: string;
16
- input: any;
16
+ input?: any;
17
17
  thread_id: string;
18
- "x-tenant-id": number;
18
+ command?: any;
19
+ "x-tenant-id"?: string;
19
20
  }) {
20
21
  return new Promise((resolve, reject) => {
21
- const callback_event = `${queue.assistant_id}::${queue.thread_id}`;
22
- eventBus.subscribeOnce(callback_event, (data) => {
23
- if (data.success) {
24
- console.log("AgentManager callAgentInQueue success", data);
25
- resolve(data.result);
26
- } else {
27
- console.log("AgentManager callAgentInQueue error", data);
28
- reject(data.error);
29
- }
30
- });
31
- eventBus.publish(
32
- AGENT_TASK_EVENT,
33
- {
34
- ...queue,
35
- thread_id: callback_event,
36
- callback_event,
37
- },
38
- true
39
- );
22
+ //const callback_event = `${queue.assistant_id}::${queue.thread_id}`;
23
+ // eventBus.subscribeOnce(callback_event, (data) => {
24
+ // if (data.success) {
25
+ // console.log("AgentManager callAgentInQueue success", data);
26
+ // resolve(data.result);
27
+ // } else {
28
+ // console.log("AgentManager callAgentInQueue error", data);
29
+ // reject(data.error);
30
+ // }
31
+ // });
32
+ try {
33
+ eventBus.publish(
34
+ AGENT_TASK_EVENT,
35
+ {
36
+ ...queue,
37
+ },
38
+ true
39
+ );
40
+ resolve(true);
41
+ } catch (error) {
42
+ reject(error);
43
+ }
40
44
  });
41
45
  }
42
46
  }
@@ -0,0 +1,325 @@
1
+ import eventBus from "./event_bus";
2
+ import { AGENT_TASK_EVENT } from "./agent_task_types";
3
+ import { popAgentTaskFromQueue } from "./queue_service";
4
+
5
+ // 任务请求结构
6
+ export interface AgentTaskRequest {
7
+ assistant_id: string;
8
+ input: any;
9
+ thread_id: string;
10
+ "x-tenant-id": string;
11
+ command?: any;
12
+ callback_event?: string; // 可选的回调事件名称
13
+ }
14
+
15
+ /**
16
+ * 处理Agent任务事件
17
+ * @param taskRequest 任务请求
18
+ * @param retryCount 重试次数
19
+ */
20
+ const handleAgentTask = async (
21
+ taskRequest: AgentTaskRequest,
22
+ retryCount: number = 0
23
+ ): Promise<boolean> => {
24
+ const {
25
+ assistant_id,
26
+ input = {},
27
+ thread_id,
28
+ "x-tenant-id": tenant_id,
29
+ command,
30
+ callback_event,
31
+ } = taskRequest;
32
+
33
+ try {
34
+ console.log(
35
+ `开始处理任务 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
36
+ );
37
+
38
+ // 检查API服务器是否可用
39
+ const apiUrl = AgentTaskConsumer.agent_run_endpoint;
40
+
41
+ console.log(`apiUrl: ${apiUrl}`);
42
+
43
+ const response = await fetch(apiUrl, {
44
+ method: "POST",
45
+ body: JSON.stringify({
46
+ assistant_id,
47
+ streaming: true,
48
+ ...input,
49
+ thread_id,
50
+ command,
51
+ }),
52
+ headers: {
53
+ "Content-Type": "application/json",
54
+ "x-tenant-id": tenant_id,
55
+ },
56
+ }).catch((err) => {
57
+ console.error(`fetch请求失败: ${err.message || String(err)}`);
58
+ throw new Error(`fetch失败: ${err.message || String(err)}`);
59
+ });
60
+
61
+ if (!response.ok) {
62
+ throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
63
+ }
64
+
65
+ if (callback_event) {
66
+ eventBus.publish(callback_event, {
67
+ success: true,
68
+ config: { assistant_id, thread_id, tenant_id },
69
+ });
70
+ }
71
+
72
+ console.log(
73
+ `任务处理成功 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
74
+ );
75
+ return true;
76
+ } catch (error) {
77
+ console.error(
78
+ `Agent任务执行失败: ${assistant_id}, 线程: ${thread_id}`,
79
+ error
80
+ );
81
+
82
+ // 重试逻辑,暂不用重试
83
+ const maxRetries = 0;
84
+ if (retryCount < maxRetries) {
85
+ const nextRetryCount = retryCount + 1;
86
+ const delayMs = Math.pow(2, nextRetryCount) * 1000; // 指数退避策略
87
+
88
+ console.log(
89
+ `将在 ${delayMs}ms 后重试任务 (${nextRetryCount}/${maxRetries})`
90
+ );
91
+
92
+ // 延迟后重试
93
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
94
+ return handleAgentTask(taskRequest, nextRetryCount);
95
+ }
96
+
97
+ if (callback_event) {
98
+ eventBus.publish(callback_event, {
99
+ success: false,
100
+ error: error instanceof Error ? error.message : String(error),
101
+ config: { assistant_id, thread_id, tenant_id },
102
+ });
103
+ }
104
+
105
+ console.error(
106
+ `任务处理失败,已达到最大重试次数 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
107
+ );
108
+ return false;
109
+ }
110
+ };
111
+
112
+ /**
113
+ * Agent任务消费者
114
+ * 负责监听和执行Agent任务事件,同时从队列中消费任务
115
+ */
116
+ export class AgentTaskConsumer {
117
+ private isPolling: boolean = false;
118
+ private pollingInterval: NodeJS.Timeout | null = null;
119
+ private pollingIntervalMs: number = 5000; // 默认5秒轮询一次
120
+ private maxConcurrentTasks: number = 15; // 最大并发任务数
121
+ private activeTasks: number = 0; // 当前活跃的任务数
122
+ private processing: boolean = false; // 是否正在处理任务批次
123
+ private immediateProcessingEnabled: boolean = true; // 是否启用即时处理模式
124
+ public gatewayPort: number = 4001;
125
+ public static agent_run_endpoint: string = "http://localhost:4001/api/runs";
126
+
127
+ constructor(gatewayPort: number, pollingIntervalMs?: number) {
128
+ this.gatewayPort = gatewayPort;
129
+ AgentTaskConsumer.agent_run_endpoint = `http://localhost:${this.gatewayPort}/api/runs`;
130
+ if (pollingIntervalMs) {
131
+ this.pollingIntervalMs = pollingIntervalMs;
132
+ }
133
+ this.initialize();
134
+ }
135
+
136
+ /**
137
+ * 初始化事件监听和队列轮询
138
+ */
139
+ private initialize(): void {
140
+ // 监听事件总线上的任务
141
+ eventBus.subscribe(AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
142
+
143
+ // 启动队列轮询
144
+ this.startPollingQueue();
145
+
146
+ console.log("Agent任务消费者已启动并监听任务事件和队列");
147
+ }
148
+
149
+ /**
150
+ * 启动队列轮询
151
+ */
152
+ startPollingQueue(): void {
153
+ if (this.isPolling) {
154
+ return;
155
+ }
156
+
157
+ this.isPolling = true;
158
+
159
+ this.pollingInterval = setInterval(async () => {
160
+ try {
161
+ // 如果上一批次任务还在处理中,跳过本次轮询
162
+ if (this.processing) {
163
+ console.log("队列处理中,跳过本次轮询");
164
+ return;
165
+ }
166
+
167
+ await this.consumeFromQueue();
168
+ } catch (error) {
169
+ console.error("队列轮询出错:", error);
170
+ }
171
+ }, this.pollingIntervalMs);
172
+
173
+ console.log(
174
+ `开始轮询队列,间隔: ${this.pollingIntervalMs}ms,最大并发任务数: ${this.maxConcurrentTasks}`
175
+ );
176
+ }
177
+
178
+ /**
179
+ * 停止队列轮询
180
+ */
181
+ stopPollingQueue(): void {
182
+ if (this.pollingInterval) {
183
+ clearInterval(this.pollingInterval);
184
+ this.pollingInterval = null;
185
+ this.isPolling = false;
186
+ console.log("已停止队列轮询");
187
+ }
188
+ }
189
+
190
+ /**
191
+ * 处理单个任务并在完成后立即尝试处理下一个
192
+ */
193
+ async processNextTask(): Promise<boolean> {
194
+ try {
195
+ // 从队列中获取任务
196
+ const queueResult = await popAgentTaskFromQueue();
197
+
198
+ // 检查队列结果
199
+ if (queueResult && queueResult.data) {
200
+ const taskItem = queueResult.data;
201
+
202
+ if (taskItem && typeof taskItem === "object") {
203
+ const taskRequest = taskItem as AgentTaskRequest;
204
+
205
+ console.log(
206
+ `从队列中获取到任务 [assistant: ${taskRequest.assistant_id}, thread: ${taskRequest.thread_id}]`
207
+ );
208
+
209
+ // 增加活跃任务计数
210
+ this.activeTasks++;
211
+
212
+ // 处理任务(不使用await,允许并发执行)
213
+ handleAgentTask(taskRequest)
214
+ .then((success) => {
215
+ if (!success) {
216
+ console.error(`任务 处理失败`);
217
+ // 可以在这里记录失败任务或执行补偿逻辑
218
+ } else {
219
+ console.log(`任务 处理成功`);
220
+ }
221
+ })
222
+ .catch((error) => {
223
+ console.error(`任务 处理时出错:`, error);
224
+ })
225
+ .finally(() => {
226
+ // 减少活跃任务计数
227
+ this.activeTasks--;
228
+
229
+ // 如果启用了即时处理模式,尝试处理更多任务
230
+ if (this.immediateProcessingEnabled && !this.processing) {
231
+ this.checkQueueForTasks();
232
+ }
233
+ });
234
+
235
+ // 任务已派发,返回true表示有任务被处理
236
+ return true;
237
+ } else {
238
+ console.log("队列任务格式无效:", taskItem);
239
+ return false;
240
+ }
241
+ } else {
242
+ // 队列为空
243
+ return false;
244
+ }
245
+ } catch (error) {
246
+ console.error("处理任务失败:", error);
247
+ return false;
248
+ }
249
+ }
250
+
251
+ /**
252
+ * 检查队列中是否有任务并处理
253
+ */
254
+ async checkQueueForTasks(): Promise<void> {
255
+ // 如果已经在处理中或者并发任务已达上限,则不处理
256
+ if (this.processing || this.activeTasks >= this.maxConcurrentTasks) {
257
+ return;
258
+ }
259
+
260
+ this.processing = true;
261
+
262
+ try {
263
+ // 尝试获取并处理任务,直到达到并发上限
264
+ while (this.activeTasks < this.maxConcurrentTasks) {
265
+ const taskProcessed = await this.processNextTask();
266
+ if (!taskProcessed) {
267
+ // 没有更多任务,退出循环
268
+ break;
269
+ }
270
+ }
271
+ } catch (error) {
272
+ console.error("检查队列任务失败:", error);
273
+ } finally {
274
+ this.processing = false;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * 从队列中消费任务
280
+ */
281
+ async consumeFromQueue(): Promise<void> {
282
+ // 如果已经在处理中,跳过
283
+ if (this.processing) {
284
+ return;
285
+ }
286
+
287
+ // 调用任务检查方法
288
+ await this.checkQueueForTasks();
289
+ }
290
+
291
+ /**
292
+ * 处理通过事件触发的任务
293
+ */
294
+ trigger_agent_task(taskRequest: AgentTaskRequest): void {
295
+ console.log(
296
+ `通过事件触发任务: [assistant: ${taskRequest.assistant_id}, thread: ${taskRequest.thread_id}]`
297
+ );
298
+
299
+ // 处理任务,不阻塞事件处理流程
300
+ handleAgentTask(taskRequest).catch((error) => {
301
+ console.error("处理Agent任务时发生未捕获的错误:", error);
302
+
303
+ // 如果有回调事件,确保即使发生错误也能通知
304
+ if (taskRequest.callback_event) {
305
+ eventBus.publish(taskRequest.callback_event, {
306
+ success: false,
307
+ error: error instanceof Error ? error.message : String(error),
308
+ config: {
309
+ assistant_id: taskRequest.assistant_id,
310
+ thread_id: taskRequest.thread_id,
311
+ tenant_id: taskRequest["x-tenant-id"],
312
+ },
313
+ });
314
+ }
315
+ });
316
+
317
+ // 如果启用了即时处理且当前活跃任务数低于阈值,检查队列
318
+ if (
319
+ this.immediateProcessingEnabled &&
320
+ this.activeTasks < this.maxConcurrentTasks
321
+ ) {
322
+ setImmediate(() => this.checkQueueForTasks());
323
+ }
324
+ }
325
+ }
@@ -1,82 +1,43 @@
1
- import { createClient } from "redis";
2
-
3
- // 创建Redis客户端 - 使用正确的密码
4
- const redisOptions: any = {
5
- url: process.env.REDIS_URL || "redis://localhost:6379",
6
- password: process.env.REDIS_PASSWORD,
1
+ // Queue service adapter that supports both Redis and memory implementations
2
+ // Defaults to memory implementation
3
+
4
+ import * as memoryQueueService from "./queue_service_memory";
5
+ import * as redisQueueService from "./queue_service_redis";
6
+
7
+ export type QueueServiceType = "memory" | "redis";
8
+
9
+ // Global configuration for queue service type
10
+ // Can be set via environment variable QUEUE_SERVICE_TYPE or via LatticeGateway configuration
11
+ let queueServiceType: QueueServiceType =
12
+ (process.env.QUEUE_SERVICE_TYPE as QueueServiceType) || "memory";
13
+
14
+ /**
15
+ * Configure the queue service type
16
+ * @param type - "memory" or "redis"
17
+ */
18
+ export const setQueueServiceType = (type: QueueServiceType): void => {
19
+ queueServiceType = type;
20
+ console.log(`Queue service type set to: ${type}`);
7
21
  };
8
22
 
9
- const redisClient = createClient(redisOptions);
10
-
11
- // 连接Redis
12
- (async () => {
13
- redisClient.on("error", (err: Error) =>
14
- console.error("Redis Client Error", err)
15
- );
16
- try {
17
- await redisClient.connect();
18
- console.log("Redis连接成功");
19
- } catch (error) {
20
- console.error("Redis连接失败:", error);
21
- }
22
- })();
23
-
24
- const queue_name = process.env.QUEUE_NAME || "tasks"; // 队列名称,本地开发需要自己配置自己的队列
25
-
26
- //Enqueue Message
27
- const sendToQueue = async (queue_name: string, message: any) => {
28
- try {
29
- // 将消息推送到队列
30
- const result = await redisClient.lPush(queue_name, JSON.stringify(message));
31
- console.log("lPush", result);
32
- return { data: result, error: null };
33
- } catch (error) {
34
- console.error(error);
35
- return { data: null, error };
36
- }
23
+ /**
24
+ * Get the current queue service type
25
+ */
26
+ export const getQueueServiceType = (): QueueServiceType => {
27
+ return queueServiceType;
37
28
  };
38
29
 
39
- //Dequeue Message
40
- const popFromQueue = async (queue_name: string) => {
41
- try {
42
- // 从队列中获取消息
43
- const message = await redisClient.rPop(queue_name);
44
- if (message) {
45
- return { data: JSON.parse(message), error: null };
46
- }
47
- return { data: null, error: null };
48
- } catch (error) {
49
- console.error(error);
50
- return { data: null, error };
51
- }
52
- };
53
-
54
- const createTenantQueue = async () => {
55
- // Redis不需要显式创建队列,但我们可以保留这个函数以保持API兼容
56
- // 可以进行一些初始化操作,如检查队列是否存在
57
- try {
58
- const exists = await redisClient.exists(queue_name);
59
- return { success: true, queue_name };
60
- } catch (error) {
61
- console.error(error);
62
- return { success: false, error };
63
- }
30
+ // Select the appropriate implementation based on configuration
31
+ const getQueueService = () => {
32
+ return queueServiceType === "redis" ? redisQueueService : memoryQueueService;
64
33
  };
65
34
 
66
35
  export const pushAgentTaskToQueue = async (agentTask: any) => {
67
- const tenantId = agentTask["x-tenant-id"];
68
-
69
- try {
70
- await createTenantQueue();
71
- const result = await sendToQueue(`${queue_name}`, agentTask);
72
- return result;
73
- } catch (error) {
74
- console.error(error);
75
- return null;
76
- }
36
+ const service = getQueueService();
37
+ return service.pushAgentTaskToQueue(agentTask);
77
38
  };
78
39
 
79
40
  export const popAgentTaskFromQueue = async () => {
80
- const result = await popFromQueue(`${queue_name}`);
81
- return result;
41
+ const service = getQueueService();
42
+ return service.popAgentTaskFromQueue();
82
43
  };
@@ -0,0 +1,85 @@
1
+ // In-memory queue service implementation
2
+ // This is a memory-based alternative to the Redis queue service
3
+
4
+ const queue_name = process.env.QUEUE_NAME || "tasks"; // Queue name, local development needs to configure its own queue
5
+
6
+ // In-memory storage for queues: Map<queue_name, message[]>
7
+ // Using Map to support multiple queues (e.g., per tenant)
8
+ const queues = new Map<string, any[]>();
9
+
10
+ // Initialize queue if it doesn't exist
11
+ const ensureQueue = (queueName: string): void => {
12
+ if (!queues.has(queueName)) {
13
+ queues.set(queueName, []);
14
+ }
15
+ };
16
+
17
+ // Enqueue Message (equivalent to lPush - push to left/beginning)
18
+ const sendToQueue = async (queueName: string, message: any) => {
19
+ try {
20
+ ensureQueue(queueName);
21
+ const queue = queues.get(queueName)!;
22
+
23
+ // lPush behavior: add to the beginning of the array
24
+ queue.unshift(message);
25
+
26
+ const result = queue.length;
27
+ console.log("lPush (memory)", result);
28
+ return { data: result, error: null };
29
+ } catch (error) {
30
+ console.error(error);
31
+ return { data: null, error };
32
+ }
33
+ };
34
+
35
+ // Dequeue Message (equivalent to rPop - pop from right/end)
36
+ const popFromQueue = async (queueName: string) => {
37
+ try {
38
+ ensureQueue(queueName);
39
+ const queue = queues.get(queueName)!;
40
+
41
+ // rPop behavior: remove from the end of the array
42
+ // This creates FIFO behavior when combined with lPush (unshift + pop = FIFO)
43
+ const message = queue.pop();
44
+
45
+ if (message) {
46
+ return { data: message, error: null };
47
+ }
48
+ return { data: null, error: null };
49
+ } catch (error) {
50
+ console.error(error);
51
+ return { data: null, error };
52
+ }
53
+ };
54
+
55
+ const createTenantQueue = async () => {
56
+ // Initialize the queue in memory
57
+ try {
58
+ ensureQueue(queue_name);
59
+ const queue = queues.get(queue_name)!;
60
+ const exists = queue !== undefined;
61
+ return { success: true, queue_name };
62
+ } catch (error) {
63
+ console.error(error);
64
+ return { success: false, error };
65
+ }
66
+ };
67
+
68
+ export const pushAgentTaskToQueue = async (agentTask: any) => {
69
+ const tenantId = agentTask["x-tenant-id"];
70
+
71
+ try {
72
+ await createTenantQueue();
73
+ const result = await sendToQueue(`${queue_name}`, agentTask);
74
+ return result;
75
+ } catch (error) {
76
+ console.error(error);
77
+ return null;
78
+ }
79
+ };
80
+
81
+ export const popAgentTaskFromQueue = async () => {
82
+ const result = await popFromQueue(`${queue_name}`);
83
+ return result;
84
+ };
85
+
@@ -0,0 +1,86 @@
1
+ import { createClient } from "redis";
2
+
3
+ // Redis queue service implementation
4
+ // This is a Redis-based queue service
5
+
6
+ const queue_name = process.env.QUEUE_NAME || "tasks"; // Queue name, local development needs to configure its own queue
7
+
8
+ // Create Redis client with correct password
9
+ const redisOptions: any = {
10
+ url: process.env.REDIS_URL || "redis://localhost:6379",
11
+ password: process.env.REDIS_PASSWORD,
12
+ };
13
+
14
+ const redisClient = createClient(redisOptions);
15
+
16
+ // Connect to Redis
17
+ (async () => {
18
+ redisClient.on("error", (err: Error) =>
19
+ console.error("Redis Client Error", err)
20
+ );
21
+ try {
22
+ await redisClient.connect();
23
+ console.log("Redis connection successful");
24
+ } catch (error) {
25
+ console.error("Redis connection failed:", error);
26
+ }
27
+ })();
28
+
29
+ // Enqueue Message
30
+ const sendToQueue = async (queueName: string, message: any) => {
31
+ try {
32
+ // Push message to queue
33
+ const result = await redisClient.lPush(queueName, JSON.stringify(message));
34
+ console.log("lPush", result);
35
+ return { data: result, error: null };
36
+ } catch (error) {
37
+ console.error(error);
38
+ return { data: null, error };
39
+ }
40
+ };
41
+
42
+ // Dequeue Message
43
+ const popFromQueue = async (queueName: string) => {
44
+ try {
45
+ // Get message from queue
46
+ const message = await redisClient.rPop(queueName);
47
+ if (message) {
48
+ return { data: JSON.parse(message), error: null };
49
+ }
50
+ return { data: null, error: null };
51
+ } catch (error) {
52
+ console.error(error);
53
+ return { data: null, error };
54
+ }
55
+ };
56
+
57
+ const createTenantQueue = async () => {
58
+ // Redis doesn't need explicit queue creation, but we keep this function for API compatibility
59
+ // Can perform some initialization operations, such as checking if queue exists
60
+ try {
61
+ const exists = await redisClient.exists(queue_name);
62
+ return { success: true, queue_name };
63
+ } catch (error) {
64
+ console.error(error);
65
+ return { success: false, error };
66
+ }
67
+ };
68
+
69
+ export const pushAgentTaskToQueue = async (agentTask: any) => {
70
+ const tenantId = agentTask["x-tenant-id"];
71
+
72
+ try {
73
+ await createTenantQueue();
74
+ const result = await sendToQueue(`${queue_name}`, agentTask);
75
+ return result;
76
+ } catch (error) {
77
+ console.error(error);
78
+ return null;
79
+ }
80
+ };
81
+
82
+ export const popAgentTaskFromQueue = async () => {
83
+ const result = await popFromQueue(`${queue_name}`);
84
+ return result;
85
+ };
86
+