Last active
June 19, 2023 04:22
-
-
Save johnkueh/0340ee25da52c005f41e01735aff2e24 to your computer and use it in GitHub Desktop.
openai-function-calling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ChatOpenAI } from 'langchain/chat_models/openai'; | |
import { HumanChatMessage } from 'langchain/schema'; | |
import { ZodSchema, z } from 'zod'; | |
import zodToJsonSchema from 'zod-to-json-schema'; | |
import { winstonLogger } from '../winston'; | |
const logger = winstonLogger.child({ service: 'AIService' }); | |
const chatOpenAIModel = new ChatOpenAI({ | |
temperature: 0, | |
timeout: 80000, | |
maxConcurrency: 10, | |
modelName: 'gpt-3.5-turbo-16k-0613', | |
openAIApiKey: process.env.OPENAI_API_KEY, | |
}); | |
// Example usage | |
// AIService.callChatOpenAI({ | |
// promptTemplate: AIService.extractRecipeIngredientsFromTextPrompt, | |
// variables: { | |
// contentType, | |
// text, | |
// }, | |
// }); | |
// Example prompt template | |
// export const extractShoppingListItemFromTextPrompt = { | |
// prompt: `For this shopping list item: "{text}"`, | |
// inputSchema: z.object({ | |
// text: z.string(), | |
// }), | |
// responseSchema: z.object({ | |
// isRecipeIngredient: z.boolean().describe('is the item a food ingredient?'), | |
// aisle: z | |
// .string() | |
// .describe('pick one from "Household, Pet, Personal Care, Baby, Uncategorized"'), | |
// quantity: z.number().describe('quantity of the item, 1 if unknown'), | |
// unit: z.string().nullable().describe('unit of the item, null if unknown'), | |
// }), | |
// }; | |
export async function callChatOpenAI<I extends ZodSchema, R extends ZodSchema>({ | |
promptTemplate: { prompt, inputSchema, responseSchema }, | |
variables, | |
}: CallOpenAI<I, R>) { | |
const startTime = performance.now(); | |
const content = `${interpolatePrompt(prompt, inputSchema.parse(variables))}`; | |
const jsonSchema = zodToJsonSchema(responseSchema, 'schema'); | |
try { | |
const functionName = 'print_response'; | |
const response = await chatOpenAIModel.call([new HumanChatMessage(content)], { | |
functions: [{ name: functionName, parameters: jsonSchema.definitions?.schema }], | |
function_call: { name: functionName }, | |
}); | |
const elapsedTime = `${performance.now() - startTime}ms`; | |
const responseJson = response.additional_kwargs?.function_call as { | |
arguments: string; | |
}; | |
const data = JSON.parse(responseJson.arguments); | |
const parsedResponse = responseSchema.parse(data) as z.infer<R>; | |
logger.info('callOpenAI success', { prompt, parsedResponse, elapsedTime }); | |
return data; | |
} catch (e: any) { | |
logger.error('callOpenAI', { prompt, error: e.message }); | |
throw e; | |
} | |
} | |
export function interpolatePrompt(prompt: string, variables: Record<string, string>): string { | |
let interpolatedPrompt = prompt; | |
for (const variable in variables) { | |
const regex = new RegExp(`\\{${variable}\\}`, 'g'); | |
interpolatedPrompt = interpolatedPrompt.replace(regex, variables[variable]); | |
} | |
return interpolatedPrompt; | |
} | |
type PromptTemplate<I extends z.ZodType<any, any>, R extends z.ZodType<any, any>> = { | |
prompt: string; | |
inputSchema: I; | |
responseSchema: R; | |
}; | |
type CallOpenAI<I extends ZodSchema, R extends ZodSchema> = { | |
promptTemplate: PromptTemplate<I, R>; | |
variables: z.infer<I>; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment