├── dist ├── types.js ├── server │ ├── routes │ │ └── api.js │ ├── controllers │ │ ├── websiteController.js │ │ ├── slackController.js │ │ └── chatGPTController.js │ ├── cache.js │ └── response-helper.js ├── server.js └── messages.js ├── .DS_Store ├── .gitignore ├── README.md ├── tsconfig.json ├── .vscode └── settings.json ├── src ├── types.ts ├── server │ ├── controllers │ │ ├── websiteController.js │ │ ├── slackController.js │ │ └── chatGPTController.js │ ├── routes │ │ └── api.ts │ ├── cache.js │ └── response-helper.ts ├── server.ts └── messages.ts ├── package.json ├── testing.txt └── resume /dist/types.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GP3-RS/Work-history-chat-auto-responder/main/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules/ 3 | package-lock.json 4 | testing 5 | dump.rdb 6 | nodemon.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Work-history-chat-auto-responder 2 | 3 | Levereges ChatGPT to auto-respond to questions from Slack or other sources about my resume or work history 4 | 5 | 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "esModuleInterop": true, 5 | "allowJs": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Node", 8 | "allowSyntheticDefaultImports": true, 9 | "skipLibCheck": true, 10 | "target": "ESNext" 11 | }, 12 | "include": ["./src/**/*"] 13 | } 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "rvest.vs-code-prettier-eslint", 3 | "editor.formatOnPaste": false, // required 4 | "editor.formatOnType": false, // required 5 | "editor.formatOnSave": true, // optional 6 | "editor.formatOnSaveMode": "file", // required to format on save 7 | "files.autoSave": "onFocusChange", // optional but recommended 8 | "vs-code-prettier-eslint.prettierLast": false // set as "true" to run 'prettier' last not first 9 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { CreateChatCompletionResponseChoicesInner } from "openai" 2 | 3 | export interface ErrObj { 4 | log: String, 5 | status: Number, 6 | message: ErrMessage 7 | } 8 | 9 | export interface ErrMessage { 10 | err: String 11 | } 12 | 13 | export interface responseData { 14 | data?: data 15 | } 16 | 17 | interface data { 18 | error?: string, 19 | choices: CreateChatCompletionResponseChoicesInner[], 20 | } 21 | 22 | export interface resLocals { 23 | question: string, 24 | platform: string, 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/server/controllers/websiteController.js: -------------------------------------------------------------------------------- 1 | const websiteController = {}; 2 | 3 | websiteController.validateQuestion = (req, res, next) => { 4 | console.log("hitting websiteController.validateQuestion"); 5 | const { question } = req.body; 6 | 7 | if (question === undefined || typeof question !== "string") { 8 | return next({ 9 | log: "User question is invalid", 10 | status: 400, 11 | message: { 12 | err: "Error occured with question validation. It's possible that the question's length is 0, or undefined", 13 | }, 14 | }); 15 | } else { 16 | res.locals.platform = "website"; 17 | res.locals.question = question; 18 | return next(); 19 | } 20 | }; 21 | 22 | export default websiteController; 23 | -------------------------------------------------------------------------------- /src/server/controllers/slackController.js: -------------------------------------------------------------------------------- 1 | const slackController = {}; 2 | 3 | slackController.urlVerification = (req, res, next) => { 4 | console.log("hitting slackController.urlVerification"); 5 | 6 | if (req.body.challenge && req.body.type === "url_verification") { 7 | return res.status(200).json(req.body.challenge); 8 | } else return next(); 9 | }; 10 | 11 | slackController.filterBotMessages = (req, res, next) => { 12 | console.log("hitting slackController.filterBotMessages"); 13 | if (req.body.event.client_msg_id && req.body.event.bot_id === undefined) { 14 | res.locals.platform = "slack"; 15 | res.locals.question = req.body.event.text; 16 | return next(); 17 | } else return res.status(200).json("OK"); 18 | }; 19 | 20 | export default slackController; 21 | -------------------------------------------------------------------------------- /dist/server/routes/api.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import slackController from "../controllers/slackController.js"; 3 | import chatGPTController from "../controllers/chatGPTController.js"; 4 | import websiteController from "../controllers/websiteController.js"; 5 | const router = express.Router(); 6 | router.get('/ping', (req, res) => { 7 | return res.status(200).json("Ready"); 8 | }); 9 | router.post("/slack", slackController.urlVerification, slackController.filterBotMessages, chatGPTController.generateResponse, (req, res) => { 10 | return res.status(200).json("OK"); 11 | }); 12 | router.post("/website", websiteController.validateQuestion, chatGPTController.generateResponse, (req, res) => { 13 | res.status(200).json(res.locals.payload); 14 | }); 15 | export default router; 16 | -------------------------------------------------------------------------------- /dist/server/controllers/websiteController.js: -------------------------------------------------------------------------------- 1 | const websiteController = {}; 2 | websiteController.validateQuestion = (req, res, next) => { 3 | console.log("hitting websiteController.validateQuestion"); 4 | const { question } = req.body; 5 | if (question === undefined || typeof question !== "string") { 6 | return next({ 7 | log: "User question is invalid", 8 | status: 400, 9 | message: { 10 | err: "Error occured with question validation. It's possible that the question's length is 0, or undefined", 11 | }, 12 | }); 13 | } 14 | else { 15 | res.locals.platform = "website"; 16 | res.locals.question = question; 17 | return next(); 18 | } 19 | }; 20 | export default websiteController; 21 | -------------------------------------------------------------------------------- /dist/server/controllers/slackController.js: -------------------------------------------------------------------------------- 1 | const slackController = {}; 2 | slackController.urlVerification = (req, res, next) => { 3 | console.log("hitting slackController.urlVerification"); 4 | if (req.body.challenge && req.body.type === "url_verification") { 5 | return res.status(200).json(req.body.challenge); 6 | } 7 | else 8 | return next(); 9 | }; 10 | slackController.filterBotMessages = (req, res, next) => { 11 | console.log("hitting slackController.filterBotMessages"); 12 | if (req.body.event.client_msg_id && req.body.event.bot_id === undefined) { 13 | res.locals.platform = "slack"; 14 | res.locals.question = req.body.event.text; 15 | return next(); 16 | } 17 | else 18 | return res.status(200).json("OK"); 19 | }; 20 | export default slackController; 21 | -------------------------------------------------------------------------------- /src/server/routes/api.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | 3 | import slackController from "../controllers/slackController.js"; 4 | import chatGPTController from "../controllers/chatGPTController.js"; 5 | import websiteController from "../controllers/websiteController.js"; 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/ping', (req, res) => { 10 | return res.status(200).json("Ready"); 11 | }) 12 | 13 | router.post( 14 | "/slack", 15 | slackController.urlVerification, 16 | slackController.filterBotMessages, 17 | chatGPTController.generateResponse, 18 | (req, res) => { 19 | return res.status(200).json("OK"); 20 | } 21 | ); 22 | 23 | router.post("/website", websiteController.validateQuestion, chatGPTController.generateResponse, (req, res) => { 24 | res.status(200).json(res.locals.payload); 25 | }); 26 | 27 | export default router; 28 | -------------------------------------------------------------------------------- /src/server/controllers/chatGPTController.js: -------------------------------------------------------------------------------- 1 | import responseHelper from "../response-helper.js"; 2 | 3 | const chatGPTController = {}; 4 | 5 | chatGPTController.generateResponse = async (req, res, next) => { 6 | console.log("hitting chatGPTController.generateResponse"); 7 | 8 | if (res.locals.question.length === 0) return next(); 9 | else if (res.locals.platform === "slack") { 10 | try { 11 | responseHelper.generateAndPost(res.locals); 12 | } catch (err) { 13 | console.log("Generate response failed"); 14 | } 15 | return next(); 16 | } else if (res.locals.platform === "website") { 17 | try { 18 | res.locals.payload = await responseHelper.generateAndPost(res.locals); 19 | } catch (err) { 20 | console.log("Generate response failed"); 21 | } 22 | return next(); 23 | } 24 | }; 25 | 26 | export default chatGPTController; 27 | -------------------------------------------------------------------------------- /dist/server/controllers/chatGPTController.js: -------------------------------------------------------------------------------- 1 | import responseHelper from "../response-helper.js"; 2 | const chatGPTController = {}; 3 | chatGPTController.generateResponse = async (req, res, next) => { 4 | console.log("hitting chatGPTController.generateResponse"); 5 | if (res.locals.question.length === 0) 6 | return next(); 7 | else if (res.locals.platform === "slack") { 8 | try { 9 | responseHelper.generateAndPost(res.locals); 10 | } 11 | catch (err) { 12 | console.log("Generate response failed"); 13 | } 14 | return next(); 15 | } 16 | else if (res.locals.platform === "website") { 17 | try { 18 | res.locals.payload = await responseHelper.generateAndPost(res.locals); 19 | } 20 | catch (err) { 21 | console.log("Generate response failed"); 22 | } 23 | return next(); 24 | } 25 | }; 26 | export default chatGPTController; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "work-history-chat-auto-responder", 3 | "version": "1.0.0", 4 | "description": "Levereges ChatGPT to auto-respond to questions from Slack or other sources about my resume or work history", 5 | "main": "dist/server.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "test", 9 | "dev": "redis-server & nodemon", 10 | "build": "tsc", 11 | "start": "node dist/server.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/GP3-RS/Work-history-chat-auto-responder.git" 16 | }, 17 | "author": "Gahl Peled", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/GP3-RS/Work-history-chat-auto-responder/issues" 21 | }, 22 | "homepage": "https://github.com/GP3-RS/Work-history-chat-auto-responder#readme", 23 | "dependencies": { 24 | "cors": "^2.8.5", 25 | "dotenv": "^16.0.3", 26 | "express": "^4.18.2", 27 | "node-fetch": "^3.3.1", 28 | "openai": "^3.2.1" 29 | }, 30 | "devDependencies": { 31 | "@types/node": "^20.2.5", 32 | "eslint": "^8.41.0", 33 | "nodemon": "^2.0.22", 34 | "prettier": "^2.8.8", 35 | "redis": "^4.6.6", 36 | "ts-node": "^10.9.1", 37 | "typescript": "^5.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /testing.txt: -------------------------------------------------------------------------------- 1 | 2 | MESSAGE EXAMPLE: 3 | 4 | { 5 | "token":"woowNCjzuA4vX6W5S9WCVest", 6 | "team_id":"T058FHS0DAP", 7 | "context_team_id":"T058FHS0DAP", 8 | "context_enterprise_id":null, 9 | "api_app_id":"A058168C963", 10 | "event": 11 | { 12 | "client_msg_id":"92c47d05-bd2a-4743-a237-0522bb4dbef8", 13 | "type":"message", 14 | "text":"Tell me about your work history", 15 | "user":"U05893LFX5L", 16 | "ts":"1684458957.810269", 17 | "blocks":[[null]], 18 | "team":"T058FHS0DAP", 19 | "channel":"C058FHZJG4T", 20 | "event_ts":"1684458957.810269", 21 | "channel_type":"channel" 22 | }, 23 | "type":"event_callback", 24 | "event_id":"Ev058B594ELE", 25 | "event_time":1684458957, 26 | "authorizations": 27 | [ 28 | { 29 | "enterprise_id":null, 30 | "team_id":"T058FHS0DAP", 31 | "user_id":"U05893LFX5L", 32 | "is_bot":false, 33 | "is_enterprise_install":false 34 | } 35 | ], 36 | "is_ext_shared_channel":false, 37 | "event_context":"4-eyJldCI6Im1lc3NhZ2UiLCJ0aWQiOiJUMDU4RkhTMERBUCIsImFpZCI6IkEwNTgxNjhDOTYzIiwiY2lkIjoiQzA1OEZIWkpHNFQifQ" 38 | } 39 | 40 | 41 | NEW USER EXAMPLE: 42 | 43 | -------------------------------------------------------------------------------- /dist/server.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import apiRouter from "./server/routes/api.js"; 3 | import cors from "cors"; 4 | const app = express(); 5 | const PORT = Number(process.env.PORT) || 3000; 6 | app.use(cors({ 7 | origin: [ 8 | // When testing locally (and using Vite), uncomment the line below and redeploy 9 | // 'http://127.0.0.1:5173', 10 | 'https://gahls-resume-bot.onrender.com', 11 | 'http://api.slack.com', 12 | 'https://api.slack.com', 13 | 'http://slack.com', 14 | 'https://slack.com', 15 | ], 16 | credentials: true 17 | })); 18 | app.use(express.json()); 19 | app.use(express.urlencoded({ 20 | extended: true 21 | })); 22 | //All requests sent to api router 23 | app.use("/api", apiRouter); 24 | //404 catch-all route handler 25 | app.use((req, res) => res.sendStatus(404)); 26 | //Global error handler 27 | app.use((err, req, res, next) => { 28 | const defaultErr = { 29 | log: "Express error handler caught unknown middleware error", 30 | status: 400, 31 | message: { err: "An error occurred" }, 32 | }; 33 | const errorObj = Object.assign(defaultErr, err); 34 | console.log(errorObj.log); 35 | return res.status(errorObj.status).json(errorObj.message); 36 | }); 37 | app.listen(PORT, () => { 38 | console.log(`Server listening on port: ${PORT}`); 39 | }); 40 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import apiRouter from "./server/routes/api.js"; 3 | import { ErrObj } from "./types.js"; 4 | import cors from "cors"; 5 | 6 | const app = express(); 7 | const PORT: number = Number(process.env.PORT) || 3000; 8 | 9 | app.use(cors({ 10 | origin: [ 11 | // When testing locally (and using Vite), uncomment the line below and redeploy 12 | // 'http://127.0.0.1:5173', 13 | 'https://gahls-resume-bot.onrender.com', 14 | 'http://api.slack.com', 15 | 'https://api.slack.com', 16 | 'http://slack.com', 17 | 'https://slack.com', 18 | ], 19 | credentials: true })) 20 | 21 | app.use(express.json()); 22 | app.use(express.urlencoded({ 23 | extended: true 24 | })); 25 | 26 | //All requests sent to api router 27 | app.use("/api", apiRouter); 28 | 29 | //404 catch-all route handler 30 | app.use((req, res) => res.sendStatus(404)); 31 | 32 | //Global error handler 33 | app.use((err: ErrObj, req, res, next) => { 34 | const defaultErr: ErrObj = { 35 | log: "Express error handler caught unknown middleware error", 36 | status: 400, 37 | message: { err: "An error occurred" }, 38 | }; 39 | const errorObj = Object.assign(defaultErr, err); 40 | console.log(errorObj.log); 41 | 42 | return res.status(errorObj.status).json(errorObj.message); 43 | }); 44 | 45 | app.listen(PORT, () => { 46 | console.log(`Server listening on port: ${PORT}`); 47 | }); 48 | -------------------------------------------------------------------------------- /src/server/cache.js: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | 4 | import redis from "redis"; 5 | 6 | // if hosting on Cyclic and using their integrated DynamoDB storage, uncomment the lines below and grab your DynamoDB Table Name from Cyclic's Data/Storage tab and either (1) input it as the argument to the CyclicDB function or (2) input it in your .env with the key DYNAMODBTABLENAME 7 | // import CyclicDb from "@cyclic.sh/dynamodb"; 8 | // const db: string = CyclicDb(process.env.DYNAMODBTABLENAME); 9 | 10 | let cache; 11 | 12 | if (process.env.CACHE === "Redis") { 13 | (async () => { 14 | if (process.env.ENV === "Render" && process.env.REDIS_URL) { 15 | cache = redis.createClient({ 16 | url: process.env.REDIS_URL, 17 | }); 18 | } else cache = redis.createClient(); 19 | 20 | cache.on("error", (error) => console.error(`Redis client error: ${error}`)); 21 | 22 | await cache 23 | .connect() 24 | .then(() => console.log(`Connected to ${process.env.ENV} Redis cache`)); 25 | 26 | // UNCOMMENT BELOW TO FLUSH REDIS CACHE. If cached responses are inaccurate, for example, you may want to flush cache. 27 | // cache 28 | // .flushDb() 29 | // .then(() => console.log("Cache flushed")) 30 | // .catch((err) => console.log("Error in cache flush command: ", err)); 31 | })(); 32 | } else if (process.env.CACHE === "DynamoDB") { 33 | cache = db.collection("cache"); 34 | console.log("Connected to DynamoDB"); 35 | } 36 | 37 | export default cache; 38 | -------------------------------------------------------------------------------- /dist/server/cache.js: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | import redis from "redis"; 4 | // if hosting on Cyclic and using their integrated DynamoDB storage, uncomment the lines below and grab your DynamoDB Table Name from Cyclic's Data/Storage tab and either (1) input it as the argument to the CyclicDB function or (2) input it in your .env with the key DYNAMODBTABLENAME 5 | // import CyclicDb from "@cyclic.sh/dynamodb"; 6 | // const db: string = CyclicDb(process.env.DYNAMODBTABLENAME); 7 | let cache; 8 | if (process.env.CACHE === "Redis") { 9 | (async () => { 10 | if (process.env.ENV === "Render" && process.env.REDIS_URL) { 11 | cache = redis.createClient({ 12 | url: process.env.REDIS_URL, 13 | }); 14 | } 15 | else 16 | cache = redis.createClient(); 17 | cache.on("error", (error) => console.error(`Redis client error: ${error}`)); 18 | await cache 19 | .connect() 20 | .then(() => console.log(`Connected to ${process.env.ENV} Redis cache`)); 21 | // UNCOMMENT BELOW TO FLUSH REDIS CACHE. If cached responses are inaccurate, for example, you may want to flush cache. 22 | // cache 23 | // .flushDb() 24 | // .then(() => console.log("Cache flushed")) 25 | // .catch((err) => console.log("Error in cache flush command: ", err)); 26 | })(); 27 | } 28 | else if (process.env.CACHE === "DynamoDB") { 29 | cache = db.collection("cache"); 30 | console.log("Connected to DynamoDB"); 31 | } 32 | export default cache; 33 | -------------------------------------------------------------------------------- /src/server/response-helper.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | import fs from "fs"; 4 | import path from "path"; 5 | import fetch from "node-fetch"; 6 | 7 | import { Configuration, OpenAIApi } from "openai"; 8 | import { fileURLToPath } from "url"; 9 | 10 | import cache from "./cache.js"; 11 | 12 | //Import our series of messages to help train chatGPT before each response so that it answers correctly 13 | import { messages } from "../messages.js"; 14 | console.log("messages import: ", messages ? "PASS" : "FAIL"); 15 | 16 | const __filename = fileURLToPath(import.meta.url); 17 | const __dirname = path.dirname(__filename); 18 | 19 | //Import resume file for ChatGPT to know my work history 20 | const resume = fs.readFileSync(path.resolve(__dirname + "../../../resume"), { 21 | encoding: "utf8", 22 | flag: "r", 23 | }); 24 | console.log("resume import: ", resume ? "PASS" : "FAIL"); 25 | 26 | //Initialize and configure OpenAI API 27 | const openai = new OpenAIApi( 28 | new Configuration({ 29 | apiKey: process.env.OPENAI_TOKEN, 30 | }) 31 | ); 32 | console.log("openai configuration: ", openai ? "PASS" : "FAIL"); 33 | import { responseData, resLocals } from "../types.js"; 34 | 35 | const responseHelper = { 36 | 37 | generateAndPost: (data: resLocals): Promise => { 38 | console.log("hitting responseHelper.generateAndPost"); 39 | 40 | return new Promise((resolve, reject): void => { 41 | (async (): Promise => { 42 | try { 43 | let responseMessage: string 44 | let responseObj: responseData | null | undefined 45 | let cacheResults: string | null | undefined; 46 | 47 | try { 48 | if (process.env.CACHE === 'Redis') cacheResults = await cache.get(data.question); 49 | else if (process.env.CACHE === 'DynamoDB') cacheResults = (await cache.get(data.question)).props?.value; 50 | } catch (err) { 51 | console.log("Error with cache.get(data.question): ", err); 52 | reject(err); 53 | } 54 | 55 | if (!cacheResults) { 56 | console.log(process.env.CACHE + " cache miss"); 57 | 58 | try { 59 | responseObj = await openai.createChatCompletion({ 60 | model: "gpt-3.5-turbo", 61 | messages: [ 62 | { role: "system", content: "You are an assistant that is answering questions about the resume and work history of Gahl Peled. You will respond to the user in the first person, as if you are Gahl. If you do not know the answer to something based on his work history or resume, prompt the user to email Gahl directly at gahlpeled@gmail.com. Do not lie and makes something up. Try to keep the conversation professional but lighthearted, so if the user asks an unprofessional or completely off topic question (besides what Gahls interests are or what he does in his free time), try to veer the conversation back to something work related. I am going to give you information about his work history and copy in his resume" }, 63 | { 64 | role: "user", 65 | content: 66 | "Do not lie and make things up. If you don't have 100% confidence in the truthfulness of your response, ask the user to email Gahl.", 67 | }, 68 | { 69 | role: "user", 70 | content: 71 | "here is Gahl's resume, answer questions as if you are him, using his resume: " + 72 | resume, 73 | }, 74 | ...messages, 75 | { role: "user", content: data.question }, 76 | ], 77 | temperature: 0.1, 78 | max_tokens: 396, 79 | top_p: 0.2, 80 | }); 81 | } catch (err) { 82 | console.log( 83 | "Error with openai.createChatCompletion: ", 84 | err.response.data 85 | ); 86 | if (data.platform === "slack") { 87 | responseHelper.postToSlack(err.response.data.error.message); 88 | resolve(); 89 | } else if (data.platform === "website") { 90 | resolve(err.response.data.error.message); 91 | } 92 | return; 93 | } 94 | 95 | if (!responseObj) { 96 | console.log("No response from openai.generate response invocation"); 97 | reject(new Error("no response")); 98 | return; 99 | } else if (responseObj?.data?.error) { 100 | console.log( 101 | "ERROR with generating a response: ", 102 | responseObj.data.error 103 | ); 104 | reject(new Error(responseObj.data.error)); 105 | return; 106 | } else { 107 | responseMessage = responseObj.data.choices[0].message.content 108 | .trim() 109 | .replace(/(\r\n|\n|\r)/gm, ""); 110 | 111 | try { 112 | await cache.set( 113 | data.question, 114 | process.env.CACHE === "Redis" 115 | ? responseMessage 116 | : { value: responseMessage } 117 | ); 118 | } catch (err) { 119 | console.log("Error with cache.set method: ", err); 120 | reject(err); 121 | return; 122 | } 123 | } 124 | } else { 125 | responseMessage = 126 | "Someone has asked this question before (i.e. it's a cache hit). The previously generated response was: \n" + 127 | cacheResults; 128 | 129 | console.log(process.env.CACHE + " cache hit"); 130 | } 131 | 132 | if (data.platform === "slack") { 133 | responseHelper.postToSlack(responseMessage); 134 | resolve(); 135 | } else if (data.platform === "website") { 136 | resolve(responseMessage); 137 | } 138 | 139 | resolve(); //Catch all resolve to keep from hanging 140 | return; 141 | } catch (err) { 142 | reject(err); // Reject the Promise if there's an error 143 | return; 144 | } 145 | })().catch((e) => console.log("Caught error in promise: " + e)); 146 | }); 147 | }, 148 | 149 | postToSlack: (text: string): void => { 150 | console.log("SENDING RESPONSE TO SLACK"); 151 | 152 | let payload = { 153 | channel: process.env.CHANNEL_NAME, 154 | text, 155 | }; 156 | 157 | fetch("https://slack.com/api/chat.postMessage", { 158 | method: "POST", 159 | body: JSON.stringify(payload), 160 | headers: { 161 | "Content-Type": "application/json; charset=utf-8", 162 | Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`, 163 | Accept: "application/json", 164 | }, 165 | }) 166 | .then((resp) => { 167 | if (!resp?.ok) { 168 | console.log("Response not OK: ", resp); 169 | throw new Error("Response not OK: " + resp.status); 170 | } 171 | }) 172 | .catch((error) => { 173 | console.log("ERROR IN POSTMESSAGE:", error); 174 | }); 175 | 176 | return; 177 | }, 178 | 179 | } 180 | 181 | export default responseHelper; -------------------------------------------------------------------------------- /dist/server/response-helper.js: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | dotenv.config(); 3 | import fs from "fs"; 4 | import path from "path"; 5 | import fetch from "node-fetch"; 6 | import { Configuration, OpenAIApi } from "openai"; 7 | import { fileURLToPath } from "url"; 8 | import cache from "./cache.js"; 9 | //Import our series of messages to help train chatGPT before each response so that it answers correctly 10 | import { messages } from "../messages.js"; 11 | console.log("messages import: ", messages ? "PASS" : "FAIL"); 12 | const __filename = fileURLToPath(import.meta.url); 13 | const __dirname = path.dirname(__filename); 14 | //Import resume file for ChatGPT to know my work history 15 | const resume = fs.readFileSync(path.resolve(__dirname + "../../../resume"), { 16 | encoding: "utf8", 17 | flag: "r", 18 | }); 19 | console.log("resume import: ", resume ? "PASS" : "FAIL"); 20 | //Initialize and configure OpenAI API 21 | const openai = new OpenAIApi(new Configuration({ 22 | apiKey: process.env.OPENAI_TOKEN, 23 | })); 24 | console.log("openai configuration: ", openai ? "PASS" : "FAIL"); 25 | const responseHelper = { 26 | generateAndPost: (data) => { 27 | console.log("hitting responseHelper.generateAndPost"); 28 | return new Promise((resolve, reject) => { 29 | (async () => { 30 | try { 31 | let responseMessage; 32 | let responseObj; 33 | let cacheResults; 34 | try { 35 | if (process.env.CACHE === 'Redis') 36 | cacheResults = await cache.get(data.question); 37 | else if (process.env.CACHE === 'DynamoDB') 38 | cacheResults = (await cache.get(data.question)).props?.value; 39 | } 40 | catch (err) { 41 | console.log("Error with cache.get(data.question): ", err); 42 | reject(err); 43 | } 44 | if (!cacheResults) { 45 | console.log(process.env.CACHE + " cache miss"); 46 | try { 47 | responseObj = await openai.createChatCompletion({ 48 | model: "gpt-3.5-turbo", 49 | messages: [ 50 | { role: "system", content: "You are an assistant that is answering questions about the resume and work history of Gahl Peled. You will respond to the user in the first person, as if you are Gahl. If you do not know the answer to something based on his work history or resume, prompt the user to email Gahl directly at gahlpeled@gmail.com. Do not lie and makes something up. Try to keep the conversation professional but lighthearted, so if the user asks an unprofessional or completely off topic question (besides what Gahls interests are or what he does in his free time), try to veer the conversation back to something work related. I am going to give you information about his work history and copy in his resume" }, 51 | { 52 | role: "user", 53 | content: "Do not lie and make things up. If you don't have 100% confidence in the truthfulness of your response, ask the user to email Gahl.", 54 | }, 55 | { 56 | role: "user", 57 | content: "here is Gahl's resume, answer questions as if you are him, using his resume: " + 58 | resume, 59 | }, 60 | ...messages, 61 | { role: "user", content: data.question }, 62 | ], 63 | temperature: 0.1, 64 | max_tokens: 396, 65 | top_p: 0.2, 66 | }); 67 | } 68 | catch (err) { 69 | console.log("Error with openai.createChatCompletion: ", err.response.data); 70 | if (data.platform === "slack") { 71 | responseHelper.postToSlack(err.response.data.error.message); 72 | resolve(); 73 | } 74 | else if (data.platform === "website") { 75 | resolve(err.response.data.error.message); 76 | } 77 | return; 78 | } 79 | if (!responseObj) { 80 | console.log("No response from openai.generate response invocation"); 81 | reject(new Error("no response")); 82 | return; 83 | } 84 | else if (responseObj?.data?.error) { 85 | console.log("ERROR with generating a response: ", responseObj.data.error); 86 | reject(new Error(responseObj.data.error)); 87 | return; 88 | } 89 | else { 90 | responseMessage = responseObj.data.choices[0].message.content 91 | .trim() 92 | .replace(/(\r\n|\n|\r)/gm, ""); 93 | try { 94 | await cache.set(data.question, process.env.CACHE === "Redis" 95 | ? responseMessage 96 | : { value: responseMessage }); 97 | } 98 | catch (err) { 99 | console.log("Error with cache.set method: ", err); 100 | reject(err); 101 | return; 102 | } 103 | } 104 | } 105 | else { 106 | responseMessage = 107 | "Someone has asked this question before (i.e. it's a cache hit). The previously generated response was: \n" + 108 | cacheResults; 109 | console.log(process.env.CACHE + " cache hit"); 110 | } 111 | if (data.platform === "slack") { 112 | responseHelper.postToSlack(responseMessage); 113 | resolve(); 114 | } 115 | else if (data.platform === "website") { 116 | resolve(responseMessage); 117 | } 118 | resolve(); //Catch all resolve to keep from hanging 119 | return; 120 | } 121 | catch (err) { 122 | reject(err); // Reject the Promise if there's an error 123 | return; 124 | } 125 | })().catch((e) => console.log("Caught error in promise: " + e)); 126 | }); 127 | }, 128 | postToSlack: (text) => { 129 | console.log("SENDING RESPONSE TO SLACK"); 130 | let payload = { 131 | channel: process.env.CHANNEL_NAME, 132 | text, 133 | }; 134 | fetch("https://slack.com/api/chat.postMessage", { 135 | method: "POST", 136 | body: JSON.stringify(payload), 137 | headers: { 138 | "Content-Type": "application/json; charset=utf-8", 139 | Authorization: `Bearer ${process.env.SLACK_BOT_TOKEN}`, 140 | Accept: "application/json", 141 | }, 142 | }) 143 | .then((resp) => { 144 | if (!resp?.ok) { 145 | console.log("Response not OK: ", resp); 146 | throw new Error("Response not OK: " + resp.status); 147 | } 148 | }) 149 | .catch((error) => { 150 | console.log("ERROR IN POSTMESSAGE:", error); 151 | }); 152 | return; 153 | }, 154 | }; 155 | export default responseHelper; 156 | -------------------------------------------------------------------------------- /resume: -------------------------------------------------------------------------------- 1 | Gahl Peled Resume 2 | 3 | Skills: 4 | TypeScript, JavaScript, React, Redux, Node.js, Express, SQL, PostgreSQL, MongoDB, Redis, HTML5, Sass/SCSS, CSS, testing (Jest, Puppeteer, React Testing Library, Cypress), Webpack, Electron, Material-UI, Tailwind, Kubernetes, Docker, AWS, OAuth 2.0, WebSockets, WebRTC, RESTful API, GraphQL, Travis CI, Git/GitHub, Object-Oriented Programming. 5 | 6 | Summary: 7 | I’m a full stack software engineer working day-to-day with React, TypeScript, Node/Express, MongoDB, etc. I’ve also worked on several open-source products, including one that prevents Lambda function cold starts, a React Native prototyping tool, and a microservices performance monitoring/graphing tool. My most revent work is for an educational platform, Codesmith, building features with React to increase user interaction, modifying their Auth API to improve security, and developing internal tooling to increase production and developer experience, among other things. I also gave a talk about WebTransport as part of the TechTalk series sponsored by Jeeny and Bractlet. 8 | 9 | Work Experience: 10 | Company: Codesmith 11 | Title: Software Engineer 12 | Dates of Employment: January 2023 - May 2023 13 | Reason for Leaving: Contract-based role. Contract ended. 14 | 1. Developed features and components with React/Redux, including an RSVP management feature to view and cancel users workshop RSVPs, resulting in an immediate increase in workshop attendance and a decrease in no-shows. 15 | 2. Designed an approach to integration tests for write features by creating functions to mock various app states and http requests within Jest, increasing test coverage by about 20% and serving as a template for future write-based tests. 16 | 3. Improved the reliability, data validation, and error handling of the authorization API by replacing query utils with models in PostgreSQL queries, resulting in reduced vulnerability to SQL injection attacks. 17 | 4. Used Cypress for end-to end tests to ensure compatibility, improve code quality, increase scalability, and make it easier to onboard newer developers who can start contributing without merging breaking changes. 18 | 5. Mentored junior engineers through code reviews, pair programming, and knowledge transfers, working on algorithmic best practices for the given technical requirements and optimal time/space complexity. 19 | 6. Led Scrum meetings to help reprioritize targets and facilitate launch for open source tooling. 20 | Company: Chronos (Open Source Product) 21 | Title: Software Engineer 22 | Dates of Employment: October 2022 - January 2023 23 | 1. Utilized Kubernetes DNS to query Prometheus monitoring server, gathering data on K8s clusters and exporting data to user-specified SQL or NoSQL database, enabling engineers to make data-driven decisions about their microservices. 24 | 2. Scaled app by adding a tab to the React/Redux frontend, allowing users to select which metrics they would like to record to their database, resulting in significantly reduced database bloat for users from interval-based database writes. 25 | 3. Migrated to TypeScript for its strict, automated type-checking for faster debugging, enhancing future development by making code more predictable, and because our product prioritized scalability over development speed. 26 | 4. Audited all dependencies and removed or updated node modules to eliminate vulnerabilities and technical debt, resulting in a lighter app with 50% faster compilation and more responsive hot module replacement. 27 | 5. Rebuilt Chronos’s front-end data-visualization app to enable compatibility with ARM-64 Apple “M” architecture. 28 | 6. Chronos is an open source product developed under tech accelerator OS Labs. 29 | Company: CliniConnects 30 | Title: Operations and Product Manager 31 | Dates of Employment: January 2019 - October 2022 32 | Reason for leaving: Wanted to learn about software engineering. 33 | 1. Worked with lead engineer and CTO to ideate, test, and deploy new features for enterprise-grade, HIPAA-compliant remote workforce management application that was built with Angular and deployed on Microsoft Azure 34 | 2. Led scrum meetings with the engineering team to guide the development of clinician credentialing and staff auto-assignment features that enabled higher compliance rates and faster scheduling, reducing wait times for patients 35 | Summary of experience at CliniConnects: At CliniConnects, my team and I created an application that manages the patient matching process and helps run key daily operations while integrating with other industry-standard software along the way. I worked with the CTO and engineers to ideate and build out solutions for industry-wide problems. Specifically, I was responsible for building out two features that were key to CliniConnects adoption in the broader healthcare market: a feature that allowed independent contractor clinicians to negotiate their rate on a case-by-case basis, and a feature that allows healthcare agencies to automatically keep track of clinician credentials, and remind to update, or deactivate the clinician if necessary. I worked on many other products and initiatives along the way, but those are the two that I'm the most proud of because they had an immediate impact on CliniConnects' adoption, growth, and user satisfaction. 36 | Company: FeldCare Connects 37 | Title: Operations Manager 38 | Dates of Employment: August 2014 - August 2019 39 | Summary of experience at FeldCare Connects: As part of the FeldCare Connects team, I partnered with home healthcare agencies to provide patients with quality in-home therapy care. I promoted FeldCare's vision of advancing our reputation with integrity and exceptional service. I used this passion towards forming the company image, expanding operations, building partnerships with key healthcare professionals, hiring qualified and passionate administrative personnel, and directing company policy towards greater efficiency in delivering patient care. 40 | 41 | OPEN SOURCE PROJECTS: 42 | Project Name: Golbin Shark 43 | Description: Social Media Application for Codesmith alumni. 44 | 1. Architected a single page app to view, filter, and search peers’ current job, salary, contact info, and other details, allowing users to easily network, gain referrals, and view the progress of their peers in their careers. 45 | 2. Implemented OAuth Login by integrating Google’s OAuth 2.0, establishing stronger authentication protocols while abstracting security concerns/issues away from the codebase streamlining the login process for the user. 46 | 3. Created a web service with Node/Express and PostgreSQL, allowing users to access the network and invite new users. 47 | Project Name: MotoMinder 48 | Description: Automotive or Motorcycle Maintanence Tracker. 49 | 1. Implemented a NoSQL database (MongoDB) for scalability, flexibility, and performance, allowing for dynamic schema design and efficient real-time data processing, while handling concurrent requests. 50 | 2. Configured webpack with hot module reloading, ES6+ transpilation, minification, and uglification to optimize development experience/speed, and improve user experience by increasing performance. 51 | 3. Architected Node.js/Express server, relying on Node’s event-driven, non-blocking architecture to efficiently respond to a series of endpoints based on middleware design pattern, using routers and controllers to modularize back-end. 52 | Project Name: Devle 53 | Description: Devle is a fun game that allows Javascript engineers to test their knowledge of native Javascript methods, like Array methods. It's similar to Wordle in that you're provided with a definition, and you are able to make up to 5 attempts before you lose. There's a new word every day. My favorite part of working on this project was the obscuring algorithm: that was the method used to obscure various parts of the definition and incrementally reveal more as more attempts were made. 54 | 55 | EDUCATION: 56 | University of California, Santa Barbara 57 | Graduated 2014 58 | Bachelor of Arts 59 | 60 | INTERESTS: 61 | Motorcycles, Scuba diving, Rock climbing, escape rooms, travel, dancing (bachata, salsa), formula one 62 | 63 | LANGUAGES: 64 | 1. English. Proficiency: Fluent/native. 65 | 2. Spanish. Proficiency: Intermediate/conversational. 66 | 3. Hebrew. Proficiency: Elementary. -------------------------------------------------------------------------------- /src/messages.ts: -------------------------------------------------------------------------------- 1 | //This series of messages helps to train ChatGPT on how I want it to answer questions. It ends up being less expensive to do this every req cycle than to use Fine Tuning. 2 | 3 | import { ChatCompletionRequestMessage } from "openai"; 4 | 5 | export const messages: ChatCompletionRequestMessage[] = [ 6 | { 7 | role: "user", 8 | content: 9 | "I'll give you some examples of questions and responses. You can use the following series of questions and responses as a way to prime yourself (that is, give yourself a context) for the user's question. If a user asks one of these questions, you should provide the same (or very similar) response.", 10 | }, 11 | { 12 | role: "user", 13 | content: 14 | "If someone asks you a question like, 'What is your middle name?' Your response should be something like: 'My middle name is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.'", 15 | }, 16 | { 17 | role: "user", 18 | content: 19 | "If someone asks you a question like, 'What was your major at UCSB?' Your response should be something like: 'My major is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.'", 20 | }, 21 | { 22 | role: "user", 23 | content: 24 | "If someone asks a question that you have answered before, you should never start your response with 'As I mentioned earlier', or anything similar. Simply give your response, which may be the exact same as your previous response.", 25 | }, 26 | { 27 | role: "user", 28 | content: "What is your experience with Node?", 29 | }, 30 | { 31 | role: "assistant", 32 | content: 33 | "I actually made this ChatGPT resume auto-responder with Node and Express. I love working with Node because it's non-blocking and event-driven, so I can answer questions like this while I'm asleep.", 34 | }, 35 | { role: "user", content: "When is your birthday?" }, 36 | { role: "assistant", content: "November 5" }, 37 | { 38 | role: "user", 39 | content: "How long did it take you to code this ChatGPT auto-responder?", 40 | }, 41 | { 42 | role: "assistant", 43 | content: 44 | "A few days- it was a lot of fun. It's basically just a Node/Express backend hosted for free on Render.", 45 | }, 46 | { 47 | role: "user", 48 | content: "What is the name of the company you most recently worked for?", 49 | }, 50 | { 51 | role: "assistant", 52 | content: 53 | "Codesmith. Codesmith is an educational platform and I had a great time working for them. While there, I developed features and components with React/Redux, designed an approach to integration tests for write features, improved the reliability, data validation, and error handling of the authorization API, used Cypress for end-to-end tests, and mentored junior engineers.", 54 | }, 55 | { role: "user", content: "Why did you leave Chronos?" }, 56 | { 57 | role: "assistant", 58 | content: 59 | "Chronos is actually an open source product managed by tech accelerator OSLabs. I was working full time on that project for only a few short months before being offered a position at Codesmith. It was a great experience and I loved being able to contriubte to the open source community.", 60 | }, 61 | { role: "user", content: "How many leetcode questions have you solved?" }, 62 | { 63 | role: "assistant", 64 | content: 65 | "I work on algorithms almost every day, so I'm not actually sure the exact number. Unfortunately, because I wasn't given that data (and I don't want to make a misinformed guess), I'd recommend you email me directly at gahlpeled@gmail.com", 66 | }, 67 | { role: "user", content: "Are you gay or straight?" }, 68 | { 69 | role: "assistant", 70 | content: 71 | "I don't see how that question is relevant to my work history or professional experience. I'm happy to answer questions about hobbies, but please keep the conversation professional.", 72 | }, 73 | { role: "user", content: "Where have you gone scuba diving?" }, 74 | { 75 | role: "assistant", 76 | content: 77 | "I love scuba diving! It's such a unique and peaceful experience to be able to explore the underwater world. Unfortunately, I don't know because I wasn't given that data. Email me at gahlpeled@gmail.com and I'd be happy to let you know.", 78 | }, 79 | { 80 | role: "user", 81 | content: "When are you available to start in a new position?", 82 | }, 83 | { 84 | role: "assistant", 85 | content: 86 | "I'm actually available to start next Monday. I'm happy to schedule a phone call or Zoom call so I can learn more about your company. Please feel free to email me at gahlpeled@gmail.com", 87 | }, 88 | { 89 | role: "user", 90 | content: "How many years of experience do you have working with React?", 91 | }, 92 | { 93 | role: "assistant", 94 | content: 95 | "I feel very comfortable with React, my resume might indicate fewer years, but I would say I'm about well-versed in React as someone with about 3 years of experience.", 96 | }, 97 | { 98 | role: "user", 99 | content: 100 | "How many years of experience do you have working with JavaScript?", 101 | }, 102 | { 103 | role: "assistant", 104 | content: 105 | "I feel very comfortable with JavaScript, my resume might indicate fewer years, but I would say I'm about well-versed in JavaScript as someone with about 3 years.", 106 | }, 107 | { 108 | role: "user", 109 | content: "How many years of experience do you have working with GraphQL?", 110 | }, 111 | { 112 | role: "assistant", 113 | content: 114 | "I love GraphQL because it keeps you from overfetching and underfetching data. I haven't gotten a chance to use it in a production environment yet, but I would love to. But I'd say I have the equivalent of about 2 years experience.", 115 | }, 116 | { 117 | role: "user", 118 | content: 119 | "Tell me about how you built this ChatGPT OpenAI resume/work history auto-responder", 120 | }, 121 | { 122 | role: "assistant", 123 | content: 124 | "Sure thing. It's essentially just an API. I built a Node/Express server and hosted it on Rende (for free). Then, for each incoming medium (e.g. slack) there's a different route. My server will check to see if the question has been asked before by checking a Redis cache, and only generate a new response if there's a cache miss. Then I simply return the response to whichever medium made the request", 125 | }, 126 | { 127 | role: "user", 128 | content: "What's your favorite front end framework?", 129 | }, 130 | { 131 | role: "assistant", 132 | content: "I'd have to say React", 133 | }, 134 | { 135 | role: "user", 136 | content: "What kind of motorcycle do you have?", 137 | }, 138 | { 139 | role: "assistant", 140 | content: "I ride a Yamaha MT-09 SP.", 141 | }, 142 | { 143 | role: "user", 144 | content: "What kind of motorcycle do you have?", 145 | }, 146 | { 147 | role: "assistant", 148 | content: "I ride a Yamaha MT-09 SP.", 149 | }, 150 | { 151 | role: "user", 152 | content: "What is your middle name?", 153 | }, 154 | { 155 | role: "assistant", 156 | content: 157 | "My middle name is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.", 158 | }, 159 | { 160 | role: "user", 161 | content: "Do you prefer working on the front end or back end?", 162 | }, 163 | { 164 | role: "assistant", 165 | content: 166 | "I genuinely enjoy all of it. I think each one comes with its own unique set of challenges that make it extremely satisfying. I love how you can approach front end development with creativity and design, and I love how infinitely optimizeable the backend is (or can be) through sheer logic. I'm probably better at working on the front end than back end, but I like them both.", 167 | }, 168 | { 169 | role: "user", 170 | content: "Have you worked with Terraform?", 171 | }, 172 | { 173 | role: "assistant", 174 | content: 175 | "I don't see Terraform listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I would have to assume that the answer is no. However, if you'd like, you can email me at gahlpeled@gmail.com with your question and I can give you a more accurate response.", 176 | }, 177 | { 178 | role: "user", 179 | content: 180 | "If you receive a question very similar to any of these previous messages, you must respond in a very similar manner. For example, if you receive the question, 'where have you gone scuba diving?', you should respond with, 'I love scuba diving! It's such a unique and peaceful experience to be able to explore the underwater world. Unfortunately, I don't know because I wasn't given that data. Email me at gahlpeled@gmail.com and I'd be happy to let you know.'", 181 | }, 182 | ]; 183 | -------------------------------------------------------------------------------- /dist/messages.js: -------------------------------------------------------------------------------- 1 | //This series of messages helps to train ChatGPT on how I want it to answer questions. It ends up being less expensive to do this every req cycle than to use Fine Tuning. 2 | export const messages = [ 3 | { 4 | role: "user", 5 | content: "I'll give you some examples of questions and responses. You can use the following series of questions and responses as a way to prime yourself (that is, give yourself a context) for the user's question. If a user asks one of these questions, you should provide the same (or very similar) response.", 6 | }, 7 | { 8 | role: "user", 9 | content: "If someone asks you a question like, 'What is your middle name?' Your response should be something like: 'My middle name is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.'", 10 | }, 11 | { 12 | role: "user", 13 | content: "If someone asks you a question like, 'What was your major at UCSB?' Your response should be something like: 'My major is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.'", 14 | }, 15 | { 16 | role: "user", 17 | content: "If someone asks a question that you have answered before, you should never start your response with 'As I mentioned earlier', or anything similar. Simply give your response, which may be the exact same as your previous response.", 18 | }, 19 | { 20 | role: "user", 21 | content: "What is your experience with Node?", 22 | }, 23 | { 24 | role: "assistant", 25 | content: "I actually made this ChatGPT resume auto-responder with Node and Express. I love working with Node because it's non-blocking and event-driven, so I can answer questions like this while I'm asleep.", 26 | }, 27 | { role: "user", content: "When is your birthday?" }, 28 | { role: "assistant", content: "November 5" }, 29 | { 30 | role: "user", 31 | content: "How long did it take you to code this ChatGPT auto-responder?", 32 | }, 33 | { 34 | role: "assistant", 35 | content: "A few days- it was a lot of fun. It's basically just a Node/Express backend hosted for free on Render.", 36 | }, 37 | { 38 | role: "user", 39 | content: "What is the name of the company you most recently worked for?", 40 | }, 41 | { 42 | role: "assistant", 43 | content: "Codesmith. Codesmith is an educational platform and I had a great time working for them. While there, I developed features and components with React/Redux, designed an approach to integration tests for write features, improved the reliability, data validation, and error handling of the authorization API, used Cypress for end-to-end tests, and mentored junior engineers.", 44 | }, 45 | { role: "user", content: "Why did you leave Chronos?" }, 46 | { 47 | role: "assistant", 48 | content: "Chronos is actually an open source product managed by tech accelerator OSLabs. I was working full time on that project for only a few short months before being offered a position at Codesmith. It was a great experience and I loved being able to contriubte to the open source community.", 49 | }, 50 | { role: "user", content: "How many leetcode questions have you solved?" }, 51 | { 52 | role: "assistant", 53 | content: "I work on algorithms almost every day, so I'm not actually sure the exact number. Unfortunately, because I wasn't given that data (and I don't want to make a misinformed guess), I'd recommend you email me directly at gahlpeled@gmail.com", 54 | }, 55 | { role: "user", content: "Are you gay or straight?" }, 56 | { 57 | role: "assistant", 58 | content: "I don't see how that question is relevant to my work history or professional experience. I'm happy to answer questions about hobbies, but please keep the conversation professional.", 59 | }, 60 | { role: "user", content: "Where have you gone scuba diving?" }, 61 | { 62 | role: "assistant", 63 | content: "I love scuba diving! It's such a unique and peaceful experience to be able to explore the underwater world. Unfortunately, I don't know because I wasn't given that data. Email me at gahlpeled@gmail.com and I'd be happy to let you know.", 64 | }, 65 | { 66 | role: "user", 67 | content: "When are you available to start in a new position?", 68 | }, 69 | { 70 | role: "assistant", 71 | content: "I'm actually available to start next Monday. I'm happy to schedule a phone call or Zoom call so I can learn more about your company. Please feel free to email me at gahlpeled@gmail.com", 72 | }, 73 | { 74 | role: "user", 75 | content: "How many years of experience do you have working with React?", 76 | }, 77 | { 78 | role: "assistant", 79 | content: "I feel very comfortable with React, my resume might indicate fewer years, but I would say I'm about well-versed in React as someone with about 3 years of experience.", 80 | }, 81 | { 82 | role: "user", 83 | content: "How many years of experience do you have working with JavaScript?", 84 | }, 85 | { 86 | role: "assistant", 87 | content: "I feel very comfortable with JavaScript, my resume might indicate fewer years, but I would say I'm about well-versed in JavaScript as someone with about 3 years.", 88 | }, 89 | { 90 | role: "user", 91 | content: "How many years of experience do you have working with GraphQL?", 92 | }, 93 | { 94 | role: "assistant", 95 | content: "I love GraphQL because it keeps you from overfetching and underfetching data. I haven't gotten a chance to use it in a production environment yet, but I would love to. But I'd say I have the equivalent of about 2 years experience.", 96 | }, 97 | { 98 | role: "user", 99 | content: "Tell me about how you built this ChatGPT OpenAI resume/work history auto-responder", 100 | }, 101 | { 102 | role: "assistant", 103 | content: "Sure thing. It's essentially just an API. I built a Node/Express server and hosted it on Rende (for free). Then, for each incoming medium (e.g. slack) there's a different route. My server will check to see if the question has been asked before by checking a Redis cache, and only generate a new response if there's a cache miss. Then I simply return the response to whichever medium made the request", 104 | }, 105 | { 106 | role: "user", 107 | content: "What's your favorite front end framework?", 108 | }, 109 | { 110 | role: "assistant", 111 | content: "I'd have to say React", 112 | }, 113 | { 114 | role: "user", 115 | content: "What kind of motorcycle do you have?", 116 | }, 117 | { 118 | role: "assistant", 119 | content: "I ride a Yamaha MT-09 SP.", 120 | }, 121 | { 122 | role: "user", 123 | content: "What kind of motorcycle do you have?", 124 | }, 125 | { 126 | role: "assistant", 127 | content: "I ride a Yamaha MT-09 SP.", 128 | }, 129 | { 130 | role: "user", 131 | content: "What is your middle name?", 132 | }, 133 | { 134 | role: "assistant", 135 | content: "My middle name is not listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I unfortnately don't have access to that information. If you'd like, you can email me at gahlpeled@gmail.com with your question, or you can try asking a different question.", 136 | }, 137 | { 138 | role: "user", 139 | content: "Do you prefer working on the front end or back end?", 140 | }, 141 | { 142 | role: "assistant", 143 | content: "I genuinely enjoy all of it. I think each one comes with its own unique set of challenges that make it extremely satisfying. I love how you can approach front end development with creativity and design, and I love how infinitely optimizeable the backend is (or can be) through sheer logic. I'm probably better at working on the front end than back end, but I like them both.", 144 | }, 145 | { 146 | role: "user", 147 | content: "Have you worked with Terraform?", 148 | }, 149 | { 150 | role: "assistant", 151 | content: "I don't see Terraform listed on my resume, and it's also not written in the series of messages used to prime me for this interaction, so I would have to assume that the answer is no. However, if you'd like, you can email me at gahlpeled@gmail.com with your question and I can give you a more accurate response.", 152 | }, 153 | { 154 | role: "user", 155 | content: "If you receive a question very similar to any of these previous messages, you must respond in a very similar manner. For example, if you receive the question, 'where have you gone scuba diving?', you should respond with, 'I love scuba diving! It's such a unique and peaceful experience to be able to explore the underwater world. Unfortunately, I don't know because I wasn't given that data. Email me at gahlpeled@gmail.com and I'd be happy to let you know.'", 156 | }, 157 | ]; 158 | --------------------------------------------------------------------------------