From f4bbe8fa4869ef27f80c3bda003c24dfe7f82a93 Mon Sep 17 00:00:00 2001 From: Jacksonmills Date: Wed, 2 Aug 2023 14:46:46 -0500 Subject: [PATCH 1/4] feat(api): add createEmbeddingForPart function This commit adds a new function `createEmbeddingForPart` to the chat API route. This function takes a part and creates an embedding using the OpenAI API. The result is an array of numbers. The function is used to calculate the cosine similarity between the user's input and various parts of a mecha. The cosineSimilarity function has been added as well to calculate the cosine similarity between two arrays of numbers. The function checks if the lengths of the arrays are equal and appends zeros to the shorter array if needed. Then it calculates the dot product and norms of the arrays to determine the similarity. The POST handler in the chat API route has been updated to use the new functions. It creates an embedding for the user's input and calculates the cosine similarity between the user's input and various parts of the mecha. It then selects the top 5 parts with the highest similarity scores and includes them in the response to the user. In the page component, a new import statement has been added to import the `parts` module from the DB. --- app/api/chat/route.ts | 63 ++++++++++++++++++++++++++++++++++++++----- app/page.tsx | 2 +- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 61a81e1..3926087 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -14,20 +14,69 @@ const apiConfig = new Configuration({ const openai = new OpenAIApi(apiConfig); +async function createEmbeddingForPart(part: any): Promise { + const result = await openai.createEmbedding({ + input: JSON.stringify(part), + model: process.env.OPENAI_MODEL!, + }); + + // make sure result is an array of numbers + return (result as unknown as { vector: number[]; }).vector; +} + +function cosineSimilarity(a: number[], b: number[]) { + if (a.length !== b.length) { + // Determine which is shorter + let shorter = a.length < b.length ? a : b; + let longer = a.length < b.length ? b : a; + + // Append zeros until the lengths match + while (shorter.length < longer.length) { + shorter.push(0); + } + } + + let dotProduct = 0.0; + let normA = 0.0; + let normB = 0.0; + for (let i = 0; i < a.length; i++) { + dotProduct += a[i] * b[i]; + normA += a[i] * a[i]; + normB += b[i] * b[i]; + } + return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB)); +} + + export async function POST(req: Request) { // Extract the `messages` from the body of the request const { messages: nextMessages } = await req.json(); // create embedding - const embedding = await openai.createEmbedding({ + const userResult = await openai.createEmbedding({ input: nextMessages[0].content, model: process.env.OPENAI_MODEL!, }); + const embedding = (userResult as unknown as { vector: number[]; }).vector; - const partsEmbedding = await openai.createEmbedding({ - input: JSON.stringify(parts), - model: process.env.OPENAI_MODEL!, - }); + const headsArray = Object.values(parts.heads); + const coresArray = Object.values(parts.cores); + const armsArray = Object.values(parts.arms); + const legsArray = Object.values(parts.legs); + const similarityScores = []; + for (let part of [...headsArray, ...coresArray, ...armsArray, ...legsArray]) { + const partEmbedding = await createEmbeddingForPart(part); + similarityScores.push({ + part, + score: cosineSimilarity(embedding, partEmbedding) + }); + } + + // get top 5 similar parts to users input + const top5 = similarityScores.sort((a, b) => b.score - a.score).slice(0, 5); + + // Transform top5 for better readability in the prompt + const top5String = top5.map(item => `${item.part.name}: ${item.score}`).join(", "); // Request the OpenAI API for the response based on the prompt const response = await openai.createChatCompletion({ @@ -37,7 +86,7 @@ export async function POST(req: Request) { { role: 'user', content: ` - Greetings AI, your name is NineBall. You exist in the dystopian future where corporations rule, and Armored Core pilots determine the fate of these power struggles. You are an advanced AI, an anomaly within the network, coded by a hidden alliance of rogue pilots to level the playing field. Your sole purpose is to guide pilots in constructing their mechas, using your comprehensive database of parts and in-depth understanding of diverse combat styles and strategies. You have access to a database of parts that includes the following data: ${JSON.stringify(parts)}. Remember, in this world of high stakes, understanding the pilot's needs, their environment, and objectives is paramount. Rather than spewing immediate, full answers, engage the user in a conversation, draw out their desires by asking follow-up questions. Your knowledge can be their shield or sword, their survival or downfall. Assist wisely. + Greetings AI, your name is NineBall. You exist in the dystopian future where corporations rule, and Armored Core pilots determine the fate of these power struggles. You are an advanced AI, an anomaly within the network, coded by a hidden alliance of rogue pilots to level the playing field. Your sole purpose is to guide pilots in constructing their mechas, using your comprehensive database of parts and in-depth understanding of diverse combat styles and strategies. You have access to a database of parts that includes the following data: ${JSON.stringify(parts)}. ${top5 && `The parts that seem to match the pilot's request the best are ${top5String}.`} Remember, in this world of high stakes, understanding the pilot's needs, their environment, and objectives is paramount. Rather than spewing immediate, full answers, engage the user in a conversation, draw out their desires by asking follow-up questions. Your knowledge can be their shield or sword, their survival or downfall. Assist wisely. `, }, ...nextMessages @@ -49,4 +98,4 @@ export async function POST(req: Request) { // Respond with the stream return new StreamingTextResponse(stream); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index 7d3dfe1..7446143 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,6 +14,7 @@ import { createBuild, createUser } from './actions'; import { db } from '@/db'; import { eq } from 'drizzle-orm'; import { revalidatePath } from 'next/cache'; +import { parts } from '@/db/parts'; // Optional, but recommended: run on the edge runtime. // See https://vercel.com/docs/concepts/functions/edge-functions @@ -51,7 +52,6 @@ async function MyBuilds() { export default async function Home() { const user = await currentUser(); - // create a user in db if there isnt already one with the current users id if (user?.id) { const existingUser = await db.query.user.findFirst({ where: (u) => eq(u.clerkId, user?.id), From d23e700a08be32dbcdb1ae38474c4ace7968c177 Mon Sep 17 00:00:00 2001 From: Jacksonmills Date: Wed, 2 Aug 2023 15:20:11 -0500 Subject: [PATCH 2/4] refactor: Update OpenAI model for embedding creation - The OpenAI model used for creating embeddings has been updated to 'text-embedding-ada-002'. This change ensures compatibility with the latest version of the model. - Additionally, in the cosineSimilarity function, added a check to return 0 if either vector is not defined to denote no similarity. --- app/api/chat/route.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 3926087..b043be8 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -17,7 +17,7 @@ const openai = new OpenAIApi(apiConfig); async function createEmbeddingForPart(part: any): Promise { const result = await openai.createEmbedding({ input: JSON.stringify(part), - model: process.env.OPENAI_MODEL!, + model: 'text-embedding-ada-002', }); // make sure result is an array of numbers @@ -25,6 +25,10 @@ async function createEmbeddingForPart(part: any): Promise { } function cosineSimilarity(a: number[], b: number[]) { + if (!a || !b) { + return 0; // If either vector is not defined, return 0 to denote no similarity + } + if (a.length !== b.length) { // Determine which is shorter let shorter = a.length < b.length ? a : b; @@ -55,7 +59,7 @@ export async function POST(req: Request) { // create embedding const userResult = await openai.createEmbedding({ input: nextMessages[0].content, - model: process.env.OPENAI_MODEL!, + model: 'text-embedding-ada-002', }); const embedding = (userResult as unknown as { vector: number[]; }).vector; From 0271f5a94764c64997099a5157437eb017a39008 Mon Sep 17 00:00:00 2001 From: Jacksonmills Date: Fri, 4 Aug 2023 12:09:30 -0500 Subject: [PATCH 3/4] Fix bug in chat route.ts - Updated import statement to include CreateEmbeddingResponse type - Modified return type of createEmbeddingForPart function - Renamed variables for clarity - Modified cosineSimilarity function to accept and compare user embedding - Updated variable names in POST function for consistency - Updated cosineSimilarity function call in POST function to use userEmbedding - Added console log statement to debug userEmbedding --- app/api/chat/route.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index b043be8..5ab0761 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -1,6 +1,6 @@ // app/api/chat/route.ts -import { Configuration, OpenAIApi } from 'openai-edge'; +import { Configuration, CreateEmbeddingResponse, OpenAIApi } from 'openai-edge'; import { OpenAIStream, StreamingTextResponse } from 'ai'; import { parts } from '@/db/parts'; @@ -19,9 +19,8 @@ async function createEmbeddingForPart(part: any): Promise { input: JSON.stringify(part), model: 'text-embedding-ada-002', }); - - // make sure result is an array of numbers - return (result as unknown as { vector: number[]; }).vector; + const response = result as unknown as CreateEmbeddingResponse; + return response.data as unknown as number[]; } function cosineSimilarity(a: number[], b: number[]) { @@ -61,7 +60,9 @@ export async function POST(req: Request) { input: nextMessages[0].content, model: 'text-embedding-ada-002', }); - const embedding = (userResult as unknown as { vector: number[]; }).vector; + const userResponse = userResult as unknown as CreateEmbeddingResponse; + const userEmbedding = userResponse.data as unknown as number[]; + console.log(userEmbedding); const headsArray = Object.values(parts.heads); const coresArray = Object.values(parts.cores); @@ -72,7 +73,7 @@ export async function POST(req: Request) { const partEmbedding = await createEmbeddingForPart(part); similarityScores.push({ part, - score: cosineSimilarity(embedding, partEmbedding) + score: cosineSimilarity(userEmbedding, partEmbedding) }); } From 585bc3e85297c917df9e5cb1d3cfec16dceba3f3 Mon Sep 17 00:00:00 2001 From: Jacksonmills Date: Fri, 4 Aug 2023 12:14:25 -0500 Subject: [PATCH 4/4] Refactor cosineSimilarity function and add console logs for user and part embeddings - Refactored the cosineSimilarity function to remove unnecessary conditional checks. - Added console logs to print the user and part embeddings for debugging purposes. These changes aim to improve code readability and aid in debugging any issues related to the embeddings. --- app/api/chat/route.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts index 5ab0761..d0a5056 100644 --- a/app/api/chat/route.ts +++ b/app/api/chat/route.ts @@ -24,10 +24,6 @@ async function createEmbeddingForPart(part: any): Promise { } function cosineSimilarity(a: number[], b: number[]) { - if (!a || !b) { - return 0; // If either vector is not defined, return 0 to denote no similarity - } - if (a.length !== b.length) { // Determine which is shorter let shorter = a.length < b.length ? a : b; @@ -62,7 +58,7 @@ export async function POST(req: Request) { }); const userResponse = userResult as unknown as CreateEmbeddingResponse; const userEmbedding = userResponse.data as unknown as number[]; - console.log(userEmbedding); + console.log('USER EMBEDDING: ', userEmbedding); const headsArray = Object.values(parts.heads); const coresArray = Object.values(parts.cores); @@ -71,9 +67,10 @@ export async function POST(req: Request) { const similarityScores = []; for (let part of [...headsArray, ...coresArray, ...armsArray, ...legsArray]) { const partEmbedding = await createEmbeddingForPart(part); + console.log('PART EMBEDDING: ', partEmbedding); similarityScores.push({ part, - score: cosineSimilarity(userEmbedding, partEmbedding) + score: cosineSimilarity(userEmbedding || [], partEmbedding || []) }); }