はじめに
この記事では、LINEアプリ上でChatGPTと会話できるボットを作成します。
友達や家族と気軽にシェアできて、スマホから簡単にAIアシスタントを使えます!
完成品の機能
- LINEでChatGPTと自然な会話
- 画像を送ると内容を説明してくれる
- 前回の会話を記憶(文脈理解)
- 使用制限機能(1日50メッセージまで)
- データベース不要のシンプル実装
- 完全無料で運用可能(Vercel無料枠)
デモ
技術スタック
- Next.js 14
- TypeScript
- Vercel(デプロイ)
- LINE Messaging API
- OpenAI GPT-5 nano
必要なもの
アカウント・APIキー
-
LINE Developers アカウント
-
OpenAI API キー
- https://platform.openai.com/
- GPT-5 nanoは最新の推論モデル
-
Vercel アカウント
- https://vercel.com/
- 無料プランでOK
プロジェクト作成
Next.jsプロジェクトを初期化
npx create-next-app@latest line-chatgpt-bot
cd line-chatgpt-bot
オプション選択:
- TypeScript: Yes
- Tailwind CSS: Yes
- App Router: Yes
必要なパッケージをインストール
npm install @line/bot-sdk openai
環境変数を設定
.env.local を作成:
LINE_CHANNEL_ACCESS_TOKEN=your_line_channel_access_token
LINE_CHANNEL_SECRET=your_line_channel_secret
OPENAI_API_KEY=your_openai_api_key
MAX_MESSAGES_PER_DAY=50
MAX_CONVERSATION_HISTORY=5
実装
メモリ管理(会話履歴・使用制限)
lib/memory.ts を作成:
interface Message {
role: 'user' | 'assistant';
content: string;
timestamp: number;
}
interface UserData {
messages: Message[];
dailyCount: number;
lastResetDate: string;
}
const userDataMap = new Map<string, UserData>();
const MAX_CONVERSATION_HISTORY = parseInt(process.env.MAX_CONVERSATION_HISTORY || '5');
const MAX_MESSAGES_PER_DAY = parseInt(process.env.MAX_MESSAGES_PER_DAY || '50');
export function getConversationHistory(userId: string): Message[] {
const userData = getUserData(userId);
return userData.messages;
}
export function addMessage(userId: string, role: 'user' | 'assistant', content: string): void {
const userData = getUserData(userId);
userData.messages.push({ role, content, timestamp: Date.now() });
const maxMessages = MAX_CONVERSATION_HISTORY * 2;
if (userData.messages.length > maxMessages) {
userData.messages.splice(0, userData.messages.length - maxMessages);
}
}
export function canSendMessage(userId: string): boolean {
const userData = getUserData(userId);
return userData.dailyCount < MAX_MESSAGES_PER_DAY;
}
export function incrementMessageCount(userId: string): void {
const userData = getUserData(userId);
userData.dailyCount++;
}
function getUserData(userId: string): UserData {
if (!userDataMap.has(userId)) {
userDataMap.set(userId, {
messages: [],
dailyCount: 0,
lastResetDate: new Date().toISOString().split('T')[0],
});
}
const userData = userDataMap.get(userId)!;
const today = new Date().toISOString().split('T')[0];
if (userData.lastResetDate !== today) {
userData.dailyCount = 0;
userData.lastResetDate = today;
}
return userData;
}
OpenAI APIラッパー
lib/openai.ts を作成:
重要: GPT-5 nanoは推論モデルのため、特殊なパラメータ設定が必要です。
import OpenAI from 'openai';
export const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY || '',
});
interface Message {
role: 'user' | 'assistant' | 'system';
content: string;
}
export async function chatWithGPT(messages: Message[]): Promise<string> {
try {
const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
messages: messages,
max_completion_tokens: 16000,
});
const content = response.choices[0]?.message?.content;
if (!content) {
return 'すみません、応答を生成できませんでした。';
}
return content;
} catch (error) {
console.error('OpenAI API Error:', error);
throw new Error('ChatGPTとの通信中にエラーが発生しました。');
}
}
export async function chatWithImage(text: string, imageBase64: string): Promise<string> {
try {
const response = await openai.chat.completions.create({
model: 'gpt-5-nano',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: text },
{
type: 'image_url',
image_url: {
url: `data:image/jpeg;base64,${imageBase64}`
}
},
],
},
],
max_completion_tokens: 16000,
});
return response.choices[0]?.message?.content || '画像を認識できませんでした。';
} catch (error) {
console.error('OpenAI Vision API Error:', error);
throw new Error('画像認識中にエラーが発生しました。');
}
}
GPT-5 nano の注意点:
-
max_tokensは非対応 →max_completion_tokensを使用 -
temperatureのカスタマイズ不可 → デフォルト値(1)のみ - 推論モデルのため、大きなトークン数が必要(16000推奨)
LINE APIラッパー
lib/line.ts を作成:
重要: ビルド時エラーを防ぐため、遅延初期化を使用します。
import { Client, ClientConfig } from '@line/bot-sdk';
let lineClientInstance: Client | null = null;
function getLineClient(): Client {
if (!lineClientInstance) {
const config: ClientConfig = {
channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN || '',
channelSecret: process.env.LINE_CHANNEL_SECRET || '',
};
lineClientInstance = new Client(config);
}
return lineClientInstance;
}
export async function replyTextMessage(replyToken: string, text: string) {
const client = getLineClient();
return await client.replyMessage(replyToken, {
type: 'text',
text: text,
});
}
export async function getImageContent(messageId: string): Promise<Buffer> {
const client = getLineClient();
const stream = await client.getMessageContent(messageId);
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
stream.on('data', (chunk: Buffer) => chunks.push(chunk));
stream.on('end', () => resolve(Buffer.concat(chunks)));
stream.on('error', reject);
});
}
Webhook API(メインロジック)
app/api/webhook/route.ts を作成:
重要: 推論トークン削減のため、会話履歴は最新1往復のみに制限します。
import { NextRequest, NextResponse } from 'next/server';
import { WebhookEvent } from '@line/bot-sdk';
import { replyTextMessage, getImageContent } from '@/lib/line';
import { chatWithGPT, chatWithImage } from '@/lib/openai';
import {
getConversationHistory,
addMessage,
canSendMessage,
incrementMessageCount,
} from '@/lib/memory';
export async function POST(req: NextRequest) {
try {
const body = await req.text();
const signature = req.headers.get('x-line-signature');
// 署名検証
if (!signature) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const channelSecret = process.env.LINE_CHANNEL_SECRET || '';
const crypto = require('crypto');
const hash = crypto.createHmac('SHA256', channelSecret).update(body).digest('base64');
if (hash !== signature) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = JSON.parse(body);
const events: WebhookEvent[] = data.events || [];
await Promise.all(events.map(handleEvent));
return NextResponse.json({ status: 'ok' });
} catch (error) {
console.error('Webhook Error:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
async function handleEvent(event: WebhookEvent) {
if (event.type !== 'message') return;
const userId = event.source.userId;
if (!userId) return;
if (!canSendMessage(userId)) {
await replyTextMessage(event.replyToken, '本日の利用上限に達しました');
return;
}
if (event.message.type === 'text') {
await handleTextMessage(event, userId);
} else if (event.message.type === 'image') {
await handleImageMessage(event, userId);
}
}
async function handleTextMessage(event: any, userId: string) {
const userMessage = event.message.text;
const history = getConversationHistory(userId);
addMessage(userId, 'user', userMessage);
// 推論トークン削減のため、最新1往復のみ使用
const recentHistory = history.slice(-2);
const messages = [
{ role: 'system' as const, content: 'あなたは親切で役立つAIアシスタントです。' },
...recentHistory.map(msg => ({ role: msg.role, content: msg.content })),
{ role: 'user' as const, content: userMessage },
];
const reply = await chatWithGPT(messages);
addMessage(userId, 'assistant', reply);
incrementMessageCount(userId);
await replyTextMessage(event.replyToken, reply);
}
async function handleImageMessage(event: any, userId: string) {
const imageBuffer = await getImageContent(event.message.id);
const imageBase64 = imageBuffer.toString('base64');
const reply = await chatWithImage('この画像について説明してください。', imageBase64);
addMessage(userId, 'user', '[画像を送信]');
addMessage(userId, 'assistant', reply);
incrementMessageCount(userId);
await replyTextMessage(event.replyToken, reply);
}
会話履歴を1往復に制限した理由:
- GPT-5 nanoは推論モデルで、内部思考に大量のトークンを使用
- 会話履歴が多いと推論トークンが増え、出力用のトークンが残らない
- 最新1往復のみに制限することで、推論トークンを削減
Vercelにデプロイ
GitHubにプッシュ
git init
git add .
git commit -m "Add LINE ChatGPT Bot with GPT-5 nano"
git branch -M main
git remote add origin https://github.com/your-username/line-chatgpt-bot.git
git push -u origin main
Vercelで連携
- https://vercel.com/ にアクセス
- Import Project をクリック
- GitHubリポジトリを選択
-
環境変数を必ず設定(全環境にチェック):
- LINE_CHANNEL_ACCESS_TOKEN
- LINE_CHANNEL_SECRET
- OPENAI_API_KEY
- MAX_MESSAGES_PER_DAY
- MAX_CONVERSATION_HISTORY
- Production, Preview, Development 全てにチェックを入れる
- Deploy をクリック
LINE側の設定
LINE公式アカウント作成
- https://developers.line.biz/console/ にアクセス
- Business IDを作成(個人利用でもOK)
- Providerを作成
- Messaging API チャネルを作成
Messaging APIを有効化
- LINE Official Account Manager で設定を開く
- 設定 → Messaging API
- 「Messaging APIを利用する」をクリック
- Providerを選択
Webhook URLを設定
- LINE Developers Console → Messaging API タブ
- Webhook URL に以下を入力:
https://your-app-name.vercel.app/api/webhook
- 「検証」をクリック → 成功を確認
- 「Webhookの利用」を ON にする
応答設定
最重要: 自動応答をOFFにしないとWebhookが動きません!
LINE Official Account Manager で:
- 設定 → 応答設定
- チャット: ON
- Webhook: ON
- 応答メッセージ: チャットONで自動的にOFF
GPT-5 nano 特有の注意点
推論モデルの特性
GPT-5 nanoは推論専用モデルです。従来のモデルとは異なります。
パラメータの違い
従来のモデル:
max_tokens: 1000,
temperature: 0.7,
GPT-5 nano:
max_completion_tokens: 16000, // 大きな値が必要!
// temperature は指定しない
推論トークンとは
GPT-5 nanoは内部で「考える」プロセスがあります:
質問: 「Pythonについて教えて」
↓
内部思考(推論トークン):
「Pythonの特徴は...初心者向けに...
具体例を入れて...どう説明するか...」
↓ 数千トークン消費
実際の出力:
「Pythonはプログラミング言語で...」
そのため、max_completion_tokens を大きく設定する必要があります。
会話履歴の制限
推論トークンを削減するため、会話履歴は最新1往復のみに制限:
const recentHistory = history.slice(-2); // 最新1往復
動作確認
QRコードで友だち追加
LINE Developersの Messaging API タブにあるQRコードをスキャン
メッセージを送信
あなた: こんにちは!
Bot: こんにちは!何かお手伝いできることはありますか?
画像を送信
写真を送ると、AIが内容を詳しく説明してくれます!

実際のLINE画面での会話例
トラブルシューティング
ビルドエラー
問題: "no channel access token"
解決: LINE SDKの遅延初期化を実装
Webhook 401 エラー
原因:
- 環境変数が未設定
- 環境変数が Preview 環境に適用されていない
解決:
- Vercel → Settings → Environment Variables
- 全環境(Production, Preview, Development)にチェック
空の応答が返る
原因: 推論トークンが多すぎて出力用トークンが残らない
解決:
- max_completion_tokens を 16000 に設定
- 会話履歴を最新1往復に制限
費用
実際にかかる費用
| 項目 | 費用 |
|---|---|
| Vercel | 無料 |
| LINE Messaging API | 無料(月1000通まで) |
| OpenAI GPT-5 nano | 従量課金 |
推論トークンについて:
- GPT-5 nanoは推論に大量のトークンを使用
- 1メッセージあたり5000〜15000トークン消費
- 従来モデルより費用が高くなる可能性あり
- 使用量制限の設定を推奨
まとめ
学んだこと
- GPT-5 nanoは推論モデルで特殊なパラメータが必要
- max_completion_tokens を大きく設定する
- 会話履歴を制限して推論トークンを削減
- 遅延初期化でビルドエラーを回避
- 環境変数は全環境に設定が必要
- 署名検証でセキュリティを確保
Next.js + Vercel の組み合わせで、デプロイが超簡単でした!
ぜひ作ってみてください!
ソースコード
完全なソースコードはGitHubで公開しています:
参考文献
著者について
SNAMO(@snamo-suzuki)
フルスタックエンジニア / AI統合開発専門家
専門分野
- 🤖 AI統合開発: ChatGPT・Google AI・Claude統合システムの設計・開発
- 🎬 AI動画生成: Runway API等の最新AI技術
- ⚛️ フルスタック開発: Next.js/React, Supabase, pgvector
- 🔍 レリバンスエンジニアリング: AI検索時代のSEO戦略・GEO対策
サービス
- 💼 企業のDX推進・AI技術導入コンサルティング
- 💬 無料相談受付中
リンク
- 🌐 ポートフォリオ: https://snamo.jp/
- 💻 GitHub: nobu-suzuki345
この記事がお役に立ちましたら、いいね・ストックをお願いします!
ご質問やご相談があれば、コメント欄またはポートフォリオサイトからお気軽にお問い合わせください 😊
