From 7983cb43796a066d56153f8e31d77cab8aaf7dba Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Sat, 7 Dec 2024 14:14:18 +0100 Subject: [PATCH 1/3] chore: lint and configure eslint --- .vscode/settings.json | 8 ++++++++ packages/framework/src/index.ts | 31 +++++++++++++++---------------- 2 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0ccaac2 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "typescript.tsdk": "node_modules/typescript/lib", + "typescript.enablePromptUseWorkspaceTsdk": true, + "editor.formatOnSave": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "always" + } +} diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index 7575bed..beac631 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -176,30 +176,30 @@ export class Agent { } } - - -export async function teamwork(workflow: Workflow, context: WorkflowContext = { - messages: [ - { - role: 'assistant', - content: s` +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.description} ${workflow.output} `, - }, - ] -}): Promise { - -// tbd: set reasonable max iterations -// eslint-disable-next-line no-constant-condition + }, + ], + } +): Promise { + // tbd: set reasonable max iterations + // 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); + console.debug('Max iterations exceeded ', workflow.maxIterations) return context.messages.at(-1)!.content as string } @@ -230,10 +230,9 @@ export async function teamwork(workflow: Workflow, context: WorkflowContext = { }) } - return teamwork(workflow, context);// next iteration + return teamwork(workflow, context) // next iteration } - type Workflow = { description: string output: string From 2921519fb452a29e9b66f2f4bc7e8e269dd1e599 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Sat, 7 Dec 2024 14:32:16 +0100 Subject: [PATCH 2/3] refactor --- packages/framework/src/agent.ts | 4 ++- packages/framework/src/executor.ts | 34 ++++++++++++++++++++++++ packages/framework/src/index.ts | 42 ++++++++---------------------- packages/framework/src/types.ts | 4 +++ packages/framework/src/workflow.ts | 26 ++++++++++++++++++ 5 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 packages/framework/src/executor.ts create mode 100644 packages/framework/src/workflow.ts diff --git a/packages/framework/src/agent.ts b/packages/framework/src/agent.ts index 6c0a202..1b5c9fc 100644 --- a/packages/framework/src/agent.ts +++ b/packages/framework/src/agent.ts @@ -1,7 +1,7 @@ import { Tool } from './tool.js' import { RequiredOptionals } from './types.js' -export type AgentOptions = { +type AgentOptions = { role: string description: string tools?: { @@ -24,3 +24,5 @@ export const agent = (options: AgentOptions) => { ...options, } } + +export type Agent = Required diff --git a/packages/framework/src/executor.ts b/packages/framework/src/executor.ts new file mode 100644 index 0000000..eeabe8a --- /dev/null +++ b/packages/framework/src/executor.ts @@ -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: + ${options.workflow.description} + ${options.workflow.output} + `, + }, + ], + } +} + +export type ExecutionContext = Required + +// tbd: helper utilities to create contexts from workflows with concrete single task etc. diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index beac631..90bc073 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -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() @@ -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.description} - ${workflow.output} - `, - }, - ], - } -): Promise { - // tbd: set reasonable max iterations +async function execute(context: ExecutionContext): Promise { // 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 } @@ -211,13 +198,13 @@ export async function teamwork( }) // 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, @@ -230,23 +217,16 @@ export async function teamwork( }) } - 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 { + 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 { const response = await openai.beta.chat.completions.parse({ model: 'gpt-4o', diff --git a/packages/framework/src/types.ts b/packages/framework/src/types.ts index 55d4ce2..f09cf35 100644 --- a/packages/framework/src/types.ts +++ b/packages/framework/src/types.ts @@ -1,3 +1,5 @@ +import { ChatCompletionMessageParam } from 'openai/resources/index.mjs' + /** * Utility type to get optional keys from T. */ @@ -14,3 +16,5 @@ export type OptionalProperties = Pick> * Utility type to make optional properties required (only includes optional props). */ export type RequiredOptionals = Required> + +export type Message = ChatCompletionMessageParam diff --git a/packages/framework/src/workflow.ts b/packages/framework/src/workflow.ts new file mode 100644 index 0000000..38da7e7 --- /dev/null +++ b/packages/framework/src/workflow.ts @@ -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 = { + // 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 From 0bcca32c6d4b38e887b02675f5d84a7089ceb261 Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Sat, 7 Dec 2024 14:36:01 +0100 Subject: [PATCH 3/3] chore: update type names --- packages/framework/src/agent.ts | 2 +- packages/framework/src/executor.ts | 4 ++-- packages/framework/src/index.ts | 4 ++-- packages/framework/src/workflow.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/framework/src/agent.ts b/packages/framework/src/agent.ts index 1b5c9fc..cd0147b 100644 --- a/packages/framework/src/agent.ts +++ b/packages/framework/src/agent.ts @@ -18,7 +18,7 @@ const defaults: RequiredOptionals = { /** * Helper utility to create an agent with defaults. */ -export const agent = (options: AgentOptions) => { +export const agent = (options: AgentOptions): Agent => { return { ...defaults, ...options, diff --git a/packages/framework/src/executor.ts b/packages/framework/src/executor.ts index eeabe8a..92eb222 100644 --- a/packages/framework/src/executor.ts +++ b/packages/framework/src/executor.ts @@ -13,7 +13,7 @@ type ContextOptions = { /** * Helper utility to create a context with defaults. */ -export const context = (options: ContextOptions): ExecutionContext => { +export const context = (options: ContextOptions): Context => { return { ...options, messages: [ @@ -29,6 +29,6 @@ export const context = (options: ContextOptions): ExecutionContext => { } } -export type ExecutionContext = Required +export type Context = Required // tbd: helper utilities to create contexts from workflows with concrete single task etc. diff --git a/packages/framework/src/index.ts b/packages/framework/src/index.ts index 90bc073..5d5c127 100644 --- a/packages/framework/src/index.ts +++ b/packages/framework/src/index.ts @@ -4,7 +4,7 @@ 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 { Context, context } from './executor.js' import { Tool } from './tool.js' import { Workflow, WorkflowContext } from './workflow.js' @@ -178,7 +178,7 @@ export class Agent { } } -async function execute(context: ExecutionContext): Promise { +async function execute(context: Context): Promise { // eslint-disable-next-line no-constant-condition const task = await getNextTask(context.messages) if (!task) { diff --git a/packages/framework/src/workflow.ts b/packages/framework/src/workflow.ts index 38da7e7..73a9151 100644 --- a/packages/framework/src/workflow.ts +++ b/packages/framework/src/workflow.ts @@ -16,7 +16,7 @@ const defaults: RequiredOptionals = { /** * Helper utility to create a workflow with defaults. */ -export const workflow = (options: WorkflowOptions) => { +export const workflow = (options: WorkflowOptions): Workflow => { return { ...defaults, ...options,