Skip to content

Commit 23fd1bc

Browse files
committed
feat: compile calls in a friendlier way
Additionally, this change adds a "call stack" that a caller can use to get a stack-like data structure for the calls in a run.
1 parent 0c8c0f1 commit 23fd1bc

File tree

2 files changed

+135
-52
lines changed

2 files changed

+135
-52
lines changed

src/gptscript.ts

+103-52
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ export class Run {
214214
public readonly filePath: string
215215
public readonly content: string
216216
public state: RunState = RunState.Creating
217-
public calls: CallFrame[] = []
217+
public calls: Record<string, CallFrame> = {}
218218
public err: string = ""
219219

220220
protected stdout?: string
@@ -226,6 +226,10 @@ export class Run {
226226
private stderr?: string
227227
private callbacks: Record<string, ((f: Frame) => void)[]> = {}
228228
private chatState?: string
229+
private callIdsByParentIds: Record<string, string[]> = {}
230+
private parentCallId: string = ""
231+
private prg?: Program
232+
private respondingToolId?: string
229233

230234
constructor(subCommand: string, path: string, content: string, opts: RunOpts, gptscriptURL?: string) {
231235
this.id = randomId("run-")
@@ -279,6 +283,7 @@ export class Run {
279283
if (out.done === undefined || !out.done) {
280284
this.chatState = JSON.stringify(out.state)
281285
this.state = RunState.Continue
286+
this.respondingToolId = out.toolId
282287
} else {
283288
this.state = RunState.Finished
284289
this.chatState = undefined
@@ -412,7 +417,87 @@ export class Run {
412417
}
413418
}
414419

415-
emitEvent(data: string): string {
420+
public on(event: RunEventType.RunStart | RunEventType.RunFinish, listener: (data: RunFrame) => void): this;
421+
public on(event: RunEventType.CallStart | RunEventType.CallProgress | RunEventType.CallContinue | RunEventType.CallChat | RunEventType.CallConfirm | RunEventType.CallFinish, listener: (data: CallFrame) => void): this;
422+
public on(event: RunEventType.Prompt, listener: (data: PromptFrame) => void): this;
423+
public on(event: RunEventType.Event, listener: (data: Frame) => void): this;
424+
public on(event: RunEventType, listener: (data: any) => void): this {
425+
if (!this.callbacks[event]) {
426+
this.callbacks[event] = []
427+
}
428+
429+
this.callbacks[event].push(listener)
430+
431+
return this
432+
}
433+
434+
public text(): Promise<string> {
435+
if (this.err) {
436+
throw new Error(this.err)
437+
}
438+
439+
if (!this.promise) {
440+
throw new Error("Run not started")
441+
}
442+
443+
return this.promise
444+
}
445+
446+
public async json(): Promise<any> {
447+
return JSON.parse(await this.text())
448+
}
449+
450+
public currentChatState(): string | undefined {
451+
return this.chatState
452+
}
453+
454+
public firstCallId(): string {
455+
return this.parentCallId
456+
}
457+
458+
public program(): Program | undefined {
459+
return this.prg
460+
}
461+
462+
public respondingTool(): Tool | undefined {
463+
return this.respondingToolId ? this.prg?.toolSet[this.respondingToolId] : undefined
464+
}
465+
466+
public callStack(): CallStack {
467+
const cs = this.newCallStack("")
468+
return cs && cs.length > 0 ? cs[0] : {} as CallStack
469+
}
470+
471+
public close(): void {
472+
if (this.req) {
473+
this.req.destroy()
474+
return
475+
}
476+
throw new Error("Run not started")
477+
}
478+
479+
private newCallStack(parentID: string): CallStack[] | undefined {
480+
if (!this.callIdsByParentIds[parentID]) {
481+
return undefined
482+
}
483+
484+
const callStack: CallStack[] = []
485+
let callIds = this.callIdsByParentIds[parentID] || []
486+
while (callIds.length != 0) {
487+
const callId: string = callIds.shift()!
488+
489+
const cs = {
490+
call: this.calls[callId],
491+
subCalls: this.newCallStack(callId),
492+
} as CallStack
493+
494+
callStack.push(cs)
495+
}
496+
497+
return callStack
498+
}
499+
500+
private emitEvent(data: string): string {
416501
for (let event of data.split("\n")) {
417502
event = event.trim()
418503

@@ -448,6 +533,7 @@ export class Run {
448533

449534
if (f.type === RunEventType.RunStart) {
450535
this.state = RunState.Running
536+
this.prg = f.program
451537
} else if (f.type === RunEventType.RunFinish) {
452538
if (f.error) {
453539
this.state = RunState.Error
@@ -457,14 +543,16 @@ export class Run {
457543
this.stdout = f.output || ""
458544
}
459545
} else if ((f.type as string).startsWith("call")) {
460-
f = (f as CallFrame)
461-
const idx = this.calls?.findIndex((x) => x.id === f.id)
462-
463-
if (idx === -1) {
464-
this.calls.push(f)
465-
} else {
466-
this.calls[idx] = f
546+
f = f as CallFrame
547+
const siblingIds = this.callIdsByParentIds[f.parentID || ""] || []
548+
if (siblingIds.findIndex((x) => x === f.id) === -1) {
549+
siblingIds.push(f.id)
550+
this.callIdsByParentIds[f.parentID || ""] = siblingIds
467551
}
552+
if (f.parentID === "" && this.parentCallId === "") {
553+
this.parentCallId = f.id
554+
}
555+
this.calls[f.id] = f
468556
}
469557

470558
this.emit(RunEventType.Event, f)
@@ -474,48 +562,6 @@ export class Run {
474562
return ""
475563
}
476564

477-
public on(event: RunEventType.RunStart | RunEventType.RunFinish, listener: (data: RunFrame) => void): this;
478-
public on(event: RunEventType.CallStart | RunEventType.CallProgress | RunEventType.CallContinue | RunEventType.CallChat | RunEventType.CallConfirm | RunEventType.CallFinish, listener: (data: CallFrame) => void): this;
479-
public on(event: RunEventType.Prompt, listener: (data: PromptFrame) => void): this;
480-
public on(event: RunEventType.Event, listener: (data: Frame) => void): this;
481-
public on(event: RunEventType, listener: (data: any) => void): this {
482-
if (!this.callbacks[event]) {
483-
this.callbacks[event] = []
484-
}
485-
486-
this.callbacks[event].push(listener)
487-
488-
return this
489-
}
490-
491-
public text(): Promise<string> {
492-
if (this.err) {
493-
throw new Error(this.err)
494-
}
495-
496-
if (!this.promise) {
497-
throw new Error("Run not started")
498-
}
499-
500-
return this.promise
501-
}
502-
503-
public async json(): Promise<any> {
504-
return JSON.parse(await this.text())
505-
}
506-
507-
public currentChatState(): string | undefined {
508-
return this.chatState
509-
}
510-
511-
public close(): void {
512-
if (this.req) {
513-
this.req.destroy()
514-
return
515-
}
516-
throw new Error("Run not started")
517-
}
518-
519565
private emit(event: RunEventType, data: any) {
520566
for (const cb of this.callbacks[event] || []) {
521567
cb(data)
@@ -556,7 +602,7 @@ export interface ArgumentSchema {
556602

557603
export interface Program {
558604
name: string
559-
blocks: Block[]
605+
toolSet: Record<string, Tool>
560606
openAPICache: Record<string, any>
561607
}
562608

@@ -708,6 +754,11 @@ export interface PromptResponse {
708754
responses: Record<string, string>
709755
}
710756

757+
export interface CallStack {
758+
call: CallFrame
759+
subCalls: CallStack[]
760+
}
761+
711762
function getCmdPath(): string {
712763
if (process.env.GPTSCRIPT_BIN) {
713764
return process.env.GPTSCRIPT_BIN

tests/gptscript.test.ts

+32
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,38 @@ describe("gptscript module", () => {
203203
}, 30000)
204204
})
205205

206+
test("simple call stack", async () => {
207+
const t0 = {
208+
tools: ["ask"],
209+
instructions: "Only use the ask tool to ask who was the president of the united states in 1928?"
210+
}
211+
const t1 = {
212+
name: "ask",
213+
description: "This tool is used to ask a question",
214+
arguments: {
215+
type: "object",
216+
question: "The question to ask"
217+
},
218+
instructions: "${question}"
219+
}
220+
221+
const run = await client.evaluate([t0 as any, t1 as any])
222+
const response = await run.text()
223+
expect(response).toBeDefined()
224+
225+
const cs = run.callStack()
226+
227+
expect(cs.call).toBeDefined()
228+
expect(cs.call.id).toBeDefined()
229+
expect(cs.subCalls).toBeDefined()
230+
expect(cs.subCalls.length).toEqual(1)
231+
expect(cs.subCalls[0].call.parentID).toEqual(cs.call.id)
232+
expect(cs.subCalls[0].call.tool).toBeDefined()
233+
expect(cs.subCalls[0].call.tool?.name).toEqual("ask")
234+
expect(cs.subCalls[0].subCalls).toBeUndefined()
235+
236+
}, 30000)
237+
206238
test("parse file", async () => {
207239
const response = await client.parse(path.join(__dirname, "fixtures", "test.gpt"))
208240
expect(response).toBeDefined()

0 commit comments

Comments
 (0)