惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
D
DataBreaches.Net
C
Check Point Blog
雷峰网
雷峰网
小众软件
小众软件
GbyAI
GbyAI
美团技术团队
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
Apple Machine Learning Research
Apple Machine Learning Research
Y
Y Combinator Blog
Jina AI
Jina AI
爱范儿
爱范儿
Last Week in AI
Last Week in AI
MongoDB | Blog
MongoDB | Blog
I
InfoQ
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美

Vercel News

Vercel Open Source Program: Winter 2026 cohort How Notion Workers run untrusted code at scale with Vercel Sandbox How we run Vercel's CDN in front of Discourse From idea to secure checkout in minutes with Stripe Building Slack agents can be easy Scaling redirects to infinity on Vercel Advancing Python typing Gamma builds design-first agents with Vercel How Avalara turns pipe dreams into patent-pending with v0 Keeping community human while scaling with agents How OpenEvidence built a healthcare AI that physicians actually trust Security boundaries in agentic architectures Skills Night: 69,000+ ways agents are getting smarter Video Generation with AI Gateway We Ralph Wiggumed WebStreams to make them 10x faster How Stably ships AI testing agents in hours, not weeks How we built AEO tracking for coding agents Anyone can build agents, but it takes a platform to run them Introducing Geist Pixel The Vercel AI Accelerator is back with $6m in credits Making agent-friendly pages with content negotiation The Vercel OSS Bug Bounty program is now available Introducing the new v0 Run untrusted code with Vercel Sandbox, now generally available How Stripe built a game-changing app in a single flight with v0 How Sensay went from zero to product in six weeks AGENTS.md outperforms skills in our agent evals Agent skills explained: An FAQ Testing if "bash is all you need" AWS databases are now live on the Vercel Marketplace and v0
Build a GPT-3 app with Next.js and Vercel Edge Functions ...
Hassan El Mghari · 2023-02-01 · via Vercel News

6 min read

A full-stack template for building fast GPT-3 apps.

The field of artificial intelligence continues to take the world by storm. Huge strides have been made in text and image generation through tools like ChatGPT, GPT-3, DALL-E, and Stable Diffusion. It’s spawned a wave of exciting AI startups, many of which we’re seeing built with Vercel and Next.js.

One of the most exciting developments in the AI space is GPT-3, a cutting-edge natural language processing model developed by OpenAI. With its ability to understand and generate human-like text, GPT-3 has the potential to disrupt how we perform many of our tasks.

In this blog post, we’re going to break down how to build GPT-3 Apps with OpenAI, Next.js, and Vercel Edge Functions. We’ll do this by building twitterbio.com—first with serverless functions, then rebuilding it with Edge Functions and streaming to showcase the speed and UX benefits. By the end, you should be able to build your own GPT-3-powered applications.

Screenshot of the site we’re going to be building.

Screenshot of the site we’re going to be building.

Link to headingThe frontend

The Next.js frontend consists of a few elements:

  • A text box for users to copy their current bio or write a few sentences about themselves

  • A dropdown where they can select the tone of the bio they want to be generated

  • A submit button for generating their bio, which when clicked calls an API route that uses OpenAI’s GPT-3 model and returns two generated bios

  • Two containers to display the generated bios after we get them back from the API route

Here’s what the code for our index page looks like. We have a few pieces of state that correspond to the elements mentioned above. We’re also defining a prompt—like ChatGPT, we need to send a prompt to GPT-3 to instruct it to generate the new bios. Finally, we ask GPT-3 to generate two bios clearly labeled (so we can parse them correctly) using the user-provided bio and vibe as context.

const prompt = `Generate 2 ${vibe} twitter bios with no hashtags and clearly labeled "1."

and "2.". Make sure each generated bio is at least 14 words and at max 20 words and base

them on this context: ${bio}`;

The rest of our index page is comprised of the UI elements themselves: our text box, dropdown, submit button, and two containers on the bottom that we display when we get the generated bios. There's also some loading logic for the button to show a loading indicator when clicked.

GenerateBio.tsx

<textarea

value={bio}

onChange={(e) => setBio(e.target.value)}

rows={4}

className="..."

placeholder={"e.g. Senior Engineer @vercel. Tweeting about web dev & AI."}

/>

<div className="...">

<Image src="/2-black.png" width={30} height={30} alt="1 icon" />

<p className="...">Select your vibe.</p>

</div>

<div className="block">

<DropDown vibe={vibe} setVibe={(newVibe) => setVibe(newVibe)} />

</div>

<button className="..." onClick={(e) => generateBio(e)}>

Generate your bio &rarr;

</button>

<hr className="..." />

<div className="...">

{generatedBios && (

<>

<div>

<h2 className="...">Your generated bios</h2>

</div>

<div className="...">

{generatedBios

.substring(generatedBios.indexOf("1") + 3)

.split("2.")

.map((generatedBio: any) => {

return (

<div className="..." key={generatedBio}>

<p>{generatedBio}</p>

</div>

);

})}

</div>

</>

)}

</div>

In addition to the UI elements and the loading logic, we have a generateBio function that’s called when the user clicks the submit button. This sends a POST request to our /api/generate API route with the prompt in the body.

GenerateBio.tsx

const generateBio = async (e: any) => {

e.preventDefault();

setGeneratedBios("");

setLoading(true);

const response = await fetch("/api/generate", {

method: "POST",

headers: {

"Content-Type": "application/json",

},

body: JSON.stringify({

prompt,

}),

});

if (!response.ok) {

throw new Error(response.statusText);

}

let answer = await response.json();

setGeneratedBios(answer.choices[0].text);

setLoading(false);

};

We get the generated bios back from the API route, save it to the generatedBios state, then display it to the user. Because we asked GPT-3 to return the text in a specific numbered format, we can split it based on the “2.” to show the user the two bios separated nicely into containers as seen below.

Screenshot of the site showing two generated bios.

Screenshot of the site showing two generated bios.

Link to headingThe backend

A great advantage of using Next.js is being able to handle both our frontend and backend in a single application. We can spin up an API route just by creating a file called generate.ts in our api folder. Let’s take a look at our /api/generate API Route.

We get the prompt from the request body that’s passed in on the frontend, then construct a payload to send to OpenAI. In this payload, we specify some important information like the exact model (GPT-3) and how many tokens we want OpenAI to respond with (a token is approximately 4 characters). In this case, we’re limiting the max tokens because Twitter bios have a character constraint.

After the payload is constructed, we send it in a POST request to OpenAI, await the result to get back the generated bios, then send them back to the client as JSON.

generate.js

export default async function handler(req, res) {

const { prompt } = req.body;

const payload = {

model: "text-davinci-003",

prompt,

temperature: 0.7,

top_p: 1,

frequency_penalty: 0,

presence_penalty: 0,

max_tokens: 200,

n: 1,

};

const response = await fetch("https://api.openai.com/v1/completions", {

headers: {

"Content-Type": "application/json",

Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ""}`,

},

method: "POST",

body: JSON.stringify(payload),

});

const json = await response.json();

res.status(200).json(json);

}

There we have it! We built the first version of our application. Feel free to check out the code and demo for this approach.

Link to headingLimitations of the serverless function approach

While this serverless approach works, there are some limitations that make edge a better fit for this kind of application:

  1. If we’re building an app where we want to await longer responses, such as generating entire blog posts, responses will likely take over 10 seconds which can lead to serverless timeout issues on Vercel’s Hobby tier. Vercel's Pro tier has a 60-second timeout which is usually enough for GPT-3 applications.

  2. Waiting several seconds before seeing any data isn't a good user experience. Ideally, we want to incrementally show the user data as it’s being generated—similar to how ChatGPT works.

  3. The responses may take even longer due to the cold start that’s present in serverless lambda functions.

Thankfully, there is a better way to build this application that addresses all three of these problems: Vercel Edge Functions with streaming. Edge Functions may not always be the answer, especially if you're replying on specific Node.js libraries that are not edge compatible. In this case however, they will work great.

Let’s explore what Edge Functions are and how we can migrate our app to use them for faster generations and a better user experience.

Link to headingEdge Functions vs. Serverless Functions

You can think of Edge Functions as serverless functions with a more lightweight runtime. They have a smaller code size limit, smaller memory, and don’t support all Node.js libraries. So you may be thinking—why would I want to use them?

Three answers: speed, UX, and longer timeouts.

  1. Because Edge Functions use a smaller edge runtime and run very close to users on the edge, they’re also fast. They have virtually no cold starts and are significantly faster than serverless functions.

  2. They allow for a great user experience, especially when paired with streaming. Streaming a response breaks it down into small chunks and progressively sends them to the client, as opposed to waiting for the entire response before sending it.

  3. Edge Functions have a timeout of 25 seconds and even longer when streaming, which far exceeds the timeout limit for serverless functions on Vercel’s Hobby plan. Using these can allow you to get past timeout issues when using AI APIs that take longer to respond. As an added benefit, Edge Functions are also cheaper to run.

To see a demo of Serverless vs Edge Functions in action, check out the video below, specifically from 4:05 to 4:40.

Link to headingEdge Functions with streaming

Now that we understand the benefits and cost-effectiveness of using Edge Functions, let’s refactor our existing code to use them. Let’s start with our backend's API route.

The first thing we do is define a config variable and set the runtime to "edge". This is all you need to define this API route as an Edge Function. We also added an extra variable to our payload, stream: true, to make sure OpenAI streams in chunks back to the client.

Finally, the last major change to this file is to define the stream variable after specifying the payload. We used a helper function, OpenAIStream, to enable us to incrementally stream responses to the client as we get data from OpenAI.

generate.ts

import { OpenAIStream } from "../../utils/OpenAIStream";

export const config = {

runtime: "edge",

};

const handler = async (req: Request): Promise<Response> => {

const { prompt } = (await req.json()) as {

prompt?: string;

};

const payload = {

model: "text-davinci-003",

prompt,

temperature: 0.7,

top_p: 1,

frequency_penalty: 0,

presence_penalty: 0,

max_tokens: 200,

stream: true,

n: 1,

};

const stream = await OpenAIStream(payload);

return new Response(stream);

};

export default handler;

Let’s take a look at the helper function we used. It sends a POST request to OpenAI with the payload, similar to how we did it in the serverless version, but this is where the similarities stop. We create a stream to continuously parse the data we’re receiving from OpenAI, all while waiting for the [DONE] token to be sent since this signifies the end. When this happens, we close the stream.

OpenAIStream.ts

import { createParser, ParsedEvent, ReconnectInterval } from "eventsource-parser";

export async function OpenAIStream(payload) {

const encoder = new TextEncoder();

const decoder = new TextDecoder();

let counter = 0;

const res = await fetch("https://api.openai.com/v1/completions", {

headers: {

"Content-Type": "application/json",

Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ""}`,

},

method: "POST",

body: JSON.stringify(payload),

});

const stream = new ReadableStream({

async start(controller) {

function onParse(event: ParsedEvent | ReconnectInterval) {

if (event.type === "event") {

const data = event.data;

if (data === "[DONE]") {

controller.close();

return;

}

try {

const json = JSON.parse(data);

const text = json.choices[0].text;

if (counter < 2 && (text.match(/\n/) || []).length) {

return;

}

const queue = encoder.encode(text);

controller.enqueue(queue);

counter++;

} catch (e) {

controller.error(e);

}

}

}

// stream response (SSE) from OpenAI may be fragmented into multiple chunks

// this ensures we properly read chunks & invoke an event for each SSE event stream

const parser = createParser(onParse);

// https://web.dev/streams/#asynchronous-iteration

for await (const chunk of res.body as any) {

parser.feed(decoder.decode(chunk));

}

},

});

return stream;

}

In our frontend, the only code that changes is our generateBio function. Specifically, we define a reader using the native web API, getReader(), and progressively add data to our generatedBio state as it’s streamed in.

GenerateBio.tsx

const generateBio = async (e: any) => {

e.preventDefault();

setGeneratedBios("");

setLoading(true);

const response = await fetch("/api/generate", {

method: "POST",

headers: {

"Content-Type": "application/json",

},

body: JSON.stringify({

prompt,

}),

});

if (!response.ok) {

throw new Error(response.statusText);

}

const data = response.body;

if (!data) {

return;

}

const reader = data.getReader();

const decoder = new TextDecoder();

let done = false;

while (!done) {

const { value, done: doneReading } = await reader.read();

done = doneReading;

const chunkValue = decoder.decode(value);

setGeneratedBios((prev) => prev + chunkValue);

}

setLoading(false);

};

We’ve now refactored our app to use Edge Functions with streaming, making it faster and greatly improving the user experience by incrementally displaying data as it comes in.

Link to headingResources

We hope this walkthrough helps you build incredible GPT-3 powered applications. We’ve already seen several sites built with this template such as Rephraser, GenzTranslator, and ChefGPT—some of which have thousands of users. Visit the Twitter Bio site to see everything we talked about in action, check out our other AI templates, or start optimizing prompts across various models with Vercel's AI Playground.