Skip to content

Commit 16503b0

Browse files
committed
add deep-researcher and software-architecture build-in agents and Agent Collaboration Optimization
1 parent 704846b commit 16503b0

File tree

13 files changed

+1671
-3
lines changed

13 files changed

+1671
-3
lines changed

packages/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
export * from './src/index.js';
88
export { Storage } from './src/config/storage.js';
9+
export * from './src/agent-collaboration/index.js';
910
export {
1011
DEFAULT_QWEN_MODEL,
1112
DEFAULT_QWEN_EMBEDDING_MODEL,
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import type { Config } from '../config/config.js';
3+
import {
4+
createAgentCollaborationAPI,
5+
executeCollaborativeTask,
6+
createAgentTeam,
7+
} from './index.js';
8+
9+
// Mock the DynamicAgentManager
10+
vi.mock('../subagents/dynamic-agent-manager.js', async () => {
11+
const actual = await vi.importActual('../subagents/dynamic-agent-manager.js');
12+
return {
13+
...actual,
14+
DynamicAgentManager: class MockDynamicAgentManager {
15+
async executeAgent(
16+
name: string,
17+
systemPrompt: string,
18+
task: string,
19+
_tools?: string[],
20+
) {
21+
// Simulate a successful execution with mock results
22+
return `Agent ${name} completed task: ${task}`;
23+
}
24+
},
25+
};
26+
});
27+
28+
describe('Agent Collaboration API', () => {
29+
let mockConfig: Config;
30+
31+
beforeEach(() => {
32+
// Create a mock config for testing
33+
const mockToolRegistry = {
34+
registerTool: vi.fn(),
35+
};
36+
37+
mockConfig = {
38+
getToolRegistry: vi.fn().mockReturnValue(mockToolRegistry),
39+
getGeminiClient: vi.fn(),
40+
getModel: vi.fn(),
41+
getWorkspaceContext: vi.fn(),
42+
// Add other required methods as needed
43+
} as unknown as Config;
44+
});
45+
46+
it('should create collaboration API with all systems', () => {
47+
const api = createAgentCollaborationAPI(mockConfig);
48+
49+
expect(api).toBeDefined();
50+
expect(api.coordination).toBeDefined();
51+
expect(api.communication).toBeDefined();
52+
expect(api.orchestration).toBeDefined();
53+
expect(api.memory).toBeDefined();
54+
});
55+
56+
it('should execute a collaborative task in parallel', async () => {
57+
const agents = ['agent1', 'agent2', 'agent3'];
58+
const task = 'Perform a simple calculation';
59+
const strategy = 'parallel';
60+
61+
const results = await executeCollaborativeTask(
62+
mockConfig,
63+
agents,
64+
task,
65+
strategy,
66+
);
67+
68+
expect(results).toBeDefined();
69+
expect(Object.keys(results)).toEqual(agents);
70+
// All agents should have results
71+
for (const agent of agents) {
72+
expect(results[agent]).toBeDefined();
73+
}
74+
});
75+
76+
it('should execute a collaborative task sequentially', async () => {
77+
const agents = ['agent1', 'agent2'];
78+
const task = 'Perform a sequential task';
79+
const strategy = 'sequential';
80+
81+
const results = await executeCollaborativeTask(
82+
mockConfig,
83+
agents,
84+
task,
85+
strategy,
86+
);
87+
88+
expect(results).toBeDefined();
89+
expect(Object.keys(results)).toEqual(agents);
90+
// Both agents should have results
91+
for (const agent of agents) {
92+
expect(results[agent]).toBeDefined();
93+
}
94+
});
95+
96+
it('should execute a collaborative task with round-robin strategy', async () => {
97+
const agents = ['agent1', 'agent2', 'agent3'];
98+
const task = 'Process data in round-robin fashion';
99+
const strategy = 'round-robin';
100+
101+
const results = await executeCollaborativeTask(
102+
mockConfig,
103+
agents,
104+
task,
105+
strategy,
106+
);
107+
108+
expect(results).toBeDefined();
109+
expect(Object.keys(results)).toEqual(agents);
110+
// All agents should have results
111+
for (const agent of agents) {
112+
expect(results[agent]).toBeDefined();
113+
}
114+
});
115+
116+
it('should create an agent team', async () => {
117+
const teamName = 'test-team';
118+
const agents = [
119+
{ name: 'researcher', role: 'research specialist' },
120+
{ name: 'architect', role: 'system designer' },
121+
];
122+
const task = 'Design a new system architecture';
123+
124+
const api = await createAgentTeam(mockConfig, teamName, agents, task);
125+
126+
expect(api).toBeDefined();
127+
128+
// Check if team info is stored in memory
129+
const teamInfo = await api.memory.get(`team:${teamName}`);
130+
expect(teamInfo).toBeDefined();
131+
const typedTeamInfo = teamInfo as {
132+
name: string;
133+
agents: Array<{ name: string; role: string }>;
134+
};
135+
expect(typedTeamInfo.name).toBe(teamName);
136+
expect(typedTeamInfo.agents).toEqual(agents);
137+
});
138+
});
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Qwen
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import type { Config } from '../config/config.js';
8+
import { AgentSharedMemory } from './shared-memory.js';
9+
10+
export interface AgentMessage {
11+
id: string;
12+
from: string;
13+
to: string | 'broadcast';
14+
type: 'request' | 'response' | 'notification' | 'data';
15+
content: string | Record<string, unknown>;
16+
timestamp: string;
17+
correlationId?: string; // For matching requests with responses
18+
priority?: 'low' | 'medium' | 'high';
19+
}
20+
21+
/**
22+
* Communication system for agents to send messages to each other
23+
*/
24+
export class AgentCommunicationSystem {
25+
private readonly memory: AgentSharedMemory;
26+
private config: Config;
27+
28+
constructor(config: Config) {
29+
this.config = config;
30+
this.memory = new AgentSharedMemory(config);
31+
32+
// Use config to log initialization if needed
33+
void this.config;
34+
}
35+
36+
/**
37+
* Send a message to another agent
38+
* @param from The sending agent
39+
* @param to The receiving agent, or 'broadcast' for all agents
40+
* @param type The type of message
41+
* @param content The content of the message
42+
* @param options Additional options like priority or correlation ID
43+
*/
44+
async sendMessage(
45+
from: string,
46+
to: string | 'broadcast',
47+
type: 'request' | 'response' | 'notification' | 'data',
48+
content: string | Record<string, unknown>,
49+
options?: {
50+
correlationId?: string;
51+
priority?: 'low' | 'medium' | 'high';
52+
},
53+
): Promise<string> {
54+
const message: AgentMessage = {
55+
id: `msg-${Date.now()}-${Math.floor(Math.random() * 10000)}`,
56+
from,
57+
to,
58+
type,
59+
content,
60+
timestamp: new Date().toISOString(),
61+
correlationId: options?.correlationId,
62+
priority: options?.priority || 'medium',
63+
};
64+
65+
// Store in shared memory
66+
await this.memory.set(`message:${message.id}`, message);
67+
68+
// Also store in the recipient's inbox if not broadcasting
69+
if (to !== 'broadcast') {
70+
const inboxKey = `inbox:${to}`;
71+
const inbox: AgentMessage[] =
72+
(await this.memory.get<AgentMessage[]>(inboxKey)) || [];
73+
inbox.push(message);
74+
await this.memory.set(inboxKey, inbox);
75+
} else {
76+
// For broadcast, add to all agents' inboxes
77+
const agentKeys = await this.memory.keys();
78+
for (const key of agentKeys) {
79+
if (key.startsWith('inbox:')) {
80+
const inbox: AgentMessage[] =
81+
(await this.memory.get<AgentMessage[]>(key)) || [];
82+
inbox.push(message);
83+
await this.memory.set(key, inbox);
84+
}
85+
}
86+
}
87+
88+
return message.id;
89+
}
90+
91+
/**
92+
* Get messages from an agent's inbox
93+
* @param agentId The agent to get messages for
94+
* @param count The maximum number of messages to return
95+
* @param priority Optional priority filter
96+
*/
97+
async getInbox(
98+
agentId: string,
99+
count?: number,
100+
priority?: 'low' | 'medium' | 'high',
101+
): Promise<AgentMessage[]> {
102+
const inboxKey = `inbox:${agentId}`;
103+
const inbox: AgentMessage[] =
104+
(await this.memory.get<AgentMessage[]>(inboxKey)) || [];
105+
106+
let filteredMessages = inbox;
107+
if (priority) {
108+
filteredMessages = inbox.filter((msg) => msg.priority === priority);
109+
}
110+
111+
// Sort by timestamp (most recent first)
112+
filteredMessages.sort(
113+
(a, b) =>
114+
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
115+
);
116+
117+
return count ? filteredMessages.slice(0, count) : filteredMessages;
118+
}
119+
120+
/**
121+
* Get all messages (for broadcast or admin purposes)
122+
*/
123+
async getAllMessages(): Promise<AgentMessage[]> {
124+
const allKeys = await this.memory.keys();
125+
const messages: AgentMessage[] = [];
126+
127+
for (const key of allKeys) {
128+
if (key.startsWith('message:')) {
129+
const message = await this.memory.get<AgentMessage>(key);
130+
if (message) {
131+
messages.push(message);
132+
}
133+
}
134+
}
135+
136+
return messages;
137+
}
138+
139+
/**
140+
* Clear an agent's inbox
141+
* @param agentId The agent whose inbox to clear
142+
*/
143+
async clearInbox(agentId: string): Promise<void> {
144+
const inboxKey = `inbox:${agentId}`;
145+
await this.memory.delete(inboxKey);
146+
}
147+
148+
/**
149+
* Send a request and wait for a response
150+
* @param from The requesting agent
151+
* @param to The responding agent
152+
* @param request The request content
153+
* @param timeoutMs How long to wait for a response (in ms)
154+
*/
155+
async sendRequestAndWait(
156+
from: string,
157+
to: string,
158+
request: string | Record<string, unknown>,
159+
timeoutMs: number = 5000,
160+
): Promise<AgentMessage | null> {
161+
const correlationId = `req-${Date.now()}`;
162+
163+
// Send the request
164+
await this.sendMessage(from, to, 'request', request, {
165+
correlationId,
166+
priority: 'high',
167+
});
168+
169+
// Wait for a response with the matching correlation ID
170+
const startTime = Date.now();
171+
while (Date.now() - startTime < timeoutMs) {
172+
const inbox = await this.getInbox(from);
173+
const response = inbox.find(
174+
(msg) => msg.correlationId === correlationId && msg.type === 'response',
175+
);
176+
177+
if (response) {
178+
// Remove the response from inbox if it's a direct request-response
179+
const inboxKey = `inbox:${from}`;
180+
const inbox: AgentMessage[] =
181+
(await this.memory.get<AgentMessage[]>(inboxKey)) || [];
182+
const updatedInbox = inbox.filter((msg) => msg.id !== response.id);
183+
await this.memory.set(inboxKey, updatedInbox);
184+
185+
return response;
186+
}
187+
188+
await new Promise((resolve) => setTimeout(resolve, 100)); // Wait 100ms before checking again
189+
}
190+
191+
return null; // Timeout
192+
}
193+
194+
/**
195+
* Get the shared memory instance for direct access
196+
*/
197+
getMemory(): AgentSharedMemory {
198+
return this.memory;
199+
}
200+
}

0 commit comments

Comments
 (0)