Skip to content

Commit 4a71943

Browse files
committed
more progress on iterative tool calls
1 parent 1260ec6 commit 4a71943

File tree

3 files changed

+105
-20
lines changed

3 files changed

+105
-20
lines changed

src/helpers/beta/betaZodTool.ts

Whitespace-only changes.

src/helpers/beta/zod.ts

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { transformJSONSchema } from '../..//lib/transform-json-schema';
22
import type { infer as zodInfer, ZodType } from 'zod';
33
import * as z from 'zod';
44
import { OpenAIError } from '../../core/error';
5+
import type { BetaRunnableTool, Promisable } from '../../lib/beta/BetaRunnableTool';
6+
import type { AutoParseableBetaOutputFormat } from '../../lib/beta-parser';
57
// import { AutoParseableBetaOutputFormat } from '../../lib/beta-parser';
68
// import { BetaRunnableTool, Promisable } from '../../lib/tools/BetaRunnableTool';
79
// import { BetaToolResultContentBlockParam } from '../../resources/beta';
@@ -61,12 +63,68 @@ export function betaZodTool<InputSchema extends ZodType>(options: {
6163
// TypeScript doesn't narrow the type after the runtime check, so we need to assert it
6264
const objectSchema = jsonSchema as typeof jsonSchema & { type: 'object' };
6365

66+
// return {
67+
// type: 'function', // TODO: should this be custom or function?
68+
// name: options.name,
69+
// input_schema: objectSchema,
70+
// description: options.description,
71+
// run: options.run,
72+
// parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
73+
// };
6474
return {
65-
type: 'custom',
66-
name: options.name,
67-
input_schema: objectSchema,
68-
description: options.description,
69-
run: options.run,
70-
parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
75+
type: 'function',
76+
function: {
77+
name: options.name,
78+
// input_schema: objectSchema,
79+
description: options.description,
80+
// run: options.run,
81+
// parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
82+
parameters: {
83+
type: 'object',
84+
properties: objectSchema.properties,
85+
},
86+
},
7187
};
7288
}
89+
90+
// /**
91+
// * Creates a tool using the provided Zod schema that can be passed
92+
// * into the `.toolRunner()` method. The Zod schema will automatically be
93+
// * converted into JSON Schema when passed to the API. The provided function's
94+
// * input arguments will also be validated against the provided schema.
95+
// */
96+
// export function betaZodTool<InputSchema extends ZodType>(options: {
97+
// name: string;
98+
// inputSchema: InputSchema;
99+
// description: string;
100+
// run: (args: zodInfer<InputSchema>) => Promisable<string | Array<any>>;
101+
// }): BetaRunnableTool<zodInfer<InputSchema>> {
102+
// const jsonSchema = z.toJSONSchema(options.inputSchema, { reused: 'ref' });
103+
104+
// if (jsonSchema.type !== 'object') {
105+
// throw new Error(`Zod schema for tool "${options.name}" must be an object, but got ${jsonSchema.type}`);
106+
// }
107+
108+
// // TypeScript doesn't narrow the type after the runtime check, so we need to assert it
109+
// const objectSchema = jsonSchema as typeof jsonSchema & { type: 'object' };
110+
111+
// // return {
112+
// // type: 'function', // TODO: should this be custom or function?
113+
// // name: options.name,
114+
// // input_schema: objectSchema,
115+
// // description: options.description,
116+
// // run: options.run,
117+
// // parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
118+
// // };
119+
// return {
120+
// type: 'function',
121+
// function: {
122+
// name: options.name,
123+
// // input_schema: objectSchema,
124+
// description: options.description,
125+
// // run: options.run,
126+
// // parse: (args: unknown) => options.inputSchema.parse(args) as zodInfer<InputSchema>,
127+
// parameters: objectSchema.properties ?? {}, // the json schema
128+
// },
129+
// };
130+
// }

src/lib/beta/BetaToolRunner.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ export class BetaToolRunner<Stream extends boolean> {
7272
this.#completion = promiseWithResolvers();
7373
}
7474

75+
get #firstChoiceInCurrentMessage(): ChatCompletionMessageParam | null {
76+
const lastMessage = this.#state.params.messages[this.#state.params.messages.length - 1];
77+
return lastMessage ?? null;
78+
}
79+
7580
async *[Symbol.asyncIterator](): AsyncIterator<
7681
Stream extends true ?
7782
ChatCompletionStream // TODO: for now!
@@ -112,18 +117,38 @@ export class BetaToolRunner<Stream extends boolean> {
112117
// this.#message.catch(() => {});
113118
// yield stream as any;
114119
} else {
115-
this.#message = this.client.beta.chat.completions.create({ ...params, stream: false });
120+
console.log('making request with params:', JSON.stringify(params, null, 2));
121+
this.#message = this.client.beta.chat.completions.create({
122+
stream: false,
123+
tools: params.tools,
124+
messages: params.messages,
125+
model: params.model,
126+
});
127+
console.log('Message created:', JSON.stringify(await this.#message, null, 2));
116128
yield this.#message as any;
117129
}
118130

131+
if (!this.#message) {
132+
throw new Error('No message created'); // TODO: use better error
133+
}
134+
135+
// TODO: we should probably hit the user with a callback or somehow offer for them to choice between the choices
136+
const { choices } = await this.#message;
137+
138+
if (!this.#firstChoiceInCurrentMessage) {
139+
throw new Error('No choices found in message'); // TODO: use better error
140+
}
141+
142+
const { role: firstChoiceRole, content: firstChoiceContent } = this.#firstChoiceInCurrentMessage;
143+
119144
if (!this.#mutated) {
120-
const { choices } = await this.#message;
121-
const { role, content } = choices[0]!.message; // TODO: ensure there's at least one choice
145+
console.log(choices);
122146
// this.#state.params.messages.push({ role, content }); TODO: we want to add all
123-
this.#state.params.messages.push({ role, content });
147+
this.#state.params.messages.push(this.#firstChoiceInCurrentMessage as ChatCompletionMessageParam);
124148
}
125149

126-
const toolMessage = await this.#generateToolResponse(this.#state.params.messages.at(-1)!);
150+
const toolMessage = await this.#generateToolResponse((await this.#message).choices[0]!);
151+
console.log('Tool message:', toolMessage);
127152
if (toolMessage) {
128153
this.#state.params.messages.push(toolMessage);
129154
}
@@ -204,15 +229,17 @@ export class BetaToolRunner<Stream extends boolean> {
204229
if (!message) {
205230
return null;
206231
}
232+
console.log("Message:", message[0]);
207233
// TODO: this cast is probably bad
208-
return this.#generateToolResponse(message as ChatCompletionMessageParam);
234+
return this.#generateToolResponse(message[0]);
209235
}
210236

211-
async #generateToolResponse(lastMessage: ChatCompletionMessageParam) {
237+
async #generateToolResponse(lastMessage: ChatCompletion.Choice) {
238+
console.log('Last message:', lastMessage.message);
212239
if (this.#toolResponse !== undefined) {
213240
return this.#toolResponse;
214241
}
215-
this.#toolResponse = generateToolResponse(this.#state.params, lastMessage);
242+
this.#toolResponse = generateToolResponse(this.#state.params, lastMessage.message!); // TODO: maybe undefined
216243
return this.#toolResponse;
217244
}
218245

@@ -313,19 +340,19 @@ export class BetaToolRunner<Stream extends boolean> {
313340

314341
async function generateToolResponse(
315342
params: BetaToolRunnerParams,
316-
lastMessage = params.messages.at(-1),
343+
lastMessageFirstChoice = params.messages.at(-1),
317344
): Promise<ChatCompletionToolMessageParam | null> {
318345
// Only process if the last message is from the assistant and has tool use blocks
319346
if (
320-
!lastMessage ||
321-
lastMessage.role !== 'assistant' ||
322-
!lastMessage.tool_calls ||
323-
lastMessage.tool_calls.length === 0
347+
!lastMessageFirstChoice ||
348+
lastMessageFirstChoice.role !== 'assistant' ||
349+
!lastMessageFirstChoice.tool_calls ||
350+
lastMessageFirstChoice.tool_calls.length === 0
324351
) {
325352
return null;
326353
}
327354

328-
const toolUseBlocks = lastMessage.tool_calls.filter((toolCall) => toolCall.type === 'function');
355+
const toolUseBlocks = lastMessageFirstChoice.tool_calls.filter((toolCall) => toolCall.type === 'function');
329356
if (toolUseBlocks.length === 0) {
330357
return null;
331358
}

0 commit comments

Comments
 (0)