# gpt-oss-20b をLM studioで動かしてそれをnext.jsを用いてブラウザでchat botっぽく表示する方法です。
## 結果
↑こんな感じになると思います。
## やりかた
### LM-studio & gpt-oss-20b
なんか最近(8/8現在)でたgpt-oss-20bです。
LM studioの導入はこのお方
の記事を、例えば参考にしてください。
-300x180.png)
こことか見ておく。
### Approuter
pnp install
pnpm add vercel/ai openai
みたいなのを打っといてください。
Create-next-appとターミナルで打って、いい感じに質問に答えておきましょう。
app/api/chat/route.ts
にディレクトリを作りつつ、以下のコードをコードを貼ってください。
/* app/api/chat/route.ts */
import { NextResponse } from 'next/server';
export async function POST(req: Request) {
const { messages } = await req.json();
const response = await fetch(process.env.AI_API_URL!, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'openai/gpt-oss-20b', messages }),
});
if (!response.ok) {
return new NextResponse('Error contacting LM Studio API', { status: 500 });
}
const data = await response.json();
// LM Studioのレスポンスから必要な部分だけを抽出して返す
const message = data.choices?.[0]?.message;
if (!message) {
return new NextResponse('Invalid response format from LM Studio API', { status: 500 });
}
// 必要最低限の形で返す
return NextResponse.json({
choices: [
{
message,
},
],
});
}
### 実際の表示画面部分
これをapp/src にpage.tsxとして置いといてください。(とりあえず)
'use client';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
type Message = {
role: 'user' | 'assistant';
content: string;
id: string;
};
export default function Chat() {
const [messages, setMessages] = useState<Message[]>([]);
const [input, setInput] = useState('');
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim()) return;
const userMessage: Message = {
role: 'user',
content: input,
id: crypto.randomUUID(),
};
// 表示用にユーザーメッセージを先に追加
setMessages(prev => [...prev, userMessage]);
setInput('');
try {
const res = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: [...messages, userMessage] }),
});
const data = await res.json();
const aiContent = data?.choices?.[0]?.message?.content;
if (aiContent) {
const aiMessage: Message = {
role: 'assistant',
content: aiContent,
id: crypto.randomUUID(),
};
setMessages(prev => [...prev, aiMessage]);
}
} catch (err) {
console.error('API error:', err);
}
};
return (
<main>
<div> {messages.map((m) => (
<div key={m.id}>
<strong>{m.role === 'user' ? '👤 User' : '🤖 AI'}:</strong>
<ReactMarkdown>{m.content}</ReactMarkdown>
</div> ))}
</div>
<form onSubmit={handleSubmit}>
<input type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder="質問を入力..."
/>
<button type="submit">送信</button>
</form> </main> );
}
### `.env.local` 設定
env.localファイルを作って、
AI_API_URL=http://localhost:1234/v1/chat/completions
OPENAI_API_KEY=lm-studio
# - `AI_API_URL`: LM Studio のチャットエンドポイント(デフォルト)
# - `OPENAI_API_KEY`: 実際には使われないが、ライブラリ上必須なのでダミーを入れる
あとは、
npm run dev
でも打って、でてきた
▲ Next.js 15.4.6 (Turbopack)
- Local: http://localhost:3000
- Network: http://192.168.21.9:3000
- Environments: .env.local
みたいなのに飛べばいいと思います。
### 参考
Vercel AI SDK を使って Next.js アプリに AI 機能を追加する
## おまけ(## Google APIでの検索を入れたい場合)
## Google APIでの検索を入れたい場合 https://programmablesearchengine.google.com/controlpanel/all からGoogle CSXの番号を拾ってくる。 Google Custom APIを検索して、登録する。 API key このAPI keyとCSXコードを.env.localっていうmy app直下につけたす。 ## 📎 参考 - (https://ai-sdk.dev/providers/ai-sdk-providers/openai)[Vercelのai/sdkの説明] - [https://github.com/vercel/ai](https://github.com/vercel/ai) - [https://lmstudio.ai](https://lmstudio.ai)
# Google Custom Search API
# GOOGLE_API_KEY=あなたのAPIキー
# GOOGLE_CX=あなたの検索エンジンID
GOOGLE_API_KEY=AIUEO_WADDLE_DEE
GOOGLE_CX=WADDLE_DEE
そしたら、src/app/api/chat/route.tsの中身を
import { NextResponse } from 'next/server'; export async function POST(req: Request) { const { messages } = await req.json(); if (!Array.isArray(messages) || messages.length === 0) { return new NextResponse('Invalid messages', { status: 400 }); } // 最新のユーザーメッセージを取得 const lastUserMessage = [...messages].reverse().find(m => m.role === 'user'); if (!lastUserMessage) { return new NextResponse('No user message found', { status: 400 }); } // Google検索API呼び出し const searchUrl = `https://www.googleapis.com/customsearch/v1?q=${encodeURIComponent( lastUserMessage.content )}&key=${process.env.GOOGLE_API_KEY}&cx=${process.env.GOOGLE_CX}`; const searchRes = await fetch(searchUrl); if (!searchRes.ok) { console.error('Google Search API error:', await searchRes.text()); return new NextResponse('Google Search API error', { status: 500 }); } const searchData = await searchRes.json(); const searchText = searchData.items?.map((item: any) => `${item.title}: ${item.snippet}`).join('\n') || '検索結果なし'; // LM Studioに渡すmessages配列を構築 const lmMessages = [ { role: 'system', content: '以下の検索結果を参考にして質問に答えてください。' }, ...messages, { role: 'user', content: `検索結果:\n${searchText}` }, ]; // LM Studio APIへ問い合わせ const lmRes = await fetch(process.env.AI_API_URL!, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'openai/gpt-oss-20b', // ここはLM Studioで使うモデル名に置き換えてください messages: lmMessages, }), }); if (!lmRes.ok) { console.error('LM Studio API error:', await lmRes.text()); return new NextResponse('Error contacting LM Studio API', { status: 500 }); } const data = await lmRes.json(); const message = data.choices?.[0]?.message; if (!message) { return new NextResponse('Invalid response from LM Studio', { status: 500 }); } return NextResponse.json({ choices: [{ message }], }); }
にしてください。以上です。心配だったら、
src/app/example/page.tsx
を
'use client'; import React, { useState } from 'react'; export default function Example() { const [value, setValue] = useState(''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setValue(e.target.value); }; return ( <div> <input type="text" value={value} onChange={handleChange} /> <p>入力された値: {value}</p> </div> ); }
src/app/page.tsxを
'use client'; import { useState } from 'react'; import ReactMarkdown from 'react-markdown'; type Message = { role: 'user' | 'assistant' | 'system'; content: string; id: string; }; export default function Chat() { const [messages, setMessages] = useState<Message[]>([ { role: 'system', content: '僕は、親切なAIアシスタントだよ。検索結果を参考にして回答するよ。', id: crypto.randomUUID(), }, ]); const [input, setInput] = useState(''); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!input.trim()) return; const userMessage: Message = { role: 'user', content: input, id: crypto.randomUUID(), }; // ユーザーメッセージを追加 const updatedMessages = [...messages, userMessage]; setMessages(updatedMessages); setInput(''); try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ messages: updatedMessages }), }); const data = await res.json(); const aiContent = data?.choices?.[0]?.message?.content; if (aiContent) { const aiMessage: Message = { role: 'assistant', content: aiContent, id: crypto.randomUUID(), }; setMessages(prev => [...prev, aiMessage]); } } catch (err) { console.error('API error:', err); } }; return ( <main style={{ maxWidth: 600, margin: 'auto', padding: 20 }}> <div style={{ marginBottom: 20 }}> {messages.map(m => ( <div key={m.id} style={{ marginBottom: 10 }}> <strong>{m.role === 'user' ? '👤 User' : m.role === 'assistant' ? '🤖 AI' : '⚙️ System'}:</strong> <ReactMarkdown>{m.content}</ReactMarkdown> </div> ))} </div> <form onSubmit={handleSubmit}> <input type="text" value={input} onChange={e => setInput(e.target.value)} placeholder="質問を入力..." style={{ width: '80%', padding: '8px' }} /> <button type="submit" style={{ padding: '8px 16px', marginLeft: 8 }}> 送信 </button> </form> </main> ); }
src/app/layout.tsxを
export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> <body> {/* class名を削除 */} {children} </body> </html> ); }
.env.localのまとめ↓
# LM Studioのローカルサーバーアドレスを指定
# 通常は "http://localhost:1234/v1" です(LM studioの設定画面を見てください)
# OPENAI_API_BASE_URL="http://localhost:1234/v1"
AI_API_URL=http://localhost:3141592/v1/chat/completions
# LM StudioはAPIキーを要求しませんが、ライブラリ側で必須の場合があるため
# ダミーの文字列を入れておきます
OPENAI_API_KEY="lm-studio"
# Google Custom Search API
# GOOGLE_API_KEY=あなたのAPIキー
# GOOGLE_CX=あなたの検索エンジンID
GOOGLE_API_KEY=AIUEO_WADDLE_DEE
GOOGLE_CX=WADDLE_DEE
