├── .dev.vars-example ├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── data.ndjson ├── e2e └── api.test.ts ├── package.json ├── src ├── apps │ ├── auth │ │ ├── index.ts │ │ └── routes │ │ │ └── login.ts │ ├── chat │ │ ├── index.ts │ │ └── routes │ │ │ ├── code_llama.ts │ │ │ ├── llama.ts │ │ │ └── openai.ts │ ├── image │ │ ├── index.ts │ │ └── routes │ │ │ └── image.ts │ ├── search │ │ └── index.ts │ └── transcribe │ │ ├── index.ts │ │ └── routes │ │ └── whisper.ts ├── bindings.ts ├── chains │ ├── code_llama_chain.ts │ ├── llama_chain.ts │ └── openai_chain.ts ├── index.ts ├── services │ └── image.ts └── utils │ └── getGatewayUrl.ts ├── tsconfig.json ├── vitest.config.ts └── wrangler.toml /.dev.vars-example: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY = "" 2 | CLOUDFLARE_ACCOUNT_ID = "" 3 | CLOUDFLARE_API_TOKEN = "" 4 | PALM_API_KEY 5 | JWT_SECRET="" 6 | AUTH_EMAIL="" 7 | AUTH_PASSWORD="" 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | # editorconfig-tools is unable to ignore longs strings or urls 11 | max_line_length = off 12 | 13 | [CHANGELOG.md] 14 | indent_size = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .wrangler 4 | .dev.vars 5 | *.ndjson 6 | 7 | # Change them to your taste: 8 | package-lock.json 9 | yarn.lock 10 | pnpm-lock.yaml 11 | 12 | # Created by https://www.toptal.com/developers/gitignore/api/macos,linux,windows 13 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,linux,windows 14 | 15 | ### Linux ### 16 | *~ 17 | 18 | # temporary files which can be created if a process still has a handle open of a deleted file 19 | .fuse_hidden* 20 | 21 | # KDE directory preferences 22 | .directory 23 | 24 | # Linux trash folder which might appear on any partition or disk 25 | .Trash-* 26 | 27 | # .nfs files are created when an open file is removed but is still being accessed 28 | .nfs* 29 | 30 | ### macOS ### 31 | # General 32 | .DS_Store 33 | .AppleDouble 34 | .LSOverride 35 | 36 | # Icon must end with two \r 37 | Icon 38 | 39 | 40 | # Thumbnails 41 | ._* 42 | 43 | # Files that might appear in the root of a volume 44 | .DocumentRevisions-V100 45 | .fseventsd 46 | .Spotlight-V100 47 | .TemporaryItems 48 | .Trashes 49 | .VolumeIcon.icns 50 | .com.apple.timemachine.donotpresent 51 | 52 | # Directories potentially created on remote AFP share 53 | .AppleDB 54 | .AppleDesktop 55 | Network Trash Folder 56 | Temporary Items 57 | .apdisk 58 | 59 | ### macOS Patch ### 60 | # iCloud generated files 61 | *.icloud 62 | 63 | ### Windows ### 64 | # Windows thumbnail cache files 65 | Thumbs.db 66 | Thumbs.db:encryptable 67 | ehthumbs.db 68 | ehthumbs_vista.db 69 | 70 | # Dump file 71 | *.stackdump 72 | 73 | # Folder config file 74 | [Dd]esktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Windows Installer files 80 | *.cab 81 | *.msi 82 | *.msix 83 | *.msm 84 | *.msp 85 | 86 | # Windows shortcuts 87 | *.lnk 88 | 89 | # End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows 90 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "peacock.color": "green", 3 | "workbench.colorCustomizations": { 4 | "activityBar.activeBackground": "#00b300", 5 | "activityBar.background": "#00b300", 6 | "activityBar.foreground": "#e7e7e7", 7 | "activityBar.inactiveForeground": "#e7e7e799", 8 | "activityBarBadge.background": "#dfdfff", 9 | "activityBarBadge.foreground": "#15202b", 10 | "commandCenter.border": "#e7e7e799", 11 | "sash.hoverBorder": "#00b300", 12 | "statusBar.background": "#008000", 13 | "statusBar.foreground": "#e7e7e7", 14 | "statusBarItem.hoverBackground": "#00b300", 15 | "statusBarItem.remoteBackground": "#008000", 16 | "statusBarItem.remoteForeground": "#e7e7e7", 17 | "titleBar.activeBackground": "#008000", 18 | "titleBar.activeForeground": "#e7e7e7", 19 | "titleBar.inactiveBackground": "#00800099", 20 | "titleBar.inactiveForeground": "#e7e7e799" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | npm install 3 | npm run dev 4 | ``` 5 | 6 | ``` 7 | npm run deploy 8 | ``` 9 | 10 | ``` 11 | npx wrangler vectorize create movies_index --dimensions=768 --metric=cosine 12 | npx wrangler vectorize delete movies_index 13 | npx wrangler vectorize insert movies_index --file=data.ndjson 14 | ``` 15 | -------------------------------------------------------------------------------- /e2e/api.test.ts: -------------------------------------------------------------------------------- 1 | import app from "@src/index"; 2 | import { test, expect, describe } from "vitest"; 3 | 4 | describe("Hello ", () => { 5 | test("GET /", async () => { 6 | const res = await app.request("/"); 7 | expect(res.status).toBe(200); 8 | expect(await res.text()).toBe("Hello!"); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "scripts": { 4 | "dev": "wrangler dev src/index.ts --remote", 5 | "deploy": "wrangler deploy --minify src/index.ts", 6 | "test": "vitest run", 7 | "test:coverage": "vitest run --coverage" 8 | }, 9 | "dependencies": { 10 | "@cloudflare/ai": "1.0.36", 11 | "@hono/swagger-ui": "0.2.1", 12 | "@hono/zod-openapi": "0.9.5", 13 | "hono": "3.12.0", 14 | "langchain": "0.0.198" 15 | }, 16 | "devDependencies": { 17 | "@cloudflare/workers-types": "4.20231121.0", 18 | "@vitest/coverage-v8": "0.34.6", 19 | "vitest": "0.34.6", 20 | "wrangler": "3.17.1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/apps/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIHono } from "@hono/zod-openapi"; 2 | import { sign } from 'hono/jwt' 3 | import { Bindings } from "@src/bindings"; 4 | import loginRoute from "./routes/login"; 5 | 6 | const app = new OpenAPIHono<{ Bindings: Bindings }>(); 7 | 8 | app.openapi(loginRoute, async (c) => { 9 | const email = c.env.AUTH_EMAIL; 10 | const password = c.env.AUTH_PASSWORD; 11 | const data = await c.req.json(); 12 | if (data.email === email && data.password === password) { 13 | const secret = c.env.JWT_SECRET; 14 | const access_token = await sign({sub: email}, secret); 15 | return c.json({ access_token }, 200); 16 | } else { 17 | return c.json({ message: "Invalid Credentials" }, 401); 18 | } 19 | }); 20 | 21 | export default app; 22 | -------------------------------------------------------------------------------- /src/apps/auth/routes/login.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from "@hono/zod-openapi"; 2 | 3 | const BodySchema = z.object({ 4 | email: z.string(), 5 | password: z.string(), 6 | }); 7 | 8 | const ResponseSchema = z.object({ 9 | access_token: z.string(), 10 | }); 11 | 12 | 13 | const ErrorSchema = z.object({ 14 | message: z.string(), 15 | }); 16 | 17 | const route = createRoute({ 18 | method: "post", 19 | path: "/login", 20 | request: { 21 | body: { 22 | content: { 23 | "application/json": { 24 | schema: BodySchema, 25 | }, 26 | }, 27 | }, 28 | }, 29 | responses: { 30 | 200: { 31 | description: "Access Token", 32 | content: { 33 | "application/json": { 34 | schema: ResponseSchema, 35 | }, 36 | }, 37 | }, 38 | 401: { 39 | description: "Invalid Credentials", 40 | content: { 41 | "application/json": { 42 | schema: ErrorSchema, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }); 48 | 49 | export default route; 50 | -------------------------------------------------------------------------------- /src/apps/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIHono } from "@hono/zod-openapi"; 2 | import { Bindings } from "@src/bindings"; 3 | import { buildLlamaChain } from "@src/chains/llama_chain"; 4 | import { buildCodeLlamaChain } from "@src/chains/code_llama_chain"; 5 | import { buildOpenAIChain } from "@src/chains/openai_chain"; 6 | import { buildCodeLlamaChain } from "@src/chains/code_llama_chain"; 7 | import llamaRoute from "./routes/llama"; 8 | import codeLlamaRoute from "./routes/code_llama"; 9 | import openAiRoute from "./routes/openai"; 10 | 11 | const app = new OpenAPIHono<{ Bindings: Bindings }>(); 12 | 13 | app.openapi(codeLlamaRoute, async (c) => { 14 | const { message } = await c.req.json(); 15 | const chain = await buildCodeLlamaChain({ 16 | cloudflareAccountId: c.env.CLOUDFLARE_ACCOUNT_ID, 17 | cloudflareApiToken: c.env.CLOUDFLARE_API_TOKEN, 18 | baseUrl: c.env.AI_GATEWAY_URL, 19 | }); 20 | const inputs = { input: message }; 21 | const responseMsg = await chain.invoke(inputs); 22 | return c.json({ message: responseMsg }, 201); 23 | }); 24 | 25 | app.openapi(llamaRoute, async (c) => { 26 | const { message } = await c.req.json(); 27 | const chain = await buildLlamaChain({ 28 | cloudflareAccountId: c.env.CLOUDFLARE_ACCOUNT_ID, 29 | cloudflareApiToken: c.env.CLOUDFLARE_API_TOKEN, 30 | baseUrl: c.env.AI_GATEWAY_URL, 31 | }); 32 | const inputs = { input: message }; 33 | const responseMsg = await chain.invoke(inputs); 34 | return c.json({ message: responseMsg }, 201); 35 | }); 36 | 37 | app.openapi(codeLlamaRoute, async (c) => { 38 | const { message } = await c.req.json(); 39 | const chain = await buildCodeLlamaChain({ 40 | cloudflareAccountId: c.env.CLOUDFLARE_ACCOUNT_ID, 41 | cloudflareApiToken: c.env.CLOUDFLARE_API_TOKEN, 42 | baseUrl: c.env.AI_GATEWAY_URL, 43 | }); 44 | const inputs = { input: message }; 45 | const responseMsg = await chain.invoke(inputs); 46 | return c.json({ message: responseMsg }, 201); 47 | }); 48 | 49 | app.openapi(openAiRoute, async (c) => { 50 | const { message } = await c.req.json(); 51 | const chain = await buildOpenAIChain({ 52 | openAIApiKey: c.env.OPENAI_API_KEY, 53 | }); 54 | const inputs = { input: message }; 55 | const responseMsg = await chain.invoke(inputs); 56 | return c.json({ message: responseMsg }, 201); 57 | }); 58 | 59 | export default app; 60 | -------------------------------------------------------------------------------- /src/apps/chat/routes/code_llama.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from '@hono/zod-openapi'; 2 | 3 | const BodySchema = z.object({ 4 | message: z.string() 5 | }); 6 | 7 | const ResponseSchema = z.object({ 8 | message: z.string() 9 | }); 10 | 11 | const route = createRoute({ 12 | method: 'post', 13 | path: '/code', 14 | request: { 15 | body: { 16 | content: { 17 | 'application/json': { 18 | schema: BodySchema, 19 | }, 20 | }, 21 | }, 22 | }, 23 | responses: { 24 | 200: { 25 | content: { 26 | 'application/json': { 27 | schema: ResponseSchema, 28 | }, 29 | }, 30 | description: 'Lamma completion', 31 | }, 32 | }, 33 | }); 34 | 35 | 36 | export default route; 37 | -------------------------------------------------------------------------------- /src/apps/chat/routes/llama.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from '@hono/zod-openapi'; 2 | 3 | const BodySchema = z.object({ 4 | message: z.string() 5 | }); 6 | 7 | const ResponseSchema = z.object({ 8 | message: z.string() 9 | }); 10 | 11 | const route = createRoute({ 12 | method: 'post', 13 | path: '/llama', 14 | request: { 15 | body: { 16 | content: { 17 | 'application/json': { 18 | schema: BodySchema, 19 | }, 20 | }, 21 | }, 22 | }, 23 | responses: { 24 | 200: { 25 | content: { 26 | 'application/json': { 27 | schema: ResponseSchema, 28 | }, 29 | }, 30 | description: 'Lamma completion', 31 | }, 32 | }, 33 | }); 34 | 35 | export default route; 36 | -------------------------------------------------------------------------------- /src/apps/chat/routes/openai.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from '@hono/zod-openapi'; 2 | 3 | const BodySchema = z.object({ 4 | message: z.string() 5 | }); 6 | 7 | const ResponseSchema = z.object({ 8 | message: z.string() 9 | }); 10 | 11 | const route = createRoute({ 12 | method: 'post', 13 | path: '/openai', 14 | request: { 15 | body: { 16 | content: { 17 | 'application/json': { 18 | schema: BodySchema, 19 | }, 20 | }, 21 | }, 22 | }, 23 | responses: { 24 | 200: { 25 | content: { 26 | 'application/json': { 27 | schema: ResponseSchema, 28 | }, 29 | }, 30 | description: 'Lamma completion', 31 | }, 32 | }, 33 | }); 34 | 35 | 36 | export default route; 37 | -------------------------------------------------------------------------------- /src/apps/image/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIHono } from "@hono/zod-openapi"; 2 | import { Bindings } from "@src/bindings"; 3 | import { generateImage } from "@src/services/image"; 4 | import { Buffer } from 'node:buffer'; 5 | import imageRoute from "./routes/image"; 6 | 7 | const app = new OpenAPIHono<{ Bindings: Bindings }>(); 8 | 9 | app.openapi(imageRoute, async (c) => { 10 | const { prompt, num_steps } = await c.req.json(); 11 | const imageArray = await generateImage({ prompt, num_steps, AI: c.env.AI }); 12 | return c.json({ 13 | content_type: "image/png", 14 | format: "base64", 15 | content: Buffer.from(imageArray).toString("base64"), 16 | }, 201); 17 | }); 18 | 19 | export default app; 20 | -------------------------------------------------------------------------------- /src/apps/image/routes/image.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from '@hono/zod-openapi'; 2 | 3 | const BodySchema = z.object({ 4 | prompt: z.string(), 5 | num_steps: z.number() 6 | }); 7 | 8 | const ResponseSchema = z.object({ 9 | content_type: z.string(), 10 | format: z.string(), 11 | content: z.string() 12 | }); 13 | 14 | const route = createRoute({ 15 | method: 'post', 16 | path: '', 17 | request: { 18 | body: { 19 | content: { 20 | 'application/json': { 21 | schema: BodySchema, 22 | }, 23 | }, 24 | }, 25 | }, 26 | responses: { 27 | 200: { 28 | content: { 29 | 'application/json': { 30 | schema: ResponseSchema, 31 | }, 32 | }, 33 | description: 'Generate Image', 34 | }, 35 | }, 36 | }); 37 | 38 | 39 | export default route; 40 | -------------------------------------------------------------------------------- /src/apps/search/index.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono'; 2 | import { Bindings } from "@src/bindings"; 3 | 4 | const app = new Hono<{ Bindings: Bindings }>(); 5 | 6 | app.get('/', async (c) => { 7 | const index = c.env.VECTORIZE_INDEX; 8 | const query = c.req.query('query'); 9 | const api = c.env.PALM_API_KEY; 10 | const url = `https://generativelanguage.googleapis.com/v1beta2/models/embedding-gecko-001:embedText?key=${api}`; 11 | const response = await fetch(url, { 12 | method: 'POST', 13 | body: JSON.stringify({ 14 | text: query 15 | }), 16 | headers: { 17 | 'Content-Type': 'application/json' 18 | } 19 | }); 20 | if (!response.ok) { 21 | throw new Error('Network response was not ok'); 22 | } 23 | const data = await response.json() as Record; 24 | const vector = data?.embedding?.value ?? null; 25 | if (!vector) { 26 | throw new Error('No vector'); 27 | } 28 | const matches = await index.query(vector, { topK: 5, returnMetadata: true }); 29 | return c.json(matches); 30 | }) 31 | 32 | export default app; 33 | -------------------------------------------------------------------------------- /src/apps/transcribe/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIHono, z } from "@hono/zod-openapi"; 2 | import { Bindings } from "@src/bindings"; 3 | import whisperRoute from "./routes/whisper"; 4 | 5 | const app = new OpenAPIHono<{ Bindings: Bindings }>(); 6 | 7 | app.openapi(whisperRoute, async (c) => { 8 | const data = await c.req.formData(); 9 | const audioFile = data.get("audio") as unknown as File; 10 | 11 | const blob = await audioFile.arrayBuffer(); 12 | const audio = [...new Uint8Array(blob)]; 13 | 14 | const gatewayUrl = `${c.env.AI_GATEWAY_URL}/workers-ai/@cf/openai/whisper`; 15 | try { 16 | const request = await fetch(gatewayUrl, { 17 | method: "POST", 18 | headers: { 19 | "Content-Type": "application/json", 20 | Authorization: `Bearer ${c.env.CF_API_TOKEN}`, 21 | }, 22 | body: JSON.stringify({ audio }), 23 | }); 24 | if (!request.ok) { 25 | const detail = await request.text(); 26 | return c.json({ error: request.statusText, detail }, request.status); 27 | } 28 | const response = (await request.json()) as { result: { text: string } }; 29 | return c.json({ transcribe: response.result.text }, 200); 30 | } catch (error) { 31 | if (error instanceof SyntaxError) { 32 | return c.json({ error: "Invalid JSON" }, 500); 33 | } 34 | return c.json({ error: error }, 500); 35 | } 36 | }); 37 | 38 | export default app; 39 | -------------------------------------------------------------------------------- /src/apps/transcribe/routes/whisper.ts: -------------------------------------------------------------------------------- 1 | import { z, createRoute } from "@hono/zod-openapi"; 2 | 3 | const ACCEPTED_AUDIO_TYPES = ["audio/wave", "audio/wav"]; 4 | 5 | export const BodySchema = z.object({ 6 | audio: z 7 | .any({ description: "Audio file in wav format for transcription" }) 8 | .refine((file) => file, "the file is required") 9 | .refine((file) => { 10 | if (!file) return false; 11 | 12 | return ACCEPTED_AUDIO_TYPES.includes(file.type); 13 | }, "only accepted audio types are wav"), 14 | }); 15 | 16 | const ResponseSchema = z.object({ 17 | transcribe: z.string(), 18 | }); 19 | 20 | const ErrorSchema = z.object({ 21 | error: z.any(), 22 | }); 23 | 24 | const route = createRoute({ 25 | method: "post", 26 | path: "/whisper", 27 | request: { 28 | body: { 29 | content: { 30 | "multipart/form-data": { 31 | schema: BodySchema, 32 | }, 33 | }, 34 | }, 35 | }, 36 | responses: { 37 | 201: { 38 | description: "Audio transcription", 39 | content: { 40 | "application/json": { 41 | schema: ResponseSchema, 42 | }, 43 | }, 44 | }, 45 | 500: { 46 | description: "Error", 47 | content: { 48 | "application/json": { 49 | schema: ErrorSchema, 50 | }, 51 | }, 52 | }, 53 | }, 54 | }); 55 | 56 | export default route; 57 | -------------------------------------------------------------------------------- /src/bindings.ts: -------------------------------------------------------------------------------- 1 | export type Bindings = { 2 | OPENAI_API_KEY: string; 3 | AI_GATEWAY_URL: string; 4 | AI: any; 5 | VECTORIZE_INDEX: VectorizeIndex; 6 | PALM_API_KEY: string; 7 | JWT_SECRET: string; 8 | AUTH_EMAIL: string; 9 | AUTH_PASSWORD: string; 10 | CF_ACCOUNT_ID: string; 11 | CF_API_TOKEN: string; 12 | CF_AI_GATEWAY_ENDPOINT: string; 13 | CF_AI_GATEWAY_SLUG: string; 14 | CF_AI_GATEWAY_PROVIDER: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/chains/code_llama_chain.ts: -------------------------------------------------------------------------------- 1 | import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts"; 2 | import { StringOutputParser } from "langchain/schema/output_parser"; 3 | import { RunnableSequence } from "langchain/schema/runnable"; 4 | import { BufferWindowMemory } from "langchain/memory"; 5 | import { ChatCloudflareWorkersAI } from "langchain/chat_models/cloudflare_workersai"; 6 | 7 | type Params = { 8 | cloudflareAccountId: string; 9 | cloudflareApiToken: string; 10 | baseUrl: string; 11 | }; 12 | 13 | export const buildCodeLlamaChain = async ({ cloudflareAccountId, cloudflareApiToken, baseUrl }: Params) => { 14 | const model = new ChatCloudflareWorkersAI({ 15 | model: "@hf/thebloke/codellama-7b-instruct-awq", 16 | cloudflareAccountId, 17 | cloudflareApiToken, 18 | baseUrl 19 | }); 20 | 21 | const prompt = buildPrompt(); 22 | 23 | const memory = new BufferWindowMemory({ 24 | k: 5, 25 | returnMessages: true, 26 | inputKey: "input", 27 | outputKey: "output", 28 | memoryKey: "history", 29 | }); 30 | const outputParser = new StringOutputParser(); 31 | 32 | return RunnableSequence.from([ 33 | { 34 | input: (initialInput) => initialInput.input, 35 | memory: () => memory.loadMemoryVariables({}), 36 | }, 37 | { 38 | input: (previousOutput) => previousOutput.input, 39 | history: (previousOutput) => previousOutput.memory.history, 40 | }, 41 | prompt, 42 | model, 43 | outputParser 44 | ]); 45 | }; 46 | 47 | const systemPrompt = `Tu eres un bot experto en Angular`; 48 | 49 | const buildPrompt = () => { 50 | const prompt = ChatPromptTemplate.fromMessages([ 51 | ["system", systemPrompt], 52 | new MessagesPlaceholder("history"), 53 | ["human", "{input}"] 54 | ]); 55 | return prompt; 56 | } 57 | -------------------------------------------------------------------------------- /src/chains/llama_chain.ts: -------------------------------------------------------------------------------- 1 | import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts"; 2 | import { StringOutputParser } from "langchain/schema/output_parser"; 3 | import { RunnableSequence } from "langchain/schema/runnable"; 4 | import { BufferWindowMemory } from "langchain/memory"; 5 | import { ChatCloudflareWorkersAI } from "langchain/chat_models/cloudflare_workersai"; 6 | 7 | type Params = { 8 | cloudflareAccountId: string; 9 | cloudflareApiToken: string; 10 | baseUrl: string; 11 | }; 12 | 13 | export const buildLlamaChain = async ({ cloudflareAccountId, cloudflareApiToken, baseUrl }: Params) => { 14 | const model = new ChatCloudflareWorkersAI({ 15 | model: "@cf/meta/llama-2-7b-chat-int8", 16 | cloudflareAccountId, 17 | cloudflareApiToken, 18 | // baseUrl 19 | }); 20 | 21 | const prompt = ChatPromptTemplate.fromMessages([ 22 | ["system", "Tu eres un bot expeto en Angular y tu idioma es en español"], 23 | new MessagesPlaceholder("history"), 24 | ["human", "{input}"] 25 | ]); 26 | 27 | const memory = new BufferWindowMemory({ 28 | k: 5, 29 | returnMessages: true, 30 | inputKey: "input", 31 | outputKey: "output", 32 | memoryKey: "history", 33 | }); 34 | const outputParser = new StringOutputParser(); 35 | 36 | return RunnableSequence.from([ 37 | { 38 | input: (initialInput) => initialInput.input, 39 | memory: () => memory.loadMemoryVariables({}), 40 | }, 41 | { 42 | input: (previousOutput) => previousOutput.input, 43 | history: (previousOutput) => previousOutput.memory.history, 44 | }, 45 | prompt, 46 | model, 47 | outputParser 48 | ]); 49 | }; 50 | -------------------------------------------------------------------------------- /src/chains/openai_chain.ts: -------------------------------------------------------------------------------- 1 | import { ChatPromptTemplate, MessagesPlaceholder } from "langchain/prompts"; 2 | import { StringOutputParser } from "langchain/schema/output_parser"; 3 | import { RunnableSequence } from "langchain/schema/runnable"; 4 | import { BufferWindowMemory } from "langchain/memory"; 5 | import { ChatOpenAI } from "langchain/chat_models/openai"; 6 | 7 | type Params = { 8 | openAIApiKey: string; 9 | }; 10 | 11 | export const buildOpenAIChain = async ({ openAIApiKey }: Params) => { 12 | const model = new ChatOpenAI({ 13 | modelName: "gpt-3.5-turbo", 14 | openAIApiKey: openAIApiKey, 15 | }); 16 | 17 | const prompt = ChatPromptTemplate.fromMessages([ 18 | ["system", "Tu eres un bot expeto en Angular"], 19 | new MessagesPlaceholder("history"), 20 | ["human", "{input}"] 21 | ]); 22 | 23 | const memory = new BufferWindowMemory({ 24 | k: 5, 25 | returnMessages: true, 26 | inputKey: "input", 27 | outputKey: "output", 28 | memoryKey: "history", 29 | }); 30 | const outputParser = new StringOutputParser(); 31 | 32 | return RunnableSequence.from([ 33 | { 34 | input: (initialInput) => initialInput.input, 35 | memory: () => memory.loadMemoryVariables({}), 36 | }, 37 | { 38 | input: (previousOutput) => previousOutput.input, 39 | history: (previousOutput) => previousOutput.memory.history, 40 | }, 41 | prompt, 42 | model, 43 | outputParser 44 | ]); 45 | }; 46 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { OpenAPIHono } from "@hono/zod-openapi"; 2 | import { swaggerUI } from "@hono/swagger-ui"; 3 | import { cors } from "hono/cors"; 4 | import { prettyJSON } from 'hono/pretty-json'; 5 | import { jwt } from 'hono/jwt'; 6 | import chatApp from "./apps/chat"; 7 | import imageApp from "./apps/image"; 8 | import searchApp from "./apps/search"; 9 | import authApp from "./apps/auth"; 10 | import transcribeApp from "./apps/transcribe"; 11 | import { Bindings } from "@src/bindings"; 12 | 13 | const app = new OpenAPIHono<{Bindings: Bindings}>(); 14 | 15 | app.use("*", cors()); 16 | app.use('*', prettyJSON()); 17 | app.notFound((c) => c.json({ message: 'Not Found', ok: false }, 404)) 18 | 19 | app.get("/", (c) => c.text("Hello!")); 20 | 21 | app.route("/auth", authApp); 22 | 23 | app.use('/api/*', async (c, next) => { 24 | const jwtMiddleware = jwt({ 25 | secret: c.env.JWT_SECRET, 26 | }); 27 | return jwtMiddleware(c, next); 28 | }); 29 | 30 | app.route("/api/v1/chat", chatApp); 31 | app.route("/api/v1/image", imageApp); 32 | app.route("/api/v1/search", searchApp); 33 | app.route("/api/v1/transcribe", transcribeApp); 34 | 35 | app.get("/ui", swaggerUI({ url: "/docs" })); 36 | app.doc("/docs", { 37 | info: { 38 | title: "An API", 39 | version: "v1", 40 | }, 41 | openapi: "3.1.0", 42 | }); 43 | 44 | export default app; 45 | -------------------------------------------------------------------------------- /src/services/image.ts: -------------------------------------------------------------------------------- 1 | import { Ai } from "@cloudflare/ai"; 2 | 3 | interface Params { 4 | prompt: string; 5 | num_steps: number; 6 | AI: any; 7 | } 8 | 9 | export const generateImage = async (params: Params) => { 10 | const ai = new Ai(params.AI); 11 | const inputs = { 12 | prompt: params.prompt, 13 | num_steps: params.num_steps, 14 | }; 15 | const model = "@cf/stabilityai/stable-diffusion-xl-base-1.0"; 16 | const response = await ai.run(model, inputs); 17 | return response; 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/getGatewayUrl.ts: -------------------------------------------------------------------------------- 1 | interface Params { 2 | account_id: string; 3 | endpoint: string; 4 | slug: string; 5 | provider: string; 6 | model: string; 7 | } 8 | 9 | export const getGatewayUrl = (data: Params) => { 10 | const { endpoint, account_id, slug, provider, model } = data; 11 | 12 | return `${endpoint}/${account_id}/${slug}/${provider}/${model}`; 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "esModuleInterop": true, 7 | "strict": true, 8 | "lib": [ 9 | "esnext" 10 | ], 11 | "types": [ 12 | "@cloudflare/workers-types" 13 | ], 14 | "jsx": "react-jsx", 15 | "jsxImportSource": "hono/jsx", 16 | "paths": { 17 | "@src/*": ["./src/*"] 18 | } 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import path from "path"; 3 | import { configDefaults, defineConfig } from "vitest/config"; 4 | 5 | export default defineConfig({ 6 | test: { 7 | globals: true, 8 | include: ["**/*test.ts"], 9 | exclude: [...configDefaults.exclude], 10 | coverage: { 11 | provider: "v8", 12 | reporter: ["text"], 13 | }, 14 | }, 15 | resolve: { 16 | alias: { 17 | "@src": path.resolve(__dirname, "./src"), 18 | }, 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "ai-api" 2 | compatibility_date = "2023-01-01" 3 | node_compat = true 4 | 5 | [ai] 6 | binding = "AI" 7 | 8 | [[vectorize]] 9 | binding = "VECTORIZE_INDEX" 10 | index_name = "movies_index" 11 | 12 | # [[kv_namespaces]] 13 | # binding = "MY_KV_NAMESPACE" 14 | # id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 15 | 16 | # [[r2_buckets]] 17 | # binding = "MY_BUCKET" 18 | # bucket_name = "my-bucket" 19 | 20 | # [[d1_databases]] 21 | # binding = "DB" 22 | # database_name = "my-database" 23 | # database_id = "" 24 | --------------------------------------------------------------------------------