From 41fc5d5fb25ec7b89e54e792c7b12c50b0ce9359 Mon Sep 17 00:00:00 2001 From: Claudio Poli Date: Tue, 17 Sep 2024 14:09:27 +0200 Subject: [PATCH] feat(route.ts): refactor OpenAI integration to use @ai-sdk/openai for improved functionality and maintainability chore(package.json): update dependencies and remove unused openai package to streamline the project --- app/api/completion/route.ts | 53 +++++------- package.json | 5 +- yarn.lock | 162 +++++------------------------------- 3 files changed, 48 insertions(+), 172 deletions(-) diff --git a/app/api/completion/route.ts b/app/api/completion/route.ts index 2c0284d8..c4382b5c 100644 --- a/app/api/completion/route.ts +++ b/app/api/completion/route.ts @@ -1,13 +1,12 @@ import { authOptions } from '@/lib/auth'; import prisma from '@/lib/db'; -import { OpenAIStream, StreamingTextResponse } from 'ai'; +import { createOpenAI } from '@ai-sdk/openai'; +import { CoreMessage, streamText } from 'ai'; import { getServerSession } from 'next-auth'; import { NextRequest, NextResponse } from 'next/server'; -import OpenAI from 'openai'; -// doesn't work with next-auth https://github.com/vercel/next.js/issues/50444#issuecomment-1602746782 -// export const runtime = 'edge'; export const dynamic = 'force-dynamic'; +export const maxDuration = 30; export async function POST(request: NextRequest) { const session = await getServerSession(authOptions); @@ -15,12 +14,11 @@ export async function POST(request: NextRequest) { return new NextResponse('You are not logged in', { status: 401 }); } - // const pass = request.nextUrl.searchParams.get('pass'); const extraData = request.nextUrl.searchParams.get('sendExtraData'); const occurrenceId = request.nextUrl.searchParams.get('occurrence'); if (!process.env.AIRBROKE_OPENAI_API_KEY) { - return new Response('Unauthorized', { status: 401, headers: { 'content-type': 'text/event-stream' } }); + return new NextResponse('Unauthorized', { status: 401 }); } if (!occurrenceId) { @@ -38,41 +36,36 @@ export async function POST(request: NextRequest) { const { notice, ...occurrence } = occurrenceWithRelations; - const openai = new OpenAI({ - apiKey: process.env.AIRBROKE_OPENAI_API_KEY, - organization: process.env.AIRBROKE_OPENAI_ORGANIZATION || null, - }); - const errorType = notice.kind; const errorMessage = occurrence.message; - let prompt = - `I encountered an error of type "${errorType}" with the following message: "${errorMessage}". ` + - `Explain what this error means and suggest possible solutions.`; + let prompt = `I encountered an error of type "${errorType}" with the following message: "${errorMessage}". Explain what this error means and suggest possible solutions.`; if (extraData) { const backtraceString = JSON.stringify(occurrence.backtrace, null, 2); prompt += ` The backtrace of the error is as follows: ${backtraceString}`; } - // Truncate the prompt to fit within the OpenAI token limit - const maxTokens = 4096; - const promptTruncated = prompt.slice(0, maxTokens); + // Prepare the messages array for the AI model + const messages: CoreMessage[] = [ + { + role: 'user', + content: prompt, + }, + ]; - // createCompletion at the moment results in a 404. - const response = await openai.chat.completions.create({ - model: process.env.AIRBROKE_OPENAI_ENGINE || 'gpt-4o', - stream: true, - temperature: 0.6, - messages: [ - { - role: 'system', - content: promptTruncated, - }, - ], + const openaiProvider = createOpenAI({ + apiKey: process.env.AIRBROKE_OPENAI_API_KEY, + organization: process.env.AIRBROKE_OPENAI_ORGANIZATION, }); + const model = openaiProvider(process.env.AIRBROKE_OPENAI_ENGINE || 'o1-mini'); - const stream = OpenAIStream(response); + // Stream the AI's response using streamText + const result = await streamText({ + model, + messages, + }); - return new StreamingTextResponse(stream); + // Return the streamed response + return result.toDataStreamResponse(); } diff --git a/package.json b/package.json index d28d8bfe..c6de20cc 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,14 @@ "dep:set": "ncu -u" }, "dependencies": { + "@ai-sdk/openai": "^0.0.60", "@airbrake/browser": "2.1.8", "@airbrake/node": "2.1.8", "@headlessui/react": "2.1.8", "@next-auth/prisma-adapter": "1.0.7", "@prisma/client": "5.19.1", "@tailwindcss/forms": "0.5.9", - "ai": "3.3.39", + "ai": "^3.3.40", "autoprefixer": "10.4.20", "chance": "1.1.12", "chart.js": "4.4.4", @@ -32,7 +33,6 @@ "next-auth": "4.24.7", "numeral": "2.0.6", "octokit": "3.2.1", - "openai": "^4.56.0", "postcss": "8.4.47", "react": "18.3.1", "react-chartjs-2": "5.2.0", @@ -44,6 +44,7 @@ "tailwindcss": "3.4.11", "timeago-react": "3.0.6", "typescript": "5.6.2", + "zod": "^3.23.8", "zod-error": "1.5.0", "zod-form-data": "2.0.2" }, diff --git a/yarn.lock b/yarn.lock index 64e4e136..90b4d0c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,6 +12,18 @@ __metadata: languageName: node linkType: hard +"@ai-sdk/openai@npm:^0.0.60": + version: 0.0.60 + resolution: "@ai-sdk/openai@npm:0.0.60" + dependencies: + "@ai-sdk/provider": "npm:0.0.23" + "@ai-sdk/provider-utils": "npm:1.0.19" + peerDependencies: + zod: ^3.0.0 + checksum: 10c0/bd91e0890b9769eec2989c9f942af1227ecaf3b5409ba8294b7393a3ef1d25b6d604381edf0cea74663455cf8a6347137ec92b19f7236e2ed4cffae0bf178267 + languageName: node + linkType: hard + "@ai-sdk/provider-utils@npm:1.0.19": version: 1.0.19 resolution: "@ai-sdk/provider-utils@npm:1.0.19" @@ -2286,16 +2298,6 @@ __metadata: languageName: node linkType: hard -"@types/node-fetch@npm:^2.6.4": - version: 2.6.11 - resolution: "@types/node-fetch@npm:2.6.11" - dependencies: - "@types/node": "npm:*" - form-data: "npm:^4.0.0" - checksum: 10c0/5283d4e0bcc37a5b6d8e629aee880a4ffcfb33e089f4b903b2981b19c623972d1e64af7c3f9540ab990f0f5c89b9b5dda19c5bcb37a8e177079e93683bfd2f49 - languageName: node - linkType: hard - "@types/node@npm:*": version: 22.4.0 resolution: "@types/node@npm:22.4.0" @@ -2314,15 +2316,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.11.18": - version: 18.19.44 - resolution: "@types/node@npm:18.19.44" - dependencies: - undici-types: "npm:~5.26.4" - checksum: 10c0/298915f9097ad4b2749417b159eeb39fcd8c8bb60866759c9c5139c9492b7030ba6c359da510db5f5305c637c3448ac58dfafefe9801549e04ea11ca29c2a046 - languageName: node - linkType: hard - "@types/numeral@npm:2.0.5": version: 2.0.5 resolution: "@types/numeral@npm:2.0.5" @@ -2344,13 +2337,6 @@ __metadata: languageName: node linkType: hard -"@types/qs@npm:^6.9.15": - version: 6.9.15 - resolution: "@types/qs@npm:6.9.15" - checksum: 10c0/49c5ff75ca3adb18a1939310042d273c9fc55920861bd8e5100c8a923b3cda90d759e1a95e18334092da1c8f7b820084687770c83a1ccef04fb2c6908117c823 - languageName: node - linkType: hard - "@types/react-dom@npm:^18.3.0": version: 18.3.0 resolution: "@types/react-dom@npm:18.3.0" @@ -2577,15 +2563,6 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: "npm:^5.0.0" - checksum: 10c0/90ccc50f010250152509a344eb2e71977fbf8db0ab8f1061197e3275ddf6c61a41a6edfd7b9409c664513131dd96e962065415325ef23efa5db931b382d24ca5 - languageName: node - linkType: hard - "acorn-globals@npm:^7.0.0": version: 7.0.1 resolution: "acorn-globals@npm:7.0.1" @@ -2641,15 +2618,6 @@ __metadata: languageName: node linkType: hard -"agentkeepalive@npm:^4.2.1": - version: 4.5.0 - resolution: "agentkeepalive@npm:4.5.0" - dependencies: - humanize-ms: "npm:^1.2.1" - checksum: 10c0/394ea19f9710f230722996e156607f48fdf3a345133b0b1823244b7989426c16019a428b56c82d3eabef616e938812981d9009f4792ecc66bd6a59e991c62612 - languageName: node - linkType: hard - "aggregate-error@npm:^3.0.0, aggregate-error@npm:^3.1.0": version: 3.1.0 resolution: "aggregate-error@npm:3.1.0" @@ -2660,9 +2628,9 @@ __metadata: languageName: node linkType: hard -"ai@npm:3.3.39": - version: 3.3.39 - resolution: "ai@npm:3.3.39" +"ai@npm:^3.3.40": + version: 3.3.40 + resolution: "ai@npm:3.3.40" dependencies: "@ai-sdk/provider": "npm:0.0.23" "@ai-sdk/provider-utils": "npm:1.0.19" @@ -2695,7 +2663,7 @@ __metadata: optional: true zod: optional: true - checksum: 10c0/af2da2752a8716976bc76fb5fe940a6d820e809452e309296314dfd21150ae1220035dcdc9ed013cf931db4ae7aacaa783c0e38462e05fffa1a2d03e84f3f613 + checksum: 10c0/4fc586a09c2df0797bb58b84f28baa289fe1da1c7d1dc3ed580b8f35a24d416fba28a69c223b6ac37f69991bdb3b6c5f23ede6e02bdc0c3c7b2fa0c1c06bcfb2 languageName: node linkType: hard @@ -2703,6 +2671,7 @@ __metadata: version: 0.0.0-use.local resolution: "airbroke@workspace:." dependencies: + "@ai-sdk/openai": "npm:^0.0.60" "@airbrake/browser": "npm:2.1.8" "@airbrake/node": "npm:2.1.8" "@headlessui/react": "npm:2.1.8" @@ -2719,7 +2688,7 @@ __metadata: "@types/numeral": "npm:2.0.5" "@types/react": "npm:18.3.6" "@types/react-dom": "npm:^18.3.0" - ai: "npm:3.3.39" + ai: "npm:^3.3.40" autoprefixer: "npm:10.4.20" chance: "npm:1.1.12" chart.js: "npm:4.4.4" @@ -2735,7 +2704,6 @@ __metadata: npm-check-updates: "npm:^17.0.6" numeral: "npm:2.0.6" octokit: "npm:3.2.1" - openai: "npm:^4.56.0" postcss: "npm:8.4.47" prettier: "npm:3.3.3" prettier-plugin-tailwindcss: "npm:0.6.6" @@ -2751,6 +2719,7 @@ __metadata: timeago-react: "npm:3.0.6" tsx: "npm:4.19.1" typescript: "npm:5.6.2" + zod: "npm:^3.23.8" zod-error: "npm:1.5.0" zod-form-data: "npm:2.0.2" languageName: unknown @@ -4619,13 +4588,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 10c0/0255d9f936215fd206156fd4caa9e8d35e62075d720dc7d847e89b417e5e62cf1ce6c9b4e0a1633a9256de0efefaf9f8d26924b1f3c8620cffb9db78e7d3076b - languageName: node - linkType: hard - "eventsource-parser@npm:1.1.2": version: 1.1.2 resolution: "eventsource-parser@npm:1.1.2" @@ -4804,13 +4766,6 @@ __metadata: languageName: node linkType: hard -"form-data-encoder@npm:1.7.2": - version: 1.7.2 - resolution: "form-data-encoder@npm:1.7.2" - checksum: 10c0/56553768037b6d55d9de524f97fe70555f0e415e781cb56fc457a68263de3d40fadea2304d4beef2d40b1a851269bd7854e42c362107071892cb5238debe9464 - languageName: node - linkType: hard - "form-data@npm:^2.5.0": version: 2.5.1 resolution: "form-data@npm:2.5.1" @@ -4833,16 +4788,6 @@ __metadata: languageName: node linkType: hard -"formdata-node@npm:^4.3.2": - version: 4.4.1 - resolution: "formdata-node@npm:4.4.1" - dependencies: - node-domexception: "npm:1.0.0" - web-streams-polyfill: "npm:4.0.0-beta.3" - checksum: 10c0/74151e7b228ffb33b565cec69182694ad07cc3fdd9126a8240468bb70a8ba66e97e097072b60bcb08729b24c7ce3fd3e0bd7f1f80df6f9f662b9656786e76f6a - languageName: node - linkType: hard - "fraction.js@npm:^4.3.7": version: 4.3.7 resolution: "fraction.js@npm:4.3.7" @@ -5249,15 +5194,6 @@ __metadata: languageName: node linkType: hard -"humanize-ms@npm:^1.2.1": - version: 1.2.1 - resolution: "humanize-ms@npm:1.2.1" - dependencies: - ms: "npm:^2.0.0" - checksum: 10c0/f34a2c20161d02303c2807badec2f3b49cbfbbb409abd4f95a07377ae01cfe6b59e3d15ac609cffcd8f2521f0eb37b7e1091acf65da99aa2a4f1ad63c21e7e7a - languageName: node - linkType: hard - "iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" @@ -6895,7 +6831,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:^2.1.1": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10c0/d924b57e7312b3b63ad21fc5b3dc0af5e78d61a1fc7cfb5457edaf26326bf62be5307cc87ffb6862ef1c2b33b0233cdb5d4f01c4c958cc0d660948b65a287a48 @@ -7037,14 +6973,7 @@ __metadata: languageName: node linkType: hard -"node-domexception@npm:1.0.0": - version: 1.0.0 - resolution: "node-domexception@npm:1.0.0" - checksum: 10c0/5e5d63cda29856402df9472335af4bb13875e1927ad3be861dc5ebde38917aecbf9ae337923777af52a48c426b70148815e890a5d72760f1b4d758cc671b1a2b - languageName: node - linkType: hard - -"node-fetch@npm:^2.6.12, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.12": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -7302,30 +7231,6 @@ __metadata: languageName: node linkType: hard -"openai@npm:^4.56.0": - version: 4.61.1 - resolution: "openai@npm:4.61.1" - dependencies: - "@types/node": "npm:^18.11.18" - "@types/node-fetch": "npm:^2.6.4" - "@types/qs": "npm:^6.9.15" - abort-controller: "npm:^3.0.0" - agentkeepalive: "npm:^4.2.1" - form-data-encoder: "npm:1.7.2" - formdata-node: "npm:^4.3.2" - node-fetch: "npm:^2.6.7" - qs: "npm:^6.10.3" - peerDependencies: - zod: ^3.23.8 - peerDependenciesMeta: - zod: - optional: true - bin: - openai: bin/cli - checksum: 10c0/848e0453d618ec9ddfbdf494ae9a730cfccf7c737852b5bdfb84bae072863b9f1845a2bc685c7c8e68c1c9b0a0c4975b87e1b53763d058cad1c3e535bac63c09 - languageName: node - linkType: hard - "opener@npm:^1.5.2": version: 1.5.2 resolution: "opener@npm:1.5.2" @@ -7851,15 +7756,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.10.3": - version: 6.13.0 - resolution: "qs@npm:6.13.0" - dependencies: - side-channel: "npm:^1.0.6" - checksum: 10c0/62372cdeec24dc83a9fb240b7533c0fdcf0c5f7e0b83343edd7310f0ab4c8205a5e7c56406531f2e47e1b4878a3821d652be4192c841de5b032ca83619d8f860 - languageName: node - linkType: hard - "querystringify@npm:^2.1.1": version: 2.2.0 resolution: "querystringify@npm:2.2.0" @@ -9155,13 +9051,6 @@ __metadata: languageName: node linkType: hard -"undici-types@npm:~5.26.4": - version: 5.26.5 - resolution: "undici-types@npm:5.26.5" - checksum: 10c0/bb673d7876c2d411b6eb6c560e0c571eef4a01c1c19925175d16e3a30c4c428181fb8d7ae802a261f283e4166a0ac435e2f505743aa9e45d893f9a3df017b501 - languageName: node - linkType: hard - "undici-types@npm:~6.19.2": version: 6.19.6 resolution: "undici-types@npm:6.19.6" @@ -9298,13 +9187,6 @@ __metadata: languageName: node linkType: hard -"web-streams-polyfill@npm:4.0.0-beta.3": - version: 4.0.0-beta.3 - resolution: "web-streams-polyfill@npm:4.0.0-beta.3" - checksum: 10c0/a9596779db2766990117ed3a158e0b0e9f69b887a6d6ba0779940259e95f99dc3922e534acc3e5a117b5f5905300f527d6fbf8a9f0957faf1d8e585ce3452e8e - languageName: node - linkType: hard - "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -9634,7 +9516,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:^3.20.2": +"zod@npm:^3.20.2, zod@npm:^3.23.8": version: 3.23.8 resolution: "zod@npm:3.23.8" checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69