@axiom-lattice/gateway 1.0.20

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.
@@ -0,0 +1,292 @@
1
+ import {
2
+ AIMessage,
3
+ AIMessageChunk,
4
+ BaseMessage,
5
+ filterMessages,
6
+ HumanMessage,
7
+ ToolMessage,
8
+ ToolMessageChunk,
9
+ } from "@langchain/core/messages";
10
+ import { Command, CommandParams } from "@langchain/langgraph";
11
+ import { v4 } from "uuid";
12
+ // 修改导入路径,使用 lattice_core 包
13
+ import { getAgentClient, getAgentLattice } from "@axiom-lattice/core";
14
+
15
+ function isAIMessageChunk(msg: any): msg is AIMessageChunk {
16
+ return msg && msg.constructor.name === "AIMessageChunk";
17
+ }
18
+ function isToolMessageChunk(msg: any): msg is ToolMessageChunk {
19
+ return msg && msg.constructor.name === "ToolMessageChunk";
20
+ }
21
+ function isAIMessage(msg: any): msg is AIMessage {
22
+ return msg && msg.constructor.name === "AIMessage";
23
+ }
24
+ function isToolMessage(msg: any): msg is ToolMessage {
25
+ return msg && msg.constructor.name === "ToolMessage";
26
+ }
27
+
28
+ export async function agent_invoke({
29
+ input,
30
+ thread_id,
31
+ assistant_id,
32
+ tenant_id,
33
+ command,
34
+ run_id,
35
+ }: {
36
+ assistant_id: string;
37
+ input: any;
38
+ thread_id: string;
39
+ tenant_id: string;
40
+ run_id?: string;
41
+ command?: CommandParams<any>;
42
+ }) {
43
+ const runnable_agent = getAgentLattice(assistant_id)?.client;
44
+ const { files, message, ...rest } = input;
45
+ const humanMessage = new HumanMessage(message || "");
46
+ humanMessage.additional_kwargs = { files: files };
47
+ const messages = [humanMessage];
48
+ if (!runnable_agent) {
49
+ throw new Error(`Agent ${assistant_id} not found`);
50
+ }
51
+
52
+ const result = await runnable_agent.invoke(
53
+ command
54
+ ? new Command(command)
55
+ : { ...rest, messages, "x-tenant-id": tenant_id },
56
+ {
57
+ configurable: {
58
+ thread_id: thread_id,
59
+ run_id: run_id || v4(),
60
+ recursionLimit: 200,
61
+ "x-tenant-id": tenant_id,
62
+ "x-request-id": run_id,
63
+ "x-thread-id": thread_id,
64
+ },
65
+ }
66
+ );
67
+
68
+ return result;
69
+ }
70
+
71
+ export async function agent_stream({
72
+ input,
73
+ thread_id,
74
+ command,
75
+ tenant_id,
76
+ assistant_id,
77
+ run_id,
78
+ }: {
79
+ assistant_id: string;
80
+ input: any;
81
+ thread_id: string;
82
+ command?: CommandParams<any>;
83
+ tenant_id: string;
84
+ run_id?: string;
85
+ }) {
86
+ const runnable_agent = getAgentClient(assistant_id);
87
+ const { files, message, ...rest } = input;
88
+ let messages: BaseMessage[] = [];
89
+ if (!command) {
90
+ const humanMessage = new HumanMessage(message);
91
+ humanMessage.additional_kwargs = { files: files };
92
+ messages = [humanMessage];
93
+ }
94
+
95
+ try {
96
+ if (!runnable_agent) {
97
+ throw new Error(`Agent ${assistant_id} not found`);
98
+ }
99
+ const agentStream = await runnable_agent.stream(
100
+ command
101
+ ? new Command(command)
102
+ : {
103
+ ...rest,
104
+ messages,
105
+ "x-tenant-id": tenant_id,
106
+ },
107
+
108
+ {
109
+ configurable: {
110
+ thread_id: thread_id,
111
+ run_id: run_id || v4(),
112
+ "x-tenant-id": tenant_id,
113
+ "x-request-id": run_id,
114
+ "x-thread-id": thread_id,
115
+ },
116
+ streamMode: ["updates", "messages"],
117
+ subgraphs: false,
118
+ }
119
+ );
120
+
121
+ // 创建一个可迭代的 ReadableStream
122
+ return {
123
+ [Symbol.asyncIterator]: async function* () {
124
+ try {
125
+ for await (const chunk of agentStream) {
126
+ // console.log("-----chunk----", chunk);
127
+ let data;
128
+ // for (const [node, values] of Object.entries(chunk)) {
129
+ // console.log(`Receiving update from node: ${node}`);
130
+ // console.log(values);
131
+ // console.log("\n====\n");
132
+ // }
133
+ //console.log("chunk", JSON.stringify(chunk));
134
+ // if (chunk[0] === "updates") {
135
+ // const updates = chunk[1];
136
+ // console.log("update", updates);
137
+ // }
138
+ if (chunk[0] === "messages") {
139
+ //console.log(chunk[1][0]);
140
+ const messages = chunk[1];
141
+ data = messages?.[0]?.toDict();
142
+
143
+ // const messagesArray = messages
144
+ // .map((message: any) => {
145
+ // if (isAIMessageChunk(message)) {
146
+ // return {
147
+ // id: message.id,
148
+ // role: "ai",
149
+ // name: message.name,
150
+ // content: message.content,
151
+ // type: "message_chunk",
152
+ // tool_calls: message.tool_calls,
153
+ // tool_call_chunks: message.tool_call_chunks,
154
+ // };
155
+ // } else if (isToolMessage(message)) {
156
+ // return {
157
+ // id: message.id,
158
+ // role: "tool",
159
+ // content: message.content,
160
+ // type: "tool_message",
161
+ // tool_call_id: message.tool_call_id,
162
+ // name: message.name,
163
+ // };
164
+ // } else if (isAIMessage(message)) {
165
+ // return {
166
+ // id: message.id,
167
+ // role: "ai",
168
+ // content: message.content,
169
+ // type: "message",
170
+ // };
171
+ // } else if (isToolMessageChunk(message)) {
172
+ // return {
173
+ // id: message.id,
174
+ // role: "tool",
175
+ // type: "tool_message_chunk",
176
+ // content: message.content,
177
+ // };
178
+ // }
179
+ // })
180
+ // .filter((item: any) => item);
181
+ // // 如果messagesArray为空,则不返回数据,如果返回数据会导致界面清掉历史message
182
+ // if (messagesArray.length > 0) {
183
+ // data = {
184
+ // messages: messagesArray,
185
+ // };
186
+ // }
187
+ }
188
+
189
+ if ((chunk?.[1] as any)?.__interrupt__) {
190
+ data = chunk?.[1]?.[0]?.toDict();
191
+ // 原有的中断消息处理
192
+ // data = {
193
+ // messages: [
194
+ // {
195
+ // role: "ai",
196
+ // content: (chunk?.[1] as any)?.__interrupt__[0].value,
197
+ // type: "action",
198
+ // },
199
+ // ],
200
+ // };
201
+ }
202
+
203
+ if (data) {
204
+ console.log(data);
205
+ yield data;
206
+ }
207
+ }
208
+ } catch (error) {
209
+ console.error("Stream error:", error);
210
+ throw error;
211
+ }
212
+ },
213
+ };
214
+ } catch (error) {
215
+ throw error;
216
+ }
217
+ }
218
+
219
+ export async function agent_state({
220
+ thread_id,
221
+ assistant_id,
222
+ }: {
223
+ assistant_id: string;
224
+ thread_id: string;
225
+ }) {
226
+ const runnable_agent = getAgentClient(assistant_id);
227
+ if (!runnable_agent) {
228
+ throw new Error(`Agent ${assistant_id} not found`);
229
+ }
230
+ const state = await runnable_agent.getState({
231
+ configurable: { thread_id: thread_id, subgraphs: false },
232
+ });
233
+ return state;
234
+ }
235
+
236
+ export async function agent_messages({
237
+ thread_id,
238
+ tenant_id,
239
+ assistant_id,
240
+ }: {
241
+ assistant_id: string;
242
+ thread_id: string;
243
+ tenant_id: string;
244
+ }) {
245
+ const runnable_agent = getAgentClient(assistant_id);
246
+ if (!runnable_agent) {
247
+ throw new Error(`Agent ${assistant_id} not found`);
248
+ }
249
+ const state = await runnable_agent.getState({
250
+ configurable: { thread_id: thread_id, subgraphs: false },
251
+ });
252
+
253
+ const messages = state.values.messages || [];
254
+ const filteredMessages = filterMessages(messages, {
255
+ includeTypes: ["ai", "human", "tool"], //["human", "ai", "tool"],
256
+ });
257
+
258
+ // console.log(filteredMessages);
259
+
260
+ let messagesArray = filteredMessages.map((message: BaseMessage) => ({
261
+ id: message.id,
262
+ role: message.getType(),
263
+ content: message.content,
264
+ files: message.additional_kwargs.files,
265
+ ...message.lc_kwargs,
266
+ }));
267
+
268
+ const action_messages = state.tasks.flatMap((task) => {
269
+ return task.interrupts.map((interrupt) => {
270
+ return {
271
+ role: "ai",
272
+ content: interrupt.value,
273
+ type: "action",
274
+ };
275
+ });
276
+ });
277
+
278
+ const new_messages = [...messagesArray, ...action_messages];
279
+ // console.log(messagesArray);
280
+
281
+ return new_messages;
282
+ }
283
+
284
+ export async function draw_graph(assistant_id: string) {
285
+ const runnable_agent = getAgentClient(assistant_id);
286
+ if (!runnable_agent) {
287
+ throw new Error(`Agent ${assistant_id} not found`);
288
+ }
289
+ const drawableGraph = await runnable_agent.getGraphAsync();
290
+ const image = await drawableGraph.drawMermaid();
291
+ return image;
292
+ }
@@ -0,0 +1,2 @@
1
+ // 任务事件名称
2
+ export const AGENT_TASK_EVENT = "agent:execute";
@@ -0,0 +1,62 @@
1
+ import { EventEmitter } from "events";
2
+ import { pushAgentTaskToQueue } from "./queue_service";
3
+
4
+ /**
5
+ * 事件总线服务
6
+ * 用于系统内部组件间的事件发布与订阅
7
+ */
8
+ class EventBus {
9
+ private emitter: EventEmitter;
10
+
11
+ constructor() {
12
+ this.emitter = new EventEmitter();
13
+ // 设置最大监听器数量,避免内存泄漏警告
14
+ this.emitter.setMaxListeners(100);
15
+ }
16
+
17
+ /**
18
+ * 发布事件
19
+ * @param eventName 事件名称
20
+ * @param data 事件数据
21
+ */
22
+ publish(eventName: string, data: any, useQueue: boolean = false): void {
23
+ //console.log(`发布事件: ${eventName}`, data);
24
+ if (useQueue) {
25
+ pushAgentTaskToQueue(data);
26
+ } else {
27
+ this.emitter.emit(eventName, data);
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 订阅事件
33
+ * @param eventName 事件名称
34
+ * @param callback 回调函数
35
+ */
36
+ subscribe(eventName: string, callback: (data: any) => void): void {
37
+ this.emitter.on(eventName, callback);
38
+ }
39
+
40
+ /**
41
+ * 取消订阅事件
42
+ * @param eventName 事件名称
43
+ * @param callback 回调函数
44
+ */
45
+ unsubscribe(eventName: string, callback: (data: any) => void): void {
46
+ this.emitter.off(eventName, callback);
47
+ }
48
+
49
+ /**
50
+ * 只订阅一次事件
51
+ * @param eventName 事件名称
52
+ * @param callback 回调函数
53
+ */
54
+ subscribeOnce(eventName: string, callback: (data: any) => void): void {
55
+ this.emitter.once(eventName, callback);
56
+ }
57
+ }
58
+
59
+ // 创建单例实例
60
+ const eventBus = new EventBus();
61
+
62
+ export default eventBus;
@@ -0,0 +1,82 @@
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,
7
+ };
8
+
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
+ }
37
+ };
38
+
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
+ }
64
+ };
65
+
66
+ 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
+ }
77
+ };
78
+
79
+ export const popAgentTaskFromQueue = async () => {
80
+ const result = await popFromQueue(`${queue_name}`);
81
+ return result;
82
+ };
@@ -0,0 +1,26 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+ import { config } from "../config";
3
+ // import { Database } from "@/types/database.types";
4
+ // import { Database as PgmqPublic } from "@/types/pgmq_public.types";
5
+ // 创建Supabase客户端
6
+ const supabaseClient = createClient(config.supabase.url, config.supabase.key);
7
+
8
+ export default supabaseClient;
9
+
10
+ export const createSupabaseClient = (headers: Record<string, string>) => {
11
+ const currentTenantId = headers["x-tenant-id"];
12
+ return createClient(config.supabase.url, config.supabase.key, {
13
+ global: {
14
+ fetch: async (input, init) => {
15
+ const headers = new Headers(init?.headers);
16
+ if (currentTenantId) headers.set("x-tenant-id", currentTenantId);
17
+ return fetch(input, { ...init, headers });
18
+ },
19
+ },
20
+ });
21
+ };
22
+
23
+ export const supabaseQueueClient = createClient(
24
+ config.supabase.url,
25
+ config.supabase.key
26
+ );
@@ -0,0 +1,123 @@
1
+ // 助手类型
2
+ export interface Assistant {
3
+ id: string;
4
+ name: string;
5
+ description?: string;
6
+ graphDefinition: any;
7
+ createdAt: Date;
8
+ updatedAt: Date;
9
+ }
10
+
11
+ // 运行状态枚举
12
+ export enum RunStatus {
13
+ CREATED = "created",
14
+ RUNNING = "running",
15
+ COMPLETED = "completed",
16
+ FAILED = "failed",
17
+ WAITING_FOR_INPUT = "waiting_for_input",
18
+ CANCELLED = "cancelled",
19
+ }
20
+
21
+ // 运行类型
22
+ export interface Run {
23
+ id: string;
24
+ assistantId: string;
25
+ status: RunStatus;
26
+ input: any;
27
+ output?: any;
28
+ error?: string;
29
+ checkpoints: any[];
30
+ createdAt: Date;
31
+ updatedAt: Date;
32
+ completedAt?: Date;
33
+ }
34
+
35
+ // 消息类型
36
+ export interface Message {
37
+ id: string;
38
+ runId: string;
39
+ role: "user" | "assistant" | "system";
40
+ content: any;
41
+ createdAt: Date;
42
+ }
43
+
44
+ // 内存存储类型
45
+ export interface MemoryItem {
46
+ id: string;
47
+ assistantId: string;
48
+ key: string;
49
+ value: any;
50
+ createdAt: Date;
51
+ updatedAt: Date;
52
+ }
53
+
54
+ // 定时任务类型
55
+ export interface CronJob {
56
+ id: string;
57
+ assistantId: string;
58
+ name: string;
59
+ schedule: string;
60
+ action: any;
61
+ isActive: boolean;
62
+ lastRunAt?: Date;
63
+ createdAt: Date;
64
+ updatedAt: Date;
65
+ }
66
+
67
+ // Webhook类型
68
+ export interface Webhook {
69
+ id: string;
70
+ assistantId: string;
71
+ url: string;
72
+ events: string[];
73
+ isActive: boolean;
74
+ createdAt: Date;
75
+ updatedAt: Date;
76
+ }
77
+
78
+ // 创建助手请求类型
79
+ export interface CreateAssistantRequest {
80
+ name: string;
81
+ description?: string;
82
+ graphDefinition: any;
83
+ }
84
+
85
+ // 创建运行请求类型
86
+ export interface CreateRunRequest {
87
+ tenant_id: string;
88
+ thread_id: string;
89
+ assistant_id: string;
90
+ message: any;
91
+ command?: any;
92
+ streaming?: boolean;
93
+ background?: boolean;
94
+ }
95
+
96
+ // 添加消息请求类型
97
+ export interface AddMessageRequest {
98
+ runId: string;
99
+ role: "user" | "assistant" | "system";
100
+ content: any;
101
+ }
102
+
103
+ // 流式响应类型
104
+ export interface StreamEvent {
105
+ type: "start" | "data" | "end" | "error" | "heartbeat";
106
+ data?: any;
107
+ error?: string;
108
+ }
109
+
110
+ // 双重消息处理策略
111
+ export enum DoubleTextStrategy {
112
+ IGNORE = 0,
113
+ QUEUE = 1,
114
+ CANCEL_PREVIOUS = 2,
115
+ RESTART = 3,
116
+ }
117
+
118
+ // 服务响应类型
119
+ export interface ServiceResponse<T = any> {
120
+ success: boolean;
121
+ data?: T;
122
+ error?: string;
123
+ }
@@ -0,0 +1,7 @@
1
+ import "koa";
2
+
3
+ declare module "koa" {
4
+ interface Request {
5
+ body?: any;
6
+ }
7
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "preserve",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "moduleResolution": "Bundler",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "types": ["node", "jest"],
17
+ "paths": {
18
+ "@*": ["./src/*"]
19
+ },
20
+ "sourceMap": true,
21
+ "incremental": true, // 确保启用增量编译
22
+ "tsBuildInfoFile": "./.tsbuildinfo" // 指定构建信息文件位置
23
+ },
24
+ "include": ["src/index.ts"],
25
+ "exclude": ["node_modules", "dist"]
26
+ }