Skip to content

Commit 3c8bdba

Browse files
committed
Solve more TODOs
1 parent d75a202 commit 3c8bdba

File tree

2 files changed

+69
-21
lines changed

2 files changed

+69
-21
lines changed

src/lib/beta/BetaToolRunner.ts

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,7 @@ export class BetaToolRunner<Stream extends boolean>
9696
}
9797

9898
async *[Symbol.asyncIterator](): AsyncIterator<
99-
Stream extends true ?
100-
ChatCompletionStream // TODO: for now!
101-
: ChatCompletion
99+
Stream extends true ? ChatCompletionStream : ChatCompletion
102100
> {
103101
if (this.#consumed) {
104102
throw new OpenAIError('Cannot iterate over a consumed stream');
@@ -241,7 +239,7 @@ export class BetaToolRunner<Stream extends boolean>
241239
* }
242240
*/
243241
async generateToolResponse() {
244-
// The most recent message from the assistant. TODO: do we want || this.params.messages.at(-1)?
242+
// The most recent message from the assistant.
245243
const message = await this.#message;
246244
if (!message) {
247245
return null;
@@ -281,7 +279,7 @@ export class BetaToolRunner<Stream extends boolean>
281279
* console.log('Final response:', finalMessage.content);
282280
*/
283281
done(): Promise<ChatCompletionMessage> {
284-
return this.#completion.promise; // TODO: find a more type safe way to do this
282+
return this.#completion.promise;
285283
}
286284

287285
/**
@@ -351,7 +349,7 @@ export class BetaToolRunner<Stream extends boolean>
351349
* Makes the ToolRunner directly awaitable, equivalent to calling .runUntilDone()
352350
* This allows using `await runner` instead of `await runner.runUntilDone()`
353351
*/
354-
then<TResult1 = ChatCompletionMessage, TResult2 = never>( // TODO: make sure these types are OK
352+
then<TResult1 = ChatCompletionMessage, TResult2 = never>(
355353
onfulfilled?: ((value: ChatCompletionMessage) => TResult1 | PromiseLike<TResult1>) | undefined | null,
356354
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null,
357355
): Promise<TResult1 | TResult2> {
@@ -364,12 +362,7 @@ async function generateToolResponse(
364362
tools: BetaRunnableChatCompletionFunctionTool<any>[],
365363
): Promise<null | ChatCompletionToolMessageParam[]> {
366364
// Only process if the last message is from the assistant and has tool use blocks
367-
if (
368-
!lastMessage ||
369-
lastMessage.role !== 'assistant' ||
370-
// !lastMessage.content || TODO: openai doesn't give content at the same time. ensure this is really always true though
371-
typeof lastMessage.content === 'string'
372-
) {
365+
if (!lastMessage || lastMessage.role !== 'assistant' || typeof lastMessage.content === 'string') {
373366
return null;
374367
}
375368

@@ -382,7 +375,7 @@ async function generateToolResponse(
382375
return (
383376
await Promise.all(
384377
prevToolCalls.map(async (toolUse) => {
385-
if (toolUse.type !== 'function') return; // TODO: what about other calls?
378+
if (toolUse.type !== 'function') return; // TODO: eventually we should support additional tool call types
386379

387380
const tool = tools.find(
388381
(t) => t.type === 'function' && toolUse.function.name === t.function.name,
@@ -398,14 +391,7 @@ async function generateToolResponse(
398391
}
399392

400393
try {
401-
let input = toolUse.function.arguments;
402-
// TODO: is this always safe?
403-
if (typeof input === 'string') {
404-
input = JSON.parse(input);
405-
}
406-
input = tool.parse(input);
407-
408-
const result = await tool.run(input);
394+
const result = await tool.run(tool.parse(JSON.parse(toolUse.function.arguments)));
409395
return {
410396
type: 'tool_result' as const,
411397
tool_call_id: toolUse.id,

tests/lib/tools/BetaToolRunner.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
} from 'openai/resources';
1212
import type { Fetch } from 'openai/internal/builtin-types';
1313
import type { BetaToolRunnerParams } from 'openai/lib/beta/BetaToolRunner';
14+
import { betaZodFunctionTool } from 'openai/helpers/beta/zod';
1415

1516
const weatherTool: BetaRunnableChatCompletionFunctionTool<{ location: string }> = {
1617
type: 'function',
@@ -1202,4 +1203,65 @@ describe('ToolRunner', () => {
12021203
await expectDone(iterator);
12031204
});
12041205
});
1206+
1207+
it('caches tool responses properly and handles text messages', async () => {
1208+
let callCount = 0;
1209+
1210+
const { runner, handleAssistantMessage } = setupTest({
1211+
messages: [{ role: 'user', content: 'What is the weather?' }],
1212+
tools: [
1213+
{
1214+
type: 'function',
1215+
function: {
1216+
name: 'getWeather',
1217+
description: 'Get weather information',
1218+
parameters: {
1219+
type: 'object',
1220+
properties: {
1221+
location: {
1222+
type: 'string',
1223+
description: 'The location to get weather information for',
1224+
},
1225+
},
1226+
},
1227+
},
1228+
run: async ({ location }: { location: string }) => {
1229+
callCount++;
1230+
return `Sunny in ${location}`;
1231+
},
1232+
parse: (input: unknown) => input as { location: string },
1233+
},
1234+
],
1235+
});
1236+
1237+
const iterator = runner[Symbol.asyncIterator]();
1238+
1239+
// Initial tool response should be null (no assistant message yet)
1240+
const initialResponse = await runner.generateToolResponse();
1241+
expect(initialResponse).toBeNull();
1242+
expect(callCount).toBe(0);
1243+
1244+
// Assistant requests weather tool
1245+
handleAssistantMessage([getWeatherToolUse('Paris')]);
1246+
await iterator.next();
1247+
1248+
// Tool response should be cached
1249+
const toolResponse1 = await runner.generateToolResponse();
1250+
expect(toolResponse1).toMatchObject([getWeatherToolResult('Paris')]);
1251+
expect(callCount).toBe(1);
1252+
1253+
const toolResponse2 = await runner.generateToolResponse();
1254+
expect(toolResponse2).toMatchObject([getWeatherToolResult('Paris')]);
1255+
expect(callCount).toBe(1); // Still 1 - cached response
1256+
1257+
// Now test with a text message
1258+
handleAssistantMessage(getTextContent());
1259+
await iterator.next();
1260+
1261+
const textResponse = await runner.generateToolResponse();
1262+
expect(textResponse).toBeNull(); // Text messages return null
1263+
expect(callCount).toBe(1); // Still 1 - no new tool calls
1264+
1265+
await expectDone(iterator);
1266+
});
12051267
});

0 commit comments

Comments
 (0)