🐱
Next.jsでAI Chat UIを作る(ローカルLLMとGPT-4o mini)
はじめに
GPT-4o miniが発表されたので、APIを使ってNext.js App routerにて簡単なAIチャットページを実装してみました。Vercel AI SDKを使用しています。
以前にも記事を書きましたが実装内容が古くなりましたので新たに記事にしてみました。(今回はLangChain JSは使っていません)
GPT-4o mini
また、今回はローカルLLM(Ollama)でも利用できる内容としています。
※ マシンスペックが足りないなど環境がない方は、Ollamaの部分は読み飛ばしてください。
以前の記事
読者対象
- Next.js開発の経験者
事前準備
-
OpenAI プラットフォームから API キーを作成&取得しておく。
https://platform.openai.com/ -
Ollamaも使いたい場合は起動しておきます。
参考ドキュメント
コードを書く
ステップ
- Next.jsを作成する
- .env.localにOPENAI_API_KEYを記述する
OPENAI_API_KEY=******
- app/api/chat/route.tsを作成する
- app/page.tsxを作成する
Next.jsを作成
Typescript + App router + Tailwind
yarn create next-app sample-ai-chat --typescript
cd sample-ai-chat
yarn add ai @ai-sdk/openai zod
package.json(抜粋)
"dependencies": {
"@ai-sdk/openai": "^0.0.36",
"ai": "^3.2.29",
"next": "14.2.5",
"react": "^18",
"react-dom": "^18",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.5",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
実装
- gpt-4o-miniの場合
- app/api/chat/route.ts
import { openai, createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
// Vercel edgeを使う場合
export const runtime = "edge";
// ストリーミング応答を最大 30 秒まで許可
export const maxDuration = 30;
export const POST = async (req: Request) => {
const { messages, ...data } = await req.json();
// dataにはuseChat()でbodyで送ったsystemPromptとtemperatureが格納されています
console.log(data);
// OpenAI 使う場合
const result = await streamText({
model: openai("gpt-4o-mini"),
system: data.systemPrompt,
temperature: data.temperature,
abortSignal: req.signal, // stopを使う場合に必要
messages,
});
// ローカルLLMのOllamaをOpenAI互換で使う場合(例:モデルはgemma2:9b)
// const openai = createOpenAI({
// baseURL: "http://localhost:11434/v1/",
// apiKey: "EMPTY",
// });
// const result = await streamText({
// model: openai("gemma2"),
// system: data.systemPrompt,
// temperature: data.temperature,
// abortSignal: req.signal, // stopを使う場合に必要
// messages,
// });
return result.toAIStreamResponse();
};
- OllamaをOpenAI互換で使う場合
- app/api/chat/route.ts
import { openai, createOpenAI } from "@ai-sdk/openai";
import { streamText } from "ai";
// Vercel edgeを使う場合
// export const runtime = "edge";
// ストリーミング応答を最大 30 秒まで許可
export const maxDuration = 30;
export const POST = async (req: Request) => {
const { messages, ...data } = await req.json();
// dataにはuseChat()でbodyで送ったsystemPromptとtemperatureが格納されています
console.log(data);
// OpenAI 使う場合
// const result = await streamText({
// model: openai("gpt-4o-mini"),
// system: data.systemPrompt,
// temperature: data.temperature,
// abortSignal: req.signal, // stopを使う場合に必要
// messages,
// });
// ローカルLLMのOllamaをOpenAI互換で使う場合(例:モデルはgemma2:9b)
const openai = createOpenAI({
baseURL: "http://localhost:11434/v1/",
apiKey: "EMPTY",
});
const result = await streamText({
model: openai("gemma2"),
system: data.systemPrompt,
temperature: data.temperature,
abortSignal: req.signal, // stopを使う場合に必要
messages,
});
return result.toAIStreamResponse();
};
- app/page.ts
- systemPromptとtemperatureを設定しています。
検証でわかりやすくするのに、「にゃん」を言うように指示。
"use client";
import { useChat } from "ai/react";
const Chat = () => {
// システムプロンプトの設定
const systemPrompt =
"フレンドリーに会話してください。語尾は必ず「にゃん」をつけて。";
// temperatureの設定
const temperature = 0.5;
// useChatはデフォルトでAPI ルート (/api/chat) を使用する
const { messages, input, isLoading, stop, handleInputChange, handleSubmit } =
useChat({
body: {
systemPrompt: systemPrompt,
temperature: temperature,
},
});
// チャット履歴をコンソール出力
if (!isLoading && messages.length > 0) console.log(messages);
return (
<>
<div className="mx-auto w-full max-w-md py-24 flex flex-col">
<p className="font-bold text-lg">AI CHAT</p>
{messages.map((m) => (
<div key={m.id} className="w-96 mb-2 p-2">
{m.role === "user" ? "Human: " : "AI: "}
{m.content}
</div>
))}
<form onSubmit={handleSubmit}>
<input
name="box"
className="w-96 flex rounded bottom-0 border border-gray-300 text-gray-700 mb-2 p-2"
value={input}
onChange={handleInputChange}
/>
{isLoading ? (
<button
type="submit"
className="opacity-50 cursor-not-allowed w-96 rounded bg-sky-500 hover:bg-sky-700 mb-2 p-2"
disabled
>
Send message
</button>
) : (
<button
type="submit"
className="w-96 rounded bg-sky-500 hover:bg-sky-700 mb-2 p-2"
>
Send message
</button>
)}
</form>
<p className="w-96 text-slate-500 text-xs">
Chromeブラウザで画面を右クリックして「検証」を選ぶと、DEVツールのコンソールからメッセージの値が確認できます。
</p>
</div>
</>
);
};
export default Chat;
動作確認
GPT-4o mini
レスポンス速いです。コストも安くていいですね。
gemma2:9b
ちょっと理解が弱いですね。
まとめ
Vercel AI SDKを使って、ストリーミング回答するシンプルなAIチャットページを実装してみました。今回は、Ollamaでも使えるようにしているのと、ページからsystemPromptやtemperatureの指定もできるようにuseChatでbodyから渡すようにしています。また、ボタンの実装はしていませんが、useChatのstopも動作するように、api側ソースにはabortSignalも記述してありますので、いろいろと改造して遊んでみてください。
Discussion