diff --git a/.gitignore b/.gitignore index 82d5ede..766e911 100644 --- a/.gitignore +++ b/.gitignore @@ -130,4 +130,5 @@ dist .pnp.* .DS_Store -docs \ No newline at end of file +docs +project \ No newline at end of file diff --git a/LICENSE b/LICENSE index a612ad9..cebceb5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,373 +1,4 @@ -Mozilla Public License Version 2.0 -================================== +Copyright (c) 2024 Ahyve Inc. All rights reserved. +https://bazed.ai/ -1. Definitions --------------- - -1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. - -1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. - -1.3. "Contribution" - means Covered Software of a particular Contributor. - -1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. - -1.5. "Incompatible With Secondary Licenses" - means - - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or - - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. - -1.6. "Executable Form" - means any form of the work other than Source Code Form. - -1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. - -1.8. "License" - means this document. - -1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. - -1.10. "Modifications" - means any of the following: - - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or - - (b) any new file in Source Code Form that contains any Covered - Software. - -1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. - -1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. - -1.13. "Source Code Form" - means the form of the work preferred for making modifications. - -1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. - -2. License Grants and Conditions --------------------------------- - -2.1. Grants - -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: - -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and - -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. - -2.2. Effective Date - -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. - -2.3. Limitations on Grant Scope - -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: - -(a) for any code that a Contributor has removed from Covered Software; - or - -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or - -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. - -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). - -2.4. Subsequent Licenses - -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). - -2.5. Representation - -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. - -2.6. Fair Use - -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. - -2.7. Conditions - -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. - -3. Responsibilities -------------------- - -3.1. Distribution of Source Form - -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. - -3.2. Distribution of Executable Form - -If You distribute Covered Software in Executable Form then: - -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and - -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. - -3.3. Distribution of a Larger Work - -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). - -3.4. Notices - -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. - -3.5. Application of Additional Terms - -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. - -4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- - -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. - -5. Termination --------------- - -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. - -5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. - -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. - -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ - -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ - -8. Litigation -------------- - -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. - -9. Miscellaneous ----------------- - -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. - -10. Versions of the License ---------------------------- - -10.1. New Versions - -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. - -10.2. Effect of New Versions - -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. - -10.3. Modified Versions - -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). - -10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. - -Exhibit A - Source Code Form License Notice -------------------------------------------- - - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. - -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. - -You may add additional accurate notices of copyright ownership. - -Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. +THIS IS A PREVIEW RELEASE - DO NOT DISTRIBUTE diff --git a/README.md b/README.md index aadd733..7731a32 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ To create a new Bazed.ai Agent Framework project, run the following command and follow the instructions: ```bash -npx @bazed/bazed init my-project +npx @xiv-bazed-ai/bazed-af init my-project ``` It will create `my-project` directory and set up a fresh Bazed.ai Agent Framework project there. diff --git a/bin/bazed.ts b/bin/bazed.ts index 9cad0bf..e32c6e9 100755 --- a/bin/bazed.ts +++ b/bin/bazed.ts @@ -8,15 +8,23 @@ import prompts from "prompts"; import chalk from "chalk"; import { startServer } from "../src/server/server"; import dotenv from "dotenv"; +import child_process from "child_process"; +import axios from "axios"; +import FormData from "form-data"; +import { SingleBar, Presets } from "cli-progress"; dotenv.config(); const PACKAGE_PATH = Path.resolve(__dirname, ".."); // TODO: set this to NPM package name when published -const PACKAGE_NAME = PACKAGE_PATH; +const PACKAGE_NAME = "@xiv-bazed-ai/bazed-af"; const banner = () => { - console.log(`\nšŸ˜Ž ${chalk.yellow(`Bazed.ai Agent Framework`)} ${chalk.gray("v" + version)}\n`); + console.log( + `\nšŸ˜Ž ${chalk.yellow(`Bazed.ai Agent Framework`)} ${chalk.gray( + "v" + version + )}\n` + ); }; const outro = (pm: "yarn" | "npm", projectPath: string) => { @@ -32,16 +40,16 @@ const outro = (pm: "yarn" | "npm", projectPath: string) => { const relativeStep = cdCommand.length === 0 ? "" - : `${i++}. Change directory to your project folder: + : `${i++}. Enter your project directory:\n ${chalk.cyan(cdCommand)}\n`; - console.log(`šŸ™Œ ${chalk.yellow("You're all set!")}\n -Next steps:\n + console.log(`\nšŸ™Œ ${chalk.yellow("You're all set!")}\n +${chalk.bold("Next steps")}\n ${relativeStep} ${i++}. Set your OpenAI API key in the ${chalk.cyan(".env")} file.\n -${i++}. Install dependencies and run the development server: +${i++}. Install dependencies:\n ${chalk.cyan(installCommand)}\n -${i++}. Start the development server: +${i++}. Start the development server:\n ${chalk.cyan(runCommand)}\n`); }; @@ -73,6 +81,40 @@ const copyTemplate = ( } }; +const copySrcTemplate = ( + templateName: string, + targetPath: string, + variables: Record +) => { + const templatePath = Path.join(PACKAGE_PATH, "templates", templateName); + let content = FS.readFileSync(templatePath, "utf-8"); + for (const key in variables) { + content = content.replace(new RegExp(`${key}`, "g"), variables[key]); + } + FS.writeFileSync(targetPath, content); +}; + +const toPascalCase = (name: string): string => { + return name + .split("-") + .map((word) => word[0].toUpperCase() + word.slice(1)) + .join(""); +}; + +const toKebabCase = (name: string): string => { + return name + .split(/(?=[A-Z])/) + .map((word) => word.toLowerCase()) + .join("-"); +}; + +const toCamelCase = (name: string): string => { + return name + .split("-") + .map((word, i) => (i === 0 ? word : word[0].toUpperCase() + word.slice(1))) + .join(""); +}; + const program = new Command(); program @@ -100,12 +142,12 @@ program // if path doesn't exist, create it if (!targetPathExists) { - console.log(`${chalk.blue(path)} doesn't exist.`); + console.log(`Directory ${chalk.blue(path)} doesn't exist.`); const { ok } = await prompts({ type: "confirm", name: "ok", initial: true, - message: `Create ${chalk.blue(path)}?`, + message: `Create ${chalk.blue(path)} directory?`, }); if (!ok) { console.log("Aborting"); @@ -129,14 +171,6 @@ program BAZED_PACKAGE: PACKAGE_NAME, }; copyTemplate("project", fullPath, variables); - - console.log( - "Initializing a new project at", - path, - process.cwd(), - options.name, - fullPath - ); outro("yarn", fullPath); }); @@ -144,20 +178,49 @@ const commandNew = program .command("new") .description("Scaffold agents and tools"); +const addExport = (path: string, name: string, importPath: string) => { + const content = FS.readFileSync(path, "utf-8"); + const lines = content.split("\n"); + const lastImport = lines.findIndex((line) => line.startsWith("import")); + lines.splice(lastImport + 1, 0, `import ${name} from "./${importPath}";`); + + const exportIndex = lines.findIndex((line) => line.startsWith("export")); + lines.splice(exportIndex + 1, 0, ` ${name},`); + + FS.writeFileSync(path, lines.join("\n")); +}; + commandNew .command("agent") .argument("", "Name of the new agent") + .argument("[type]", "Type of the new agent", "reactive") .description("Scaffold a new agent") - .action(() => { - console.log("Creating new agent"); + .action(async (name: string, type: string) => { + const fullPath = Path.join( + process.cwd(), + "agents", + toKebabCase(name) + ".ts" + ); + copySrcTemplate(`agents/${type}.ts`, fullPath, { + Example: toPascalCase(name), + }); + addExport( + Path.join(process.cwd(), "index.ts"), + toPascalCase(name), + `agents/${toKebabCase(name)}` + ); }); commandNew .command("tool") .argument("", "Name of the new tool") .description("Scaffold a new tool") - .action(() => { - console.log("Running a project"); + .action((name: string) => { + const fullPath = Path.join(process.cwd(), "tools", toKebabCase(name)); + const type = "tool"; + copySrcTemplate(`tools/${type}.ts`, fullPath, { + ExampleAgent: toCamelCase(name), + }); }); program @@ -175,11 +238,72 @@ program }); }); +program.command("platform").action(async (_options: object) => { + await startServer({ + port: process.env.PORT ? parseInt(process.env.PORT) : 9000, + openaiApiKey: process.env.OPENAI_API_KEY || "", + imports: [], + platform: true, + }); +}); + program .command("deploy") - .description("Deploy a project to Bazed.ai") - .action(() => { - console.log("Deploying project"); + .description("Deploy a project to bazed.ai") + .action(async () => { + const progress = new SingleBar({}); + try { + const path = process.cwd(); + const distPath = Path.join(path, "dist"); + if (!FS.existsSync(distPath)) { + console.log( + `No ${chalk.cyan("dist")} folder found in ${chalk.blue( + path + )}. Please build your project first.` + ); + return; + } + + // parse the package.json file + const packageJsonPath = Path.join(path, "package.json"); + const packageJson = JSON.parse(FS.readFileSync(packageJsonPath, "utf-8")); + packageJson.dependencies.bazed = "../../dist"; + // write the package.json file to dist + const distPackageJsonPath = Path.join(distPath, "package.json"); + FS.writeFileSync( + distPackageJsonPath, + JSON.stringify(packageJson, null, 2) + ); + + console.log("Deploying project"); + // zip the dist folder + const zipPath = Path.join(path, "dist.zip"); + // invoke zip command with dist folder + const zipCommand = `zip -r ${zipPath} dist/*`; + child_process.execSync(zipCommand); + // upload to bazed.ai with axios + const url = "http://p.bazed.ai/deploy"; + const formData = new FormData(); + formData.append("file", FS.createReadStream(zipPath)); + const headers = formData.getHeaders(); + + progress.start(100, 0); + const response = await axios.post(url, formData, { + headers, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) + progress.update((100 * progressEvent.loaded) / progressEvent.total); + }, + }); + progress.stop(); + // delete the zip file + FS.unlinkSync(zipPath); + // console.log(response.data); + console.log("Project deployed successfully"); + } catch (e) { + progress.stop(); + console.log(e); + } }); program.parse(process.argv); diff --git a/index.ts b/index.ts index 3d9d761..5a80de2 100644 --- a/index.ts +++ b/index.ts @@ -8,4 +8,5 @@ export * from "./src/logging"; export * from "./src/common"; export * from "./src/ledger"; export * from "./src/tool"; -export * from "./src/agents/one-shot"; \ No newline at end of file +export * from "./src/agents/one-shot"; +export * from "./src/agents/reactive"; diff --git a/package.json b/package.json index 08cbd98..92c46f5 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,17 @@ { - "name": "bazed-af", - "version": "0.0.1", + "name": "@xiv-bazed-ai/bazed-af", + "version": "0.0.6", "description": "Bazed.ai Agent Framework", - "main": "./dist/index.js", + "homepage": "https://bazed.ai", "repository": "git@github.com:bazed-ai/bazed-af.git", "author": "XIV ", - "license": "MPL-2.0", - "private": true, + "license": "UNLICENSED", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scope": "@xiv-bazed-ai", "bin": { "bazed": "./dist/bin/bazed.js" }, @@ -14,17 +19,21 @@ "node": ">=20.0.0" }, "scripts": { - "build": "tsc && cp -R templates dist/templates && chmod u+x dist/bin/bazed.js", + "build": "rm -rf dist && tsc && cp -R templates dist/templates && chmod u+x dist/bin/bazed.js", "test": "jest --coverage", "prepare": "husky install", "docs": "typedoc --options typedoc.json", "cli": "yarn build && node ./dist/bin/bazed.js", - "lint": "eslint --ext .ts src" + "lint": "eslint --ext .ts src", + "local": "yarn build && npm install -g .", + "package:publish": "yarn build && npm publish --access private" }, "dependencies": { "@fastify/middie": "^8.3.0", + "@fastify/multipart": "^8.1.0", "axios": "^1.6.0", "chalk": "=4.1.0", + "cli-progress": "^3.12.0", "commander": "^11.1.0", "dotenv": "^16.3.1", "fastify": "^4.20.0", @@ -44,6 +53,7 @@ }, "devDependencies": { "@jest/globals": "^29.7.0", + "@types/cli-progress": "^3.11.5", "@types/jest": "^29.5.6", "@types/node": "^20.5.0", "@types/prompts": "^2.4.9", diff --git a/src/agent.ts b/src/agent.ts index 5bf4f33..0e6b7c1 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -1,3 +1,4 @@ +import chalk from "chalk"; import { ChildOf, Conclusible, @@ -119,6 +120,7 @@ export class BaseAgent * @returns the final result of the agent's work */ async run(): Promise { + console.log("Agent", chalk.yellow(this.metadata.ID), chalk.blue("start")); this.trace(`${this.metadata.ID} run started: ${this.metadata.topic}`); this.state = await this.initialize(this.options); this.trace("initialize finished", this.state); @@ -133,6 +135,14 @@ export class BaseAgent this.isActive = false; this.result = await this.finalize(this.state); this.conclude(); + console.log( + "Agent", + chalk.yellow(this.metadata.ID), + chalk.green("done"), + chalk.gray( + `(took ${this.metadata.timing.elapsed.as("seconds").toFixed(2)}s)` + ) + ); return this.result; } @@ -208,7 +218,6 @@ export class BaseAgent throw new Error("Thread is complete"); } const messages = thread.messages; - const response = await this.session.invokeModel( this, this.model, @@ -336,6 +345,11 @@ export class BaseAgent constructor: Constructor, options?: AgentOptions ): T { + console.log( + chalk.yellow(this.metadata.ID), + chalk.blue("spawn"), + constructor.name + ); return this.session.spawnAgent(constructor, options); } diff --git a/src/agents/one-shot.ts b/src/agents/one-shot.ts index f5e1af0..5bdd505 100644 --- a/src/agents/one-shot.ts +++ b/src/agents/one-shot.ts @@ -3,29 +3,28 @@ import { ModelType } from "../models"; import { Session } from "../session"; import { Thread } from "../thread"; -export class OneShotAgent extends BaseAgent< - O, - void, - R -> { +export class OneShotAgent< + OptionsType extends AgentOptions, + ResultType +> extends BaseAgent { model: ModelType = ModelType.GPT35Turbo; thread: Thread; - constructor(session: Session, options: O) { + constructor(session: Session, options: OptionsType) { super(session, options); this.thread = this.createThread(); } - input(): string { + async input(): Promise { throw new Error("Method not implemented."); } - output(_answer: string): R { + async output(_answer: string): Promise { throw new Error("Method not implemented."); } - async initialize(_options: O): Promise { - this.thread.appendUserMessage(this.input()); + async initialize(_options: OptionsType): Promise { + this.thread.appendUserMessage(await this.input()); } async step(): Promise { @@ -33,7 +32,7 @@ export class OneShotAgent extends BaseAgent< this.stop(); } - async finalize(): Promise { + async finalize(): Promise { return this.output(this.thread.assistantResponse); } } diff --git a/src/agents/reactive.ts b/src/agents/reactive.ts new file mode 100644 index 0000000..8063e36 --- /dev/null +++ b/src/agents/reactive.ts @@ -0,0 +1,185 @@ +import { z } from "zod"; +import { AgentOptions, BaseAgent } from "../agent"; +import { jsonSchema, parseMultipleObjects } from "../common"; +import { Session } from "../session"; +import { Thread } from "../thread"; + +type ReactionFunction = (state: S, input: T) => S | Promise; + +export interface Reaction { + type: string; + match: z.ZodType; + then: ReactionFunction; +} + +/** + * Decorator for defining reactions to structured messages. + * @param rule + * @param schema + */ +export const when = ( + rule: string, + schema: z.ZodObject +) => { + return function when], Return>( + target: (this: This, ...args: Args) => Return, + context: ClassMethodDecoratorContext< + This, + (this: This, ...args: Args) => Return + > + ) { + const methodName = String(context.name); + const eschema = schema.extend({ + type: z.literal(methodName), + }); + + context.addInitializer(function () { + const reactions = (this as ReactiveAgent) + .reactions as Reaction[]; + const rules = (this as ReactiveAgent).rules as string[]; + reactions.push({ + type: methodName, + match: eschema, + then: (state: S, input: T) => + target.call(this, ...([state, input] as unknown as Args)), + }); + rules.push( + `When ${rule} answer adhering to the following schema:\n${jsonSchema( + eschema + )}` + ); + }); + + return target; + }; +}; + +/** + * Decorator for defining the default reaction to unstructured messages. + */ +export const otherwise = ( + target: (this: This, ...args: Args) => Return, + context: ClassMethodDecoratorContext< + This, + (this: This, ...args: Args) => Return + > +) => { + context.addInitializer(function () { + (this as ReactiveAgent).defaultReaction = ( + state: S, + input: string + ) => target.call(this, ...([state, input] as unknown as Args)); + }); + + return target; +}; + +/** + * ReactiveAgent is a base class for agents that are defined in terms of reactions to structured messages. + * + * @param OptionsType Options for the agent + * @param StateType State of the agent + * @param ReturnType Return type of the agent + */ +export class ReactiveAgent< + OptionsType extends AgentOptions, + StateType, + ReturnType +> extends BaseAgent { + thread: Thread; + expectsJSON: boolean = true; + + reactions: Reaction[] = []; + defaultReaction: ((s: StateType, m: string) => StateType) | undefined = + undefined; + rules: string[] = ["ANSWER ONLY WITH JSON"]; + + /** Create a new reactive agent. + * @param session Session to use + * @param options Options for the agent + * */ + constructor(session: Session, options: OptionsType) { + super(session, options); + this.thread = this.createThread(); + } + + async initialize(options: OptionsType): Promise { + this.systemPrompt = + this.systemPrompt + "\n\n# Output rules\n" + this.rules.join("\n"); + return await this.input(options); + } + + async finalize(state: StateType): Promise { + return await this.output(state); + } + + respond(message: string): void { + this.abandon(this.thread); + this.thread = this.thread.appendUserMessage(message); + this.adopt(this.thread); + } + + /** + * Transform the input into the initial agent state. + * @param options Options for the agent + * @returns Initial state of the agent + */ + async input(_options: OptionsType): Promise { + throw new Error("Method not implemented."); + } + + /** + * Transform the final agent state into the output. + * @param state Final state of the agent + * @returns Output of the agent + */ + async output(_state: StateType): Promise { + throw new Error("Method not implemented."); + } + + async step(state: StateType): Promise { + this.thread = await this.advance(this.thread); + const lastMessage = this.thread.assistantResponse; + let parsed: unknown[]; + let lastState = state; + const expectedTypes = this.reactions.map((r) => r.type); + try { + parsed = parseMultipleObjects(lastMessage); + let matched = false; + for (const p of parsed) { + for (const reaction of this.reactions) { + const value = reaction.match.safeParse(p); + if (value.success) { + lastState = await reaction.then(lastState, value.data); + matched = true; + break; + } else if ( + typeof (p as any).type === "string" && + reaction.type === (p as any).type + ) { + throw new Error( + `You've sent "${reaction.type}" but it does not adhere to the schema, here is the error message: ${value.error}` + ); + } + } + } + if (!matched) { + if (this.defaultReaction) { + return await this.defaultReaction(state, lastMessage); + } else { + throw new Error( + `You've sent a message but it does not adhere to any of the schemas. Please send a message that adheres to one of the accepted schemas.` + ); + } + } + console.log("out"); + return lastState; + } catch (e: any) { + this.abandon(this.thread); + this.thread = this.thread.appendUserMessage(e.message); + this.adopt(this.thread); + console.log("error", e.message); + return lastState; + } + } +} diff --git a/src/common.ts b/src/common.ts index d8419e4..05b0dbc 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,6 +1,8 @@ import moment, { Moment } from "moment"; import shortUUID from "short-uuid"; import { get_encoding } from "tiktoken"; +import { z } from "zod"; +import zodToJsonSchema from "zod-to-json-schema"; const randomUUID = () => shortUUID.generate(); @@ -149,3 +151,67 @@ export interface ParentOf { export const countTokens = (text: string): number => { return encoding.encode(text).length; }; + +/// --- utils + +export const jsonSchema = (zodSchema: z.ZodType) => { + return JSON.stringify(zodToJsonSchema(zodSchema)); +}; + +const parseObject = (source: string): [any, string] => { + const stack = []; + let objStr = ""; + let pointer = 0; + let inString = false; + + while (pointer < source.length) { + const char = source[pointer]; + + if ( + stack.length === 0 && + (char === " " || char === "\n" || char === "\r" || char === "\t") + ) { + pointer++; + continue; + } + + if (char === '"' && (pointer === 0 || source[pointer - 1] !== "\\")) { + inString = !inString; + } + + if (!inString) { + if (char === "{") { + stack.push(char); + } + + if (char === "}") { + stack.pop(); + } + } + + objStr += char; + + if (stack.length === 0) { + return [JSON.parse(objStr), source.slice(pointer + 1)]; + } + + pointer++; + } + + throw new Error("Invalid JSON"); +}; + +export const parseMultipleObjects = (source: string): any[] => { + const ret = []; + let rest = source; + // eslint-disable-next-line no-constant-condition + while (true) { + if (rest.length === 0) { + break; + } + const [obj, next] = parseObject(rest); + ret.push(obj); + rest = next; + } + return ret; +}; diff --git a/src/models.ts b/src/models.ts index 46cb512..35190b8 100644 --- a/src/models.ts +++ b/src/models.ts @@ -1,7 +1,7 @@ /** Available model types */ export enum ModelType { GPT4 = "gpt-4", - GPT4Turbo = "gpt-4-1106-preview", + GPT4Turbo = "gpt-4-turbo-preview", GPT35 = "gpt-3.5-turbo-16k", GPT35Turbo = "gpt-3.5-turbo-1106", diff --git a/src/registry.ts b/src/registry.ts index ea17168..d2c88d8 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -8,16 +8,16 @@ export class Registry { this.agents = {}; } - register(id: string, constructor: Constructor) { - this.agents[id] = constructor; + register(namespace: string, id: string, constructor: Constructor) { + this.agents[`${namespace}/${id}`] = constructor; } get(id: string): Constructor { return this.agents[id]; } - has(id: string): boolean { - return id in this.agents; + has(namespace: string, id: string): boolean { + return `${namespace}/${id}` in this.agents; } list(): string[] { diff --git a/src/server/server.ts b/src/server/server.ts index e410914..5976236 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -1,4 +1,5 @@ import Fastify from "fastify"; +import fastifyMultipart from "@fastify/multipart"; import path from "path"; import fs from "fs"; import { ClientMux } from "../client"; @@ -8,11 +9,13 @@ import { Registry } from "../registry"; import { Session } from "../session"; import moment from "moment"; import chalk from "chalk"; +import child_process from "child_process"; export interface ServerOptions { port?: number; openaiApiKey: string; imports?: string[]; + platform?: boolean; } const compileTypescript = async (outputDir: string) => { @@ -20,32 +23,49 @@ const compileTypescript = async (outputDir: string) => { const start = moment(); return new Promise((resolve, reject) => { - exec(`npx tsc --outDir ${outputDir}`, (error, stdout, stderr) => { + exec(`npx tsc --outDir ${outputDir}`, (error, stdout, stderr) => { if (error) { console.log(chalk.red("Failed to compile typescript\n")); - console.log(stdout.split("\n").map((line) => `|\t${line}`).join("\n")); - console.log(stderr.split("\n").map((line) => `|\t${line}`).join("\n")); + console.log( + stdout + .split("\n") + .map((line) => `|\t${line}`) + .join("\n") + ); + console.log( + stderr + .split("\n") + .map((line) => `|\t${line}`) + .join("\n") + ); reject(error); } else { const elapsed = moment().diff(start); - console.log(`${chalk.green('Compiled typescript')} ${chalk.gray("(took " + moment.duration(elapsed).as('seconds').toFixed(2) + 's)')}`); + console.log( + `${chalk.green("Compiled typescript")} ${chalk.gray( + "(took " + moment.duration(elapsed).as("seconds").toFixed(2) + "s)" + )}` + ); resolve(); } }); }); }; -const addIndexToPath = (filePath: string): string => { +const addIndexToPath = (filePath: string, js?: boolean): string => { // check if path is a directory const stats = fs.statSync(filePath); if (stats.isDirectory()) { - return path.join(filePath, "index.ts"); + return path.join(filePath, "index" + (js ? ".js" : ".ts")); } return filePath; }; -const computeFileLocationInCache = (filePath: string): string => { - const cacheDir = path.join(process.cwd(), "cache"); +const computeFileLocationInCache = ( + filePath: string, + cacheName?: string +): string => { + const cacheDir = path.join(process.cwd(), cacheName || "cache"); const relativePath = path.relative(process.cwd(), filePath); return path.join(cacheDir, relativePath).replace(".ts", ".js"); }; @@ -58,6 +78,32 @@ const clearRequireCache = () => { } }; +const constructorsFromModule = (module: any): any[] => { + const constructors = []; + if (Array.isArray(module.default)) { + constructors.push(...module.default); + } else if (Array.isArray(module.default.agents)) { + constructors.push(...module.default.agents); + } else { + constructors.push(module.default); + } + return constructors; +}; + +const namespaceFromModule = (module: any, defaultNamespace: string): string => { + if (module.default.namespace) { + return module.default.namespace; + } else { + return defaultNamespace; + } +}; + +const namespaceFromPackage = (imp: string): string => { + const packageJsonPath = path.join(path.dirname(imp), "package.json"); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8")); + return packageJson.name; +}; + const importAgents = async (registry: Registry, imports: string[]) => { try { await compileTypescript(path.join(process.cwd(), "cache")); @@ -71,27 +117,58 @@ const importAgents = async (registry: Registry, imports: string[]) => { const indexed = addIndexToPath(imp); imp = indexed; imp = computeFileLocationInCache(imp); - - console.log(`import ${chalk.cyan(path.relative(process.cwd(), indexed))}`); + console.log( + ` imported ${chalk.cyan(path.relative(process.cwd(), indexed))}` + ); try { const module = await import(imp); if (!module.default) { throw new Error(`Module ${impRaw} has no default export`); } - const constructors = []; - if (Array.isArray(module.default)) { - constructors.push(...module.default); - } else { - constructors.push(module.default); + const constructors = constructorsFromModule(module); + const namespace = namespaceFromPackage(impRaw); + + for (const constructor of constructors) { + console.log( + ` ${ + registry.has(namespace, constructor.name) ? "updated" : "registered" + } agent ${chalk.cyan(constructor.name)}` + ); + registry.register(namespace, constructor.name, constructor); + } + } catch (e: any) { + console.log(`Failed to import ${impRaw}: ${e.message}`); + continue; + } + } +}; + +const importAgentsRaw = async (registry: Registry, imports: string[]) => { + clearRequireCache(); + for (const impRaw of imports) { + let imp = path.resolve(process.cwd(), impRaw); + // if the path is a directory, select index.ts + const indexed = addIndexToPath(imp, true); + imp = indexed; + //imp = computeFileLocationInCache(imp, "dist"); + + console.log(`import ${chalk.cyan(path.relative(process.cwd(), indexed))}`); + try { + const module = await import(imp); + if (!module.default) { + throw new Error(`Module ${impRaw} has no default export`); } + const constructors = constructorsFromModule(module); + const namespace = + "xiv/" + namespaceFromModule(module, path.basename(path.dirname(imp))); for (const constructor of constructors) { console.log( - ` ${registry.has(constructor.name) ? "update" : "register"} agent ${ - chalk.cyan(constructor.name) - }` + ` ${ + registry.has(namespace, constructor.name) ? "update" : "register" + } agent ${chalk.cyan(constructor.name)}` ); - registry.register(constructor.name, constructor); + registry.register(namespace, constructor.name, constructor); } } catch (e: any) { console.log(`Failed to import ${impRaw}: ${e.message}`); @@ -104,10 +181,13 @@ export const startServer = async ({ port, openaiApiKey, imports, + platform, }: ServerOptions) => { - - console.log(`\nšŸ˜Ž ${chalk.yellow(`Bazed.ai Agent Framework`)}\n dev server ${chalk.gray("v" + version)}\n`); - + console.log( + `\nšŸ˜Ž ${chalk.yellow( + `Bazed.ai Agent Framework` + )}\n dev server ${chalk.gray("v" + version)}\n` + ); const server = Fastify(); @@ -117,51 +197,127 @@ export const startServer = async ({ throw new Error("No OpenAI API key provided"); } + const sessions: Session[] = []; + const clientMux = new ClientMux(apiKey); const registry = new Registry(); - await importAgents(registry, imports || []); + if (!platform) { + await importAgents(registry, imports || []); - fs.watch( - process.cwd(), - { recursive: true }, - async (action: fs.WatchEventType, filePath: string | null) => { - // skip cache directory - const cacheDir = path.join(process.cwd(), "cache"); - const absoluteFilePath = path.resolve(process.cwd(), filePath || ""); + fs.watch( + process.cwd(), + { recursive: true }, + async (action: fs.WatchEventType, filePath: string | null) => { + if (!filePath) return; - if (absoluteFilePath.startsWith(cacheDir)) return; + // skip cache directory + const cacheDir = path.join(process.cwd(), "cache"); + const distDir = path.join(process.cwd(), "dist"); + const absoluteFilePath = path.resolve(process.cwd(), filePath || ""); - await importAgents(registry, imports || []); - } - ); + if (absoluteFilePath.startsWith(cacheDir)) return; + if (absoluteFilePath.startsWith(distDir)) return; + if (filePath?.startsWith("node_modules")) return; + + await importAgents(registry, imports || []); + } + ); + } server.get("/", async (_request, _reply) => { return { - service: "bazed.ai dev server", + service: "bazed.ai server", version, agents: registry.list(), }; }); - server.post<{ Body: { type: string; options: AgentOptions } }>( - "/spawn", - async (request, _reply) => { - const { type, options } = request.body; - const constructor = registry.get(type); - const session = new Session(clientMux, {}); - const agent = session.spawnAgent(constructor, options); - try { - const result = await agent.run(); - return { success: true, result, session: session.report() }; - } catch (e: any) { - console.log(e); - return { error: e.message, session: session.report() }; - } finally { - session.abort(); + server.get("/stat", async (_request, _reply) => { + const response = { + sessions: [] as any[], + }; + + for (const session of sessions) { + response.sessions.push({ + cost: session.totalCost(), + elapsed: session.metadata.timing.elapsed.asSeconds(), + ended: session.isAborted, + exchanges: session.getLedger().entries, + tokensPerModel: session.getLedger().modelTokens, + tokensPerAgent: session.getLedger().callerTokens, + }); + } + + return response; + }); + + server.post<{ + Body: { type: string; options: AgentOptions; env?: Record }; + }>("/spawn", async (request, _reply) => { + const { type, options } = request.body; + const constructor = registry.get(type); + const session = new Session(clientMux, { context: request.body.env }); + sessions.push(session); + const agent = session.spawnAgent(constructor, options); + try { + const result = await agent.run(); + session.abort(); + return { success: true, result, session: report(session) }; + } catch (e: any) { + session.abort(); + return { error: e.message, session: report(session) }; + } + }); + + // After initializing your Fastify instance + server.register(fastifyMultipart); + + server.post("/deploy", async (request, reply) => { + try { + const data = await request.file(); + if (!data) { + return reply.send({ success: false }); + } + const fileContent = await data.toBuffer(); + + // save file to disk as project.zip + const zipPath = path.join(process.cwd(), "project.zip"); + + fs.writeFileSync(zipPath, fileContent); + + // unzip project.zip + child_process.execSync(`unzip -o ${zipPath} -d ${process.cwd()}/project`); + + // remove project.zip + fs.unlinkSync(zipPath); + + //run yarn in project/dist + child_process.execSync(`cd ${process.cwd()}/project/dist && yarn`); + + // import agents + await importAgentsRaw(registry, ["project/dist"]); + + return reply.send({ success: true }); + } catch (e) { + console.log(e); + return reply.send({ success: false }); + } + }); + + const report = (session: Session) => { + const rep = { + cost: session.totalCost(), + tokens: {} as Record, + elapsed: session.metadata.timing.elapsed.asSeconds(), + }; + for (const [model, cost] of Object.entries(session.report())) { + if (cost.total > 0) { + rep.tokens[model] = cost.total; } } - ); + return rep; + }; const listenPort = port; //|| (await getPort({ port: portNumbers(3000, 3100) })); @@ -171,6 +327,8 @@ export const startServer = async ({ process.exit(1); } - console.log(`\nListening on ${chalk.cyan(`http://localhost:${listenPort}`)}\n`); + console.log( + `\nListening on ${chalk.cyan(`http://localhost:${listenPort}`)}\n` + ); }); }; diff --git a/src/session.ts b/src/session.ts index 236ce13..883831d 100644 --- a/src/session.ts +++ b/src/session.ts @@ -47,7 +47,7 @@ export class Session implements Identified, ParentOf { metadata: Metadata; /** Any object that is associated with the session, can be used to pass custom data */ - private context: any; + context: any; /** Ledger to keep track of costs and performance */ private ledger: Ledger; @@ -154,15 +154,15 @@ export class Session implements Identified, ParentOf { model: type, }; const randomID = Math.floor(Math.random() * 1000000); - this.trace(`[${randomID}] ${caller.metadata.ID} -> ${type}`); + //console.log(`[${randomID}] ${caller.metadata.ID} -> ${type}`); const response = await this.clients.createChatCompletion(invocation); const pct = new PCT({ prompt: response.usage?.prompt_tokens || 0, completion: response.usage?.completion_tokens || 0, }); - this.trace( - `[${randomID}] ${caller.metadata.ID} <- ${type} (${pct.prompt}, ${pct.completion}, ${pct.total})` - ); + // console.log( + // `[${randomID}] ${caller.metadata.ID} <- ${type} (${pct.prompt}, ${pct.completion}, ${pct.total})` + // ); timing.finish(); this.ledger.add(caller.metadata.ID, type, timing, pct); @@ -185,6 +185,7 @@ export class Session implements Identified, ParentOf { * After calling this method, all agents will be notified to abort and the session won't accept new agents or invocations. */ abort() { + this.metadata.timing.finish(); this.hasBeenAborted = true; } diff --git a/src/tool.ts b/src/tool.ts index c11d4b9..e9ed2ba 100644 --- a/src/tool.ts +++ b/src/tool.ts @@ -1,7 +1,11 @@ import { ChatCompletionTool } from "openai/resources/chat/completions"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; -import { Agent } from "./agent"; +import { Agent, AgentOptions } from "./agent"; +import moment from "moment"; +import chalk from "chalk"; +import { Constructor } from "./common"; +import { OneShotAgent } from "./agents/one-shot"; export type ToolSpec = ChatCompletionTool; @@ -59,9 +63,17 @@ export class FunctionTool implements Tool { } async invoke(agent: Agent, args: Args): Promise { + const start = moment(); const validatedArgs = this.args.parse(args); const result = await this.func(agent, validatedArgs); const validatedResults = this.returns.parse(result); + const elapsed = moment().diff(start); + console.log( + chalk.yellow(agent.metadata.ID), + chalk.green("tool"), + this.name, + chalk.gray(`(took ${moment.duration(elapsed).as("seconds").toFixed(2)}s)`) + ); return validatedResults; } @@ -75,7 +87,62 @@ export class FunctionTool implements Tool { description: this.description, parameters, }, - // returns: zodToJsonSchema(tool.args), // not supported in the API yet? }; } } + +interface ToolableAgentConstructor extends Constructor { + asTool?: Tool; +} + +/** Toolable is a mixin for agent classes that can be used as tools. */ +export interface Toolable { + asTool?: Tool; +} + +export type ToolLike = Tool | Toolable; + +/** Convert a toolable to a tool. + * @param toolLike Tool or toolable + * @returns Tool + * @throws Error if the toolable doesn't have a tool + */ +export const toTool = (toolLike: ToolLike): Tool => { + if ("asTool" in toolLike) { + if (toolLike.asTool) { + return toolLike.asTool; + } else { + throw new Error("Toolable doesn't have a tool"); + } + } else { + return toolLike as Tool; + } +}; + +/** Decorator for agent classes that can be used as tools. + * @param name Name of the tool + * @param args Zod schema for parameters of the tool + * @param returns Zod schema for return value of the tool + */ +export const isTool = ( + name: string, + description: string, + args: z.ZodType, + returns: z.ZodType +) => + function (constructor: Constructor, _context: any) { + (constructor as ToolableAgentConstructor).asTool = new FunctionTool( + name, + description, + args, + returns, + async (agent, args) => { + const instance = agent.session.spawnAgent( + constructor, + args as AgentOptions + ); + const result = await instance.run(); + return result; + } + ); + }; diff --git a/templates/agents/one-shot.ts b/templates/agents/one-shot.ts new file mode 100644 index 0000000..7fa1a30 --- /dev/null +++ b/templates/agents/one-shot.ts @@ -0,0 +1,27 @@ +import { AgentOptions, OneShotAgent, ModelType } from "bazed"; + +export interface ExampleAgentOptions extends AgentOptions { + /** agent options */ +} + +export interface ExampleAgentResult { + /** agent result */ +} + +export default class ExampleAgent extends OneShotAgent< + ExampleAgentOptions, + ExampleAgentResult +> { + model: ModelType = ModelType.GPT35Turbo; + systemPrompt: string = "... add your system prompt here ..."; + + async input(): Promise { + /* Transform input into a prompt for the LLM */ + throw new Error("Method not implemented."); + } + + async output(answer: string): Promise { + /* Transform LLM response into agent result */ + throw new Error("Method not implemented."); + } +} diff --git a/templates/agents/reactive.ts b/templates/agents/reactive.ts new file mode 100644 index 0000000..7da73c4 --- /dev/null +++ b/templates/agents/reactive.ts @@ -0,0 +1,42 @@ +import { AgentOptions, ReactiveAgent, ModelType, when } from "bazed"; +import { z } from "zod"; + +export interface ExampleAgentOptions extends AgentOptions { + /** agent options */ +} + +export interface ExampleAgentState { + /** agent state */ +} + +export interface ExampleAgentResult { + /** agent result */ +} + +export default class ExampleAgent extends ReactiveAgent< + ExampleAgentOptions, + ExampleAgentState, + ExampleAgentResult +> { + model: ModelType = ModelType.GPT35Turbo; + systemPrompt: string = "... add your system prompt here ..."; + + async input(_options: ExampleAgent): Promise { + /* Transform input into the initial state */ + throw new Error("Method not implemented."); + } + + @when("you want to cause a reaction", z.object({})) + async reaction( + _state: ExampleAgentState, + _input: object + ): Promise { + /* Transform the state based on the reaction */ + throw new Error("Method not implemented."); + } + + async output(_state: ExampleAgentState): Promise { + /* Transform the final state into the agent result */ + throw new Error("Method not implemented."); + } +} diff --git a/templates/project/.env.example b/templates/project/.env.example new file mode 100644 index 0000000..264f8d0 --- /dev/null +++ b/templates/project/.env.example @@ -0,0 +1 @@ +export OPENAI_API_KEY=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 \ No newline at end of file diff --git a/templates/project/agents/hello.ts b/templates/project/agents/hello.ts index 2ed0eb5..07fb938 100644 --- a/templates/project/agents/hello.ts +++ b/templates/project/agents/hello.ts @@ -20,12 +20,12 @@ export default class HelloAgent extends OneShotAgent< "Your task is to explain why specific person is based. Speculate, limit your response to a sentence."; // Prepare the input for the LLM call - input(): string { + async input(): Promise { return `Why is ${this.options.person} based?`; } // Process the output from the LLM call - output(answer: string): string { + async output(answer: string): Promise { return answer; } } diff --git a/templates/project/index.ts b/templates/project/index.ts index d770a31..3949ad6 100644 --- a/templates/project/index.ts +++ b/templates/project/index.ts @@ -2,4 +2,6 @@ import HelloAgent from "./agents/hello"; // Export an array of them here -export default [HelloAgent]; +export default [ + HelloAgent, // +]; diff --git a/templates/project/package.json b/templates/project/package.json index edcb2ed..7cea758 100644 --- a/templates/project/package.json +++ b/templates/project/package.json @@ -1,7 +1,8 @@ { "name": "{{NAME}}", "version": "0.0.0", - "description": "Made with Bazed", + "license": "UNLICENSED", + "description": "Made with bazed.ai", "scripts": { "dev": "bazed run", "build": "tsc", diff --git a/templates/project/tools/tool.ts b/templates/project/tools/tool.ts index e69de29..b8e7087 100644 --- a/templates/project/tools/tool.ts +++ b/templates/project/tools/tool.ts @@ -0,0 +1,22 @@ +import { FunctionTool, Tool, Agent } from "bazed"; +import { z } from "zod"; + +/** Tool is a spicy function that can describe itself with OpenAI compatible JSON schema.*/ +export const adder: Tool = new FunctionTool( + /** name of the tool */ + + "adder", + /** description of the tool */ + "Adds two numbers together", + + /** Zod schema for parameters of the tool */ + z.object({ a: z.number(), b: z.number() }), + + /** Zod schema for return value of the tool */ + z.number(), + + /** Function for the tool to perform when called, notice that types of a and b are inferred */ + async (_agent: Agent, { a, b }) => { + return a + b; + } +); diff --git a/templates/tools/tool.ts b/templates/tools/tool.ts new file mode 100644 index 0000000..cf2a079 --- /dev/null +++ b/templates/tools/tool.ts @@ -0,0 +1,22 @@ +import { FunctionTool, Tool, Agent } from "bazed"; +import { z } from "zod"; + +/** Tool is a spicy function that can describe itself with OpenAI compatible JSON schema.*/ +export const exampleTool: Tool = new FunctionTool( + /** name of the tool */ + "exampleTool", + + /** description of the tool */ + "... description ...", + + /** Zod schema for parameters of the tool */ + z.object({}), + + /** Zod schema for return value of the tool */ + z.object({}), + + /** Function for the tool to perform when called, notice that types of a and b are inferred */ + async (_agent: Agent, {}) => { + throw new Error("Method not implemented."); + } +); diff --git a/tsconfig.json b/tsconfig.json index 0527f75..dbaec98 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -106,6 +106,6 @@ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ }, - "exclude": ["node_modules", "dist", "templates/**/*"], + "exclude": ["node_modules", "dist", "templates/**/*", "jest.config.js", "coverage/**/*", "project/**/*"] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d8793fd..36b8f9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -350,12 +350,19 @@ ajv-formats "^2.1.1" fast-uri "^2.0.0" +"@fastify/busboy@^1.0.0": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-1.2.1.tgz#9c6db24a55f8b803b5222753b24fe3aea2ba9ca3" + integrity sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q== + dependencies: + text-decoding "^1.0.0" + "@fastify/deepmerge@^1.0.0": version "1.3.0" resolved "https://registry.npmjs.org/@fastify/deepmerge/-/deepmerge-1.3.0.tgz" integrity sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A== -"@fastify/error@^3.2.0", "@fastify/error@^3.4.0": +"@fastify/error@^3.0.0", "@fastify/error@^3.2.0", "@fastify/error@^3.4.0": version "3.4.1" resolved "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz" integrity sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ== @@ -377,6 +384,18 @@ path-to-regexp "^6.1.0" reusify "^1.0.4" +"@fastify/multipart@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@fastify/multipart/-/multipart-8.1.0.tgz#92b1cd482202b469b6c08aba568f0cda6fc543ba" + integrity sha512-sRX9X4ZhAqRbe2kDvXY2NK7i6Wf1Rm2g/CjpGYYM7+Np8E6uWQXcj761j08qPfPO8PJXM+vJ7yrKbK1GPB+OeQ== + dependencies: + "@fastify/busboy" "^1.0.0" + "@fastify/deepmerge" "^1.0.0" + "@fastify/error" "^3.0.0" + fastify-plugin "^4.0.0" + secure-json-parse "^2.4.0" + stream-wormhole "^1.1.0" + "@humanwhocodes/config-array@^0.11.13": version "0.11.13" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz" @@ -737,6 +756,13 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cli-progress@^3.11.5": + version "3.11.5" + resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.11.5.tgz#9518c745e78557efda057e3f96a5990c717268c3" + integrity sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g== + dependencies: + "@types/node" "*" + "@types/graceful-fs@^4.1.3": version "4.1.9" resolved "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz" @@ -1288,6 +1314,13 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +cli-progress@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" + integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== + dependencies: + string-width "^4.2.3" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" @@ -3165,7 +3198,7 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz" integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== -secure-json-parse@^2.7.0: +secure-json-parse@^2.4.0, secure-json-parse@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz" integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== @@ -3269,6 +3302,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stream-wormhole@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stream-wormhole/-/stream-wormhole-1.1.0.tgz#300aff46ced553cfec642a05251885417693c33d" + integrity sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -3350,6 +3388,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-decoding@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-decoding/-/text-decoding-1.0.0.tgz#38a5692d23b5c2b12942d6e245599cb58b1bc52f" + integrity sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"