├── README.md ├── env.example ├── node-chatgpt-api.js ├── package-lock.json └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # node-chatgpt-api-with-function-calls 2 | 3 | This is a demonstration to show how to integrate function calls with the OpenAI API (ChatGPT) and NodeJS using RapidApi.com, WorldTimeAPI.org and SendGrid. 4 | 5 | 1. Clone this repository `git clone https://github.com/normandmickey/node-chatgpt-api-with-function-calls.git`. 6 | 2. Change to the node-chatgpt-with-functon-calls directory. 7 | 3. Install packages `npm install`. 8 | 4. Copy env.sample to .env and update your OPENAI_API_KEY. 9 | 5. Goto https://rapidapi.com/hub, subscribe to the WeatherAPI.com API (https://rapidapi.com/weatherapi/api/weatherapi-com). 10 | 6. Add your X-RapidAPI-Key to your .env file. 11 | 7. Register and obtain an API key from https://sendgrid.com 12 | 8. Add your SendGrid API key to the .env file and update SENDER_EMAIL. 13 | 9. Run the following command `node node-chatgpt-js` 14 | 15 | The script will return normal responses to your prompts using OpenAI's Chat Completion endpoint but also has access to three API's. 16 | 17 | 1. WeatherAPI.com - If you ask a weather related question about a specific location, ChatGPT will create the function call to lookup the current temperature using the WeatherAPI.com's API. This time the response is generated by the API call and then passed back through ChatGPT for summarization. If you ask "Whats the weather forecast for Las Vegas Nevada?" ChatGPT should return something like "The current temp is 71.1." 18 | 19 | 2. WorldTimeAPI.org - When you ask "What time is it in Rio De Janeiro?", you should get the current time in Rio. (from worldtimeapi.org). 20 | 21 | 3. SendGrid - You can also ask ChatGPT to send an email, for example "Send an email to me@mydomain.com asking when will the report on global climate change be ready?" 22 | 23 | As you can see in the sample code the OpenAI API is used to generate API calls if necessary. Howerver, it's still up to you to run them, parse the responses and either return the response directly to the use or use it's data in another function. For example, instead of immediately returning data from the API to the user, you can also append the API response to a second call to ChatGPT giving it context to answer the original question. 24 | -------------------------------------------------------------------------------- /env.example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY="" 2 | X_RAPIDAPI_KEY="" 3 | SENDGRID_API_KEY="" 4 | SENDER_EMAIL="" -------------------------------------------------------------------------------- /node-chatgpt-api.js: -------------------------------------------------------------------------------- 1 | const { Configuration, OpenAIApi } = require("openai"); 2 | const readlineSync = require("readline-sync"); 3 | require("dotenv").config(); 4 | const axios = require("axios"); // For making HTTP requests. 5 | const moment = require("moment-timezone"); 6 | 7 | 8 | (async () => { 9 | const configuration = new Configuration({ 10 | apiKey: process.env.OPENAI_API_KEY, 11 | }); 12 | const openai = new OpenAIApi(configuration); 13 | 14 | 15 | // Define a function called lookupTime that takes a location as a parameter and returns the current time in that location. 16 | // The function makes a GET request to the World Time API, extracts the current time from the response, and formats it. 17 | async function lookupTime(location) { 18 | try { 19 | const response = await axios.get(`http://worldtimeapi.org/api/timezone/${location}`); // Make a GET request to the World Time API with the location parameter as the timezone. 20 | const { datetime } = response.data; // Extract the datetime property from the data in the response. 21 | const dateTime = moment.tz(datetime, location).format("h:mmA"); // Use moment-timezone to create the Date object in the specified timezone. 22 | const timeResponse = `The current time in ${location} is ${dateTime}.`; // Log the formatted time to the console. 23 | return timeResponse; 24 | } catch (error) { 25 | console.error(error); // Log any errors that occur to the console. 26 | } 27 | } 28 | 29 | // Define a function called sendEmail 30 | async function sendEmail(to, from, subject, text) { 31 | const sgMail = require('@sendgrid/mail') 32 | sgMail.setApiKey(process.env.SENDGRID_API_KEY) 33 | const sendGMsg = "" 34 | const msg = { 35 | to: to, // Change to your recipient 36 | from: from, // Change to your verified sender 37 | subject: subject, 38 | text: text //body of the email 39 | } 40 | sgMail 41 | .send(msg) 42 | .then(() => { 43 | // console.log('Email sent') 44 | const sendGMsg = "Email Sent"; 45 | }) 46 | .catch((error) => { 47 | //console.error(error) 48 | const sendGMsg = "message not sent"; 49 | }) 50 | return sendGMsg; 51 | } 52 | 53 | // Define a function called lookupWeather that takes a location as a parameter and returns the weather forecatst in that location. 54 | // The function makes a GET request to the WeatherAPI API, extracts the current temperature from the response, and formats it. 55 | async function lookupWeather(location) { 56 | 57 | const options = { 58 | method: 'GET', 59 | url: 'https://weatherapi-com.p.rapidapi.com/forecast.json', 60 | params: { 61 | q: location, 62 | days: '3' 63 | }, 64 | headers: { 65 | 'X-RapidAPI-Key': process.env.X_RAPIDAPI_KEY, 66 | ' X-RapidAPI-Host': 'weatherapi-com.p.rapidapi.com' 67 | } 68 | }; 69 | 70 | try { 71 | const response = await axios.request(options); 72 | //console.log(response.data); 73 | let weather = response.data; 74 | //const currentTemp = weather.current.temp_f; 75 | //console.log(currentTemp); 76 | const weatherForecast = `Location: ${weather.location.name} \ 77 | Current Temperature: ${weather.current.temp_f} \ 78 | Condition: ${weather.current.condition.text}. \ 79 | Low Today: ${weather.forecast.forecastday[0].day.mintemp_f} \ 80 | High Today: ${weather.forecast.forecastday[0].day.maxtemp_f}`; 81 | return weatherForecast; 82 | } catch (error) { 83 | console.error(error); 84 | return "No forecast found"; 85 | } 86 | } 87 | 88 | const history = []; 89 | 90 | while (true) { 91 | const user_input = readlineSync.question("Your input: "); 92 | const senderEmail = process.env.SENDER_EMAIL; 93 | 94 | const messages = []; 95 | for (const [input_text, completion_text] of history) { 96 | messages.push({ role: "user", content: input_text }); 97 | messages.push({ role: "assistant", content: completion_text }); 98 | } 99 | 100 | messages.push({ role: "user", content: user_input }); 101 | 102 | try { 103 | const completion = await openai.createChatCompletion({ 104 | model: "gpt-3.5-turbo-0613", 105 | messages: messages, 106 | functions: [ 107 | { 108 | name: "sendEmail", 109 | description: "send an email to specified address", 110 | parameters: { 111 | type: "object", // specify that the parameter is an object 112 | properties: { 113 | to: { 114 | type: "string", // specify the parameter type as a string 115 | description: "The recipients email address." 116 | }, 117 | from: { 118 | type: "string", // specify the parameter type as a string 119 | description: `The senders email address, default to ${senderEmail}` 120 | }, 121 | subject: { 122 | type: "string", // specify the parameter type as a string 123 | description: "The subject of the email." 124 | }, 125 | text: { 126 | type: "string", // specify the parameter type as a string 127 | description: "The text or body of the email message." 128 | } 129 | }, 130 | required: ["to","from","subject","text"] // specify that the location parameter is required 131 | } 132 | }, 133 | { 134 | name: "lookupTime", 135 | description: "get the current time in a given location", 136 | parameters: { 137 | type: "object", // specify that the parameter is an object 138 | properties: { 139 | location: { 140 | type: "string", // specify the parameter type as a string 141 | description: "The location, e.g. Beijing, China. But it should be written in a timezone name like Asia/Shanghai" 142 | } 143 | }, 144 | required: ["location"] // specify that the location parameter is required 145 | } 146 | }, 147 | { 148 | name: "lookupWeather", 149 | description: "get the weather forecast in a given location", 150 | parameters: { 151 | type: "object", // specify that the parameter is an object 152 | properties: { 153 | location: { 154 | type: "string", // specify the parameter type as a string 155 | description: "The location, e.g. Beijing, China. But it should be written in a city, state, country" 156 | } 157 | }, 158 | required: ["location"] // specify that the location parameter is required 159 | } 160 | } 161 | ], 162 | function_call: "auto" 163 | }); 164 | 165 | // Extract the generated completion from the OpenAI API response. 166 | const completionResponse = completion.data.choices[0].message; 167 | //console.log(completionResponse); 168 | 169 | // if the response from ChatGPT does not have content, then it returned the JSON for one of the function calls. 170 | // We then need to figure out which function it created then run it to get the appropriate API response. 171 | if(!completionResponse.content) { 172 | const functionCallName = completionResponse.function_call.name; 173 | //console.log("functionCallName: ", functionCallName); 174 | 175 | if(functionCallName === "lookupTime") { 176 | const completionArguments = JSON.parse(completionResponse.function_call.arguments); 177 | //console.log("completionArguments: ", completionArguments); 178 | 179 | const completion_text = await lookupTime(completionArguments.location); 180 | history.push([user_input, completion_text]); 181 | console.log(completion_text); 182 | } else if(functionCallName === "sendEmail") { 183 | const completionArguments = JSON.parse(completionResponse.function_call.arguments); 184 | // console.log("completionArguments: ", completionArguments); 185 | 186 | const completion_text = await sendEmail(completionArguments.to, completionArguments.from, completionArguments.subject, completionArguments.text); 187 | history.push([user_input, completion_text]); 188 | console.log("Email sent: " + completionArguments.to + "\n" + "Subject: " + completionArguments.subject + "\n" + "Body: " + completionArguments.text); 189 | 190 | } else if(functionCallName === "lookupWeather") { 191 | const completionArguments = JSON.parse(completionResponse.function_call.arguments); 192 | //console.log("completionArguments: ", completionArguments); 193 | 194 | const completion_text = await lookupWeather(completionArguments.location); 195 | history.push([user_input, completion_text]); 196 | messages.push({ role: "user", content: "Summarize the following input." + completion_text }); 197 | try { 198 | const completion = await openai.createChatCompletion({ 199 | model: "gpt-3.5-turbo-0613", 200 | messages: messages 201 | }); 202 | 203 | // Extract the generated completion from the OpenAI API response. 204 | const completionResponse = completion.data.choices[0].message.content; 205 | //console.log(messages); 206 | console.log(completionResponse); 207 | } catch (error) { 208 | if (error.response) { 209 | console.log(error.response.status); 210 | console.log(error.response.data); 211 | } else { 212 | console.log(error.message); 213 | }n 214 | } 215 | } 216 | } else { 217 | const completion_text = completion.data.choices[0].message.content; 218 | history.push([user_input, completion_text]); 219 | console.log(completion_text); 220 | } 221 | 222 | const user_input_again = readlineSync.question( 223 | "\nWould you like to continue the conversation? (Y/N)" 224 | ); 225 | if (user_input_again.toUpperCase() === "N") { 226 | return; 227 | } else if (user_input_again.toUpperCase() !== "Y") { 228 | console.log("Invalid input. Please enter 'Y' or 'N'."); 229 | return; 230 | } 231 | } catch (error) { 232 | if (error.response) { 233 | console.log(error.response.status); 234 | console.log(error.response.data); 235 | } else { 236 | console.log(error.message); 237 | console.log(messages); 238 | } 239 | } 240 | } 241 | })(); 242 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-api", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "chatgpt-api", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@sendgrid/mail": "^7.7.0", 13 | "axios": "^1.4.0", 14 | "dotenv": "^16.3.1", 15 | "moment-timezone": "^0.5.43", 16 | "openai": "^3.3.0", 17 | "readline-sync": "^1.4.10" 18 | } 19 | }, 20 | "node_modules/@sendgrid/client": { 21 | "version": "7.7.0", 22 | "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.7.0.tgz", 23 | "integrity": "sha512-SxH+y8jeAQSnDavrTD0uGDXYIIkFylCo+eDofVmZLQ0f862nnqbC3Vd1ej6b7Le7lboyzQF6F7Fodv02rYspuA==", 24 | "dependencies": { 25 | "@sendgrid/helpers": "^7.7.0", 26 | "axios": "^0.26.0" 27 | }, 28 | "engines": { 29 | "node": "6.* || 8.* || >=10.*" 30 | } 31 | }, 32 | "node_modules/@sendgrid/client/node_modules/axios": { 33 | "version": "0.26.1", 34 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 35 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 36 | "dependencies": { 37 | "follow-redirects": "^1.14.8" 38 | } 39 | }, 40 | "node_modules/@sendgrid/helpers": { 41 | "version": "7.7.0", 42 | "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.7.0.tgz", 43 | "integrity": "sha512-3AsAxfN3GDBcXoZ/y1mzAAbKzTtUZ5+ZrHOmWQ279AuaFXUNCh9bPnRpN504bgveTqoW+11IzPg3I0WVgDINpw==", 44 | "dependencies": { 45 | "deepmerge": "^4.2.2" 46 | }, 47 | "engines": { 48 | "node": ">= 6.0.0" 49 | } 50 | }, 51 | "node_modules/@sendgrid/mail": { 52 | "version": "7.7.0", 53 | "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.7.0.tgz", 54 | "integrity": "sha512-5+nApPE9wINBvHSUxwOxkkQqM/IAAaBYoP9hw7WwgDNQPxraruVqHizeTitVtKGiqWCKm2mnjh4XGN3fvFLqaw==", 55 | "dependencies": { 56 | "@sendgrid/client": "^7.7.0", 57 | "@sendgrid/helpers": "^7.7.0" 58 | }, 59 | "engines": { 60 | "node": "6.* || 8.* || >=10.*" 61 | } 62 | }, 63 | "node_modules/asynckit": { 64 | "version": "0.4.0", 65 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 66 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 67 | }, 68 | "node_modules/axios": { 69 | "version": "1.4.0", 70 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", 71 | "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", 72 | "dependencies": { 73 | "follow-redirects": "^1.15.0", 74 | "form-data": "^4.0.0", 75 | "proxy-from-env": "^1.1.0" 76 | } 77 | }, 78 | "node_modules/combined-stream": { 79 | "version": "1.0.8", 80 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 81 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 82 | "dependencies": { 83 | "delayed-stream": "~1.0.0" 84 | }, 85 | "engines": { 86 | "node": ">= 0.8" 87 | } 88 | }, 89 | "node_modules/deepmerge": { 90 | "version": "4.3.1", 91 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 92 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 93 | "engines": { 94 | "node": ">=0.10.0" 95 | } 96 | }, 97 | "node_modules/delayed-stream": { 98 | "version": "1.0.0", 99 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 100 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 101 | "engines": { 102 | "node": ">=0.4.0" 103 | } 104 | }, 105 | "node_modules/dotenv": { 106 | "version": "16.3.1", 107 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", 108 | "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", 109 | "engines": { 110 | "node": ">=12" 111 | }, 112 | "funding": { 113 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 114 | } 115 | }, 116 | "node_modules/follow-redirects": { 117 | "version": "1.15.2", 118 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 119 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 120 | "funding": [ 121 | { 122 | "type": "individual", 123 | "url": "https://github.com/sponsors/RubenVerborgh" 124 | } 125 | ], 126 | "engines": { 127 | "node": ">=4.0" 128 | }, 129 | "peerDependenciesMeta": { 130 | "debug": { 131 | "optional": true 132 | } 133 | } 134 | }, 135 | "node_modules/form-data": { 136 | "version": "4.0.0", 137 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 138 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 139 | "dependencies": { 140 | "asynckit": "^0.4.0", 141 | "combined-stream": "^1.0.8", 142 | "mime-types": "^2.1.12" 143 | }, 144 | "engines": { 145 | "node": ">= 6" 146 | } 147 | }, 148 | "node_modules/mime-db": { 149 | "version": "1.52.0", 150 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 151 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 152 | "engines": { 153 | "node": ">= 0.6" 154 | } 155 | }, 156 | "node_modules/mime-types": { 157 | "version": "2.1.35", 158 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 159 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 160 | "dependencies": { 161 | "mime-db": "1.52.0" 162 | }, 163 | "engines": { 164 | "node": ">= 0.6" 165 | } 166 | }, 167 | "node_modules/moment": { 168 | "version": "2.29.4", 169 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", 170 | "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", 171 | "engines": { 172 | "node": "*" 173 | } 174 | }, 175 | "node_modules/moment-timezone": { 176 | "version": "0.5.43", 177 | "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", 178 | "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", 179 | "dependencies": { 180 | "moment": "^2.29.4" 181 | }, 182 | "engines": { 183 | "node": "*" 184 | } 185 | }, 186 | "node_modules/openai": { 187 | "version": "3.3.0", 188 | "resolved": "https://registry.npmjs.org/openai/-/openai-3.3.0.tgz", 189 | "integrity": "sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==", 190 | "dependencies": { 191 | "axios": "^0.26.0", 192 | "form-data": "^4.0.0" 193 | } 194 | }, 195 | "node_modules/openai/node_modules/axios": { 196 | "version": "0.26.1", 197 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 198 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 199 | "dependencies": { 200 | "follow-redirects": "^1.14.8" 201 | } 202 | }, 203 | "node_modules/proxy-from-env": { 204 | "version": "1.1.0", 205 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 206 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 207 | }, 208 | "node_modules/readline-sync": { 209 | "version": "1.4.10", 210 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz", 211 | "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==", 212 | "engines": { 213 | "node": ">= 0.8.0" 214 | } 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatgpt-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@sendgrid/mail": "^7.7.0", 14 | "axios": "^1.4.0", 15 | "dotenv": "^16.3.1", 16 | "moment-timezone": "^0.5.43", 17 | "openai": "^3.3.0", 18 | "readline-sync": "^1.4.10" 19 | } 20 | } 21 | --------------------------------------------------------------------------------