11import asyncHandler from "../middleware/asyncHandler" ;
22import { Request , Response } from "express" ;
33import { createOpenAI } from "@ai-sdk/openai" ;
4- import { CoreMessage , streamText } from "ai" ;
4+ import { CoreMessage , generateObject , InvalidToolArgumentsError , NoSuchToolError , streamText , tool , ToolExecutionError } from "ai" ;
55import { Index , Pinecone , RecordMetadata } from "@pinecone-database/pinecone" ;
66import { PineconeStore } from "@langchain/pinecone" ;
77import { OpenAIEmbeddings } from "@langchain/openai" ;
@@ -27,6 +27,7 @@ import {
2727 availableFunctions ,
2828 FunctionNames ,
2929} from "../constants/availableFunctions" ;
30+ import { z } from "zod" ;
3031
3132const openai = createOpenAI ( {
3233 baseURL : process . env . OPENAI_BASE_URL ,
@@ -231,6 +232,8 @@ async function reformulateQuery(
231232 content : latestQuery ,
232233 } ) ;
233234
235+ console . log ( messages )
236+
234237 const response = await openai2 . chat . completions . create ( {
235238 model : "gpt-4o-mini" ,
236239 messages : messages ,
@@ -319,55 +322,6 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
319322 if ( latestMessage . startsWith ( CHATBOT_TIMETABLE_CMD ) ) {
320323 // ----- Flow 1 - Agent performs action on timetable -----
321324
322- const openai2 = new OpenAI ( {
323- apiKey : process . env . OPENAI_API_KEY ,
324- } ) ;
325-
326- // Call with function definitions
327- const response = await openai2 . chat . completions . create ( {
328- model : "gpt-4o-mini" ,
329- messages,
330- functions : [
331- {
332- name : "getTimetables" ,
333- description :
334- "Get all the timetables of the currently logged in user." ,
335- parameters : {
336- type : "object" ,
337- properties : { } ,
338- required : [ ] ,
339- } ,
340- } ,
341- ] ,
342- function_call : "auto" ,
343- } ) ;
344-
345- let responseMessage = response . choices [ 0 ] . message ;
346-
347- console . log ( responseMessage ) ;
348-
349- // Check if the model wants to call a tool/function
350- if ( responseMessage . function_call ) {
351- // Add the assistant's message with tool calls to conversation
352- // messages.push(responseMessage);
353-
354- // Process the tool call
355- const toolCall = responseMessage . function_call ;
356- const functionName = toolCall . name as FunctionNames ;
357- const functionToCall = availableFunctions [ functionName ] ;
358- const functionArgs = JSON . parse ( toolCall . arguments ) ;
359-
360- if ( functionToCall ) {
361- // Execute the function
362- const functionResponse = await functionToCall ( functionArgs , req ) ;
363-
364- // Add the tool result to the conversation
365- messages . push ( {
366- role : "tool" ,
367- content : JSON . stringify ( functionResponse ) ,
368- } ) ;
369- }
370-
371325 // Get a new response from the model with all the tool responses
372326 const result = streamText ( {
373327 model : openai ( "gpt-4o-mini" ) ,
@@ -397,10 +351,53 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
397351 - For unrelated questions, politely explain that you're specialized in UTSC academic information
398352 ` ,
399353 messages,
354+ tools : {
355+ getTimetables : tool ( {
356+ description : "Get all the timetables of the currently logged in user." ,
357+ parameters : z . object ( { } ) ,
358+ execute : async ( args ) => {
359+ return await availableFunctions . getTimetables ( args , req ) ;
360+ }
361+ } )
362+ } ,
363+ maxSteps : 3 , // Controls how many back and forths the model can take with user or calling multiple tools
364+ experimental_repairToolCall : async ( {
365+ toolCall,
366+ tools,
367+ parameterSchema,
368+ error,
369+ } ) => {
370+ if ( NoSuchToolError . isInstance ( error ) ) {
371+ return null ; // do not attempt to fix invalid tool names
372+ }
373+
374+ const tool = tools [ toolCall . toolName as keyof typeof tools ] ;
375+ console . log ( `The model tried to call the tool "${ toolCall . toolName } "` +
376+ ` with the following arguments:` ,
377+ JSON . stringify ( toolCall . args ) ,
378+ `The tool accepts the following schema:` ,
379+ JSON . stringify ( parameterSchema ( toolCall ) ) ,
380+ 'Please fix the arguments.' )
381+
382+ const { object : repairedArgs } = await generateObject ( {
383+ model : openai ( 'gpt-4o' , { structuredOutputs : true } ) ,
384+ schema : tool . parameters ,
385+ prompt : [
386+ `The model tried to call the tool "${ toolCall . toolName } "` +
387+ ` with the following arguments:` ,
388+ JSON . stringify ( toolCall . args ) ,
389+ `The tool accepts the following schema:` ,
390+ JSON . stringify ( parameterSchema ( toolCall ) ) ,
391+ 'Please fix the arguments.' ,
392+ ] . join ( '\n' ) ,
393+ } ) ;
394+
395+ return { ...toolCall , args : JSON . stringify ( repairedArgs ) } ;
396+ } ,
400397 } ) ;
401398
402399 result . pipeDataStreamToResponse ( res ) ;
403- }
400+
404401 } else {
405402 // ----- Flow 2 - Answer query -----
406403
0 commit comments