@axiom-lattice/gateway 2.1.44 → 2.1.46

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,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, unknown>; // RunConfig for subagent execution
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
- const response = await fetch(apiUrl, {
46
- method: "POST",
47
- body: JSON.stringify({
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
- });