@lobehub/lobehub 2.0.0-next.287 → 2.0.0-next.289
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 +50 -0
- package/changelog/v1.json +18 -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 +22 -4
- package/packages/context-engine/src/engine/messages/types.ts +4 -4
- package/packages/context-engine/src/processors/GroupOrchestrationFilter.ts +211 -0
- package/packages/context-engine/src/processors/GroupRoleTransform.ts +261 -0
- package/packages/context-engine/src/processors/__tests__/GroupOrchestrationFilter.test.ts +770 -0
- package/packages/context-engine/src/processors/__tests__/GroupRoleTransform.test.ts +553 -0
- package/packages/context-engine/src/processors/index.ts +7 -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/src/store/document/slices/document/action.ts +8 -0
- package/packages/context-engine/src/processors/GroupMessageSender.ts +0 -138
- package/packages/context-engine/src/processors/__tests__/GroupMessageSender.test.ts +0 -274
|
@@ -2,17 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* API names for Group Management tool
|
|
5
|
+
*
|
|
6
|
+
* Note: Member management APIs (searchAgent, inviteAgent, createAgent, removeAgent)
|
|
7
|
+
* are handled by group-agent-builder tool. This tool focuses on orchestration.
|
|
5
8
|
*/
|
|
6
9
|
export const GroupManagementApiName = {
|
|
7
|
-
// ====================
|
|
8
|
-
/** Search for agents that can be invited to the group */
|
|
9
|
-
searchAgent: 'searchAgent',
|
|
10
|
-
/** Invite an agent to join the group */
|
|
11
|
-
inviteAgent: 'inviteAgent',
|
|
12
|
-
/** Create a new agent and add it to the group */
|
|
13
|
-
createAgent: 'createAgent',
|
|
14
|
-
/** Remove an agent from the group */
|
|
15
|
-
removeAgent: 'removeAgent',
|
|
10
|
+
// ==================== Agent Info ====================
|
|
16
11
|
/** Get detailed information about an agent */
|
|
17
12
|
getAgentInfo: 'getAgentInfo',
|
|
18
13
|
|
|
@@ -26,7 +21,9 @@ export const GroupManagementApiName = {
|
|
|
26
21
|
|
|
27
22
|
// ==================== Task Execution ====================
|
|
28
23
|
/** Let an agent execute a task asynchronously */
|
|
29
|
-
|
|
24
|
+
executeAgentTask: 'executeAgentTask',
|
|
25
|
+
/** Let multiple agents execute different tasks in parallel */
|
|
26
|
+
executeAgentTasks: 'executeAgentTasks',
|
|
30
27
|
/** Interrupt a running agent task */
|
|
31
28
|
interrupt: 'interrupt',
|
|
32
29
|
|
|
@@ -44,28 +41,7 @@ export const GroupManagementApiName = {
|
|
|
44
41
|
export type GroupManagementApiNameType =
|
|
45
42
|
(typeof GroupManagementApiName)[keyof typeof GroupManagementApiName];
|
|
46
43
|
|
|
47
|
-
// ====================
|
|
48
|
-
|
|
49
|
-
export interface SearchAgentParams {
|
|
50
|
-
limit?: number;
|
|
51
|
-
query?: string;
|
|
52
|
-
source?: 'user' | 'community';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface InviteAgentParams {
|
|
56
|
-
agentId: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface CreateAgentParams {
|
|
60
|
-
avatar?: string;
|
|
61
|
-
description?: string;
|
|
62
|
-
systemRole: string;
|
|
63
|
-
title: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface RemoveAgentParams {
|
|
67
|
-
agentId: string;
|
|
68
|
-
}
|
|
44
|
+
// ==================== Agent Info Params ====================
|
|
69
45
|
|
|
70
46
|
export interface GetAgentInfoParams {
|
|
71
47
|
agentId: string;
|
|
@@ -116,6 +92,27 @@ export interface ExecuteTaskParams {
|
|
|
116
92
|
timeout?: number;
|
|
117
93
|
}
|
|
118
94
|
|
|
95
|
+
export interface TaskItem {
|
|
96
|
+
/** The ID of the agent to execute this task */
|
|
97
|
+
agentId: string;
|
|
98
|
+
/** Detailed instruction/prompt for the task execution */
|
|
99
|
+
instruction: string;
|
|
100
|
+
/** Optional timeout in milliseconds for this specific task */
|
|
101
|
+
timeout?: number;
|
|
102
|
+
/** Brief title describing what this task does (shown in UI) */
|
|
103
|
+
title: string;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ExecuteTasksParams {
|
|
107
|
+
/**
|
|
108
|
+
* If true, the orchestration will end after all tasks complete,
|
|
109
|
+
* without calling the supervisor again.
|
|
110
|
+
*/
|
|
111
|
+
skipCallSupervisor?: boolean;
|
|
112
|
+
/** Array of tasks to execute, each assigned to a specific agent */
|
|
113
|
+
tasks: TaskItem[];
|
|
114
|
+
}
|
|
115
|
+
|
|
119
116
|
export interface InterruptParams {
|
|
120
117
|
taskId: string;
|
|
121
118
|
}
|
|
@@ -156,14 +153,6 @@ export interface VoteParams {
|
|
|
156
153
|
|
|
157
154
|
// ==================== Result Types ====================
|
|
158
155
|
|
|
159
|
-
export interface AgentSearchResult {
|
|
160
|
-
avatar?: string;
|
|
161
|
-
description?: string;
|
|
162
|
-
id: string;
|
|
163
|
-
source: 'user' | 'community';
|
|
164
|
-
title: string;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
156
|
export interface VoteResult {
|
|
168
157
|
agentId: string;
|
|
169
158
|
reasoning?: string;
|
|
@@ -7,7 +7,8 @@ import { ContextEngine } from '../../pipeline';
|
|
|
7
7
|
import {
|
|
8
8
|
AgentCouncilFlattenProcessor,
|
|
9
9
|
GroupMessageFlattenProcessor,
|
|
10
|
-
|
|
10
|
+
GroupOrchestrationFilterProcessor,
|
|
11
|
+
GroupRoleTransformProcessor,
|
|
11
12
|
HistoryTruncateProcessor,
|
|
12
13
|
InputTemplateProcessor,
|
|
13
14
|
MessageCleanupProcessor,
|
|
@@ -274,11 +275,28 @@ export class MessagesEngine {
|
|
|
274
275
|
// 15. Supervisor role restore (convert role=supervisor back to role=assistant for model)
|
|
275
276
|
new SupervisorRoleRestoreProcessor(),
|
|
276
277
|
|
|
277
|
-
//
|
|
278
|
-
|
|
278
|
+
// 15.5. Group orchestration filter (remove supervisor's orchestration messages like broadcast/speak)
|
|
279
|
+
// This must be BEFORE GroupRoleTransformProcessor so we filter based on original agentId/tools
|
|
280
|
+
...(isAgentGroupEnabled && agentGroup.agentMap && agentGroup.currentAgentId
|
|
279
281
|
? [
|
|
280
|
-
new
|
|
282
|
+
new GroupOrchestrationFilterProcessor({
|
|
283
|
+
agentMap: Object.fromEntries(
|
|
284
|
+
Object.entries(agentGroup.agentMap).map(([id, info]) => [id, { role: info.role }]),
|
|
285
|
+
),
|
|
286
|
+
currentAgentId: agentGroup.currentAgentId,
|
|
287
|
+
// Only enabled when current agent is NOT supervisor (supervisor needs to see orchestration history)
|
|
288
|
+
enabled: agentGroup.currentAgentRole !== 'supervisor',
|
|
289
|
+
}),
|
|
290
|
+
]
|
|
291
|
+
: []),
|
|
292
|
+
|
|
293
|
+
// 16. Group role transform (convert other agents' messages to user role with speaker tags)
|
|
294
|
+
// This must be BEFORE ToolCallProcessor so other agents' tool messages are converted first
|
|
295
|
+
...(isAgentGroupEnabled && agentGroup.currentAgentId
|
|
296
|
+
? [
|
|
297
|
+
new GroupRoleTransformProcessor({
|
|
281
298
|
agentMap: agentGroup.agentMap!,
|
|
299
|
+
currentAgentId: agentGroup.currentAgentId,
|
|
282
300
|
}),
|
|
283
301
|
]
|
|
284
302
|
: []),
|
|
@@ -4,12 +4,12 @@ import type { RuntimeInitialContext, RuntimeStepContext } from '@lobechat/types'
|
|
|
4
4
|
|
|
5
5
|
import type { OpenAIChatMessage, UIChatMessage } from '@/types/index';
|
|
6
6
|
|
|
7
|
-
import type { AgentInfo } from '../../processors/
|
|
7
|
+
import type { AgentInfo } from '../../processors/GroupRoleTransform';
|
|
8
8
|
import type { AgentBuilderContext } from '../../providers/AgentBuilderContextInjector';
|
|
9
|
-
import type { GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
|
|
10
|
-
import type { GroupMemberInfo } from '../../providers/GroupContextInjector';
|
|
11
9
|
import type { GTDPlan } from '../../providers/GTDPlanInjector';
|
|
12
10
|
import type { GTDTodoList } from '../../providers/GTDTodoInjector';
|
|
11
|
+
import type { GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
|
|
12
|
+
import type { GroupMemberInfo } from '../../providers/GroupContextInjector';
|
|
13
13
|
import type { LobeToolManifest } from '../tools/types';
|
|
14
14
|
|
|
15
15
|
/**
|
|
@@ -249,7 +249,7 @@ export interface MessagesEngineResult {
|
|
|
249
249
|
|
|
250
250
|
// Re-export types for convenience
|
|
251
251
|
|
|
252
|
-
export { type AgentInfo } from '../../processors/
|
|
252
|
+
export { type AgentInfo } from '../../processors/GroupRoleTransform';
|
|
253
253
|
export { type AgentBuilderContext } from '../../providers/AgentBuilderContextInjector';
|
|
254
254
|
export { type GroupAgentBuilderContext } from '../../providers/GroupAgentBuilderContextInjector';
|
|
255
255
|
export { type GTDPlan } from '../../providers/GTDPlanInjector';
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
|
|
3
|
+
import { BaseProcessor } from '../base/BaseProcessor';
|
|
4
|
+
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
|
5
|
+
|
|
6
|
+
const log = debug('context-engine:processor:GroupOrchestrationFilterProcessor');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Default orchestration tool identifier
|
|
10
|
+
*/
|
|
11
|
+
const DEFAULT_ORCHESTRATION_IDENTIFIER = 'lobe-group-management';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Default orchestration api names that should be filtered
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_ORCHESTRATION_API_NAMES = ['broadcast', 'speak', 'executeTask', 'executeTasks'];
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Agent info for identifying supervisor
|
|
20
|
+
*/
|
|
21
|
+
export interface OrchestrationAgentInfo {
|
|
22
|
+
role: 'supervisor' | 'participant';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Tool info structure
|
|
27
|
+
*/
|
|
28
|
+
interface ToolInfo {
|
|
29
|
+
apiName?: string;
|
|
30
|
+
identifier?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Configuration for GroupOrchestrationFilterProcessor
|
|
35
|
+
*/
|
|
36
|
+
export interface GroupOrchestrationFilterConfig {
|
|
37
|
+
/**
|
|
38
|
+
* Mapping from agentId to agent info
|
|
39
|
+
* Used to identify supervisor messages
|
|
40
|
+
*/
|
|
41
|
+
agentMap?: Record<string, OrchestrationAgentInfo>;
|
|
42
|
+
/**
|
|
43
|
+
* The current agent ID that is responding
|
|
44
|
+
* If the current agent is supervisor, filtering will be skipped
|
|
45
|
+
* (Supervisor needs to see its own orchestration history)
|
|
46
|
+
*/
|
|
47
|
+
currentAgentId?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Whether to enable filtering
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
enabled?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Api names of orchestration tools to filter
|
|
55
|
+
* @default ['broadcast', 'speak', 'executeTask', 'executeTasks']
|
|
56
|
+
*/
|
|
57
|
+
orchestrationApiNames?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Tool identifiers that are considered orchestration tools
|
|
60
|
+
* @default ['lobe-group-management']
|
|
61
|
+
*/
|
|
62
|
+
orchestrationToolIdentifiers?: string[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Group Orchestration Filter Processor
|
|
67
|
+
*
|
|
68
|
+
* Filters out Supervisor's orchestration messages (broadcast, speak, executeTask, etc.)
|
|
69
|
+
* from the context to reduce noise for participant agents.
|
|
70
|
+
*
|
|
71
|
+
* These messages are coordination metadata that participant agents don't need to see.
|
|
72
|
+
* Filtering them reduces context window usage and prevents model confusion.
|
|
73
|
+
*
|
|
74
|
+
* Filtering rules:
|
|
75
|
+
* - Supervisor assistant + orchestration tool_use: REMOVE
|
|
76
|
+
* - Supervisor tool_result for orchestration tools: REMOVE
|
|
77
|
+
* - Supervisor assistant without tools: KEEP (may contain meaningful summaries)
|
|
78
|
+
* - Supervisor assistant + non-orchestration tools: KEEP (e.g., search)
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* const processor = new GroupOrchestrationFilterProcessor({
|
|
83
|
+
* agentMap: {
|
|
84
|
+
* 'supervisor-id': { role: 'supervisor' },
|
|
85
|
+
* 'agent-1': { role: 'participant' },
|
|
86
|
+
* },
|
|
87
|
+
* });
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export class GroupOrchestrationFilterProcessor extends BaseProcessor {
|
|
91
|
+
readonly name = 'GroupOrchestrationFilterProcessor';
|
|
92
|
+
|
|
93
|
+
private config: GroupOrchestrationFilterConfig;
|
|
94
|
+
private orchestrationIdentifiers: Set<string>;
|
|
95
|
+
private orchestrationApiNames: Set<string>;
|
|
96
|
+
|
|
97
|
+
constructor(config: GroupOrchestrationFilterConfig = {}, options: ProcessorOptions = {}) {
|
|
98
|
+
super(options);
|
|
99
|
+
this.config = config;
|
|
100
|
+
this.orchestrationIdentifiers = new Set(
|
|
101
|
+
config.orchestrationToolIdentifiers || [DEFAULT_ORCHESTRATION_IDENTIFIER],
|
|
102
|
+
);
|
|
103
|
+
this.orchestrationApiNames = new Set(
|
|
104
|
+
config.orchestrationApiNames || DEFAULT_ORCHESTRATION_API_NAMES,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
|
109
|
+
const clonedContext = this.cloneContext(context);
|
|
110
|
+
|
|
111
|
+
// Skip if disabled or no agentMap provided
|
|
112
|
+
if (this.config.enabled === false || !this.config.agentMap) {
|
|
113
|
+
log('Processor disabled or no agentMap provided, skipping');
|
|
114
|
+
return this.markAsExecuted(clonedContext);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Skip if current agent is supervisor (supervisor needs to see its orchestration history)
|
|
118
|
+
if (this.isCurrentAgentSupervisor()) {
|
|
119
|
+
log('Current agent is supervisor, skipping orchestration filter');
|
|
120
|
+
return this.markAsExecuted(clonedContext);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let filteredCount = 0;
|
|
124
|
+
let assistantFiltered = 0;
|
|
125
|
+
let toolFiltered = 0;
|
|
126
|
+
|
|
127
|
+
const filteredMessages = clonedContext.messages.filter((msg: Message) => {
|
|
128
|
+
// Only filter supervisor messages
|
|
129
|
+
if (!this.isSupervisorMessage(msg)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check assistant messages with tools
|
|
134
|
+
if (msg.role === 'assistant' && msg.tools && msg.tools.length > 0) {
|
|
135
|
+
const hasOrchestrationTool = msg.tools.some((tool: ToolInfo) =>
|
|
136
|
+
this.isOrchestrationTool(tool),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (hasOrchestrationTool) {
|
|
140
|
+
filteredCount++;
|
|
141
|
+
assistantFiltered++;
|
|
142
|
+
log(`Filtering supervisor orchestration assistant message: ${msg.id}`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check tool result messages
|
|
148
|
+
if (msg.role === 'tool' && msg.plugin && this.isOrchestrationTool(msg.plugin)) {
|
|
149
|
+
filteredCount++;
|
|
150
|
+
toolFiltered++;
|
|
151
|
+
log(`Filtering supervisor orchestration tool result: ${msg.id}`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Keep other supervisor messages (pure text, non-orchestration tools)
|
|
156
|
+
return true;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
clonedContext.messages = filteredMessages;
|
|
160
|
+
|
|
161
|
+
// Update metadata
|
|
162
|
+
clonedContext.metadata.orchestrationFilterProcessed = {
|
|
163
|
+
assistantFiltered,
|
|
164
|
+
filteredCount,
|
|
165
|
+
toolFiltered,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
log(
|
|
169
|
+
`Orchestration filter completed: ${filteredCount} messages filtered (${assistantFiltered} assistant, ${toolFiltered} tool)`,
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
return this.markAsExecuted(clonedContext);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Check if the current agent is a supervisor
|
|
177
|
+
* Supervisor doesn't need orchestration messages filtered (they need to see their history)
|
|
178
|
+
*/
|
|
179
|
+
private isCurrentAgentSupervisor(): boolean {
|
|
180
|
+
if (!this.config.currentAgentId || !this.config.agentMap) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const currentAgentInfo = this.config.agentMap[this.config.currentAgentId];
|
|
185
|
+
return currentAgentInfo?.role === 'supervisor';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Check if a message is from a supervisor agent
|
|
190
|
+
*/
|
|
191
|
+
private isSupervisorMessage(msg: Message): boolean {
|
|
192
|
+
if (!msg.agentId || !this.config.agentMap) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const agentInfo = this.config.agentMap[msg.agentId];
|
|
197
|
+
return agentInfo?.role === 'supervisor';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Check if a tool is an orchestration tool that should be filtered
|
|
202
|
+
*/
|
|
203
|
+
private isOrchestrationTool(tool: ToolInfo): boolean {
|
|
204
|
+
if (!tool) return false;
|
|
205
|
+
|
|
206
|
+
const identifier = tool.identifier || '';
|
|
207
|
+
const apiName = tool.apiName || '';
|
|
208
|
+
|
|
209
|
+
return this.orchestrationIdentifiers.has(identifier) && this.orchestrationApiNames.has(apiName);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import debug from 'debug';
|
|
2
|
+
|
|
3
|
+
import { BaseProcessor } from '../base/BaseProcessor';
|
|
4
|
+
import type { Message, PipelineContext, ProcessorOptions } from '../types';
|
|
5
|
+
|
|
6
|
+
const log = debug('context-engine:processor:GroupRoleTransformProcessor');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Agent info for message sender identification
|
|
10
|
+
*/
|
|
11
|
+
export interface AgentInfo {
|
|
12
|
+
name: string;
|
|
13
|
+
role: 'supervisor' | 'participant';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Configuration for GroupRoleTransformProcessor
|
|
18
|
+
*/
|
|
19
|
+
export interface GroupRoleTransformConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Mapping from agentId to agent info
|
|
22
|
+
* Used to look up agent name for each message
|
|
23
|
+
*/
|
|
24
|
+
agentMap: Record<string, AgentInfo>;
|
|
25
|
+
/**
|
|
26
|
+
* The current agent ID that is responding
|
|
27
|
+
* Messages from this agent will remain as assistant role
|
|
28
|
+
*/
|
|
29
|
+
currentAgentId: string;
|
|
30
|
+
/**
|
|
31
|
+
* Custom function to generate tool name from identifier and apiName
|
|
32
|
+
*/
|
|
33
|
+
genToolName?: (identifier: string, apiName: string) => string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Group Role Transform Processor
|
|
38
|
+
*
|
|
39
|
+
* Transforms messages from other agents to user role with speaker tags.
|
|
40
|
+
* This prevents the model from imitating speaker tags in its output.
|
|
41
|
+
*
|
|
42
|
+
* From the model's perspective:
|
|
43
|
+
* - role: assistant = "my" responses (current agent)
|
|
44
|
+
* - role: user = external input (human users and other agents)
|
|
45
|
+
*
|
|
46
|
+
* Processing logic:
|
|
47
|
+
* 1. Current agent's messages: Keep as assistant (no modifications)
|
|
48
|
+
* 2. Other agents' assistant messages: Convert to user with speaker tag
|
|
49
|
+
* 3. Other agents' tool messages: Convert to user with tool_result tag
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const processor = new GroupRoleTransformProcessor({
|
|
54
|
+
* currentAgentId: 'travel-advisor',
|
|
55
|
+
* agentMap: {
|
|
56
|
+
* 'weather-expert': { name: 'Weather Expert', role: 'participant' },
|
|
57
|
+
* 'travel-advisor': { name: 'Travel Advisor', role: 'participant' },
|
|
58
|
+
* 'supervisor': { name: 'Supervisor', role: 'supervisor' },
|
|
59
|
+
* }
|
|
60
|
+
* });
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export class GroupRoleTransformProcessor extends BaseProcessor {
|
|
64
|
+
readonly name = 'GroupRoleTransformProcessor';
|
|
65
|
+
|
|
66
|
+
constructor(
|
|
67
|
+
private config: GroupRoleTransformConfig,
|
|
68
|
+
options: ProcessorOptions = {},
|
|
69
|
+
) {
|
|
70
|
+
super(options);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
protected async doProcess(context: PipelineContext): Promise<PipelineContext> {
|
|
74
|
+
const clonedContext = this.cloneContext(context);
|
|
75
|
+
|
|
76
|
+
// Skip if no currentAgentId or agentMap provided
|
|
77
|
+
if (!this.config.currentAgentId || !this.config.agentMap) {
|
|
78
|
+
log('No currentAgentId or agentMap provided, skipping processing');
|
|
79
|
+
return this.markAsExecuted(clonedContext);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let assistantTransformed = 0;
|
|
83
|
+
let toolTransformed = 0;
|
|
84
|
+
|
|
85
|
+
clonedContext.messages = clonedContext.messages.map((msg: Message) => {
|
|
86
|
+
// Process assistant messages
|
|
87
|
+
if (msg.role === 'assistant' && msg.agentId) {
|
|
88
|
+
if (msg.agentId === this.config.currentAgentId) {
|
|
89
|
+
// Current agent: keep as assistant, no modifications
|
|
90
|
+
return msg;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Other agent: transform to user with speaker tag
|
|
94
|
+
const transformed = this.transformAssistantMessage(msg);
|
|
95
|
+
if (transformed !== msg) {
|
|
96
|
+
assistantTransformed++;
|
|
97
|
+
log(`Transformed assistant message from agent: ${msg.agentId} to user`);
|
|
98
|
+
}
|
|
99
|
+
return transformed;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Process tool messages
|
|
103
|
+
if (msg.role === 'tool' && msg.agentId) {
|
|
104
|
+
if (msg.agentId === this.config.currentAgentId) {
|
|
105
|
+
// Current agent: keep as tool
|
|
106
|
+
return msg;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Other agent: transform to user with tool_result tag
|
|
110
|
+
const transformed = this.transformToolMessage(msg);
|
|
111
|
+
if (transformed !== msg) {
|
|
112
|
+
toolTransformed++;
|
|
113
|
+
log(`Transformed tool message from agent: ${msg.agentId} to user`);
|
|
114
|
+
}
|
|
115
|
+
return transformed;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return msg;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Update metadata
|
|
122
|
+
clonedContext.metadata.groupRoleTransformProcessed = {
|
|
123
|
+
assistantTransformed,
|
|
124
|
+
toolTransformed,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
log(
|
|
128
|
+
`Group role transform completed: ${assistantTransformed} assistant messages, ${toolTransformed} tool messages transformed`,
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
return this.markAsExecuted(clonedContext);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Transform an assistant message from another agent to a user message
|
|
136
|
+
*/
|
|
137
|
+
private transformAssistantMessage(msg: Message): Message {
|
|
138
|
+
const agentInfo = this.config.agentMap[msg.agentId];
|
|
139
|
+
if (!agentInfo) {
|
|
140
|
+
// No agent info found, keep original
|
|
141
|
+
return msg;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const agentName = agentInfo.name;
|
|
145
|
+
let content = this.buildSpeakerTag(agentName);
|
|
146
|
+
|
|
147
|
+
// Add original content
|
|
148
|
+
const originalContent = this.getStringContent(msg.content);
|
|
149
|
+
if (originalContent) {
|
|
150
|
+
content += originalContent;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add tool_use section if message has tools
|
|
154
|
+
if (msg.tools && msg.tools.length > 0) {
|
|
155
|
+
content += this.buildToolUseSection(msg.tools);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
...msg,
|
|
160
|
+
content,
|
|
161
|
+
role: 'user',
|
|
162
|
+
// Remove tool-related fields as they're now embedded in content
|
|
163
|
+
tools: undefined,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Transform a tool result message from another agent to a user message
|
|
169
|
+
*/
|
|
170
|
+
private transformToolMessage(msg: Message): Message {
|
|
171
|
+
const agentInfo = this.config.agentMap[msg.agentId];
|
|
172
|
+
if (!agentInfo) {
|
|
173
|
+
// No agent info found, keep original
|
|
174
|
+
return msg;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const agentName = agentInfo.name;
|
|
178
|
+
const toolName = this.getToolName(msg.plugin);
|
|
179
|
+
const toolCallId = msg.tool_call_id || 'unknown';
|
|
180
|
+
const resultContent = this.getStringContent(msg.content);
|
|
181
|
+
|
|
182
|
+
const content = `${this.buildSpeakerTag(agentName)}<tool_result id="${toolCallId}" name="${toolName}">
|
|
183
|
+
${resultContent}
|
|
184
|
+
</tool_result>`;
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
...msg,
|
|
188
|
+
content,
|
|
189
|
+
// Remove tool-related fields
|
|
190
|
+
plugin: undefined,
|
|
191
|
+
role: 'user',
|
|
192
|
+
tool_call_id: undefined,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Build the speaker tag
|
|
198
|
+
*/
|
|
199
|
+
private buildSpeakerTag(agentName: string): string {
|
|
200
|
+
return `<speaker name="${agentName}" />\n`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Build the tool_use section for assistant messages with tools
|
|
205
|
+
*/
|
|
206
|
+
private buildToolUseSection(tools: any[]): string {
|
|
207
|
+
let section = '\n\n<tool_use>\n';
|
|
208
|
+
|
|
209
|
+
for (const tool of tools) {
|
|
210
|
+
const toolName = this.getToolName(tool);
|
|
211
|
+
const toolId = tool.id || 'unknown';
|
|
212
|
+
const args = tool.arguments || '';
|
|
213
|
+
|
|
214
|
+
section += `<tool id="${toolId}" name="${toolName}">\n`;
|
|
215
|
+
section += `${args}\n`;
|
|
216
|
+
section += `</tool>\n`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
section += '</tool_use>';
|
|
220
|
+
return section;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get tool name from tool object
|
|
225
|
+
*/
|
|
226
|
+
private getToolName(tool: any): string {
|
|
227
|
+
if (!tool) return 'unknown';
|
|
228
|
+
|
|
229
|
+
const identifier = tool.identifier || '';
|
|
230
|
+
const apiName = tool.apiName || '';
|
|
231
|
+
|
|
232
|
+
if (this.config.genToolName) {
|
|
233
|
+
return this.config.genToolName(identifier, apiName);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (identifier && apiName) {
|
|
237
|
+
return `${identifier}.${apiName}`;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return identifier || apiName || 'unknown';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extract string content from message content (handles both string and array formats)
|
|
245
|
+
*/
|
|
246
|
+
private getStringContent(content: string | any[]): string {
|
|
247
|
+
if (typeof content === 'string') {
|
|
248
|
+
return content;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (Array.isArray(content)) {
|
|
252
|
+
// Extract text from array content
|
|
253
|
+
return content
|
|
254
|
+
.filter((part: any) => part.type === 'text')
|
|
255
|
+
.map((part: any) => part.text || '')
|
|
256
|
+
.join('\n');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return '';
|
|
260
|
+
}
|
|
261
|
+
}
|