最近我努力装饰博客并添加功能。
重新看了我的代码,发现完全不记得是怎么写的了。
今天再次意识到学习记录(TIL)的重要性。
所以我再次记录下来。

1. ChatGPT API 官方文档
首先,基本设置可以查看 官方文档。
步骤如下。
将 API 密钥存为环境变量。在 Nextjs 环境下,在根文件夹创建 .env.local 文件,内容为
OPENAI_API_KEY="api_key_here"。使用
npm install openai安装 openai 库。编写代码。
官方文档中提供的代码如下。
import OpenAI from "openai";
const openai = new OpenAI();
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{
role: "user",
content: "Write a haiku about recursion in programming.",
},
],
});
console.log(completion.choices[0].message);
在 Nextjs 中用 yarn dev 运行可以看到效果不错。
所以想通过 api 路由实现,不过问题没那么简单。
2. 尝试一下
在开发初期,先忽略风格,直接实现功能更快。
简单创建一个表单,用 FormData 和 fetch 搭建一个简易聊天窗口。
"use client";
import { useRef, useState } from "react";
export default function Page() {
const [value, setValue] = useState("");
const messages = useRef<HTMLDivElement>(null);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
setValue("");
const me = document.createElement("p");
me.textContent = value;
messages.current?.appendChild(me);
const formData = new FormData();
formData.append("content", value);
const res = await fetch("/api/test", {
method: "POST",
body: formData,
});
const resJson = await res.json();
const p = document.createElement("p");
p.textContent = resJson.content;
messages.current?.appendChild(p);
}
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="text"
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
/>
<button type="submit"></button>
</form>
<div ref={messages} className="border-2">
<p>聊天窗口</p>
</div>
</div>
);
}这样可以看到一个简单的输入框和聊天窗口。
输入内容后,利用 fetch 将 formdata 发送到 '/api/test' 并处理响应。

接下来编写 api 端的代码。
import { NextResponse } from "next/server";
import OpenAI from "openai";
const openai = new OpenAI();
export async function POST(req: Request) {
const formData = await req.formData();
const completion = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{
role: "user",
content: formData.get("content") as string,
},
],
});
console.log(completion.choices[0].message);
return NextResponse.json(completion.choices[0].message);
}
从请求中解析消息,然后通过 openai 库发送并接收响应。
最后将响应以 json 格式返回。
现在可以进行对话了。

测试时会发现,响应过长需要等待较久。
这里出现了问题。
3. 问题点
问题在于我们的耐心并不强大。
在网络上若响应不够快,我们容易感到疲倦。

在 Chat GPT 中数据以打字形式传输是因为 API 以分块形式响应和输出。
我努力搜索了一些实现方法。
使用 socket 实现实时聊天
使用 openai 的流式响应对象
使用 Vercel 的 AI SDK 实现
其中第 3 种看起来最简单。
4. 试用 Vercel 的 AI SDK
不懂时,搜索与 官方文档是最好的办法。
在上述链接中找到适用于 Nextjs 的组件。
把它直接应用到项目中即可实现流式传输。
首先安装 AI SDK。
yarn add ai @ai-sdk/openai zod这里的 zod 是用于输入验证的工具。
按以下路径设置路由。
import { openai } from "@ai-sdk/openai";
import { streamText } from "ai";
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages,
});
return result.toDataStreamResponse();
}代码中的 maxDuration 是最大响应时间。
若在 30 秒内未完成响应,则中断流式传输。
客户端接收数据流对象响应。
接下来是 page 组件。
"use client";
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/aisdk",
});
return (
<div className="flex flex-col w-full max-w-md py-24 mx-auto stretch">
{messages.map((m) => (
<div key={m.id} className="whitespace-pre-wrap">
{m.role === "user" ? "User: " : "AI: "}
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
className="fixed bottom-0 w-full max-w-md p-2 mb-8 border border-gray-300 rounded shadow-xl"
value={input}
placeholder="Say something..."
onChange={handleInputChange}
/>
</form>
</div>
);
}原本需要用 useState 和事件处理器复杂处理的东西,通过 useChat 就搞定了。
观察 useChat 中结构分解的变量在何处如何使用。
useChat 内接收多个对象选项。
这里输入了 API 地址。
现在运行看看。

通过简单的两次复制粘贴实现 ChatGPT 流式传输。
官方文档中还有关于创建天气相关 AI 的过程,可供参考。
5. useChat 和 streamText
每个函数的参数可以在官方文档中查到。
我会写一些我用过的。
1. useChat
useChat 内可以输入启动消息。
这样一打开页面消息就会显示。
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/aisdk",
initialMessages: [
{
id: "first-message",
role: "assistant",
content: "想说什么就输入吧",
},
],
});
2. streamText
在 streamText 中可以定义需要在响应中使用的函数。
如果对 ChatGPT 说 “请把 ‘强壮袋鼠’ 变成 ‘倒霉孩子’”,它不会正确回答。

这种情况下可以定义 “倒霉孩子” 函数并放入 streamText 中。
import { openai } from "@ai-sdk/openai";
import { streamText, tool } from "ai";
import { z } from "zod";
// Allow streaming responses up to 30 seconds
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o"),
messages,
tools: {
reverse: tool({
description: "将名字变成倒霉孩子",
parameters: z.object({
person: z.string().describe("名字"),
}),
execute: async ({ person }) => {
const newPerson = person.split("").reverse().join("");
return {
newPerson,
};
},
}),
},
});
return result.toDataStreamResponse();
}tool 函数内 description 是函数说明,parameters 在对话中寻找对应名字内容并作为参数传入,execute 中处理后返回。
这样编写代码后,再进行提问时,会把输入字符串反转后返回。

还可以定义回答的最大长度。
例如指定 maxTokens 为下列位置时,回答就会变得非常简短。
const result = streamText({
...
maxTokens:10,
...
});
6. 结论
本以为创建使用 Chat GPT 的服务会非常困难,但实际并不是。
有很多好用的库,只要有心就可以制作。
希望尽快制作教育用聊天机器人并投入服务。
댓글을 불러오는 중...