Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: #12

Merged
merged 4 commits into from
Dec 7, 2024
Merged

wip: #12

Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
}
}
4 changes: 3 additions & 1 deletion packages/framework/src/agent.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tool } from './tool.js'
import { RequiredOptionals } from './types.js'

export type AgentOptions = {
type AgentOptions = {
role: string
description: string
tools?: {
Expand All @@ -24,3 +24,5 @@ export const agent = (options: AgentOptions) => {
...options,
}
}

export type Agent = Required<AgentOptions>
34 changes: 34 additions & 0 deletions packages/framework/src/executor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import s from 'dedent'

import { Message } from './types.js'
import { Workflow } from './workflow.js'

// tbd: add more context like trace, callstack etc. context should be serializable
type ContextOptions = {
workflow: Workflow
// tbd: move messages to something such as memory
messages?: Message[]
}

/**
* Helper utility to create a context with defaults.
*/
export const context = (options: ContextOptions): ExecutionContext => {
return {
...options,
messages: [
{
role: 'assistant',
content: s`
Here is description of the workflow and expected output by the user:
<workflow>${options.workflow.description}</workflow>
<output>${options.workflow.output}</output>
`,
},
],
}
}

export type ExecutionContext = Required<ContextOptions>

// tbd: helper utilities to create contexts from workflows with concrete single task etc.
45 changes: 12 additions & 33 deletions packages/framework/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { zodFunction, zodResponseFormat } from 'openai/helpers/zod'
import type { ChatCompletionMessageParam } from 'openai/resources/chat/completions'
import { z, ZodType, ZodTypeAny } from 'zod'

import { context, ExecutionContext } from './executor.js'
import { Tool } from './tool.js'
import { Workflow, WorkflowContext } from './workflow.js'

// tbd: abstract this away or not? most APIs are OpenAI compatible
const openai = new OpenAI()
Expand Down Expand Up @@ -176,30 +178,15 @@ export class Agent {
}
}



export async function teamwork(workflow: Workflow, context: WorkflowContext = {
messages: [
{
role: 'assistant',
content: s`
Here is description of the workflow and expected output by the user:
<workflow>${workflow.description}</workflow>
<output>${workflow.output}</output>
`,
},
]
}): Promise<string> {

// tbd: set reasonable max iterations
// eslint-disable-next-line no-constant-condition
async function execute(context: ExecutionContext): Promise<string> {
// eslint-disable-next-line no-constant-condition
const task = await getNextTask(context.messages)
if (!task) {
return context.messages.at(-1)!.content as string // end of the recursion
}

if (workflow.maxIterations && context.messages.length > workflow.maxIterations) {
console.debug('Max iterations exceeded ', workflow.maxIterations);
if (context.workflow.maxIterations && context.messages.length > context.workflow.maxIterations) {
console.debug('Max iterations exceeded ', context.workflow.maxIterations)
return context.messages.at(-1)!.content as string
}

Expand All @@ -211,13 +198,13 @@ export async function teamwork(workflow: Workflow, context: WorkflowContext = {
})

// tbd: this throws, handle it
const selectedAgent = await selectAgent(task, workflow.members)
const selectedAgent = await selectAgent(task, context.workflow.members)
console.log('🚀 Selected agent:', selectedAgent.role)

// tbd: this should just be a try/catch
// tbd: do not return string, but more information or keep memory in agent
try {
const result = await selectedAgent.executeTask(context.messages, workflow.members)
const result = await selectedAgent.executeTask(context.messages, context.workflow.members)
context.messages.push({
role: 'assistant',
content: result,
Expand All @@ -230,24 +217,16 @@ export async function teamwork(workflow: Workflow, context: WorkflowContext = {
})
}

return teamwork(workflow, context);// next iteration
return execute(context) // next iteration
}


type Workflow = {
description: string
output: string
members: Agent[]
maxIterations?: number
export async function teamwork(workflow: Workflow): Promise<string> {
const ctx = context({ workflow })
return execute(ctx)
}

type Message = ChatCompletionMessageParam

type WorkflowContext = {
messages: Message[]
// tbd: add more context like trace, callstack etc. context should be serializable
}

async function selectAgent(task: string, agents: Agent[]): Promise<Agent> {
const response = await openai.beta.chat.completions.parse({
model: 'gpt-4o',
Expand Down
4 changes: 4 additions & 0 deletions packages/framework/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { ChatCompletionMessageParam } from 'openai/resources/index.mjs'

/**
* Utility type to get optional keys from T.
*/
Expand All @@ -14,3 +16,5 @@ export type OptionalProperties<T> = Pick<T, OptionalKeys<T>>
* Utility type to make optional properties required (only includes optional props).
*/
export type RequiredOptionals<T> = Required<OptionalProperties<T>>

export type Message = ChatCompletionMessageParam
26 changes: 26 additions & 0 deletions packages/framework/src/workflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Agent } from './agent.js'
import { RequiredOptionals } from './types.js'

type WorkflowOptions = {
description: string
output: string
members: Agent[]
maxIterations?: number
}

const defaults: RequiredOptionals<WorkflowOptions> = {
// tbd: set reasonable max iterations
maxIterations: 50,
}

/**
* Helper utility to create a workflow with defaults.
*/
export const workflow = (options: WorkflowOptions) => {
return {
...defaults,
...options,
}
}

export type Workflow = Required<WorkflowOptions>