Skip to content

Commit

Permalink
save
Browse files Browse the repository at this point in the history
  • Loading branch information
grabbou committed Dec 13, 2024
1 parent 538d644 commit 73a6495
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 59 deletions.
2 changes: 2 additions & 0 deletions example/src/surprise_trip.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { agent } from 'fabrice-ai/agent'
import { parallelSupervisor } from 'fabrice-ai/agents/supervisor'
import { solution } from 'fabrice-ai/solution'
import { teamwork } from 'fabrice-ai/teamwork'
import { logger } from 'fabrice-ai/telemetry'
Expand Down Expand Up @@ -48,6 +49,7 @@ const researchTripWorkflow = workflow({
restaurantScout,
landmarkScout,
itineraryCompiler,
supervisor: parallelSupervisor({ parallelism: 2 }),
},
description: `
Research and find cool things to do in Wrocław, Poland.
Expand Down
3 changes: 2 additions & 1 deletion packages/framework/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import s from 'dedent'
import { zodFunction, zodResponseFormat } from 'openai/helpers/zod.js'
import { z } from 'zod'

import { Message, request, response } from './messages.js'
import { request, response } from './messages.js'
import { openai, Provider } from './models.js'
import { finish, WorkflowState } from './state.js'
import { Tool } from './tool.js'
import { Message } from './types.js'
import { Workflow } from './workflow.js'

export type AgentOptions = Partial<Agent>
Expand Down
180 changes: 122 additions & 58 deletions packages/framework/src/agents/supervisor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,74 +7,138 @@ import { Message, system } from '../messages.js'
import { request, response } from '../messages.js'
import { delegate } from '../state.js'

const defaults: AgentOptions = {
run: async (state, context, workflow) => {
const [workflowRequest, ...messages] = state.messages

const res = await workflow.team[state.agent].provider.completions({
messages: [
system(s`
You are a planner that breaks down complex workflows into smaller, actionable steps.
Your job is to determine the next task that needs to be done based on the <workflow> and what has been completed so far.
type ParallelSupervisorOptions = AgentOptions & {
parallelism?: number
}

You can run tasks in parallel, if they do not depend on each other results. Otherwise, run them sequentially.
export const parallelSupervisor = (options: ParallelSupervisorOptions) => {
const { parallelism = 3 } = options

Rules:
1. Each task should be self-contained and achievable
2. Tasks should be specific and actionable
3. Return null when the workflow is complete
4. Consider dependencies and order of operations
5. Use context from completed tasks to inform next steps
`),
response('What is the request?'),
workflowRequest,
response('What has been completed so far?'),
...getSteps(messages),
],
temperature: 0.2,
response_format: zodResponseFormat(
z.object({
tasks: z
.array(
z.object({
task: z.string().describe('The next task to be completed'),
reasoning: z.string().describe('The reasoning for selecting the next task'),
})
)
.describe('Next tasks, or empty array if the workflow is complete'),
}),
'next_tasks'
),
})
return agent({
run: async (state, context, workflow) => {
const [workflowRequest, ...messages] = state.messages

try {
const content = res.choices[0].message.parsed
if (!content) {
throw new Error('No content in response')
}
const res = await workflow.team[state.agent].provider.completions({
messages: [
system(s`
You are a planner that breaks down complex workflows into smaller, actionable steps.
Your job is to determine the next task that needs to be done based on the <workflow> and what has been completed so far.
if (content.tasks.length === 0) {
return {
...state,
status: 'finished',
You can run tasks in parallel, if they do not depend on each other results.
Otherwise, you must run them sequentially.
Rules:
1. Each task should be self-contained and achievable
2. Tasks should be specific and actionable
3. Return empty array if all required tasks are completed
4. Consider dependencies and order of operations
5. Use context from completed tasks to inform next steps
6. You can run up to "${parallelism}" tasks in parallel.
`),
response('What is the request?'),
workflowRequest,
response('What has been completed so far?'),
...getSteps(messages),
],
temperature: 0.2,
response_format: zodResponseFormat(
z.object({
tasks: z
.array(
z.object({
task: z.string().describe('The next task to be completed'),
reasoning: z.string().describe('The reasoning for selecting the next task'),
})
)
.describe('Next tasks, or empty array if the workflow is complete'),
}),
'next_tasks'
),
})
try {
const content = res.choices[0].message.parsed
if (!content) {
throw new Error('No content in response')
}
if (content.tasks.length === 0) {
return {
...state,
status: 'finished',
}
}
return delegate(
state,
content.tasks.map((item) => ['resourcePlanner', request(item.task)])
)
} catch (error) {
throw new Error('Failed to determine next task')
}

return delegate(
state,
content.tasks.map((item) => ['resourcePlanner', request(item.task)])
)
} catch (error) {
throw new Error('Failed to determine next task')
}
},
},
...options,
})
}

export const supervisor = (options?: AgentOptions) =>
agent({
...defaults,
export const supervisor = (options: AgentOptions) => {
return agent({
run: async (state, context, workflow) => {
const [workflowRequest, ...messages] = state.messages

const res = await workflow.team[state.agent].provider.completions({
messages: [
{
role: 'system',
content: s`
You are a planner that breaks down complex workflows into smaller, actionable steps.
Your job is to determine the next task that needs to be done based on the <workflow> and what has been completed so far.
Rules:
1. Each task should be self-contained and achievable
2. Tasks should be specific and actionable
3. Return null when the workflow is complete
4. Consider dependencies and order of operations
5. Use context from completed tasks to inform next steps
`,
},
response('What is the request?'),
workflowRequest,
response('What has been completed so far?'),
...getSteps(messages),
],
temperature: 0.2,
response_format: zodResponseFormat(
z.object({
task: z
.string()
.describe('The next task to be completed or null if the workflow is complete')
.nullable(),
reasoning: z
.string()
.describe(
'The reasoning for selecting the next task or why the workflow is complete'
),
}),
'next_task'
),
})
try {
const content = res.choices[0].message.parsed
if (!content) {
throw new Error('No content in response')
}
if (!content.task) {
return {
...state,
status: 'finished',
}
}
return delegate(state, [['resourcePlanner', request(content.task)]])
} catch (error) {
throw new Error('Failed to determine next task')
}
},
...options,
})
}

const getSteps = (conversation: Message[]): Message[] => {
const messagePairs = conversation.reduce(
Expand Down

0 comments on commit 73a6495

Please sign in to comment.