From 7642f51047048e35d8b6a667d05e28adce8eb4c0 Mon Sep 17 00:00:00 2001 From: Donnie Adams Date: Thu, 30 May 2024 22:10:39 -0400 Subject: [PATCH] feat: add prompt support --- src/gptscript.ts | 38 +++++++++++++++++++++++++++++++++++--- tests/gptscript.test.ts | 22 ++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/gptscript.ts b/src/gptscript.ts index 2022704..37bdc97 100644 --- a/src/gptscript.ts +++ b/src/gptscript.ts @@ -25,6 +25,8 @@ export enum RunEventType { CallConfirm = "callConfirm", CallContinue = "callContinue", CallFinish = "callFinish", + + Prompt = "prompt" } let serverProcess: child_process.ChildProcess @@ -177,6 +179,20 @@ export class Client { } } + async promptResponse(response: PromptResponse): Promise { + if (!this.clientReady) { + this.clientReady = await this.testGPTScriptURL(20) + } + const resp = await fetch(`${this.gptscriptURL}/prompt-response/${response.id}`, { + method: "POST", + body: JSON.stringify(response.responses) + }) + + if (resp.status < 200 || resp.status >= 400) { + throw new Error(`Failed to respond to prompt ${response.id}: ${await resp.text()}`) + } + } + private async testGPTScriptURL(count: number): Promise { try { await fetch(`${this.gptscriptURL}/healthz`) @@ -405,6 +421,8 @@ export class Run { f = obj.run as Frame } else if (obj.call) { f = obj.call as Frame + } else if (obj.prompt) { + f = obj.prompt as Frame } else { return event } @@ -426,8 +444,7 @@ export class Run { this.state = RunState.Finished this.stdout = f.output || "" } - } else { - if (!(f.type as string).startsWith("call")) continue + } else if ((f.type as string).startsWith("call")) { f = (f as CallFrame) const idx = this.calls?.findIndex((x) => x.id === f.id) @@ -447,6 +464,7 @@ export class Run { public on(event: RunEventType.RunStart | RunEventType.RunFinish, listener: (data: RunFrame) => void): this; public on(event: RunEventType.CallStart | RunEventType.CallProgress | RunEventType.CallContinue | RunEventType.CallChat | RunEventType.CallConfirm | RunEventType.CallFinish, listener: (data: CallFrame) => void): this; + public on(event: RunEventType.Prompt, listener: (data: PromptFrame) => void): this; public on(event: RunEventType.Event, listener: (data: Frame) => void): this; public on(event: RunEventType, listener: (data: any) => void): this { if (!this.callbacks[event]) { @@ -656,7 +674,16 @@ export interface CallFrame { llmResponse?: any } -export type Frame = RunFrame | CallFrame +export interface PromptFrame { + id: string + type: RunEventType.Prompt + time: string + message: string + fields: string[] + sensitive: boolean +} + +export type Frame = RunFrame | CallFrame | PromptFrame export interface AuthResponse { id: string @@ -664,6 +691,11 @@ export interface AuthResponse { message?: string } +export interface PromptResponse { + id: string + responses: Record +} + function getCmdPath(): string { if (process.env.GPTSCRIPT_BIN) { return process.env.GPTSCRIPT_BIN diff --git a/tests/gptscript.test.ts b/tests/gptscript.test.ts index 5692480..a27da88 100644 --- a/tests/gptscript.test.ts +++ b/tests/gptscript.test.ts @@ -428,4 +428,26 @@ describe("gptscript module", () => { expect(run.err).toEqual("") expect(confirmFound).toBeTruthy() }) + + test("prompt", async () => { + let promptFound = false + const t = { + instructions: "Use the sys.prompt user to ask the user for 'first name' which is not sensitive. After you get their first name, say hello.", + tools: ["sys.prompt"] + } + const run = await client.evaluate(t as any) + run.on(gptscript.RunEventType.Prompt, async (data: gptscript.PromptFrame) => { + expect(data.message).toContain("first name") + expect(data.fields.length).toEqual(1) + expect(data.fields[0]).toEqual("first name") + expect(data.sensitive).toBeFalsy() + + promptFound = true + await client.promptResponse({id: data.id, responses: {[data.fields[0]]: "Clicky"}}) + }) + + expect(await run.text()).toContain("Clicky") + expect(run.err).toEqual("") + expect(promptFound).toBeTruthy() + }) })