@lupaflow/multi-agent 0.1.0
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/package.json +32 -0
- package/src/communication.ts +89 -0
- package/src/index.ts +5 -0
- package/src/orchestrator.ts +89 -0
- package/src/team.ts +40 -0
- package/tsconfig.json +8 -0
- package/tsup.config.ts +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lupaflow/multi-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"module": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"require": "./dist/index.js",
|
|
16
|
+
"import": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@lupaflow/core": "workspace:*",
|
|
25
|
+
"@lupaflow/types": "workspace:*",
|
|
26
|
+
"@lupaflow/agent": "workspace:*"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tsup": "^8.3.0",
|
|
30
|
+
"typescript": "^5.6.0"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { generateId } from "@lupaflow/core"
|
|
2
|
+
|
|
3
|
+
export interface AgentMessage {
|
|
4
|
+
id: string
|
|
5
|
+
from: string
|
|
6
|
+
to: string
|
|
7
|
+
content: string
|
|
8
|
+
type: "request" | "response" | "broadcast"
|
|
9
|
+
timestamp: number
|
|
10
|
+
metadata?: Record<string, unknown>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class AgentCommunicator {
|
|
14
|
+
private messages: AgentMessage[] = []
|
|
15
|
+
private listeners: Map<string, Set<(msg: AgentMessage) => void>> = new Map()
|
|
16
|
+
|
|
17
|
+
send(from: string, to: string, content: string, metadata?: Record<string, unknown>): AgentMessage {
|
|
18
|
+
const msg: AgentMessage = {
|
|
19
|
+
id: generateId(),
|
|
20
|
+
from,
|
|
21
|
+
to,
|
|
22
|
+
content,
|
|
23
|
+
type: "request",
|
|
24
|
+
timestamp: Date.now(),
|
|
25
|
+
metadata,
|
|
26
|
+
}
|
|
27
|
+
this.messages.push(msg)
|
|
28
|
+
const listeners = this.listeners.get(to)
|
|
29
|
+
if (listeners) {
|
|
30
|
+
for (const listener of listeners) {
|
|
31
|
+
listener(msg)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return msg
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
broadcast(from: string, content: string): void {
|
|
38
|
+
const msg: AgentMessage = {
|
|
39
|
+
id: generateId(),
|
|
40
|
+
from,
|
|
41
|
+
to: "*",
|
|
42
|
+
content,
|
|
43
|
+
type: "broadcast",
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
}
|
|
46
|
+
this.messages.push(msg)
|
|
47
|
+
for (const listeners of this.listeners.values()) {
|
|
48
|
+
for (const listener of listeners) {
|
|
49
|
+
listener(msg)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
respond(to: string, originalId: string, content: string): AgentMessage {
|
|
55
|
+
const original = this.messages.find((m) => m.id === originalId)
|
|
56
|
+
const msg: AgentMessage = {
|
|
57
|
+
id: generateId(),
|
|
58
|
+
from: to,
|
|
59
|
+
to: original?.from || to,
|
|
60
|
+
content,
|
|
61
|
+
type: "response",
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
}
|
|
64
|
+
this.messages.push(msg)
|
|
65
|
+
const listeners = this.listeners.get(msg.to)
|
|
66
|
+
if (listeners) {
|
|
67
|
+
for (const listener of listeners) {
|
|
68
|
+
listener(msg)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return msg
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
on(agentId: string, handler: (msg: AgentMessage) => void): () => void {
|
|
75
|
+
if (!this.listeners.has(agentId)) {
|
|
76
|
+
this.listeners.set(agentId, new Set())
|
|
77
|
+
}
|
|
78
|
+
this.listeners.get(agentId)!.add(handler)
|
|
79
|
+
return () => this.listeners.get(agentId)?.delete(handler)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getHistory(): AgentMessage[] {
|
|
83
|
+
return [...this.messages]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
clear(): void {
|
|
87
|
+
this.messages = []
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Agent } from "@lupaflow/agent"
|
|
2
|
+
import { AgentCommunicator, type AgentMessage } from "./communication"
|
|
3
|
+
import { Team, type TeamMember } from "./team"
|
|
4
|
+
import { generateId } from "@lupaflow/core"
|
|
5
|
+
|
|
6
|
+
export interface OrchestrationResult {
|
|
7
|
+
id: string
|
|
8
|
+
output: string
|
|
9
|
+
memberOutputs: Map<string, string>
|
|
10
|
+
messages: AgentMessage[]
|
|
11
|
+
durationMs: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class Orchestrator {
|
|
15
|
+
private team: Team
|
|
16
|
+
private communicator: AgentCommunicator
|
|
17
|
+
private agents: Map<string, Agent> = new Map()
|
|
18
|
+
private agentsReady: Map<string, Promise<void>> = new Map()
|
|
19
|
+
|
|
20
|
+
constructor(team: Team) {
|
|
21
|
+
this.team = team
|
|
22
|
+
this.communicator = new AgentCommunicator()
|
|
23
|
+
for (const member of team.config.members) {
|
|
24
|
+
const agent = new Agent(member.agentConfig)
|
|
25
|
+
this.agents.set(member.id, agent)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private async runMember(member: TeamMember, task: string): Promise<string> {
|
|
30
|
+
const agent = this.agents.get(member.id)!
|
|
31
|
+
const result = await agent.run(task)
|
|
32
|
+
return result.output
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async run(task: string): Promise<OrchestrationResult> {
|
|
36
|
+
const start = Date.now()
|
|
37
|
+
const id = generateId()
|
|
38
|
+
const memberOutputs = new Map<string, string>()
|
|
39
|
+
|
|
40
|
+
const leader = this.team.getLeader()
|
|
41
|
+
if (!leader) throw new Error("No leader found in team")
|
|
42
|
+
|
|
43
|
+
const leaderResult = await this.runMember(leader, `As the ${leader.role}, oversee this task: ${task}`)
|
|
44
|
+
memberOutputs.set(leader.id, leaderResult)
|
|
45
|
+
|
|
46
|
+
const otherMembers = this.team.config.members.filter((m) => m.id !== leader.id)
|
|
47
|
+
for (const member of otherMembers) {
|
|
48
|
+
const msg = this.communicator.send(
|
|
49
|
+
leader.id,
|
|
50
|
+
member.id,
|
|
51
|
+
`Task: ${task}\nLeader's plan: ${leaderResult}\nYour role: ${member.role}\nExecute your part.`
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const memberResult = await this.runMember(member, msg.content)
|
|
55
|
+
memberOutputs.set(member.id, memberResult)
|
|
56
|
+
|
|
57
|
+
this.communicator.respond(leader.id, msg.id, memberResult)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const allOutputs = Array.from(memberOutputs.values()).join("\n\n")
|
|
61
|
+
let finalOutput: string
|
|
62
|
+
|
|
63
|
+
if (otherMembers.length > 0) {
|
|
64
|
+
const finalResult = await this.runMember(
|
|
65
|
+
leader,
|
|
66
|
+
`Original task: ${task}\n\nHere are all the outputs from your team:\n${allOutputs}\n\nSynthesize a final cohesive response.`
|
|
67
|
+
)
|
|
68
|
+
finalOutput = finalResult
|
|
69
|
+
} else {
|
|
70
|
+
finalOutput = leaderResult
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
id,
|
|
75
|
+
output: finalOutput,
|
|
76
|
+
memberOutputs,
|
|
77
|
+
messages: this.communicator.getHistory(),
|
|
78
|
+
durationMs: Date.now() - start,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getCommunicator(): AgentCommunicator {
|
|
83
|
+
return this.communicator
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getTeam(): Team {
|
|
87
|
+
return this.team
|
|
88
|
+
}
|
|
89
|
+
}
|
package/src/team.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AgentConfig } from "@lupaflow/types"
|
|
2
|
+
import { generateId } from "@lupaflow/core"
|
|
3
|
+
|
|
4
|
+
export interface TeamMember {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
role: string
|
|
8
|
+
agentConfig: AgentConfig
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TeamConfig {
|
|
12
|
+
name: string
|
|
13
|
+
members: TeamMember[]
|
|
14
|
+
leader?: string
|
|
15
|
+
description?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class Team {
|
|
19
|
+
public config: TeamConfig
|
|
20
|
+
|
|
21
|
+
constructor(config: TeamConfig) {
|
|
22
|
+
this.config = {
|
|
23
|
+
...config,
|
|
24
|
+
members: config.members.map((m) => ({ ...m, id: m.id || generateId() })),
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getMember(idOrName: string): TeamMember | undefined {
|
|
29
|
+
return this.config.members.find((m) => m.id === idOrName || m.name === idOrName)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getLeader(): TeamMember | undefined {
|
|
33
|
+
if (!this.config.leader) return this.config.members[0]
|
|
34
|
+
return this.getMember(this.config.leader)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getByRole(role: string): TeamMember[] {
|
|
38
|
+
return this.config.members.filter((m) => m.role === role)
|
|
39
|
+
}
|
|
40
|
+
}
|
package/tsconfig.json
ADDED