From d520c50c6936fe729671608558f24430e87bc625 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:26:19 -0700 Subject: [PATCH 1/7] increase OpenAI NodeJS client version --- apps/file-q-and-a/nextjs/package-lock.json | 14 +++++++------- apps/file-q-and-a/nextjs/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/file-q-and-a/nextjs/package-lock.json b/apps/file-q-and-a/nextjs/package-lock.json index 5f03816f..1445f897 100644 --- a/apps/file-q-and-a/nextjs/package-lock.json +++ b/apps/file-q-and-a/nextjs/package-lock.json @@ -28,7 +28,7 @@ "mammoth": "^1.5.1", "next": "13.1.2", "node-html-markdown": "^1.3.0", - "openai": "^3.1.0", + "openai": "^3.2.1", "pdf-parse": "^1.1.1", "react": "18.2.0", "react-dom": "18.2.0", @@ -3978,9 +3978,9 @@ } }, "node_modules/openai": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-3.1.0.tgz", - "integrity": "sha512-v5kKFH5o+8ld+t0arudj833Mgm3GcgBnbyN9946bj6u7bvel4Yg6YFz2A4HLIYDzmMjIo0s6vSG9x73kOwvdCg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", + "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", "dependencies": { "axios": "^0.26.0", "form-data": "^4.0.0" @@ -8003,9 +8003,9 @@ } }, "openai": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-3.1.0.tgz", - "integrity": "sha512-v5kKFH5o+8ld+t0arudj833Mgm3GcgBnbyN9946bj6u7bvel4Yg6YFz2A4HLIYDzmMjIo0s6vSG9x73kOwvdCg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/openai/-/openai-3.2.1.tgz", + "integrity": "sha512-762C9BNlJPbjjlWZi4WYK9iM2tAVAv0uUp1UmI34vb0CN5T2mjB/qM6RYBmNKMh/dN9fC+bxqPwWJZUTWW052A==", "requires": { "axios": "^0.26.0", "form-data": "^4.0.0" diff --git a/apps/file-q-and-a/nextjs/package.json b/apps/file-q-and-a/nextjs/package.json index 18a46d2f..4b66847b 100644 --- a/apps/file-q-and-a/nextjs/package.json +++ b/apps/file-q-and-a/nextjs/package.json @@ -29,7 +29,7 @@ "mammoth": "^1.5.1", "next": "13.1.2", "node-html-markdown": "^1.3.0", - "openai": "^3.1.0", + "openai": "^3.2.1", "pdf-parse": "^1.1.1", "react": "18.2.0", "react-dom": "18.2.0", From a77ad9446cbefefa7d46bc3dffe43dd6d4252615 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:26:53 -0700 Subject: [PATCH 2/7] address https://github.com/openai/openai-cookbook/issues/159 --- .../nextjs-with-flask-server/client/src/services/utils.ts | 5 +++++ apps/file-q-and-a/nextjs/src/services/utils.ts | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/apps/file-q-and-a/nextjs-with-flask-server/client/src/services/utils.ts b/apps/file-q-and-a/nextjs-with-flask-server/client/src/services/utils.ts index 9863f1b3..843a4865 100644 --- a/apps/file-q-and-a/nextjs-with-flask-server/client/src/services/utils.ts +++ b/apps/file-q-and-a/nextjs-with-flask-server/client/src/services/utils.ts @@ -1,6 +1,11 @@ // A function that takes a file name and a string and returns true if the file name is contained in the string // after removing punctuation and whitespace from both export const isFileNameInString = (fileName: string, str: string) => { + // Check if the input string is null or undefined + if (!str) { + return false; + } + // Convert both to lowercase and remove punctuation and whitespace const normalizedFileName = fileName .toLowerCase() diff --git a/apps/file-q-and-a/nextjs/src/services/utils.ts b/apps/file-q-and-a/nextjs/src/services/utils.ts index 9863f1b3..843a4865 100644 --- a/apps/file-q-and-a/nextjs/src/services/utils.ts +++ b/apps/file-q-and-a/nextjs/src/services/utils.ts @@ -1,6 +1,11 @@ // A function that takes a file name and a string and returns true if the file name is contained in the string // after removing punctuation and whitespace from both export const isFileNameInString = (fileName: string, str: string) => { + // Check if the input string is null or undefined + if (!str) { + return false; + } + // Convert both to lowercase and remove punctuation and whitespace const normalizedFileName = fileName .toLowerCase() From 295b10461ab69848805c49c60b0fbcffda988b0d Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:28:29 -0700 Subject: [PATCH 3/7] use ChatGPT API in nextjs example --- .../nextjs/src/components/FileQandAArea.tsx | 9 ++ .../src/pages/api/get-answer-from-files.ts | 3 - .../nextjs/src/services/openai.ts | 93 +++++++++++++------ 3 files changed, 75 insertions(+), 30 deletions(-) diff --git a/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx b/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx index 2aaa7878..9b92bcf6 100644 --- a/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx +++ b/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx @@ -55,6 +55,8 @@ function FileQandAArea(props: FileQandAAreaProps) { if (searchResultsResponse.status === 200) { results = searchResultsResponse.data.searchResults; + } else if (searchResultsResponse.status === 500) { + setAnswerError("Internal server error. Please try again later."); } else { setAnswerError("Sorry, something went wrong!"); } @@ -74,6 +76,13 @@ function FileQandAArea(props: FileQandAAreaProps) { fileChunks: results, }), }); + + if (res.status === 500) { + setAnswerError("Internal server error. Please try again later."); + setAnswerLoading(false); + return; + } + const reader = res.body!.getReader(); while (true) { diff --git a/apps/file-q-and-a/nextjs/src/pages/api/get-answer-from-files.ts b/apps/file-q-and-a/nextjs/src/pages/api/get-answer-from-files.ts index 050fa3e8..ee9a8f68 100644 --- a/apps/file-q-and-a/nextjs/src/pages/api/get-answer-from-files.ts +++ b/apps/file-q-and-a/nextjs/src/pages/api/get-answer-from-files.ts @@ -40,8 +40,6 @@ export default async function handler( .join("\n") .slice(0, MAX_FILES_LENGTH); - console.log(filesString); - const prompt = `Given a question, try to answer it using the content of the file extracts below, and if you cannot answer, or find a relevant file, just output \"I couldn't find the answer to that question in your files.\".\n\n` + `If the answer is not contained in the files or if there are no file extracts, respond with \"I couldn't find the answer to that question in your files.\" If the question is not actually a question, respond with \"That's not a valid question.\"\n\n` + @@ -53,7 +51,6 @@ export default async function handler( const stream = completionStream({ prompt, - model: "text-davinci-003", }); // Set the response headers for streaming diff --git a/apps/file-q-and-a/nextjs/src/services/openai.ts b/apps/file-q-and-a/nextjs/src/services/openai.ts index ebb46fbd..3d6c6c80 100644 --- a/apps/file-q-and-a/nextjs/src/services/openai.ts +++ b/apps/file-q-and-a/nextjs/src/services/openai.ts @@ -1,8 +1,9 @@ import { IncomingMessage } from "http"; import { + ChatCompletionRequestMessageRoleEnum, Configuration, + CreateChatCompletionResponse, CreateCompletionRequest, - CreateCompletionResponse, OpenAIApi, } from "openai"; @@ -30,24 +31,30 @@ type EmbeddingOptions = { export async function completion({ prompt, fallback, - max_tokens = 800, + max_tokens, temperature = 0, - model = "text-davinci-003", - ...otherOptions + model = "gpt-3.5-turbo", // use gpt-4 for better results }: CompletionOptions) { try { - const result = await openai.createCompletion({ - prompt, - max_tokens, - temperature, + // Note: this is not the proper way to use the ChatGPT conversational format, but it works for now + const messages = [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: prompt ?? "", + }, + ]; + + const result = await openai.createChatCompletion({ model, - ...otherOptions, + messages, + temperature, + max_tokens: max_tokens ?? 800, }); - if (!result.data.choices[0].text) { - throw new Error("No text returned from the completions endpoint."); + if (!result.data.choices[0].message) { + throw new Error("No text returned from completions endpoint"); } - return result.data.choices[0].text; + return result.data.choices[0].message.content; } catch (error) { if (fallback) return fallback; else throw error; @@ -59,33 +66,65 @@ export async function* completionStream({ fallback, max_tokens = 800, temperature = 0, - model = "text-davinci-003", + model = "gpt-3.5-turbo", // use gpt-4 for better results }: CompletionOptions) { try { - const result = await openai.createCompletion( + // Note: this is not the proper way to use the ChatGPT conversational format, but it works for now + const messages = [ + { + role: ChatCompletionRequestMessageRoleEnum.System, + content: prompt ?? "", + }, + ]; + + const result = await openai.createChatCompletion( { - prompt, - max_tokens, - temperature, model, + messages, + temperature, + max_tokens: max_tokens ?? 800, stream: true, }, - { responseType: "stream" } + { + responseType: "stream", + } ); - const stream = result.data as any as IncomingMessage; - for await (const chunk of stream) { - const line = chunk.toString().trim(); - const message = line.split("data: ")[1]; + let buffer = ""; + const textDecoder = new TextDecoder(); - if (message === "[DONE]") { - break; + for await (const chunk of stream) { + buffer += textDecoder.decode(chunk, { stream: true }); + const lines = buffer.split("\n"); + + // Check if the last line is complete + if (buffer.endsWith("\n")) { + buffer = ""; + } else { + buffer = lines.pop() || ""; } - const data = JSON.parse(message) as CreateCompletionResponse; - - yield data.choices[0].text; + for (const line of lines) { + const message = line.trim().split("data: ")[1]; + if (message === "[DONE]") { + break; + } + + // Check if the message is not undefined and a valid JSON string + if (message) { + try { + const data = JSON.parse(message) as CreateChatCompletionResponse; + // @ts-ignore + if (data.choices[0].delta?.content) { + // @ts-ignore + yield data.choices[0].delta?.content; + } + } catch (error) { + console.error("Error parsing JSON message:", error); + } + } + } } } catch (error) { if (fallback) yield fallback; From f72b7ebdb7d3b7e91827dfa41dc03037c9845570 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:28:52 -0700 Subject: [PATCH 4/7] address https://github.com/openai/openai-cookbook/issues/248 --- apps/file-q-and-a/nextjs/src/pages/api/process-file.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/file-q-and-a/nextjs/src/pages/api/process-file.ts b/apps/file-q-and-a/nextjs/src/pages/api/process-file.ts index 72c9f366..2d874d52 100644 --- a/apps/file-q-and-a/nextjs/src/pages/api/process-file.ts +++ b/apps/file-q-and-a/nextjs/src/pages/api/process-file.ts @@ -27,6 +27,8 @@ export default async function handler( // Create a formidable instance to parse the request as a multipart form const form = new formidable.IncomingForm(); + form.maxFileSize = 30 * 1024 * 1024; // Set the max file size to 30MB + try { const { fields, files } = await new Promise<{ fields: Fields; From 308c261bc9604894261ee811ef1afb0f99db47f8 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:29:19 -0700 Subject: [PATCH 5/7] use ChatGPT API in flask app --- .../server/answer_question.py | 48 ++++++++++--------- .../server/config.yaml | 2 +- .../server/requirements.txt | 20 ++++---- 3 files changed, 36 insertions(+), 34 deletions(-) diff --git a/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py b/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py index b5869c83..031783c9 100644 --- a/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py +++ b/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py @@ -41,33 +41,35 @@ def get_answer_from_files(question, session_id, pinecone_index): f"[get_answer_from_files] score {score} is below threshold {COSINE_SIM_THRESHOLD} and i is {i}, breaking") break files_string += file_string + + messages = [ + { + "role": "system", + "content": f"Given a question, try to answer it using the content of the file extracts below, and if you cannot answer, or find " \ + f"a relevant file, just output \"I couldn't find the answer to that question in your files.\".\n\n" \ + f"If the answer is not contained in the files or if there are no file extracts, respond with \"I couldn't find the answer " \ + f"to that question in your files.\" If the question is not actually a question, respond with \"That's not a valid question.\"\n\n" \ + f"In the cases where you can find the answer, first give the answer. Then explain how you found the answer from the source or sources, " \ + f"and use the exact filenames of the source files you mention. Do not make up the names of any other files other than those mentioned "\ + f"in the files context. Give the answer in markdown format." \ + f"Use the following format:\n\nQuestion: \n\nFiles:\n<###\n\"filename 1\"\nfile text>\n<###\n\"filename 2\"\nfile text>...\n\n"\ + f"Answer: \n\n" \ + f"Question: {question}\n\n" \ + f"Files:\n{files_string}\n" \ + f"Answer:" + }, + ] - prompt = f"Given a question, try to answer it using the content of the file extracts below, and if you cannot answer, or find " \ - f"a relevant file, just output \"I couldn't find the answer to that question in your files.\".\n\n" \ - f"If the answer is not contained in the files or if there are no file extracts, respond with \"I couldn't find the answer " \ - f"to that question in your files.\" If the question is not actually a question, respond with \"That's not a valid question.\"\n\n" \ - f"In the cases where you can find the answer, first give the answer. Then explain how you found the answer from the source or sources, " \ - f"and use the exact filenames of the source files you mention. Do not make up the names of any other files other than those mentioned "\ - f"in the files context. Give the answer in markdown format." \ - f"Use the following format:\n\nQuestion: \n\nFiles:\n<###\n\"filename 1\"\nfile text>\n<###\n\"filename 2\"\nfile text>...\n\n"\ - f"Answer: \n\n" \ - f"Question: {question}\n\n" \ - f"Files:\n{files_string}\n" \ - f"Answer:" - - logging.info(f"[get_answer_from_files] prompt: {prompt}") - - response = openai.Completion.create( - prompt=prompt, - temperature=0, + response = openai.ChatCompletion.create( + messages=messages, + model=GENERATIVE_MODEL, max_tokens=1000, - top_p=1, - frequency_penalty=0, - presence_penalty=0, - engine=GENERATIVE_MODEL, + temperature=0, ) - answer = response.choices[0].text.strip() + choices = response["choices"] # type: ignore + answer = choices[0].message.content.strip() + logging.info(f"[get_answer_from_files] answer: {answer}") return jsonify({"answer": answer}) diff --git a/apps/file-q-and-a/nextjs-with-flask-server/server/config.yaml b/apps/file-q-and-a/nextjs-with-flask-server/server/config.yaml index 6f7ae5dc..1af0568c 100644 --- a/apps/file-q-and-a/nextjs-with-flask-server/server/config.yaml +++ b/apps/file-q-and-a/nextjs-with-flask-server/server/config.yaml @@ -8,7 +8,7 @@ SERVER_PORT: "8080" # ---- OPENAI CONFIG ----- EMBEDDINGS_MODEL: "text-embedding-ada-002" -GENERATIVE_MODEL: "text-davinci-003" +GENERATIVE_MODEL: "gpt-3.5-turbo" # use gpt-4 for better results EMBEDDING_DIMENSIONS: 1536 TEXT_EMBEDDING_CHUNK_SIZE: 200 # This is the minimum cosine similarity score that a file must have with the search query to be considered relevant diff --git a/apps/file-q-and-a/nextjs-with-flask-server/server/requirements.txt b/apps/file-q-and-a/nextjs-with-flask-server/server/requirements.txt index 27605bb9..a1f56b0d 100644 --- a/apps/file-q-and-a/nextjs-with-flask-server/server/requirements.txt +++ b/apps/file-q-and-a/nextjs-with-flask-server/server/requirements.txt @@ -1,11 +1,11 @@ -Flask-Cors==3.0.10 -openai==0.13.0 -pinecone-client==2.0.13 -PyPDF2==2.10.4 -numpy==1.23.2 -scikit-learn==1.1.2 -docx2txt==0.8 +Flask-Cors>=3.0.10 +openai>=0.27.2 +pinecone-client>=2.0.13 +PyPDF2>=2.10.4 +numpy>=1.23.2 +scikit-learn>=1.1.2 +docx2txt>=0.8 flask>=1.1.4 -jinja2==3.0.1 -PyYAML==6.0 -tiktoken==0.1.2 +jinja2>=3.0.1 +PyYAML>=6.0 +tiktoken>=0.1.2 \ No newline at end of file From 36458a582cdadb5ece2fd71fc4a2ec1f1608d630 Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:32:37 -0700 Subject: [PATCH 6/7] Update FileQandAArea.tsx --- apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx b/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx index 9b92bcf6..85799698 100644 --- a/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx +++ b/apps/file-q-and-a/nextjs/src/components/FileQandAArea.tsx @@ -55,8 +55,6 @@ function FileQandAArea(props: FileQandAAreaProps) { if (searchResultsResponse.status === 200) { results = searchResultsResponse.data.searchResults; - } else if (searchResultsResponse.status === 500) { - setAnswerError("Internal server error. Please try again later."); } else { setAnswerError("Sorry, something went wrong!"); } From 975f7d3bb0f5b59814ad4faf46b1a9024b5472eb Mon Sep 17 00:00:00 2001 From: isafulf <51974293+isafulf@users.noreply.github.com> Date: Sun, 2 Apr 2023 17:33:25 -0700 Subject: [PATCH 7/7] Update answer_question.py --- .../nextjs-with-flask-server/server/answer_question.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py b/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py index 031783c9..6d466ec7 100644 --- a/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py +++ b/apps/file-q-and-a/nextjs-with-flask-server/server/answer_question.py @@ -42,6 +42,7 @@ def get_answer_from_files(question, session_id, pinecone_index): break files_string += file_string + # Note: this is not the proper way to use the ChatGPT conversational format, but it works for now messages = [ { "role": "system", @@ -69,7 +70,7 @@ def get_answer_from_files(question, session_id, pinecone_index): choices = response["choices"] # type: ignore answer = choices[0].message.content.strip() - + logging.info(f"[get_answer_from_files] answer: {answer}") return jsonify({"answer": answer})