From 78d91a9b5ecb9ef3218cebfc4192dec13cb0b47b Mon Sep 17 00:00:00 2001 From: Alex Whiteside Date: Thu, 26 Sep 2024 19:39:03 -0400 Subject: [PATCH] Agents are rough --- packages/agents/src/Agent.spec.ts | 5 +- packages/agents/src/logic.ts | 209 ------------------------------ packages/agents/src/services.ts | 93 +------------ packages/agents/src/types.ts | 134 ------------------- packages/agents/test/chat.spec.ts | 30 ----- 5 files changed, 7 insertions(+), 464 deletions(-) delete mode 100644 packages/agents/src/logic.ts delete mode 100644 packages/agents/src/types.ts delete mode 100644 packages/agents/test/chat.spec.ts diff --git a/packages/agents/src/Agent.spec.ts b/packages/agents/src/Agent.spec.ts index f0840ed..6032c70 100644 --- a/packages/agents/src/Agent.spec.ts +++ b/packages/agents/src/Agent.spec.ts @@ -5,8 +5,9 @@ import { AppLayer } from "./services"; describe("Agent", () => { it("should ", { timeout: 100000 }, async () => { - const program = Effect.gen(function* (_) { - const agent = new HelloAgent(); + const agent = new HelloAgent(); + const program = Effect.gen(function* () { + const createTask = Effect.succeed( new Task( "Create a greeting for Alex Whiteside, a software architect who enjoys sarcasm and XKCD", diff --git a/packages/agents/src/logic.ts b/packages/agents/src/logic.ts deleted file mode 100644 index 8ee9ff7..0000000 --- a/packages/agents/src/logic.ts +++ /dev/null @@ -1,209 +0,0 @@ -// logic.ts - -import { Agent, AgentContext, Task, TaskManager } from "./types" -import { Effect, Context } from "effect" -import { LLMService, CMSService, DatabaseService } from "./services" - -/** - * Implements the Director agent. - */ -export class Director implements Agent { - name = "Director" - preferredModel = "gpt-4" - prompts = ["Monitor and assign tasks..."] - tools = [] - directives = [] - taskAssigner = new TaskAssigner() - - execute(context: AgentContext) { - return Effect.gen(this, function* () { - const currentTask: Task = context.currentTask - - if (!currentTask.assignee) { - yield* Effect.suspend(() => this.taskAssigner.assignTask(currentTask)) - } - } - } -} - -/** - * Implements the Task Assigner agent. - */ -export class TaskAssigner implements Agent { - name = "TaskAssigner" - preferredModel = "" - prompts = [] - tools = [] - directives = [] - - assignTask(task: Task): Effect.Effect { - return Effect.succeed(() => { - // Simple assignment logic based on task context - if (!task.context.intent) { - task.assignee = new IntentRefiner() - } else if (!task.context.plan) { - task.assignee = new SolutionPlanner() - } else if (task.context.plan && !task.context.executed) { - task.assignee = new ExecutorAgent() - } else { - task.assignee = undefined // Change null to undefined - } - task.status = task.assignee ? "in-progress" : "completed" - return task - }) - } - - execute(context: AgentContext): Effect.Effect { - // Not used in this agent - return Effect.succeed(context) - } -} - -/** - * Implements the Intent Refiner agent. - */ -export class IntentRefiner implements Agent { - name = "IntentRefiner" - preferredModel = "gpt-4" - prompts = ["Refine the user's intent..."] - tools = [] - directives = [] - - execute(context: AgentContext): Effect.Effect { - return Effect.gen(function* (_) { - // Add intent and intended artifact to the context - context.intent = "Generate Information" - context.intendedArtifact = "List of Titles (for Articles)" - - // Identify that SEO understanding is required - const needsSEOBackground = true - - if (needsSEOBackground) { - const taskManager: TaskManager = context.taskManager - const seoTask = taskManager.createTask( - { description: "Gather background about SEO...", taskManager }, - context.currentTask.id - ) - context.status = "paused" - // The Director will pick up this new task in the next iteration - } - - return context - }) - } -} - -/** - * Implements the Solution Planner agent. - */ -export class SolutionPlanner implements Agent { - name = "SolutionPlanner" - preferredModel = "gpt-4" - prompts = ["Plan the solution steps..."] - tools = [] - directives = [] - - execute(context: AgentContext): Effect.Effect { - return Effect.gen(function* (_) { - // Create a plan and add it to the context - context.plan = [ - "Determine details about existing articles", - "Bring in SEO Expert to validate and provide guidance", - "Plan actions based on guidance", - "Generate article titles", - "Present articles to user", - ] - return context - }) - } -} - -/** - * Implements the Executor agent. - */ -export class ExecutorAgent implements Agent { - name = "ExecutorAgent" - preferredModel = "gpt-4" - prompts = ["Execute the following task..."] - tools = [ - { - name: "LLMTool", - action: (args: { prompt: string }) => - Effect.flatMap(Context.get(LLMService), (llmService) => - llmService.generateText(args.prompt, "gpt-4") - ), - }, - { - name: "CMSTool", - action: (args: { query: any }) => - Effect.flatMap(Context.get(CMSService), (cmsService) => - cmsService.fetchArticles(args.query) - ), - }, - ] as const - - execute(context: AgentContext): Effect.Effect { - return Effect.gen(function* (this: ExecutorAgent, _) { - const articles = yield* _(this.tools[1].action({ query: { limit: 20 } })) - context.existingArticles = articles - - const generatedTitles = yield* _( - this.tools[0].action({ - prompt: "Generate article titles similar to existing articles.", - }) - ) - context.generatedTitles = generatedTitles - - context.executed = true - - return context - }.bind(this)) - } -} - -/** - * Implements the Scribe agent. - */ -export class ScribeAgent implements Agent { - name = "ScribeAgent" - preferredModel = "" - prompts = [] - tools = [] - directives = [] - - execute(context: AgentContext): Effect.Effect { - return Effect.flatMap(Context.get(DatabaseService), (databaseService) => - databaseService.logEvent({ - timestamp: new Date(), - agentName: context.agentName, - message: context.message, - }).pipe(Effect.as(context)) - ) - } -} - -/** - * Implements the Process Manager agent. - */ -export class ProcessManagerAgent implements Agent { - name = "ProcessManagerAgent" - preferredModel = "" - prompts = [] - tools = [] - directives = [] - - execute(context: AgentContext): Effect.Effect { - return Effect.gen(function* (_) { - const databaseService = yield* _(Context.get(DatabaseService)) - const status = yield* _(databaseService.getProcessStatus(context.processId)) - // Update status if necessary - yield* _( - databaseService.updateProcessStatus(context.processId, { - ...status, - status: "updated", - }) - ) - return context - }) - } -} \ No newline at end of file diff --git a/packages/agents/src/services.ts b/packages/agents/src/services.ts index 5ed4ec9..9d42669 100644 --- a/packages/agents/src/services.ts +++ b/packages/agents/src/services.ts @@ -1,7 +1,6 @@ // services.ts import { Effect, Context, Layer } from "effect"; -import { LogEvent, ProcessStatus, Article } from "./types"; import { Ollama } from "ollama"; /** @@ -13,57 +12,10 @@ export class LLMService extends Context.Tag("LLMService")< readonly generateText: ( prompt: string, model: string, - ) => Effect.Effect; + ) => Effect.Effect; readonly getEmbedding: ( text: string, - ) => Effect.Effect; - } ->() {} - -/** - * VectorStoreService tag - */ -export class VectorStoreService extends Context.Tag("VectorStoreService")< - VectorStoreService, - { - readonly storeVector: ( - id: string, - vector: number[], - ) => Effect.Effect; - readonly queryVector: ( - vector: number[], - topK: number, - ) => Effect.Effect; - } ->() {} - -/** - * CMSService tag - */ -export class CMSService extends Context.Tag("CMSService")< - CMSService, - { - readonly fetchArticles: (args: any) => Effect.Effect; - readonly checkArticleExists: ( - title: string, - ) => Effect.Effect; - } ->() {} - -/** - * DatabaseService tag - */ -export class DatabaseService extends Context.Tag("DatabaseService")< - DatabaseService, - { - readonly logEvent: (event: LogEvent) => Effect.Effect; - readonly getProcessStatus: ( - processId: string, - ) => Effect.Effect; - readonly updateProcessStatus: ( - processId: string, - status: ProcessStatus, - ) => Effect.Effect; + ) => Effect.Effect; } >() {} @@ -93,51 +45,14 @@ export const LLMServiceLive = () => { try: () => ollama .embed({ model: "nomic-embed-text", input: text }) - .then((res) => res.embeddings), + .then((res) => res.embeddings[0]), catch: (err) => new Error(), }), }), ); }; -export const VectorStoreServiceLive = Layer.succeed( - VectorStoreService, - VectorStoreService.of({ - storeVector: (id: string, vector: number[]) => Effect.succeed(void 0), - queryVector: (vector: number[], topK: number) => - Effect.succeed(["id1", "id2"]), - }), -); - -export const DatabaseServiceLive = Layer.succeed( - DatabaseService, - DatabaseService.of({ - logEvent: (event: LogEvent) => Effect.succeed(void 0), - getProcessStatus: (processId: string) => - Effect.succeed({ id: processId, status: "in-progress" }), - updateProcessStatus: (processId: string, status: ProcessStatus) => - Effect.succeed(void 0), - }), -); - -export const CMSServiceLive = Layer.succeed( - CMSService, - CMSService.of({ - fetchArticles: (args: any) => - Effect.succeed([ - { id: "1", title: "Existing Article 1", content: "Content 1" }, - { id: "2", title: "Existing Article 2", content: "Content 2" }, - ]), - checkArticleExists: (title: string) => Effect.succeed(false), - }), -); - /** * Defines the application layer by composing service layers. */ -export const AppLayer = Layer.mergeAll( - LLMServiceLive(), - VectorStoreServiceLive, - DatabaseServiceLive, - CMSServiceLive, -); +export const AppLayer = Layer.mergeAll(LLMServiceLive()); diff --git a/packages/agents/src/types.ts b/packages/agents/src/types.ts deleted file mode 100644 index 6464609..0000000 --- a/packages/agents/src/types.ts +++ /dev/null @@ -1,134 +0,0 @@ -// types.ts - -import { Effect } from "effect" - -/** - * Represents the shared context among agents. - */ -export interface AgentContext { - [key: string]: any -} - -/** - * Represents a tool that an agent can use. - */ -export type Tool = { - name: string - action: (args: any) => Effect.Effect -} - -/** - * Represents a directive that guides agent behavior. - */ -export type Directive = { - description: string - condition: (context: AgentContext) => boolean -} - -/** - * Base interface for all agents. - */ -export interface Agent { - name: string - preferredModel: string - prompts: string[] - tools: Tool[] - directives: Directive[] - execute: (context: AgentContext) => Effect.Effect -} - -/** - * Represents an article in the CMS. - */ -export type Article = { - id: string - title: string - content: string -} - -/** - * Represents a log event. - */ -export type LogEvent = { - timestamp: Date - agentName: string - message: string -} - -/** - * Represents the status of a process. - */ -export type ProcessStatus = { - id: string - status: string - details?: string -} - -/** - * Represents a task in the system. - */ -export interface Task { - id: string - status: "pending" | "in-progress" | "completed" | "paused" - assignee?: Agent - context: AgentContext - parentTaskId?: string - createdAt: Date - updatedAt: Date -} - -/** - * Manages tasks within the system. - */ -export class TaskManager { - private tasks: Map = new Map() - - /** - * Creates a new task. - * @param context The context for the task. - * @param parentTaskId Optional parent task ID. - * @returns The created task. - */ - createTask(context: AgentContext, parentTaskId?: string): Task { - const task: Task = { - id: this.generateTaskId(), - status: "pending", - context, - parentTaskId, - createdAt: new Date(), - updatedAt: new Date(), - } - this.tasks.set(task.id, task) - return task - } - - /** - * Updates a task. - * @param task The task to update. - */ - updateTask(task: Task): void { - task.updatedAt = new Date() - this.tasks.set(task.id, task) - } - - /** - * Retrieves the next unassigned task. - * @returns The next task or undefined. - */ - getNextTask(): Task | undefined { - for (const task of this.tasks.values()) { - if (task.status === "pending" && !task.assignee) { - return task - } - } - return undefined - } - - /** - * Generates a unique task ID. - * @returns A unique string. - */ - private generateTaskId(): string { - return Math.random().toString(36).substring(2) - } -} \ No newline at end of file diff --git a/packages/agents/test/chat.spec.ts b/packages/agents/test/chat.spec.ts deleted file mode 100644 index 8dfe2cd..0000000 --- a/packages/agents/test/chat.spec.ts +++ /dev/null @@ -1,30 +0,0 @@ -// chat.spec.ts - -import { describe, it, expect } from "vitest"; -import { Effect, Layer } from "effect"; -import { AppLayer, DatabaseService, LLMService } from "../src/services"; - -describe("Chat Use Case", () => { - it( - "should process the user's request and generate article topics", - { timeout: 1000000 }, - async () => { - const agent = (prompt: string) => - Effect.gen(function* () { - const llmService = yield* LLMService; - const model = "mistral-small"; - return yield* llmService.generateText(prompt, model); - }); - - const timedEffect = agent("What is the meaning of life?").pipe( - Effect.map((res) => { - console.log(res); - }), - Effect.timeout("113 seconds"), - ); - - const runnable = Effect.provide(timedEffect, AppLayer); - await Effect.runPromiseExit(runnable).then(console.log); - }, - ); -});