@axiom-lattice/gateway 2.1.44 → 2.1.45
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +11 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +234 -561
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +65 -399
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -4
- package/src/controllers/assistant.ts +5 -4
- package/src/controllers/memory.ts +17 -13
- package/src/controllers/run.ts +62 -52
- package/src/controllers/thread_status.ts +228 -0
- package/src/index.ts +8 -1
- package/src/routes/index.ts +18 -0
- package/src/services/agent_task_consumer.ts +12 -107
- package/src/__tests__/agent_service.test.ts +0 -238
- package/src/services/agent_service.ts +0 -375
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { eventBus, AGENT_TASK_EVENT } from "@axiom-lattice/core";
|
|
1
|
+
import { eventBus, AGENT_TASK_EVENT, agentInstanceManager, QueueMode } from "@axiom-lattice/core";
|
|
2
2
|
import { popAgentTaskFromQueue } from "./queue_service";
|
|
3
|
-
import { agent_state } from "./agent_service";
|
|
4
3
|
|
|
5
4
|
// 任务请求结构
|
|
6
5
|
export interface AgentTaskRequest {
|
|
@@ -10,7 +9,7 @@ export interface AgentTaskRequest {
|
|
|
10
9
|
"x-tenant-id": string;
|
|
11
10
|
command?: any;
|
|
12
11
|
callback_event?: string; // 可选的回调事件名称
|
|
13
|
-
runConfig?: Record<string,
|
|
12
|
+
runConfig?: Record<string, any>; // RunConfig for subagent execution
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
/**
|
|
@@ -32,6 +31,7 @@ const handleAgentTask = async (
|
|
|
32
31
|
runConfig,
|
|
33
32
|
} = taskRequest;
|
|
34
33
|
|
|
34
|
+
|
|
35
35
|
try {
|
|
36
36
|
console.log(
|
|
37
37
|
`开始处理任务 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
@@ -41,117 +41,22 @@ const handleAgentTask = async (
|
|
|
41
41
|
const apiUrl = AgentTaskConsumer.agent_run_endpoint;
|
|
42
42
|
|
|
43
43
|
console.log(`apiUrl: ${apiUrl}`);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
assistant_id,
|
|
49
|
-
streaming: true,
|
|
50
|
-
...input,
|
|
51
|
-
thread_id,
|
|
52
|
-
command,
|
|
53
|
-
custom_run_config: runConfig,
|
|
54
|
-
}),
|
|
55
|
-
headers: {
|
|
56
|
-
"Content-Type": "application/json",
|
|
57
|
-
"x-tenant-id": tenant_id,
|
|
58
|
-
"x-workspace-id": runConfig?.workspaceId as string,
|
|
59
|
-
"x-project-id": runConfig?.projectId as string,
|
|
60
|
-
},
|
|
61
|
-
}).catch((err) => {
|
|
62
|
-
console.error(`fetch请求失败: ${err.message || String(err)}`);
|
|
63
|
-
throw new Error(`fetch失败: ${err.message || String(err)}`);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
throw new Error(`API请求失败: ${response.status} ${response.statusText}`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Check if response is SSE stream
|
|
71
|
-
const contentType = response.headers.get("content-type");
|
|
72
|
-
if (contentType?.includes("text/event-stream")) {
|
|
73
|
-
// Handle SSE stream
|
|
74
|
-
const reader = response.body?.getReader();
|
|
75
|
-
const decoder = new TextDecoder();
|
|
76
|
-
|
|
77
|
-
if (!reader) {
|
|
78
|
-
throw new Error("Response body is not readable");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
let buffer = "";
|
|
82
|
-
let streamEnded = false;
|
|
83
|
-
|
|
84
|
-
try {
|
|
85
|
-
while (true) {
|
|
86
|
-
const { done, value } = await reader.read();
|
|
87
|
-
|
|
88
|
-
if (done) {
|
|
89
|
-
// Stream has ended
|
|
90
|
-
streamEnded = true;
|
|
91
|
-
console.log(
|
|
92
|
-
`SSE流已结束 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
93
|
-
);
|
|
94
|
-
break;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// // Decode the chunk and process SSE events
|
|
98
|
-
// buffer += decoder.decode(value, { stream: true });
|
|
99
|
-
// const lines = buffer.split("\n");
|
|
100
|
-
// buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
101
|
-
|
|
102
|
-
// for (const line of lines) {
|
|
103
|
-
// if (line.startsWith("data: ")) {
|
|
104
|
-
// try {
|
|
105
|
-
// const data = JSON.parse(line.slice(6));
|
|
106
|
-
// // Process SSE event data here if needed
|
|
107
|
-
// // You can emit events or handle the data as required
|
|
108
|
-
// console.log("SSE event received:", data);
|
|
109
|
-
// } catch (e) {
|
|
110
|
-
// // Ignore invalid JSON
|
|
111
|
-
// console.warn("Failed to parse SSE data:", line);
|
|
112
|
-
// }
|
|
113
|
-
// }
|
|
114
|
-
// }
|
|
115
|
-
}
|
|
116
|
-
} catch (streamError) {
|
|
117
|
-
console.error("Error reading SSE stream:", streamError);
|
|
118
|
-
throw streamError;
|
|
119
|
-
} finally {
|
|
120
|
-
reader.releaseLock();
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Stream has ended successfully
|
|
124
|
-
if (callback_event) {
|
|
125
|
-
const state = await agent_state({ assistant_id, thread_id, tenant_id });
|
|
44
|
+
const agent = agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig })
|
|
45
|
+
await agent.addMessage({ input, command }, QueueMode.STEER);
|
|
46
|
+
if (callback_event) {
|
|
47
|
+
agent.subscribeOnce("message:completed", (evt) => {
|
|
126
48
|
eventBus.publish(callback_event, {
|
|
127
49
|
success: true,
|
|
128
|
-
state: state,
|
|
50
|
+
state: evt.state,
|
|
129
51
|
config: { assistant_id, thread_id, tenant_id },
|
|
130
52
|
});
|
|
131
|
-
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return true;
|
|
132
57
|
|
|
133
|
-
console.log(
|
|
134
|
-
`任务处理成功 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
135
|
-
);
|
|
136
|
-
return true;
|
|
137
|
-
} else {
|
|
138
|
-
// Non-streaming response
|
|
139
|
-
await response.text(); // Consume the response
|
|
140
58
|
|
|
141
|
-
if (callback_event) {
|
|
142
|
-
const state = await agent_state({ assistant_id, thread_id, tenant_id });
|
|
143
|
-
eventBus.publish(callback_event, {
|
|
144
|
-
success: true,
|
|
145
|
-
state,
|
|
146
|
-
config: { assistant_id, thread_id, tenant_id },
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
59
|
|
|
150
|
-
console.log(
|
|
151
|
-
`任务处理成功 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
|
|
152
|
-
);
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
60
|
} catch (error) {
|
|
156
61
|
console.error(
|
|
157
62
|
`Agent任务执行失败: ${assistant_id}, 线程: ${thread_id}`,
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent Service Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for agent_service.ts to ensure proper async/await usage
|
|
5
|
-
* and error handling when working with agent clients.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, it, expect, beforeEach, jest } from '@jest/globals';
|
|
9
|
-
import { agent_state, agent_messages, draw_graph, agent_invoke, agent_stream } from '../services/agent_service';
|
|
10
|
-
import { getAgentClient, agentLatticeManager } from '@axiom-lattice/core';
|
|
11
|
-
|
|
12
|
-
// Mock the dependencies
|
|
13
|
-
jest.mock('@axiom-lattice/core', () => ({
|
|
14
|
-
getAgentClient: jest.fn(),
|
|
15
|
-
agentLatticeManager: {
|
|
16
|
-
getAgentLatticeWithTenant: jest.fn(),
|
|
17
|
-
},
|
|
18
|
-
InMemoryChunkBuffer: jest.fn().mockImplementation(() => ({
|
|
19
|
-
addChunk: jest.fn(),
|
|
20
|
-
completeThread: jest.fn(),
|
|
21
|
-
abortThread: jest.fn(),
|
|
22
|
-
})),
|
|
23
|
-
registerChunkBuffer: jest.fn(),
|
|
24
|
-
getChunkBuffer: jest.fn(),
|
|
25
|
-
hasChunkBuffer: jest.fn().mockReturnValue(false),
|
|
26
|
-
}));
|
|
27
|
-
|
|
28
|
-
describe('Agent Service - getAgentClient async handling', () => {
|
|
29
|
-
const mockTenantId = 'test-tenant';
|
|
30
|
-
const mockAssistantId = 'test-assistant';
|
|
31
|
-
const mockThreadId = 'test-thread';
|
|
32
|
-
|
|
33
|
-
beforeEach(() => {
|
|
34
|
-
jest.clearAllMocks();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('agent_state', () => {
|
|
38
|
-
it('should properly await getAgentClient before calling getState', async () => {
|
|
39
|
-
const mockAgent = {
|
|
40
|
-
getState: jest.fn().mockResolvedValue({
|
|
41
|
-
values: { messages: [] },
|
|
42
|
-
next: [],
|
|
43
|
-
}),
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Mock getAgentClient to return a promise that resolves to mockAgent
|
|
47
|
-
(getAgentClient as jest.Mock).mockResolvedValue(mockAgent);
|
|
48
|
-
|
|
49
|
-
const result = await agent_state({
|
|
50
|
-
tenant_id: mockTenantId,
|
|
51
|
-
assistant_id: mockAssistantId,
|
|
52
|
-
thread_id: mockThreadId,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
// Verify getAgentClient was called with correct arguments
|
|
56
|
-
expect(getAgentClient).toHaveBeenCalledWith(mockTenantId, mockAssistantId);
|
|
57
|
-
|
|
58
|
-
// Verify getState was called on the resolved agent (not on a promise)
|
|
59
|
-
expect(mockAgent.getState).toHaveBeenCalledWith({
|
|
60
|
-
configurable: { thread_id: mockThreadId, subgraphs: false },
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
expect(result).toEqual({
|
|
64
|
-
values: { messages: [] },
|
|
65
|
-
next: [],
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should throw error when agent is not found', async () => {
|
|
70
|
-
(getAgentClient as jest.Mock).mockResolvedValue(null);
|
|
71
|
-
|
|
72
|
-
await expect(agent_state({
|
|
73
|
-
tenant_id: mockTenantId,
|
|
74
|
-
assistant_id: mockAssistantId,
|
|
75
|
-
thread_id: mockThreadId,
|
|
76
|
-
})).rejects.toThrow(`Agent ${mockAssistantId} not found`);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should throw error when getAgentClient returns undefined', async () => {
|
|
80
|
-
(getAgentClient as jest.Mock).mockResolvedValue(undefined);
|
|
81
|
-
|
|
82
|
-
await expect(agent_state({
|
|
83
|
-
tenant_id: mockTenantId,
|
|
84
|
-
assistant_id: mockAssistantId,
|
|
85
|
-
thread_id: mockThreadId,
|
|
86
|
-
})).rejects.toThrow(`Agent ${mockAssistantId} not found`);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('agent_messages', () => {
|
|
91
|
-
it('should properly await getAgentClient before calling getState', async () => {
|
|
92
|
-
const mockMessages = [
|
|
93
|
-
{ id: '1', getType: () => 'human', content: 'Hello', lc_kwargs: {} },
|
|
94
|
-
{ id: '2', getType: () => 'ai', content: 'Hi there', lc_kwargs: {} },
|
|
95
|
-
];
|
|
96
|
-
|
|
97
|
-
const mockAgent = {
|
|
98
|
-
getState: jest.fn().mockResolvedValue({
|
|
99
|
-
values: { messages: mockMessages },
|
|
100
|
-
}),
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
(getAgentClient as jest.Mock).mockResolvedValue(mockAgent);
|
|
104
|
-
|
|
105
|
-
const result = await agent_messages({
|
|
106
|
-
tenant_id: mockTenantId,
|
|
107
|
-
assistant_id: mockAssistantId,
|
|
108
|
-
thread_id: mockThreadId,
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
expect(getAgentClient).toHaveBeenCalledWith(mockTenantId, mockAssistantId);
|
|
112
|
-
expect(mockAgent.getState).toHaveBeenCalledWith({
|
|
113
|
-
configurable: { thread_id: mockThreadId, subgraphs: false },
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
expect(result).toHaveLength(2);
|
|
117
|
-
expect(result[0].role).toBe('human');
|
|
118
|
-
expect(result[1].role).toBe('ai');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('draw_graph', () => {
|
|
123
|
-
it('should properly await getAgentClient before calling getGraphAsync', async () => {
|
|
124
|
-
const mockGraph = {
|
|
125
|
-
drawMermaid: jest.fn().mockResolvedValue('graph TD; A-->B;'),
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const mockAgent = {
|
|
129
|
-
getGraphAsync: jest.fn().mockResolvedValue(mockGraph),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
(getAgentClient as jest.Mock).mockResolvedValue(mockAgent);
|
|
133
|
-
|
|
134
|
-
const result = await draw_graph(mockAssistantId, mockTenantId);
|
|
135
|
-
|
|
136
|
-
expect(getAgentClient).toHaveBeenCalledWith(mockTenantId, mockAssistantId);
|
|
137
|
-
expect(mockAgent.getGraphAsync).toHaveBeenCalled();
|
|
138
|
-
expect(mockGraph.drawMermaid).toHaveBeenCalled();
|
|
139
|
-
expect(result).toBe('graph TD; A-->B;');
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should throw error when agent is not found', async () => {
|
|
143
|
-
(getAgentClient as jest.Mock).mockResolvedValue(null);
|
|
144
|
-
|
|
145
|
-
await expect(draw_graph(mockAssistantId, mockTenantId))
|
|
146
|
-
.rejects.toThrow(`Agent ${mockAssistantId} not found`);
|
|
147
|
-
});
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
describe('agent_stream', () => {
|
|
151
|
-
it('should properly await getAgentClient before calling stream', async () => {
|
|
152
|
-
const mockStream = async function* () {
|
|
153
|
-
yield ['updates', { messages: [{ toDict: () => ({ type: 'ai', content: 'Hello' }) }] }];
|
|
154
|
-
}();
|
|
155
|
-
|
|
156
|
-
const mockAgent = {
|
|
157
|
-
stream: jest.fn().mockResolvedValue(mockStream),
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
(getAgentClient as jest.Mock).mockResolvedValue(mockAgent);
|
|
161
|
-
(agentLatticeManager.getAgentLatticeWithTenant as jest.Mock).mockReturnValue({
|
|
162
|
-
config: { runConfig: {} },
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const result = await agent_stream({
|
|
166
|
-
tenant_id: mockTenantId,
|
|
167
|
-
assistant_id: mockAssistantId,
|
|
168
|
-
thread_id: mockThreadId,
|
|
169
|
-
input: { message: 'Test' },
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
expect(getAgentClient).toHaveBeenCalledWith(mockTenantId, mockAssistantId);
|
|
173
|
-
expect(mockAgent.stream).toHaveBeenCalled();
|
|
174
|
-
expect(typeof result[Symbol.asyncIterator]).toBe('function');
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it('should throw error when agent is not found', async () => {
|
|
178
|
-
(getAgentClient as jest.Mock).mockResolvedValue(null);
|
|
179
|
-
(agentLatticeManager.getAgentLatticeWithTenant as jest.Mock).mockReturnValue({
|
|
180
|
-
config: { runConfig: {} },
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// The function will throw but also try to call chunkBuffer.abortThread
|
|
184
|
-
// which might fail in test environment, so we just check it throws
|
|
185
|
-
await expect(agent_stream({
|
|
186
|
-
tenant_id: mockTenantId,
|
|
187
|
-
assistant_id: mockAssistantId,
|
|
188
|
-
thread_id: mockThreadId,
|
|
189
|
-
input: { message: 'Test' },
|
|
190
|
-
})).rejects.toThrow();
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
describe('agent_invoke', () => {
|
|
195
|
-
it('should use agentLattice.client directly without calling getAgentClient', async () => {
|
|
196
|
-
const mockResult = {
|
|
197
|
-
messages: [
|
|
198
|
-
{ toDict: () => ({ type: 'ai', content: 'Response' }) },
|
|
199
|
-
],
|
|
200
|
-
};
|
|
201
|
-
|
|
202
|
-
const mockAgent = {
|
|
203
|
-
invoke: jest.fn().mockResolvedValue(mockResult),
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
(agentLatticeManager.getAgentLatticeWithTenant as jest.Mock).mockReturnValue({
|
|
207
|
-
client: mockAgent,
|
|
208
|
-
config: { runConfig: {} },
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const result = await agent_invoke({
|
|
212
|
-
tenant_id: mockTenantId,
|
|
213
|
-
assistant_id: mockAssistantId,
|
|
214
|
-
thread_id: mockThreadId,
|
|
215
|
-
input: { message: 'Hello' },
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// agent_invoke uses agentLattice.client directly, not getAgentClient
|
|
219
|
-
expect(getAgentClient).not.toHaveBeenCalled();
|
|
220
|
-
expect(mockAgent.invoke).toHaveBeenCalled();
|
|
221
|
-
expect(result.messages).toHaveLength(1);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it('should throw error when agent lattice client is not found', async () => {
|
|
225
|
-
(agentLatticeManager.getAgentLatticeWithTenant as jest.Mock).mockReturnValue({
|
|
226
|
-
client: null,
|
|
227
|
-
config: { runConfig: {} },
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
await expect(agent_invoke({
|
|
231
|
-
tenant_id: mockTenantId,
|
|
232
|
-
assistant_id: mockAssistantId,
|
|
233
|
-
thread_id: mockThreadId,
|
|
234
|
-
input: { message: 'Hello' },
|
|
235
|
-
})).rejects.toThrow(`Agent ${mockAssistantId} not found`);
|
|
236
|
-
});
|
|
237
|
-
});
|
|
238
|
-
});
|