diff options
Diffstat (limited to 'apps/cf-ai-backend/src')
| -rw-r--r-- | apps/cf-ai-backend/src/OpenAIEmbedder.ts | 16 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/index.ts | 20 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/routes.ts | 16 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/routes/add.ts | 52 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/routes/ask.ts | 24 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/routes/chat.ts | 77 | ||||
| -rw-r--r-- | apps/cf-ai-backend/src/routes/query.ts | 58 |
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; } |