@@ -5,7 +5,15 @@ import OpenAI from 'openai';
55// import { Fetch } from 'openai/sdk/internal/builtin-types';
66import { mockFetch } from '../../utils/mock-fetch' ;
77import { BetaRunnableTool } from 'openai/lib/beta/BetaRunnableTool' ;
8- import { ChatCompletion , ChatCompletionChunk , ChatCompletionMessage , ChatCompletionTool , ChatCompletionToolMessageParam } from 'openai/resources' ;
8+ import {
9+ ChatCompletion ,
10+ ChatCompletionChunk ,
11+ ChatCompletionFunctionTool ,
12+ ChatCompletionMessage ,
13+ ChatCompletionMessageToolCall ,
14+ ChatCompletionTool ,
15+ ChatCompletionToolMessageParam ,
16+ } from 'openai/resources' ;
917import { Fetch } from 'openai/internal/builtin-types' ;
1018import { ChatCompletionStream } from 'openai/lib/ChatCompletionStream' ;
1119
@@ -48,25 +56,34 @@ const calculatorTool: BetaRunnableTool<{ a: number; b: number; operation: string
4856} ;
4957
5058// Helper functions to create content blocks
51- function getWeatherToolUse ( location : string , id : string = 'tool_1' ) : ChatCompletionFunctionTool {
52- return { type : 'tool_use' , id, name : 'getWeather' , input : { location } } ;
59+ function getWeatherToolUse ( location : string , id : string = 'tool_1' ) : ChatCompletionMessageToolCall {
60+ return {
61+ id : id ,
62+ type : 'function' ,
63+ function : {
64+ name : 'getWeather' ,
65+ arguments : JSON . stringify ( { location } ) ,
66+ } ,
67+ } ;
5368}
5469
5570function getWeatherToolResult ( location : string , id : string = 'tool_1' ) : ChatCompletionToolMessageParam {
56- return { role : 'tool' , tool_use_id : id , content : `Sunny in ${ location } ` } ;
71+ return { role : 'tool' , tool_call_id : id , content : `Sunny in ${ location } ` } ;
5772}
5873
5974function getCalculatorToolUse (
6075 a : number ,
6176 b : number ,
6277 operation : string ,
6378 id : string = 'tool_2' ,
64- ) : BetaContentBlock {
79+ ) : ChatCompletionMessageToolCall {
6580 return {
66- type : 'tool_use' ,
67- id,
68- name : 'calculate' ,
69- input : { a, b, operation } ,
81+ id : id ,
82+ type : 'function' ,
83+ function : {
84+ name : 'calculate' ,
85+ arguments : JSON . stringify ( { a, b, operation } ) ,
86+ } ,
7087 } ;
7188}
7289
@@ -75,7 +92,7 @@ function getCalculatorToolResult(
7592 b : number ,
7693 operation : string ,
7794 id : string = 'tool_2' ,
78- ) : BetaToolResultBlockParam {
95+ ) : ChatCompletionToolMessageParam {
7996 let result : string ;
8097 if ( operation === 'add' ) {
8198 result = String ( a + b ) ;
@@ -85,13 +102,13 @@ function getCalculatorToolResult(
85102 result = `Error: Unknown operation: ${ operation } ` ;
86103 }
87104 return {
88- type : 'tool_result ' ,
89- tool_use_id : id ,
105+ role : 'tool ' ,
106+ tool_call_id : id ,
90107 content : result ,
91108 } ;
92109}
93110
94- function getTextContent ( text ?: string ) : BetaContentBlock {
111+ function getTextContent ( text ?: string ) : ChatCompletionMessage {
95112 return {
96113 type : 'text' ,
97114 text : text || 'Some text content' ,
@@ -213,21 +230,34 @@ interface SetupTestResult<Stream extends boolean> {
213230 runner : OpenAI . Beta . Chat . Completions . BetaToolRunner < Stream > ;
214231 fetch : ReturnType < typeof mockFetch > [ 'fetch' ] ;
215232 handleRequest : ( fetch : Fetch ) => void ;
216- handleAssistantMessage : ( ... content : ChatCompletionMessage [ ] ) => ChatCompletion ;
233+ handleAssistantMessage : ( firstChoice : ToolCallsOrMessage ) => ChatCompletion ;
217234 handleAssistantMessageStream : ( toolCalls ?: ChatCompletionChunk [ ] ) => ChatCompletionStream ;
218235}
219236
220237type ToolRunnerParams = Parameters < typeof OpenAI . Beta . Chat . Completions . prototype . toolRunner > [ 0 ] ;
221238
239+ type ToolCallsOrMessage = ChatCompletionMessageToolCall [ ] | ChatCompletionMessage ;
240+
222241function setupTest ( params ?: Partial < ToolRunnerParams > & { stream ?: false } ) : SetupTestResult < false > ;
223242function setupTest ( params : Partial < ToolRunnerParams > & { stream : true } ) : SetupTestResult < true > ;
224243function setupTest ( params : Partial < ToolRunnerParams > = { } ) : SetupTestResult < boolean > {
225244 const { handleRequest, handleStreamEvents, fetch } = mockFetch ( ) ;
226245 let messageIdCounter = 0 ;
227246
228- const handleAssistantMessage : SetupTestResult < false > [ 'handleAssistantMessage' ] = ( ...content ) => {
229- const hasToolUse = content . some ( ( block ) => ( block . tool_calls ?. length ?? 0 ) > 0 ) ;
230- const stop_reason = hasToolUse ? 'tool_use' : 'end_turn' ;
247+ const handleAssistantMessage : SetupTestResult < false > [ 'handleAssistantMessage' ] = (
248+ messageContentOrToolCalls ,
249+ ) => {
250+ const isToolCalls = Array . isArray ( messageContentOrToolCalls ) ;
251+
252+ const messageContent =
253+ isToolCalls ?
254+ {
255+ role : 'assistant' as const ,
256+ tool_calls : messageContentOrToolCalls ,
257+ refusal : null ,
258+ content : null ,
259+ }
260+ : ( messageContentOrToolCalls as ChatCompletionMessage ) ; // TODO: check that this is right
231261
232262 const message : ChatCompletion = {
233263 id : `msg_${ messageIdCounter ++ } ` ,
@@ -237,12 +267,8 @@ function setupTest(params: Partial<ToolRunnerParams> = {}): SetupTestResult<bool
237267 choices : [
238268 {
239269 index : 0 ,
240- message : {
241- role : 'assistant' ,
242- content : JSON . stringify ( content ) ,
243- refusal : null ,
244- } ,
245- finish_reason : stop_reason === 'tool_use' ? 'tool_calls' : 'stop' ,
270+ message : messageContent ,
271+ finish_reason : 'stop' ,
246272 logprobs : null ,
247273 } ,
248274 ] ,
@@ -252,6 +278,7 @@ function setupTest(params: Partial<ToolRunnerParams> = {}): SetupTestResult<bool
252278 total_tokens : 30 ,
253279 } ,
254280 } ;
281+
255282 handleRequest ( async ( ) => {
256283 return new Response ( JSON . stringify ( message ) , {
257284 status : 200 ,
@@ -261,11 +288,9 @@ function setupTest(params: Partial<ToolRunnerParams> = {}): SetupTestResult<bool
261288 return message ;
262289 } ;
263290
264- const handleAssistantMessageStream : SetupTestResult < true > [ 'handleAssistantMessageStream' ] = (
265- ...content
266- ) => {
267- const hasToolUse = content . some (
268- ( block ) => ( block ?. at ( 0 ) ?. choices ?. at ( 0 ) ?. delta ?. tool_calls ?. length ?? 0 ) > 0 ,
291+ const handleAssistantMessageStream : SetupTestResult < true > [ 'handleAssistantMessageStream' ] = ( ...chunks ) => {
292+ const hasToolUse = chunks . some (
293+ ( chunk ) => ( chunk ?. at ( 0 ) ?. choices ?. at ( 0 ) ?. delta ?. tool_calls ?. length ?? 0 ) > 0 ,
269294 ) ;
270295 const stop_reason = hasToolUse ? 'tool_use' : 'end_turn' ;
271296
@@ -279,7 +304,7 @@ function setupTest(params: Partial<ToolRunnerParams> = {}): SetupTestResult<bool
279304 index : 0 ,
280305 message : {
281306 role : 'assistant' ,
282- content : JSON . stringify ( content ) ,
307+ content : JSON . stringify ( chunks ) ,
283308 refusal : null ,
284309 } ,
285310 finish_reason : stop_reason === 'tool_use' ? 'tool_calls' : 'stop' ,
@@ -348,19 +373,21 @@ describe('ToolRunner', () => {
348373 } ) ;
349374
350375 describe ( 'iterator.next()' , ( ) => {
351- it ( 'yields BetaMessage ' , async ( ) => {
376+ it ( 'yields CompletionMessage ' , async ( ) => {
352377 const { runner, handleAssistantMessage } = setupTest ( ) ;
353378
354379 const iterator = runner [ Symbol . asyncIterator ] ( ) ;
355380
356- handleAssistantMessage ( getWeatherToolUse ( 'SF' ) ) ;
381+ handleAssistantMessage ( [ getWeatherToolUse ( 'SF' ) ] ) ;
382+
357383 await expectEvent ( iterator , ( message ) => {
358- expect ( message . content ) . toMatchObject ( [ getWeatherToolUse ( 'SF' ) ] ) ;
384+ expect ( message ?. choices [ 0 ] ?. message . tool_calls ) . toMatchObject ( [ getWeatherToolUse ( 'SF' ) ] ) ;
359385 } ) ;
360386
361387 handleAssistantMessage ( getTextContent ( ) ) ;
388+
362389 await expectEvent ( iterator , ( message ) => {
363- expect ( message . content ) . toMatchObject ( [ getTextContent ( ) ] ) ;
390+ expect ( message ?. choices [ 0 ] ?. message ) . toMatchObject ( getTextContent ( ) ) ;
364391 } ) ;
365392
366393 await expectDone ( iterator ) ;
@@ -411,32 +438,34 @@ describe('ToolRunner', () => {
411438
412439 const iterator = runner [ Symbol . asyncIterator ] ( ) ;
413440
414- handleAssistantMessage ( getWeatherToolUse ( 'NYC' ) , getCalculatorToolUse ( 2 , 3 , 'add' ) ) ;
441+ handleAssistantMessage ( [ getWeatherToolUse ( 'NYC' ) , getCalculatorToolUse ( 2 , 3 , 'add' ) ] ) ;
415442 await expectEvent ( iterator , ( message ) => {
416- expect ( message . content ) . toHaveLength ( 2 ) ;
417- expect ( message . content ) . toMatchObject ( [ getWeatherToolUse ( 'NYC' ) , getCalculatorToolUse ( 2 , 3 , 'add' ) ] ) ;
443+ expect ( message ?. choices ) . toHaveLength ( 1 ) ;
444+ expect ( message ?. choices [ 0 ] ?. message . tool_calls ) . toHaveLength ( 2 ) ;
445+ expect ( message ?. choices [ 0 ] ?. message . tool_calls ) . toMatchObject ( [
446+ getWeatherToolUse ( 'NYC' ) ,
447+ getCalculatorToolUse ( 2 , 3 , 'add' ) ,
448+ ] ) ;
418449 } ) ;
419450
420451 // Assistant provides final response
421452 handleAssistantMessage ( getTextContent ( ) ) ;
422453 await expectEvent ( iterator , ( message ) => {
423- expect ( message . content ) . toMatchObject ( [ getTextContent ( ) ] ) ;
454+ expect ( message ?. choices ) . toHaveLength ( 1 ) ;
455+ expect ( message ?. choices [ 0 ] ?. message ) . toMatchObject ( getTextContent ( ) ) ;
424456 } ) ;
425457
426458 // Check that we have both tool results in the messages
427459 // Second message should be assistant with tool uses
428460 // Third message should be user with both tool results
429461 const messages = runner . params . messages ;
430- expect ( messages ) . toHaveLength ( 3 ) ; // user message, assistant with tools, user with results
462+ expect ( messages ) . toHaveLength ( 4 ) ; // user message, assistant with tools, tool result 1, tool result 2, assistant final
431463 expect ( messages [ 1 ] ) . toMatchObject ( {
432464 role : 'assistant' ,
433- content : [ getWeatherToolUse ( 'NYC' ) , getCalculatorToolUse ( 2 , 3 , 'add' ) ] ,
465+ tool_calls : [ getWeatherToolUse ( 'NYC' ) , getCalculatorToolUse ( 2 , 3 , 'add' ) ] ,
434466 } ) ;
435- expect ( messages [ 2 ] ) . toMatchObject ( {
436- role : 'user' ,
437- content : [ getWeatherToolResult ( 'NYC' ) , getCalculatorToolResult ( 2 , 3 , 'add' , 'tool_2' ) ] ,
438- } ) ;
439-
467+ expect ( messages [ 2 ] ) . toMatchObject ( getWeatherToolResult ( 'NYC' ) ) ;
468+ expect ( messages [ 3 ] ) . toMatchObject ( getCalculatorToolResult ( 2 , 3 , 'add' , 'tool_2' ) ) ;
440469 await expectDone ( iterator ) ;
441470 } ) ;
442471
0 commit comments