@lobehub/lobehub 2.0.0-next.287 → 2.0.0-next.288
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/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/locales/en-US/plugin.json +3 -5
- package/locales/zh-CN/plugin.json +3 -5
- package/locales/zh-CN/tool.json +2 -0
- package/package.json +1 -1
- package/packages/builtin-agents/src/agents/group-supervisor/index.ts +12 -1
- package/packages/builtin-agents/src/agents/group-supervisor/systemRole.ts +0 -7
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/EditLocalFile/index.tsx +93 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GlobLocalFiles/index.tsx +73 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/GrepContent/index.tsx +69 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ListLocalFiles/index.tsx +68 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/ReadLocalFile/index.tsx +74 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/SearchLocalFiles/index.tsx +70 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/WriteLocalFile/index.tsx +57 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Inspector/index.ts +14 -0
- package/packages/builtin-tool-cloud-sandbox/src/client/Render/WriteFile/index.tsx +54 -35
- package/packages/builtin-tool-cloud-sandbox/src/client/components/FilePathDisplay.tsx +52 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/ExecuteTasks/index.tsx +90 -0
- package/packages/builtin-tool-group-management/src/client/Inspector/index.ts +2 -0
- package/packages/builtin-tool-group-management/src/client/Intervention/ExecuteTasks.tsx +237 -0
- package/packages/builtin-tool-group-management/src/client/Intervention/index.ts +4 -1
- package/packages/builtin-tool-group-management/src/client/Render/index.ts +1 -1
- package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTask/index.tsx +69 -0
- package/packages/builtin-tool-group-management/src/client/Streaming/ExecuteTasks/index.tsx +87 -0
- package/packages/builtin-tool-group-management/src/client/Streaming/index.ts +4 -0
- package/packages/builtin-tool-group-management/src/executor.test.ts +8 -311
- package/packages/builtin-tool-group-management/src/executor.ts +5 -160
- package/packages/builtin-tool-group-management/src/manifest.ts +50 -94
- package/packages/builtin-tool-group-management/src/systemRole.ts +251 -172
- package/packages/builtin-tool-group-management/src/types.ts +29 -40
- package/packages/context-engine/src/engine/messages/MessagesEngine.ts +6 -4
- package/packages/context-engine/src/engine/messages/types.ts +4 -4
- package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
- package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
- package/packages/context-engine/src/processors/index.ts +2 -2
- package/packages/context-engine/src/providers/__tests__/GroupContextInjector.test.ts +4 -16
- package/packages/context-engine/src/providers/__tests__/__snapshots__/GroupContextInjector.test.ts.snap +23 -28
- package/packages/prompts/src/prompts/agentGroup/__snapshots__/index.test.ts.snap +0 -7
- package/packages/prompts/src/prompts/agentGroup/groupContext.ts +0 -7
- package/src/app/[variants]/(main)/group/features/Conversation/AgentWelcome/OpeningQuestions.tsx +4 -8
- package/src/app/[variants]/(main)/group/features/Conversation/MainChatInput/GroupChat.tsx +0 -3
- package/src/app/[variants]/(main)/group/features/Conversation/useGroupContext.ts +3 -0
- package/src/features/ChatInput/Desktop/index.tsx +1 -3
- package/src/features/Conversation/store/slices/message/action/crud.ts +2 -2
- package/src/locales/default/plugin.ts +3 -5
- package/src/locales/default/tool.ts +3 -0
- package/src/services/chat/mecha/agentConfigResolver.test.ts +160 -0
- package/src/services/chat/mecha/agentConfigResolver.ts +15 -3
- package/src/services/chat/mecha/contextEngineering.ts +2 -1
- package/src/store/chat/agents/GroupOrchestration/createGroupOrchestrationExecutors.ts +4 -2
- package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +2 -0
- package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +1 -18
- package/src/store/chat/slices/message/selectors/displayMessage.test.ts +24 -0
- package/src/store/chat/slices/message/selectors/displayMessage.ts +6 -1
- package/src/store/chat/slices/topic/action.test.ts +10 -4
- package/src/store/chat/slices/topic/action.ts +3 -2
- package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
- package/packages/context-engine/src/processors/__tests__/GroupMessageSender.test.ts +0 -274
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { PipelineContext } from '../../types';
|
|
4
|
+
import { GroupRoleTransformProcessor } from '../GroupRoleTransform';
|
|
5
|
+
|
|
6
|
+
describe('GroupRoleTransformProcessor', () => {
|
|
7
|
+
const createContext = (messages: any[]): PipelineContext => ({
|
|
8
|
+
initialState: { messages: [] },
|
|
9
|
+
isAborted: false,
|
|
10
|
+
messages,
|
|
11
|
+
metadata: {},
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const defaultConfig = {
|
|
15
|
+
agentMap: {
|
|
16
|
+
'agent-a': { name: 'Agent A', role: 'participant' as const },
|
|
17
|
+
'agent-b': { name: 'Agent B', role: 'participant' as const },
|
|
18
|
+
'supervisor': { name: 'Supervisor', role: 'supervisor' as const },
|
|
19
|
+
},
|
|
20
|
+
currentAgentId: 'agent-a',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('assistant message transformation', () => {
|
|
24
|
+
it('should keep current agent messages as assistant', async () => {
|
|
25
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
26
|
+
const context = createContext([
|
|
27
|
+
{ content: 'Hello', role: 'user' },
|
|
28
|
+
{ agentId: 'agent-a', content: 'Response from current agent', role: 'assistant' },
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const result = await processor.process(context);
|
|
32
|
+
|
|
33
|
+
expect(result.messages).toHaveLength(2);
|
|
34
|
+
expect(result.messages[1].role).toBe('assistant');
|
|
35
|
+
expect(result.messages[1].content).toBe('Response from current agent');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should transform other agent messages to user with speaker tag', async () => {
|
|
39
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
40
|
+
const context = createContext([
|
|
41
|
+
{ content: 'Hello', role: 'user' },
|
|
42
|
+
{ agentId: 'agent-b', content: 'Response from other agent', role: 'assistant' },
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
const result = await processor.process(context);
|
|
46
|
+
|
|
47
|
+
expect(result.messages).toHaveLength(2);
|
|
48
|
+
expect(result.messages[1].role).toBe('user');
|
|
49
|
+
expect(result.messages[1].content).toContain('<speaker name="Agent B" />');
|
|
50
|
+
expect(result.messages[1].content).toContain('Response from other agent');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should transform supervisor messages to user when current agent is participant', async () => {
|
|
54
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
55
|
+
const context = createContext([
|
|
56
|
+
{ content: 'Hello', role: 'user' },
|
|
57
|
+
{ agentId: 'supervisor', content: 'Supervisor response', role: 'assistant' },
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const result = await processor.process(context);
|
|
61
|
+
|
|
62
|
+
expect(result.messages).toHaveLength(2);
|
|
63
|
+
expect(result.messages[1].role).toBe('user');
|
|
64
|
+
expect(result.messages[1].content).toContain('<speaker name="Supervisor" />');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should keep assistant messages without agentId unchanged', async () => {
|
|
68
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
69
|
+
const context = createContext([
|
|
70
|
+
{ content: 'Hello', role: 'user' },
|
|
71
|
+
{ content: 'Response without agentId', role: 'assistant' },
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
const result = await processor.process(context);
|
|
75
|
+
|
|
76
|
+
expect(result.messages).toHaveLength(2);
|
|
77
|
+
expect(result.messages[1].role).toBe('assistant');
|
|
78
|
+
expect(result.messages[1].content).toBe('Response without agentId');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe('assistant message with tools', () => {
|
|
83
|
+
it('should transform other agent message with tools to user with tool_use section', async () => {
|
|
84
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
85
|
+
const context = createContext([
|
|
86
|
+
{ content: 'Hello', role: 'user' },
|
|
87
|
+
{
|
|
88
|
+
agentId: 'agent-b',
|
|
89
|
+
content: 'Let me check the weather',
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
tools: [
|
|
92
|
+
{
|
|
93
|
+
apiName: 'getWeather',
|
|
94
|
+
arguments: '{"city": "Beijing"}',
|
|
95
|
+
id: 'call_123',
|
|
96
|
+
identifier: 'weather-plugin',
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const result = await processor.process(context);
|
|
103
|
+
|
|
104
|
+
expect(result.messages).toHaveLength(2);
|
|
105
|
+
expect(result.messages[1].role).toBe('user');
|
|
106
|
+
expect(result.messages[1].content).toContain('<speaker name="Agent B" />');
|
|
107
|
+
expect(result.messages[1].content).toContain('Let me check the weather');
|
|
108
|
+
expect(result.messages[1].content).toContain('<tool_use>');
|
|
109
|
+
expect(result.messages[1].content).toContain(
|
|
110
|
+
'<tool id="call_123" name="weather-plugin.getWeather">',
|
|
111
|
+
);
|
|
112
|
+
expect(result.messages[1].content).toContain('{"city": "Beijing"}');
|
|
113
|
+
expect(result.messages[1].content).toContain('</tool>');
|
|
114
|
+
expect(result.messages[1].content).toContain('</tool_use>');
|
|
115
|
+
expect(result.messages[1].tools).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('should transform other agent message with multiple tools', async () => {
|
|
119
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
120
|
+
const context = createContext([
|
|
121
|
+
{
|
|
122
|
+
agentId: 'agent-b',
|
|
123
|
+
content: 'Checking multiple things',
|
|
124
|
+
role: 'assistant',
|
|
125
|
+
tools: [
|
|
126
|
+
{
|
|
127
|
+
apiName: 'getWeather',
|
|
128
|
+
arguments: '{"city": "Beijing"}',
|
|
129
|
+
id: 'call_1',
|
|
130
|
+
identifier: 'weather',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
apiName: 'getTime',
|
|
134
|
+
arguments: '{"timezone": "UTC"}',
|
|
135
|
+
id: 'call_2',
|
|
136
|
+
identifier: 'time',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
const result = await processor.process(context);
|
|
143
|
+
|
|
144
|
+
expect(result.messages[0].role).toBe('user');
|
|
145
|
+
expect(result.messages[0].content).toContain('<tool id="call_1" name="weather.getWeather">');
|
|
146
|
+
expect(result.messages[0].content).toContain('<tool id="call_2" name="time.getTime">');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should keep current agent message with tools as assistant', async () => {
|
|
150
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
151
|
+
const context = createContext([
|
|
152
|
+
{
|
|
153
|
+
agentId: 'agent-a',
|
|
154
|
+
content: 'Current agent using tools',
|
|
155
|
+
role: 'assistant',
|
|
156
|
+
tools: [
|
|
157
|
+
{
|
|
158
|
+
apiName: 'search',
|
|
159
|
+
arguments: '{"q": "test"}',
|
|
160
|
+
id: 'call_xyz',
|
|
161
|
+
identifier: 'search-plugin',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const result = await processor.process(context);
|
|
168
|
+
|
|
169
|
+
expect(result.messages[0].role).toBe('assistant');
|
|
170
|
+
expect(result.messages[0].tools).toBeDefined();
|
|
171
|
+
expect(result.messages[0].content).not.toContain('<speaker');
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
describe('tool message transformation', () => {
|
|
176
|
+
it('should transform other agent tool messages to user with tool_result', async () => {
|
|
177
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
178
|
+
const context = createContext([
|
|
179
|
+
{
|
|
180
|
+
agentId: 'agent-b',
|
|
181
|
+
content: '{"temperature": 25}',
|
|
182
|
+
plugin: {
|
|
183
|
+
apiName: 'getWeather',
|
|
184
|
+
identifier: 'weather-plugin',
|
|
185
|
+
},
|
|
186
|
+
role: 'tool',
|
|
187
|
+
tool_call_id: 'call_123',
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
|
|
191
|
+
const result = await processor.process(context);
|
|
192
|
+
|
|
193
|
+
expect(result.messages[0].role).toBe('user');
|
|
194
|
+
expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
|
|
195
|
+
expect(result.messages[0].content).toContain(
|
|
196
|
+
'<tool_result id="call_123" name="weather-plugin.getWeather">',
|
|
197
|
+
);
|
|
198
|
+
expect(result.messages[0].content).toContain('{"temperature": 25}');
|
|
199
|
+
expect(result.messages[0].content).toContain('</tool_result>');
|
|
200
|
+
expect(result.messages[0].tool_call_id).toBeUndefined();
|
|
201
|
+
expect(result.messages[0].plugin).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should keep current agent tool messages unchanged', async () => {
|
|
205
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
206
|
+
const context = createContext([
|
|
207
|
+
{
|
|
208
|
+
agentId: 'agent-a',
|
|
209
|
+
content: '{"result": "data"}',
|
|
210
|
+
plugin: {
|
|
211
|
+
apiName: 'search',
|
|
212
|
+
identifier: 'search-plugin',
|
|
213
|
+
},
|
|
214
|
+
role: 'tool',
|
|
215
|
+
tool_call_id: 'call_abc',
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
218
|
+
|
|
219
|
+
const result = await processor.process(context);
|
|
220
|
+
|
|
221
|
+
expect(result.messages[0].role).toBe('tool');
|
|
222
|
+
expect(result.messages[0].tool_call_id).toBe('call_abc');
|
|
223
|
+
expect(result.messages[0].content).not.toContain('<speaker');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should keep tool messages without agentId unchanged', async () => {
|
|
227
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
228
|
+
const context = createContext([
|
|
229
|
+
{
|
|
230
|
+
content: '{"result": "data"}',
|
|
231
|
+
role: 'tool',
|
|
232
|
+
tool_call_id: 'call_abc',
|
|
233
|
+
},
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
const result = await processor.process(context);
|
|
237
|
+
|
|
238
|
+
expect(result.messages[0].role).toBe('tool');
|
|
239
|
+
expect(result.messages[0].tool_call_id).toBe('call_abc');
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe('mixed conversation', () => {
|
|
244
|
+
it('should handle mixed conversation with multiple agents', async () => {
|
|
245
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
246
|
+
const context = createContext([
|
|
247
|
+
{ content: 'User question', role: 'user' },
|
|
248
|
+
{ agentId: 'supervisor', content: 'Supervisor intro', role: 'assistant' },
|
|
249
|
+
{ agentId: 'agent-b', content: 'Agent B response', role: 'assistant' },
|
|
250
|
+
{ agentId: 'agent-a', content: 'Current agent response', role: 'assistant' },
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
const result = await processor.process(context);
|
|
254
|
+
|
|
255
|
+
expect(result.messages).toHaveLength(4);
|
|
256
|
+
// User message unchanged
|
|
257
|
+
expect(result.messages[0].role).toBe('user');
|
|
258
|
+
expect(result.messages[0].content).toBe('User question');
|
|
259
|
+
// Supervisor transformed to user
|
|
260
|
+
expect(result.messages[1].role).toBe('user');
|
|
261
|
+
expect(result.messages[1].content).toContain('<speaker name="Supervisor" />');
|
|
262
|
+
// Agent B transformed to user
|
|
263
|
+
expect(result.messages[2].role).toBe('user');
|
|
264
|
+
expect(result.messages[2].content).toContain('<speaker name="Agent B" />');
|
|
265
|
+
// Current agent stays as assistant
|
|
266
|
+
expect(result.messages[3].role).toBe('assistant');
|
|
267
|
+
expect(result.messages[3].content).toBe('Current agent response');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should handle tool call and result from other agent', async () => {
|
|
271
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
272
|
+
const context = createContext([
|
|
273
|
+
{ content: 'Check weather', role: 'user' },
|
|
274
|
+
{
|
|
275
|
+
agentId: 'agent-b',
|
|
276
|
+
content: 'Checking...',
|
|
277
|
+
role: 'assistant',
|
|
278
|
+
tools: [{ apiName: 'get', arguments: '{}', id: 'call_1', identifier: 'weather' }],
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
agentId: 'agent-b',
|
|
282
|
+
content: '{"temp": 25}',
|
|
283
|
+
plugin: { apiName: 'get', identifier: 'weather' },
|
|
284
|
+
role: 'tool',
|
|
285
|
+
tool_call_id: 'call_1',
|
|
286
|
+
},
|
|
287
|
+
{ agentId: 'agent-a', content: 'Based on the weather...', role: 'assistant' },
|
|
288
|
+
]);
|
|
289
|
+
|
|
290
|
+
const result = await processor.process(context);
|
|
291
|
+
|
|
292
|
+
expect(result.messages).toHaveLength(4);
|
|
293
|
+
// User unchanged
|
|
294
|
+
expect(result.messages[0].role).toBe('user');
|
|
295
|
+
// Agent B assistant with tool -> user
|
|
296
|
+
expect(result.messages[1].role).toBe('user');
|
|
297
|
+
expect(result.messages[1].content).toContain('<tool_use>');
|
|
298
|
+
// Agent B tool result -> user
|
|
299
|
+
expect(result.messages[2].role).toBe('user');
|
|
300
|
+
expect(result.messages[2].content).toContain('<tool_result');
|
|
301
|
+
// Current agent -> assistant
|
|
302
|
+
expect(result.messages[3].role).toBe('assistant');
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
describe('edge cases', () => {
|
|
307
|
+
it('should skip processing when no currentAgentId provided', async () => {
|
|
308
|
+
const processor = new GroupRoleTransformProcessor({
|
|
309
|
+
agentMap: defaultConfig.agentMap,
|
|
310
|
+
currentAgentId: '',
|
|
311
|
+
});
|
|
312
|
+
const context = createContext([{ agentId: 'agent-b', content: 'Test', role: 'assistant' }]);
|
|
313
|
+
|
|
314
|
+
const result = await processor.process(context);
|
|
315
|
+
|
|
316
|
+
expect(result.messages[0].role).toBe('assistant');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should skip processing when no agentMap provided', async () => {
|
|
320
|
+
const processor = new GroupRoleTransformProcessor({
|
|
321
|
+
agentMap: {},
|
|
322
|
+
currentAgentId: 'agent-a',
|
|
323
|
+
});
|
|
324
|
+
const context = createContext([{ agentId: 'agent-b', content: 'Test', role: 'assistant' }]);
|
|
325
|
+
|
|
326
|
+
const result = await processor.process(context);
|
|
327
|
+
|
|
328
|
+
// No agentMap entry for agent-b, so message is unchanged
|
|
329
|
+
expect(result.messages[0].role).toBe('assistant');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('should handle empty messages array', async () => {
|
|
333
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
334
|
+
const context = createContext([]);
|
|
335
|
+
|
|
336
|
+
const result = await processor.process(context);
|
|
337
|
+
|
|
338
|
+
expect(result.messages).toHaveLength(0);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should handle message with empty content', async () => {
|
|
342
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
343
|
+
const context = createContext([{ agentId: 'agent-b', content: '', role: 'assistant' }]);
|
|
344
|
+
|
|
345
|
+
const result = await processor.process(context);
|
|
346
|
+
|
|
347
|
+
expect(result.messages[0].role).toBe('user');
|
|
348
|
+
expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
it('should handle array content (multimodal)', async () => {
|
|
352
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
353
|
+
const context = createContext([
|
|
354
|
+
{
|
|
355
|
+
agentId: 'agent-b',
|
|
356
|
+
content: [
|
|
357
|
+
{ text: 'Here is the image analysis', type: 'text' },
|
|
358
|
+
{ image_url: 'data:image/png;base64,...', type: 'image_url' },
|
|
359
|
+
],
|
|
360
|
+
role: 'assistant',
|
|
361
|
+
},
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const result = await processor.process(context);
|
|
365
|
+
|
|
366
|
+
expect(result.messages[0].role).toBe('user');
|
|
367
|
+
expect(result.messages[0].content).toContain('<speaker name="Agent B" />');
|
|
368
|
+
expect(result.messages[0].content).toContain('Here is the image analysis');
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('should track transformation counts in metadata', async () => {
|
|
372
|
+
const processor = new GroupRoleTransformProcessor(defaultConfig);
|
|
373
|
+
const context = createContext([
|
|
374
|
+
{ agentId: 'agent-b', content: 'Msg 1', role: 'assistant' },
|
|
375
|
+
{ agentId: 'agent-b', content: 'Result', role: 'tool', tool_call_id: 'call_1' },
|
|
376
|
+
{ agentId: 'agent-a', content: 'Current', role: 'assistant' },
|
|
377
|
+
]);
|
|
378
|
+
|
|
379
|
+
const result = await processor.process(context);
|
|
380
|
+
|
|
381
|
+
expect(result.metadata.groupRoleTransformProcessed).toEqual({
|
|
382
|
+
assistantTransformed: 1,
|
|
383
|
+
toolTransformed: 1,
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
describe('custom tool name generator', () => {
|
|
389
|
+
it('should use custom genToolName function', async () => {
|
|
390
|
+
const processor = new GroupRoleTransformProcessor({
|
|
391
|
+
...defaultConfig,
|
|
392
|
+
genToolName: (identifier, apiName) => `custom_${identifier}_${apiName}`,
|
|
393
|
+
});
|
|
394
|
+
const context = createContext([
|
|
395
|
+
{
|
|
396
|
+
agentId: 'agent-b',
|
|
397
|
+
content: 'Test',
|
|
398
|
+
role: 'assistant',
|
|
399
|
+
tools: [{ apiName: 'search', arguments: '{}', id: 'call_1', identifier: 'plugin' }],
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
|
|
403
|
+
const result = await processor.process(context);
|
|
404
|
+
|
|
405
|
+
expect(result.messages[0].content).toContain('name="custom_plugin_search"');
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
describe('comprehensive end-to-end transformation', () => {
|
|
410
|
+
it('should correctly transform a full group conversation with 8 messages', async () => {
|
|
411
|
+
const processor = new GroupRoleTransformProcessor({
|
|
412
|
+
agentMap: {
|
|
413
|
+
'agent-a': { name: 'Travel Advisor', role: 'participant' },
|
|
414
|
+
'agent-b': { name: 'Weather Expert', role: 'participant' },
|
|
415
|
+
'agent-c': { name: 'Food Critic', role: 'participant' },
|
|
416
|
+
'supervisor': { name: 'Supervisor', role: 'supervisor' },
|
|
417
|
+
},
|
|
418
|
+
currentAgentId: 'agent-a',
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const inputMessages = [
|
|
422
|
+
// 1. User's original question
|
|
423
|
+
{ content: '帮我规划一下明天去杭州的行程', id: 'msg_1', role: 'user' },
|
|
424
|
+
// 2. Supervisor's broadcast instruction (from orchestration)
|
|
425
|
+
{
|
|
426
|
+
agentId: 'supervisor',
|
|
427
|
+
content: '请各位专家从自己的角度给出建议',
|
|
428
|
+
id: 'msg_2',
|
|
429
|
+
role: 'assistant',
|
|
430
|
+
},
|
|
431
|
+
// 3. Weather Expert's response with tool
|
|
432
|
+
{
|
|
433
|
+
agentId: 'agent-b',
|
|
434
|
+
content: '让我查一下杭州明天的天气',
|
|
435
|
+
id: 'msg_3',
|
|
436
|
+
role: 'assistant',
|
|
437
|
+
tools: [
|
|
438
|
+
{
|
|
439
|
+
apiName: 'getWeather',
|
|
440
|
+
arguments: '{"city": "杭州", "date": "tomorrow"}',
|
|
441
|
+
id: 'call_weather_1',
|
|
442
|
+
identifier: 'weather-plugin',
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
// 4. Weather tool result
|
|
447
|
+
{
|
|
448
|
+
agentId: 'agent-b',
|
|
449
|
+
content: '{"temperature": 22, "weather": "多云", "humidity": 65}',
|
|
450
|
+
id: 'msg_4',
|
|
451
|
+
plugin: { apiName: 'getWeather', identifier: 'weather-plugin' },
|
|
452
|
+
role: 'tool',
|
|
453
|
+
tool_call_id: 'call_weather_1',
|
|
454
|
+
},
|
|
455
|
+
// 5. Weather Expert's final response
|
|
456
|
+
{
|
|
457
|
+
agentId: 'agent-b',
|
|
458
|
+
content: '明天杭州天气不错,22度多云,适合出行',
|
|
459
|
+
id: 'msg_5',
|
|
460
|
+
role: 'assistant',
|
|
461
|
+
},
|
|
462
|
+
// 6. Food Critic's response
|
|
463
|
+
{
|
|
464
|
+
agentId: 'agent-c',
|
|
465
|
+
content: '推荐去楼外楼吃正宗的西湖醋鱼',
|
|
466
|
+
id: 'msg_6',
|
|
467
|
+
role: 'assistant',
|
|
468
|
+
},
|
|
469
|
+
// 7. Current agent (Travel Advisor)'s response - should stay as assistant
|
|
470
|
+
{
|
|
471
|
+
agentId: 'agent-a',
|
|
472
|
+
content: '综合以上建议,我来帮你规划具体行程',
|
|
473
|
+
id: 'msg_7',
|
|
474
|
+
role: 'assistant',
|
|
475
|
+
},
|
|
476
|
+
// 8. Follow-up user question
|
|
477
|
+
{ content: '还有什么景点推荐吗?', id: 'msg_8', role: 'user' },
|
|
478
|
+
];
|
|
479
|
+
|
|
480
|
+
const context = createContext(inputMessages);
|
|
481
|
+
const result = await processor.process(context);
|
|
482
|
+
|
|
483
|
+
// Verify the entire output array
|
|
484
|
+
expect(result.messages).toEqual([
|
|
485
|
+
// 1. User message - unchanged
|
|
486
|
+
{ content: '帮我规划一下明天去杭州的行程', id: 'msg_1', role: 'user' },
|
|
487
|
+
// 2. Supervisor -> user with speaker tag
|
|
488
|
+
{
|
|
489
|
+
agentId: 'supervisor',
|
|
490
|
+
content: '<speaker name="Supervisor" />\n请各位专家从自己的角度给出建议',
|
|
491
|
+
id: 'msg_2',
|
|
492
|
+
role: 'user',
|
|
493
|
+
},
|
|
494
|
+
// 3. Weather Expert with tool -> user with speaker + tool_use
|
|
495
|
+
{
|
|
496
|
+
agentId: 'agent-b',
|
|
497
|
+
content: `<speaker name="Weather Expert" />
|
|
498
|
+
让我查一下杭州明天的天气
|
|
499
|
+
|
|
500
|
+
<tool_use>
|
|
501
|
+
<tool id="call_weather_1" name="weather-plugin.getWeather">
|
|
502
|
+
{"city": "杭州", "date": "tomorrow"}
|
|
503
|
+
</tool>
|
|
504
|
+
</tool_use>`,
|
|
505
|
+
id: 'msg_3',
|
|
506
|
+
role: 'user',
|
|
507
|
+
tools: undefined,
|
|
508
|
+
},
|
|
509
|
+
// 4. Tool result -> user with speaker + tool_result
|
|
510
|
+
{
|
|
511
|
+
agentId: 'agent-b',
|
|
512
|
+
content: `<speaker name="Weather Expert" />
|
|
513
|
+
<tool_result id="call_weather_1" name="weather-plugin.getWeather">
|
|
514
|
+
{"temperature": 22, "weather": "多云", "humidity": 65}
|
|
515
|
+
</tool_result>`,
|
|
516
|
+
id: 'msg_4',
|
|
517
|
+
plugin: undefined,
|
|
518
|
+
role: 'user',
|
|
519
|
+
tool_call_id: undefined,
|
|
520
|
+
},
|
|
521
|
+
// 5. Weather Expert's final response -> user with speaker
|
|
522
|
+
{
|
|
523
|
+
agentId: 'agent-b',
|
|
524
|
+
content: '<speaker name="Weather Expert" />\n明天杭州天气不错,22度多云,适合出行',
|
|
525
|
+
id: 'msg_5',
|
|
526
|
+
role: 'user',
|
|
527
|
+
},
|
|
528
|
+
// 6. Food Critic -> user with speaker
|
|
529
|
+
{
|
|
530
|
+
agentId: 'agent-c',
|
|
531
|
+
content: '<speaker name="Food Critic" />\n推荐去楼外楼吃正宗的西湖醋鱼',
|
|
532
|
+
id: 'msg_6',
|
|
533
|
+
role: 'user',
|
|
534
|
+
},
|
|
535
|
+
// 7. Current agent (Travel Advisor) - stays as assistant, no speaker tag
|
|
536
|
+
{
|
|
537
|
+
agentId: 'agent-a',
|
|
538
|
+
content: '综合以上建议,我来帮你规划具体行程',
|
|
539
|
+
id: 'msg_7',
|
|
540
|
+
role: 'assistant',
|
|
541
|
+
},
|
|
542
|
+
// 8. User message - unchanged
|
|
543
|
+
{ content: '还有什么景点推荐吗?', id: 'msg_8', role: 'user' },
|
|
544
|
+
]);
|
|
545
|
+
|
|
546
|
+
// Verify metadata
|
|
547
|
+
expect(result.metadata.groupRoleTransformProcessed).toEqual({
|
|
548
|
+
assistantTransformed: 4, // supervisor, agent-b x2, agent-c
|
|
549
|
+
toolTransformed: 1, // weather tool result
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Transformer processors
|
|
2
2
|
export { AgentCouncilFlattenProcessor } from './AgentCouncilFlatten';
|
|
3
3
|
export { GroupMessageFlattenProcessor } from './GroupMessageFlatten';
|
|
4
|
-
export {
|
|
4
|
+
export { GroupRoleTransformProcessor } from './GroupRoleTransform';
|
|
5
5
|
export { HistoryTruncateProcessor } from './HistoryTruncate';
|
|
6
6
|
export { InputTemplateProcessor } from './InputTemplate';
|
|
7
7
|
export { MessageCleanupProcessor } from './MessageCleanup';
|
|
@@ -19,7 +19,7 @@ export { ToolCallProcessor } from './ToolCall';
|
|
|
19
19
|
export { ToolMessageReorder } from './ToolMessageReorder';
|
|
20
20
|
|
|
21
21
|
// Re-export types
|
|
22
|
-
export type { AgentInfo,
|
|
22
|
+
export type { AgentInfo, GroupRoleTransformConfig } from './GroupRoleTransform';
|
|
23
23
|
export type { HistoryTruncateConfig } from './HistoryTruncate';
|
|
24
24
|
export type { InputTemplateConfig } from './InputTemplate';
|
|
25
25
|
export type { MessageContentConfig, UserMessageContentPart } from './MessageContent';
|
|
@@ -57,11 +57,6 @@ describe('GroupContextInjector', () => {
|
|
|
57
57
|
expect(systemContent).toContain('<member name="Writer" id="agt_writer" />');
|
|
58
58
|
expect(systemContent).toContain('<member name="Editor" id="agt_editor" you="true" />');
|
|
59
59
|
|
|
60
|
-
// Output rules section (important for LOBE-1866)
|
|
61
|
-
expect(systemContent).toContain('<critical_output_rules>');
|
|
62
|
-
expect(systemContent).toContain('<group_context>');
|
|
63
|
-
expect(systemContent).toContain('NEVER start your response with');
|
|
64
|
-
|
|
65
60
|
// Identity rules
|
|
66
61
|
expect(systemContent).toContain('<identity_rules>');
|
|
67
62
|
expect(systemContent).toContain('NEVER expose or display agent IDs');
|
|
@@ -156,8 +151,8 @@ describe('GroupContextInjector', () => {
|
|
|
156
151
|
});
|
|
157
152
|
});
|
|
158
153
|
|
|
159
|
-
describe('
|
|
160
|
-
it('should always include
|
|
154
|
+
describe('Identity Rules Section', () => {
|
|
155
|
+
it('should always include identity rules', async () => {
|
|
161
156
|
const injector = new GroupContextInjector({
|
|
162
157
|
enabled: true,
|
|
163
158
|
// Minimal config
|
|
@@ -170,15 +165,8 @@ describe('GroupContextInjector', () => {
|
|
|
170
165
|
|
|
171
166
|
const systemContent = result.messages[0].content;
|
|
172
167
|
|
|
173
|
-
// Even with minimal config,
|
|
174
|
-
expect(systemContent).
|
|
175
|
-
expect(systemContent).toContain('Your responses must contain ONLY your actual reply content');
|
|
176
|
-
expect(systemContent).toContain('NEVER start your response with');
|
|
177
|
-
expect(systemContent).toContain('<speaker');
|
|
178
|
-
|
|
179
|
-
// Identity rules should also be present
|
|
180
|
-
expect(systemContent).toContain('<identity_rules>');
|
|
181
|
-
expect(systemContent).toContain('NEVER expose or display agent IDs');
|
|
168
|
+
// Even with minimal config, identity rules should be present
|
|
169
|
+
expect(systemContent).toMatchSnapshot();
|
|
182
170
|
});
|
|
183
171
|
});
|
|
184
172
|
|