From 627ae207425a373c8aa3a0806b2860179950dc45 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Mon, 5 Aug 2024 18:30:30 -0400 Subject: [PATCH] Streaming Buffered Text Output Support --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- src/experts/assistant.js | 20 ++++++++++++++------ test/fixtures/accountsAssistant.js | 1 + test/fixtures/htmlAssistant.js | 4 ++-- test/uat/bespokeUIAssistant.test.js | 17 ++++++++++++++++- 7 files changed, 42 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f0d9a..f27147b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ See this http://keepachangelog.com link for information on how we want this document formatted. +## v1.4.2 + +### Added + +Streaming support for buffered output added in v1.4.1. + ## v1.4.1 ### Added diff --git a/package-lock.json b/package-lock.json index a7cb69e..d883396 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "experts", - "version": "1.4.1", + "version": "1.4.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "experts", - "version": "1.4.1", + "version": "1.4.2", "license": "MIT", "dependencies": { "eventemitter2": "^6.4.9", diff --git a/package.json b/package.json index bdbe44a..8f07a51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "experts", - "version": "1.4.1", + "version": "1.4.2", "description": "An opinionated panel of experts implementation using OpenAI's Assistants API", "type": "module", "main": "./src/index.js", diff --git a/src/experts/assistant.js b/src/experts/assistant.js index a3ca51b..b539a08 100644 --- a/src/experts/assistant.js +++ b/src/experts/assistant.js @@ -7,6 +7,7 @@ const { EventEmitter2 } = EventEmitter2Pkg; const ASYNC_EVENTS = [ "textDoneAsync", + "bufferedTextDoneAsync", "imageFileDoneAsync", "runStepDoneAsync", "toolCallDoneAsync", @@ -17,7 +18,7 @@ class Assistant { #stream; #streamEmitter; #expertsOutputs = []; - #bufferedOutputs = []; + #bufferedTextOutputs = []; #asyncListeners = {}; static async create(options = {}) { @@ -105,7 +106,7 @@ class Assistant { } addBufferedOutput(output) { - this.#bufferedOutputs.push(output); + this.#bufferedTextOutputs.push(output); } addAssistantTool(toolClass) { @@ -152,8 +153,8 @@ class Assistant { break; } } - if (this.#bufferedOutputs.length > 0) { - newOutput = newOutput + "\n\n" + this.#bufferedOutputs.join("\n\n"); + if (this.#bufferedTextOutputs.length > 0) { + newOutput = newOutput + "\n\n" + this.#bufferedTextOutputs.join("\n\n"); } return newOutput; } @@ -195,8 +196,8 @@ class Assistant { if (this.#expertsOutputs.length > 0) { this.#expertsOutputs.length = 0; } - if (this.#bufferedOutputs.length > 0) { - this.#bufferedOutputs.length = 0; + if (this.#bufferedTextOutputs.length > 0) { + this.#bufferedTextOutputs.length = 0; } } @@ -250,6 +251,13 @@ class Assistant { const args = [...arguments, this.#onMetaData]; this.emitter.emit("textDone", ...args); this.#forwardAsyncEvent("textDoneAsync", ...args); + this.#bufferedTextOutputs.forEach((output) => { + this.emitter.emit( + "bufferedTextDoneAsync", + { value: output }, + this.#onMetaData + ); + }); } #onImageFileDone() { diff --git a/test/fixtures/accountsAssistant.js b/test/fixtures/accountsAssistant.js index d2d86f8..c6f43f7 100644 --- a/test/fixtures/accountsAssistant.js +++ b/test/fixtures/accountsAssistant.js @@ -64,6 +64,7 @@ class AccountsTool extends Tool { class AccountsAssistant extends Assistant { constructor() { super({ + name: helperName("AccountsAssistant"), instructions: "Routes messages to the right tool.", }); this.addAssistantTool(AccountsTool); diff --git a/test/fixtures/htmlAssistant.js b/test/fixtures/htmlAssistant.js index 2f133a9..2d075c7 100644 --- a/test/fixtures/htmlAssistant.js +++ b/test/fixtures/htmlAssistant.js @@ -103,8 +103,8 @@ class HtmlAssistant extends Assistant { instructions: ` ## Rules -1. For each user message, use the 'data_tool' first. -2. The use the 'html_tool' to format the submitted outputs of the 'data_tool'. +1. For each user message, use the 'data_tool' first to find items. +2. For each item from the 'data_tool', use the 'html_tool' to HTML format them. 3. HTML output is hidden to you. Assume the user can see it appended to your message. `, temperature: 0.1, diff --git a/test/uat/bespokeUIAssistant.test.js b/test/uat/bespokeUIAssistant.test.js index e508fe3..69c9773 100644 --- a/test/uat/bespokeUIAssistant.test.js +++ b/test/uat/bespokeUIAssistant.test.js @@ -1,7 +1,7 @@ import { helperThreadID } from "../helpers.js"; import { HtmlAssistant } from "../fixtures.js"; -test("can find an expert that has many tools", async () => { +test("can attached buffered output to non-streaming output", async () => { const assistant = await HtmlAssistant.create(); const dataTool = assistant.experts[0]; const htmlTool = assistant.experts[1]; @@ -16,3 +16,18 @@ test("can find an expert that has many tools", async () => { // Ensure the output has HTML. expect(output).toMatch(/class="my-Component"/); }, 20000); + +test("can use streaming events to capture buffered output", async () => { + const dones = []; + const assistant = await HtmlAssistant.create(); + assistant.on("bufferedTextDoneAsync", (content) => { + dones.push(content.value); + }); + const threadID = await helperThreadID(); + await assistant.ask("Show me cow data.", threadID); + expect(dones.length).toBe(2); + expect(dones[0]).toMatch(/class="my-Component"/); + expect(dones[0]).toMatch(/Bessie/i); + expect(dones[1]).toMatch(/class="my-Component"/); + expect(dones[1]).toMatch(/Gertie/i); +}, 20000);