Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 28 additions & 34 deletions app/(preview)/api/chat/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,60 @@ import { z } from "zod";
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;

export const addResourceParams = z.object({
content: z
.string()
.describe("the content or resource to add to the knowledge base"),
});

export const getInformationParams = z.object({
question: z.string().describe("the users question"),
similarQuestions: z.array(z.string()).describe("keywords to search"),
});

export const understandQueryParams = z.object({
query: z.string().describe("the users query"),
toolsToCallInOrder: z
.array(z.string())
.describe(
"these are the tools you need to call in the order necessary to respond to the users query",
),
});

export async function POST(req: Request) {
const { messages } = await req.json();

const result = await streamText({
model: openai("gpt-4o"),
messages: convertToCoreMessages(messages),
system: `You are a helpful assistant acting as the users' second brain.
Use tools on every request.
Be sure to getInformation from your knowledge base before answering any questions.
If the user presents infromation about themselves, use the addResource tool to store it.
If a response requires multiple tools, call one tool after another without responding to the user.
If a response requires information from an additional tool to generate a response, call the appropriate tools in order before responding to the user.
ONLY respond to questions using information from tool calls.
if no relevant information is found in the tool calls, respond, "Sorry, I don't know."
Be sure to adhere to any instructions in tool calls ie. if they say to responsd like "...", do exactly that.
If the relevant information is not a direct match to the users prompt, you can be creative in deducing the answer.
Keep responses short and concise. Answer in a single sentence where possible.
If you are unsure, use the getInformation tool and you can use common sense to reason based on the information you do have.
Use your abilities as a reasoning machine to answer questions based on the information you do have.
`,
...`, // truncated for brevity
tools: {
addResource: tool({
description: `add a resource to your knowledge base.
If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
parameters: z.object({
content: z
.string()
.describe("the content or resource to add to the knowledge base"),
}),
If the user provides a random piece of knowledge unprompted, use this tool without asking for confirmation.`,
parameters: addResourceParams,
execute: async ({ content }) => createResource({ content }),
}),
getInformation: tool({
description: `get information from your knowledge base to answer questions.`,
parameters: z.object({
question: z.string().describe("the users question"),
similarQuestions: z.array(z.string()).describe("keywords to search"),
}),
parameters: getInformationParams,
execute: async ({ similarQuestions }) => {
const results = await Promise.all(
similarQuestions.map(
async (question) => await findRelevantContent(question),
async (question: string) => await findRelevantContent(question),
),
);
// Flatten the array of arrays and remove duplicates based on 'name'
const uniqueResults = Array.from(
new Map(results.flat().map((item) => [item?.name, item])).values(),
);

return uniqueResults;
},
}),
understandQuery: tool({
description: `understand the users query. use this tool on every prompt.`,
parameters: z.object({
query: z.string().describe("the users query"),
toolsToCallInOrder: z
.array(z.string())
.describe(
"these are the tools you need to call in the order necessary to respond to the users query",
),
}),
parameters: understandQueryParams,
execute: async ({ query }) => {
const { object } = await generateObject({
model: openai("gpt-4o"),
Expand All @@ -79,7 +73,7 @@ export async function POST(req: Request) {
.describe("similar questions to the user's query. be concise."),
}),
prompt: `Analyze this query: "${query}". Provide the following:
3 similar questions that could help answer the user's query`,
3 similar questions that could help answer the user's query`,
});
return object.questions;
},
Expand Down
25 changes: 20 additions & 5 deletions lib/ai/embedding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ import { cosineDistance, desc, gt, sql } from "drizzle-orm";
import { embeddings } from "../db/schema/embeddings";
import { db } from "../db";

type Embedding = number[];

export interface Guide {
name: string;
similarity: number;
}

const embeddingModel = openai.embedding("text-embedding-ada-002");

const generateChunks = (input: string): string[] => {
Expand All @@ -15,7 +22,7 @@ const generateChunks = (input: string): string[] => {

export const generateEmbeddings = async (
value: string,
): Promise<Array<{ embedding: number[]; content: string }>> => {
): Promise<Array<{ embedding: Embedding; content: string }>> => {
const chunks = generateChunks(value);
const { embeddings } = await embedMany({
model: embeddingModel,
Expand All @@ -24,7 +31,7 @@ export const generateEmbeddings = async (
return embeddings.map((e, i) => ({ content: chunks[i], embedding: e }));
};

export const generateEmbedding = async (value: string): Promise<number[]> => {
export const generateEmbedding = async (value: string): Promise<Embedding> => {
const input = value.replaceAll("\n", " ");
const { embedding } = await embed({
model: embeddingModel,
Expand All @@ -33,14 +40,22 @@ export const generateEmbedding = async (value: string): Promise<number[]> => {
return embedding;
};

export const findRelevantContent = async (userQuery: string) => {
export const findRelevantContent = async (
userQuery: string,
): Promise<Guide[]> => {
const userQueryEmbedded = await generateEmbedding(userQuery);
const similarity = sql<number>`1 - (${cosineDistance(embeddings.embedding, userQueryEmbedded)})`;
const similarGuides = await db

const similarity = sql<number>`1 - (${cosineDistance(
embeddings.embedding,
userQueryEmbedded,
)})`;

const similarGuides: Guide[] = await db
.select({ name: embeddings.content, similarity })
.from(embeddings)
.where(gt(similarity, 0.3))
.orderBy((t) => desc(t.similarity))
.limit(4);

return similarGuides;
};