aboutsummaryrefslogtreecommitdiff
path: root/apps/cf-ai-backend/src
diff options
context:
space:
mode:
Diffstat (limited to 'apps/cf-ai-backend/src')
-rw-r--r--apps/cf-ai-backend/src/OpenAIEmbedder.ts16
-rw-r--r--apps/cf-ai-backend/src/index.ts20
-rw-r--r--apps/cf-ai-backend/src/routes.ts16
-rw-r--r--apps/cf-ai-backend/src/routes/add.ts52
-rw-r--r--apps/cf-ai-backend/src/routes/ask.ts24
-rw-r--r--apps/cf-ai-backend/src/routes/chat.ts77
-rw-r--r--apps/cf-ai-backend/src/routes/query.ts58
7 files changed, 140 insertions, 123 deletions
diff --git a/apps/cf-ai-backend/src/OpenAIEmbedder.ts b/apps/cf-ai-backend/src/OpenAIEmbedder.ts
index 35c36c74..e227d1e3 100644
--- a/apps/cf-ai-backend/src/OpenAIEmbedder.ts
+++ b/apps/cf-ai-backend/src/OpenAIEmbedder.ts
@@ -1,4 +1,4 @@
-import { AiTextGenerationOutput } from "@cloudflare/ai/dist/ai/tasks/text-generation";
+import { AiTextGenerationOutput } from '@cloudflare/ai/dist/ai/tasks/text-generation';
interface OpenAIEmbeddingsParams {
apiKey: string;
@@ -15,7 +15,7 @@ export class OpenAIEmbeddings {
}
async embedDocuments(texts: string[]): Promise<number[][]> {
- const responses = await Promise.all(texts.map(text => this.embedQuery(text)));
+ const responses = await Promise.all(texts.map((text) => this.embedQuery(text)));
return responses;
}
@@ -24,18 +24,18 @@ export class OpenAIEmbeddings {
method: 'POST',
headers: {
'Content-Type': 'application/json',
- 'Authorization': `Bearer ${this.apiKey}`
+ Authorization: `Bearer ${this.apiKey}`,
},
body: JSON.stringify({
input: text,
- model: this.modelName
- })
+ model: this.modelName,
+ }),
});
- const data = await response.json() as {
+ const data = (await response.json()) as {
data: {
- embedding: number[]
- }[]
+ embedding: number[];
+ }[];
};
return data.data[0].embedding;
diff --git a/apps/cf-ai-backend/src/index.ts b/apps/cf-ai-backend/src/index.ts
index f55c465b..ccaf06bd 100644
--- a/apps/cf-ai-backend/src/index.ts
+++ b/apps/cf-ai-backend/src/index.ts
@@ -1,15 +1,9 @@
-import type {
- VectorizeIndex,
- Fetcher,
- Request,
-} from "@cloudflare/workers-types";
-
-import {
- CloudflareVectorizeStore,
-} from "@langchain/cloudflare";
-import { OpenAIEmbeddings } from "./OpenAIEmbedder";
-import { GoogleGenerativeAI } from "@google/generative-ai";
-import routeMap from "./routes";
+import type { VectorizeIndex, Fetcher, Request } from '@cloudflare/workers-types';
+
+import { CloudflareVectorizeStore } from '@langchain/cloudflare';
+import { OpenAIEmbeddings } from './OpenAIEmbedder';
+import { GoogleGenerativeAI } from '@google/generative-ai';
+import routeMap from './routes';
function isAuthorized(request: Request, env: Env): boolean {
return request.headers.get('X-Custom-Auth-Key') === env.SECURITY_KEY;
@@ -32,7 +26,7 @@ export default {
const genAI = new GoogleGenerativeAI(env.GOOGLE_AI_API_KEY);
- const model = genAI.getGenerativeModel({ model: "gemini-pro" });
+ const model = genAI.getGenerativeModel({ model: 'gemini-pro' });
const url = new URL(request.url);
const path = url.pathname;
diff --git a/apps/cf-ai-backend/src/routes.ts b/apps/cf-ai-backend/src/routes.ts
index 841f107a..a8a3249a 100644
--- a/apps/cf-ai-backend/src/routes.ts
+++ b/apps/cf-ai-backend/src/routes.ts
@@ -1,14 +1,20 @@
import { CloudflareVectorizeStore } from '@langchain/cloudflare';
import * as apiAdd from './routes/add';
-import * as apiQuery from "./routes/query"
-import * as apiAsk from "./routes/ask"
-import * as apiChat from "./routes/chat"
+import * as apiQuery from './routes/query';
+import * as apiAsk from './routes/ask';
+import * as apiChat from './routes/chat';
import { OpenAIEmbeddings } from './OpenAIEmbedder';
import { GenerativeModel } from '@google/generative-ai';
import { Request } from '@cloudflare/workers-types';
-
-type RouteHandler = (request: Request, store: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env: Env, ctx?: ExecutionContext) => Promise<Response>;
+type RouteHandler = (
+ request: Request,
+ store: CloudflareVectorizeStore,
+ embeddings: OpenAIEmbeddings,
+ model: GenerativeModel,
+ env: Env,
+ ctx?: ExecutionContext,
+) => Promise<Response>;
const routeMap = new Map<string, Record<string, RouteHandler>>();
diff --git a/apps/cf-ai-backend/src/routes/add.ts b/apps/cf-ai-backend/src/routes/add.ts
index 9b05e9f0..b9a6da8f 100644
--- a/apps/cf-ai-backend/src/routes/add.ts
+++ b/apps/cf-ai-backend/src/routes/add.ts
@@ -1,36 +1,38 @@
-import { Request } from "@cloudflare/workers-types";
-import { type CloudflareVectorizeStore } from "@langchain/cloudflare";
+import { Request } from '@cloudflare/workers-types';
+import { type CloudflareVectorizeStore } from '@langchain/cloudflare';
export async function POST(request: Request, store: CloudflareVectorizeStore) {
- const body = await request.json() as {
- pageContent: string,
- title?: string,
- description?: string,
- space?: string,
- url: string,
- user: string
+ const body = (await request.json()) as {
+ pageContent: string;
+ title?: string;
+ description?: string;
+ space?: string;
+ url: string;
+ user: string;
};
if (!body.pageContent || !body.url) {
- return new Response(JSON.stringify({ message: "Invalid Page Content" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid Page Content' }), { status: 400 });
}
- const newPageContent = `Title: ${body.title}\nDescription: ${body.description}\nURL: ${body.url}\nContent: ${body.pageContent}`
+ const newPageContent = `Title: ${body.title}\nDescription: ${body.description}\nURL: ${body.url}\nContent: ${body.pageContent}`;
-
- await store.addDocuments([
- {
- pageContent: newPageContent,
- metadata: {
- title: body.title ?? "",
- description: body.description ?? "",
- space: body.space ?? "",
- url: body.url,
- user: body.user,
+ await store.addDocuments(
+ [
+ {
+ pageContent: newPageContent,
+ metadata: {
+ title: body.title ?? '',
+ description: body.description ?? '',
+ space: body.space ?? '',
+ url: body.url,
+ user: body.user,
+ },
},
+ ],
+ {
+ ids: [`${body.url}-${body.user}`],
},
- ], {
- ids: [`${body.url}`]
- })
+ );
- return new Response(JSON.stringify({ message: "Document Added" }), { status: 200 });
+ return new Response(JSON.stringify({ message: 'Document Added' }), { status: 200 });
}
diff --git a/apps/cf-ai-backend/src/routes/ask.ts b/apps/cf-ai-backend/src/routes/ask.ts
index 1c48dde8..267c1513 100644
--- a/apps/cf-ai-backend/src/routes/ask.ts
+++ b/apps/cf-ai-backend/src/routes/ask.ts
@@ -1,18 +1,18 @@
-import { GenerativeModel } from "@google/generative-ai";
-import { OpenAIEmbeddings } from "../OpenAIEmbedder";
-import { CloudflareVectorizeStore } from "@langchain/cloudflare";
-import { Request } from "@cloudflare/workers-types";
+import { GenerativeModel } from '@google/generative-ai';
+import { OpenAIEmbeddings } from '../OpenAIEmbedder';
+import { CloudflareVectorizeStore } from '@langchain/cloudflare';
+import { Request } from '@cloudflare/workers-types';
export async function POST(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
- const body = await request.json() as {
- query: string
+ const body = (await request.json()) as {
+ query: string;
};
if (!body.query) {
- return new Response(JSON.stringify({ message: "Invalid Page Content" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid Page Content' }), { status: 400 });
}
- const prompt = `You are an agent that answers a question based on the query. don't say 'based on the context'.\n\n Context:\n${body.query} \nAnswer this question based on the context. Question: ${body.query}\nAnswer:`
+ const prompt = `You are an agent that answers a question based on the query. don't say 'based on the context'.\n\n Context:\n${body.query} \nAnswer this question based on the context. Question: ${body.query}\nAnswer:`;
const output = await model.generateContentStream(prompt);
const response = new Response(
@@ -22,14 +22,14 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
for await (const chunk of output.stream) {
const chunkText = await chunk.text();
console.log(chunkText);
- const encodedChunk = converter.encode("data: " + JSON.stringify({ "response": chunkText }) + "\n\n");
+ const encodedChunk = converter.encode('data: ' + JSON.stringify({ response: chunkText }) + '\n\n');
controller.enqueue(encodedChunk);
}
- const doneChunk = converter.encode("data: [DONE]");
+ const doneChunk = converter.encode('data: [DONE]');
controller.enqueue(doneChunk);
controller.close();
- }
- })
+ },
+ }),
);
return response;
}
diff --git a/apps/cf-ai-backend/src/routes/chat.ts b/apps/cf-ai-backend/src/routes/chat.ts
index 95788d03..75e298b8 100644
--- a/apps/cf-ai-backend/src/routes/chat.ts
+++ b/apps/cf-ai-backend/src/routes/chat.ts
@@ -1,28 +1,28 @@
-import { Content, GenerativeModel } from "@google/generative-ai";
-import { OpenAIEmbeddings } from "../OpenAIEmbedder";
-import { CloudflareVectorizeStore } from "@langchain/cloudflare";
-import { Request } from "@cloudflare/workers-types";
+import { Content, GenerativeModel } from '@google/generative-ai';
+import { OpenAIEmbeddings } from '../OpenAIEmbedder';
+import { CloudflareVectorizeStore } from '@langchain/cloudflare';
+import { Request } from '@cloudflare/workers-types';
export async function POST(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
const queryparams = new URL(request.url).searchParams;
- const query = queryparams.get("q");
- const topK = parseInt(queryparams.get("topK") ?? "5");
- const user = queryparams.get("user")
- const spaces = queryparams.get("spaces")
- const spacesArray = spaces ? spaces.split(",") : undefined
+ const query = queryparams.get('q');
+ const topK = parseInt(queryparams.get('topK') ?? '5');
+ const user = queryparams.get('user');
+ const spaces = queryparams.get('spaces');
+ const spacesArray = spaces ? spaces.split(',') : undefined;
- const sourcesOnly = (queryparams.get("sourcesOnly") ?? "false")
+ const sourcesOnly = queryparams.get('sourcesOnly') ?? 'false';
if (!user) {
- return new Response(JSON.stringify({ message: "Invalid User" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid User' }), { status: 400 });
}
if (!query) {
- return new Response(JSON.stringify({ message: "Invalid Query" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid Query' }), { status: 400 });
}
const filter: VectorizeVectorMetadataFilter = {
- user
- }
+ user,
+ };
const responses: VectorizeMatches = { matches: [], count: 0 };
@@ -34,12 +34,12 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
const resp = await env!.VECTORIZE_INDEX.query(queryAsVector, {
topK,
- filter
+ filter,
});
if (resp.count > 0) {
- responses.matches.push(...resp.matches)
- responses.count += resp.count
+ responses.matches.push(...resp.matches);
+ responses.count += resp.count;
}
}
} else {
@@ -47,13 +47,13 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
const resp = await env!.VECTORIZE_INDEX.query(queryAsVector, {
topK,
filter: {
- user
- }
+ user,
+ },
});
if (resp.count > 0) {
- responses.matches.push(...resp.matches)
- responses.count += resp.count
+ responses.matches.push(...resp.matches);
+ responses.count += resp.count;
}
}
@@ -61,27 +61,36 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
// return new Response(JSON.stringify({ message: "No Results Found" }), { status: 404 });
// }
- const highScoreIds = responses.matches.filter(({ score }) => score > 0.35).map(({ id }) => id)
+ const highScoreIds = responses.matches.filter(({ score }) => score > 0.35).map(({ id }) => id);
- if (sourcesOnly === "true") {
+ if (sourcesOnly === 'true') {
return new Response(JSON.stringify({ ids: highScoreIds }), { status: 200 });
}
- const vec = await env!.VECTORIZE_INDEX.getByIds(highScoreIds)
+ const vec = await env!.VECTORIZE_INDEX.getByIds(highScoreIds);
- const preparedContext = vec.map(({ metadata }) => `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`).join("\n\n");
+ const preparedContext = vec
+ .map(
+ ({ metadata }) =>
+ `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
+ )
+ .join('\n\n');
- const body = await request.json() as {
- chatHistory?: Content[]
+ const body = (await request.json()) as {
+ chatHistory?: Content[];
};
const defaultHistory = [
{
- role: "user",
- parts: [{ text: `You are an agent that summarizes a page based on the query. don't say 'based on the context'. I expect you to be like a 'Second Brain'. you will be provided with the context (old saved posts) and questions. Answer accordingly. Answer in markdown format` }],
+ role: 'user',
+ parts: [
+ {
+ text: `You are an agent that summarizes a page based on the query. don't say 'based on the context'. I expect you to be like a 'Second Brain'. you will be provided with the context (old saved posts) and questions. Answer accordingly. Answer in markdown format`,
+ },
+ ],
},
{
- role: "model",
+ role: 'model',
parts: [{ text: "Ok, I am a personal assistant, and will act as a second brain to help with user's queries." }],
},
] as Content[];
@@ -100,14 +109,14 @@ export async function POST(request: Request, _: CloudflareVectorizeStore, embedd
const converter = new TextEncoder();
for await (const chunk of output.stream) {
const chunkText = await chunk.text();
- const encodedChunk = converter.encode("data: " + JSON.stringify({ "response": chunkText }) + "\n\n");
+ const encodedChunk = converter.encode('data: ' + JSON.stringify({ response: chunkText }) + '\n\n');
controller.enqueue(encodedChunk);
}
- const doneChunk = converter.encode("data: [DONE]");
+ const doneChunk = converter.encode('data: [DONE]');
controller.enqueue(doneChunk);
controller.close();
- }
- })
+ },
+ }),
);
return response;
}
diff --git a/apps/cf-ai-backend/src/routes/query.ts b/apps/cf-ai-backend/src/routes/query.ts
index be237d7d..cd5295c5 100644
--- a/apps/cf-ai-backend/src/routes/query.ts
+++ b/apps/cf-ai-backend/src/routes/query.ts
@@ -1,59 +1,65 @@
-import { GenerativeModel } from "@google/generative-ai";
-import { OpenAIEmbeddings } from "../OpenAIEmbedder";
-import { CloudflareVectorizeStore } from "@langchain/cloudflare";
-import { Request } from "@cloudflare/workers-types";
+import { GenerativeModel } from '@google/generative-ai';
+import { OpenAIEmbeddings } from '../OpenAIEmbedder';
+import { CloudflareVectorizeStore } from '@langchain/cloudflare';
+import { Request } from '@cloudflare/workers-types';
export async function GET(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
const queryparams = new URL(request.url).searchParams;
- const query = queryparams.get("q");
- const topK = parseInt(queryparams.get("topK") ?? "5");
- const user = queryparams.get("user")
- const space = queryparams.get("space")
+ const query = queryparams.get('q');
+ const topK = parseInt(queryparams.get('topK') ?? '5');
+ const user = queryparams.get('user');
+ const space = queryparams.get('space');
- const sourcesOnly = (queryparams.get("sourcesOnly") ?? "false")
+ const sourcesOnly = queryparams.get('sourcesOnly') ?? 'false';
if (!user) {
- return new Response(JSON.stringify({ message: "Invalid User" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid User' }), { status: 400 });
}
if (!query) {
- return new Response(JSON.stringify({ message: "Invalid Query" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'Invalid Query' }), { status: 400 });
}
const filter: VectorizeVectorMetadataFilter = {
- user
- }
+ user,
+ };
if (space) {
- filter.space
+ filter.space;
}
const queryAsVector = await embeddings.embedQuery(query);
const resp = await env!.VECTORIZE_INDEX.query(queryAsVector, {
topK,
- filter
+ filter,
});
if (resp.count === 0) {
- return new Response(JSON.stringify({ message: "No Results Found" }), { status: 404 });
+ return new Response(JSON.stringify({ message: 'No Results Found' }), { status: 404 });
}
- const highScoreIds = resp.matches.filter(({ score }) => score > 0.3).map(({ id }) => id)
+ const highScoreIds = resp.matches.filter(({ score }) => score > 0.3).map(({ id }) => id);
- if (sourcesOnly === "true") {
+ if (sourcesOnly === 'true') {
return new Response(JSON.stringify({ ids: highScoreIds }), { status: 200 });
}
- const vec = await env!.VECTORIZE_INDEX.getByIds(highScoreIds)
+ const vec = await env!.VECTORIZE_INDEX.getByIds(highScoreIds);
if (vec.length === 0 || !vec[0].metadata) {
- return new Response(JSON.stringify({ message: "No Results Found" }), { status: 400 });
+ return new Response(JSON.stringify({ message: 'No Results Found' }), { status: 400 });
}
- const preparedContext = vec.slice(0, 3).map(({ metadata }) => `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`).join("\n\n");
+ const preparedContext = vec
+ .slice(0, 3)
+ .map(
+ ({ metadata }) =>
+ `Website title: ${metadata!.title}\nDescription: ${metadata!.description}\nURL: ${metadata!.url}\nContent: ${metadata!.text}`,
+ )
+ .join('\n\n');
- const prompt = `You are an agent that summarizes a page based on the query. Be direct and concise, don't say 'based on the context'.\n\n Context:\n${preparedContext} \nAnswer this question based on the context. Question: ${query}\nAnswer:`
+ const prompt = `You are an agent that summarizes a page based on the query. Be direct and concise, don't say 'based on the context'.\n\n Context:\n${preparedContext} \nAnswer this question based on the context. Question: ${query}\nAnswer:`;
const output = await model.generateContentStream(prompt);
const response = new Response(
@@ -62,14 +68,14 @@ export async function GET(request: Request, _: CloudflareVectorizeStore, embeddi
const converter = new TextEncoder();
for await (const chunk of output.stream) {
const chunkText = await chunk.text();
- const encodedChunk = converter.encode("data: " + JSON.stringify({ "response": chunkText }) + "\n\n");
+ const encodedChunk = converter.encode('data: ' + JSON.stringify({ response: chunkText }) + '\n\n');
controller.enqueue(encodedChunk);
}
- const doneChunk = converter.encode("data: [DONE]");
+ const doneChunk = converter.encode('data: [DONE]');
controller.enqueue(doneChunk);
controller.close();
- }
- })
+ },
+ }),
);
return response;
}