PHPとChatGPT APIでよくある質問のチャットボット(Chatbot)っぽいものを作る

2025/12/01 (月) - 09:00 Program

商品やサービス案内のウェブサイト内に「よくある質問」や「FAQ」ページを設けることが多いと思います。最近はChatbotのソリューションもAIを使って優秀になりましたが、今回はPHPでChatGPT APIから自分のサイトの質問・回答データを参照して答える簡易的なChatbotを自作してみました。

ChatGPTがで質問内容を回答させた例

ChatGPTを利用することでサイト内検索などを使わなくても、会話感覚で自然な情報検索が実現できます。

今回はカフェバーのよくある質問の検索ページを作る想定とします。参照するデータとして予め質問・回答データを集約したJSONファイルを用意しておきます。テキストやCSVでも構いませんし、直接SQLを叩いて呼び出しても構いません。

[
 {
 "question": "営業時間",
 "answer": "営業時間:11:00~23:00。定休日:年中無休です。"
 },
 {
 "question": "喫煙",
 "answer": "店内は全席禁煙になっております。"
 },
 〜略〜
 {
 "question": "飲酒",
 "answer": "ビール、ハイボール、サワー、シャンパン、ワイン、日本酒などのアルコール各種を豊富に用意しております。100種類のドリンクからお選び頂けます。"
 },
 {
 "question": "お会計",
 "answer": "現金、クレジットカード(VISA、MATER、JCB、AMEX、ダイナース、Discover)、電子決済(paypay、楽天Pay、LINE Pay、au Pay、WeChatPay、AliPay)がご利用いただけます。"
 }
]

次にPHPで、JSONファイルを参照してGPT APIが質問を受けるプログラムを実装します。実装する前に予めOpenAI developer platformからAPI Keysを取得しておきましょう。プロンプトは適当に変えてください。そして入力フォームからPOSTで送信された質問内容を取得して処理し、処理した質問文をプロンプトとしてAPIにリクエストします。

define('API_KEY', '取得したAPIキーを指定');
define('API', 'https://api.openai.com/v1/chat/completions');
function findFAQ($userQuestion) {
 $faqData = json_decode(file_get_contents('JSONファイルのURL'), true);
 $faqText = '';
 foreach ($faqData as $faq) {
  $faqText .= "Q: {$faq['question']}\nA: {$faq['answer']}\n\n";
 }
 $faqtext = "以下はFAQデータです。ユーザーからの質問に最適な回答を返してください。\n語尾ににゃんを付けて可愛く喋ってください。\nFAQデータ:${faqText}\n\nユーザーの質問: ${userQuestion}\n答え:";
  $prompt = [
   [
    'role' => 'user',
    'content' => $faqtext
   ]
  ];
  $url = curl_init(API);
  $header = array(
   'Authorization: Bearer '.API_KEY,
   'Content-type: application/json',
  );
  $params = json_encode([
   'messages' => $prompt,
   'model' => 'gpt-3.5-turbo',
  ]);
  $options = array(
   CURLOPT_POST => true,
   CURLOPT_HTTPHEADER =>$header,
   CURLOPT_POSTFIELDS => $params,
   CURLOPT_RETURNTRANSFER => true,
  );
  curl_setopt_array($url, $options);
  $httpResponse = curl_exec($url);
  $httpCode = curl_getinfo($url,CURLINFO_RESPONSE_CODE);
  if($httpCode === 200){
   $jsonArray = json_decode($httpResponse, true);
   $Answer = nl2br($jsonArray['choices'][0]['message']['content']);
  }else{
   $Answer = "申し訳ありませんにゃん。現在検索が使えないにゃん。";
  }
  return $Answer;
  curl_close($ch);
}
$question = htmlspecialchars($_POST['question'],ENT_QUOTES,'UTF-8') ?? '';
if ($question) {
 $answer = findFAQ($question);
} else {
 $answer = "質問を入力してにゃん♪";
}

正常にレスポンスが返ってきたらその値をフロントに表示します。質問文が空欄だったり、APIから正常にレスポンスが受け取れなかった場合はエラーを表示します。デザインはHTMLやCSSで適当に整形してください。

<form method="post">
 <dl>
  <dt><label for="question">質問</label></dt>
  <dd><input type="text" id="question" name="question" placeholder="質問文を入れてにゃん!" /></dd>
 </dl>
 <input type="submit" value="質問する" />
</form>
<hr />
<?php if ($userQuestion){ ?>
 <h2>質問:<?php echo $userQuestion; ?></h2>
 <p>回答:<?php echo nl2br(htmlspecialchars($answer)); ?></p>
<?php } ?>

なお、本格的なXSSなどのセキュリティ対策や、正常に回答が得られなかった場合の模範回答対策などは省いております。答えられなかった場合は問い合わせページへ誘導する処理などを適宜実装してください。

おさわり禁止

よくある質問ページのチャットボット(Chatbot)もどきを導入することでストレスなくユーザーの悩み解決を解決をサポートしし、問い合わせの人的な対応工数を削減できます。ただし、APIにリクエストする度にOpenAIに課金されてしまうので、その部分のコストはご注意ください。

JSで文字をシャッフルしながら1文字ずつ出現させる

2025/09/12 (金) - 10:00 JavaScript

見出しやナビの文字をランダムな文字でシャッフルしながら1文字ずつ出現するやつをJavaScriptで作りました。

昔、ActionScript 3.0(AS3)で作ったライブラリを無理矢理JavaScriptに書き換えた物なので、綺麗なコードではありませんが…。使い方としては、あらかじめHTML要素に文字を用意しておき、シャッフルテキスト用テキスト要素を指定したインスタンスを生成します。その後よきタイミング(ボタンを押下したり、スクロールが動いたタイミングなど)でplay();メソッドを実行すると対象のテキストがシャッフルします。

見出しなどあくまで短い文章向けのエフェクトなので、本文などに指定するのは避けるべきです。

HTMLのコード

<p id="txt">Chu!可愛くてごめん</p>
<button type="button" id="btn">シャッフルする</button>

実行させるためのJavaScript

const txt = document.getElementById('txt');
const btn = document.getElementById('btn');
const shuffle = new shuffleTxt(txt);
btn.addEventListener('click',function(){
 shuffle.play(); //ボタンを押下したら文字をシャッフル
});

本体のコード

const fps = 60; //インターバル 小さい値にすると遅くなる
class shuffleTxt{
 constructor(target){
  this.isFlg = false;
  this.targetEl = target;
  this.strBase = this.targetEl.textContent;
  this.strRandom = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?#$%&|/*+-=;_:'
  this.strComplete = '';
  this.intRemaining= this.strBase.length;
  this.intNowCnt = 0;
  this.intStartTime = 0;
  this.intNowTime;
  this.intFPS = 1000/fps;
  this.timeID;
 }
 play(){
  if(this.isFlg){
   return false;
  }else{
   this.isFlg = true;
   this.targetEl.textContent = '';
   this.searchTxt();
  }
 }
 stop(){
  this.intNowCnt = 0;
  this.intStartTime = 0;
  this.intNowTime = 0;
  this.intRemaining = this.strBase.length;
  this.targetEl.textContent = this.strBase;
  this.isFlg = false;
 }
 searchTxt(){
  let difTime;
  if(this.strBase.length >= this.intNowCnt){
   this.strComplete = this.strBase.substr(0,this.intNowCnt);
   this.timeID = requestAnimationFrame(() => {
    this.intNowTime = new Date().getTime();
    difTime = this.intNowTime - this.intStartTime;
    if ( difTime >= this.intFPS ) {
     this.intStartTime = this.intNowTime;
     this.randomTxt();
    }else{
     this.searchTxt();
    }
   }); 
  }else{
   cancelAnimationFrame(this.timeID);
   this.stop();
  }
 }
 randomTxt(){
  let str = '';
  for (let i=0; i<=this.intRemaining; i++){
   str += this.strRandom.charAt(Math.floor(Math.random()*this.strRandom.length))
  }
  this.targetEl.textContent = this.strComplete + str;
  this.intNowCnt++;
  this.intRemaining--;
  this.searchTxt();
 }
}

Reactで画像がクロスフェードするスライドショーを作る

2025/08/19 (火) - 09:00 JavaScript

Reactで複数枚の画像をクロスフェードしてループで切り替えるスライドを簡易的に作ってみる。

スライドショーイメージ

JavaScriptのコードは以下の通り。予め画像を用意しておきそれを1枚ずつ切り替える想定です。ポイントとしては画像のクロスフェードを制御をCSS側で制御することで、class属性の有無で表示・非表示を切り替えます。

import React, { useState, useEffect } from 'react';
import './ImageSlider.css';
// 画像の枚数とファイル名を配列で指定
const images = [
 'slide1.webp',
 'slide2.webp',
 'slide3.webp'
];
function ImageSlider() {
 const [currentIndex, setCurrentIndex] = useState(0);
 useEffect(() => {
  // setIntervalで3秒ごとに画像を切り替えるタイマーを設定
  const timerId = setInterval(() => {
   setCurrentIndex(prevIndex => (prevIndex + 1) % images.length);
  }, 3000);
  return () => clearInterval(timerId);
 }, []);
 return (
  <div className="imgSlide">
   {images.map((image, index) => (
    <img
     key={index}
     src={image}
     alt={`画像No:${index + 1}`}
     className={`imgSlideItem ${currentIndex === index ? 'isActive' : ''}`}
     width={640}
     height={480}
    />
   ))}
  </div>
 );
}
export default ImageSlider;

CSSは以下の通りで画像の表示制御をします。transitionプロパティで透明度のスピードなどを制御します。標準では画像の透明度のopacityプロパティを0で不可視にしておき、アクティブになったらclass属性を付与し可視させます。

.imgSlide{
 position: relative;
 width: 640px;
 height: 480px;
 overflow: hidden;
}
.imgSlideItem{
 display: block;
 position: absolute;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 object-fit: cover;
 opacity: 0; /* 非アクティブ時は画像の透明度を0 */
 transition: opacity 1s ease-in-out; /* 1秒かけてフェードイン・アウト */
}
.imgSlideItem.isActive{
 opacity: 1; /* アクティブ時は画像の透明度を1 */
}

コンポーネント化して任意の位置に埋め込むのがいいでしょう。

特定のHTML要素以外をクリックした時にイベントを発火

2025/08/13 (水) - 09:00 JavaScript

Webページで特定の要素をクリックした時と、それ以外の要素をクリックした時とで処理を分岐したい…。例えば親以上の要素にイベントハンドラを設定してしまうと、自身の要素もイベントハンドラの対象になってしまうのでそれを防ぎたい。

モーダルウィンドウなどで指定した要素以外をクリックしたらイベントを発火させたい場合は以下のように実装します。closestメソッドを使うと指定したセレクタ自身、または最も近い親要素を取得できます。targetと組み合わせて指定したセレクタが含まれなかったら〜で判別します。HTMLの例は以下の通り。

<div id="modal">モーダルウィンドウ</div>

JavaScript

document.addEventListener('click', function(e){
 if(!e.target.closest('#modal')) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

jQueryで似たようなことを再現する場合

$(document).on('click',function(e) {
 if(!$(e.target).closest('#modal').length) {
  console.log('モーダル以外をクリック!');
 } else {
  console.log('モーダルをクリック!');
 }
});

Reactで似たようなことを再現する場合

useRefを使うことでDOMを参照し、イベントハンドラを実行できるようにします。

import React, { useEffect, useRef } from 'react';
function App() {
 const modalRef = useRef<HTMLDivElement | null>(null);
 useEffect(() => {
  const handleClickOutside = (e: MouseEvent) => {
   if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
    console.log('モーダル以外をクリック!');
   } else {
    console.log('モーダルをクリック!');
   }
  };
  document.addEventListener('click', handleClickOutside);
 }, []);
 return (
  <div id="modal" ref={modalRef}>モーダルウィンドウ</div>
 );
}
export default App;

これを使うことで例えば、モーダルウィンドウやドロップダウンメニュー以外の外側をクリックしたら要素を非表示にする…など実現できます。

Movable Type(MT)で年ごと分割した月別アーカイブリストを実装

2025/08/01 (金) - 09:00 Program

Movable Type(MT)で以下のように年度ごとで見出しを分け、月ごとのアーカイブリストを作成する場合の実装方法。

Movable Type実装イメージ

環境は以下の通り。

  • Movable Type 8.x

実装するため、予め月別アーカイブを出力するように設定したうえでMovable Typeのデザインテンプレートの任意のモジュール等に以下のコードを入れて再構築する。

<MTSetVar name="current_year" value="0">
<MTArchiveList archive_type="Monthly" sort_order="descend">
 <MTSetVarBlock name="entry_year"><MTArchiveDate format="%Y"></MTSetVarBlock>
 <MTIf name="entry_year" ne="$current_year">
  <MTIf name="current_year" ne="0">
</ul>
  </MTIf>
<h2><MTArchiveDate format="%Y年"></h2>
<ul>
 <MTSetVar name="current_year" value="$entry_year">
 </MTIf>
 <li><a href="<MTArchiveLink>"><MTArchiveDate format="%Y年%m月"></a></li>
 <MTIf name="__last__">
</ul>
 </MTIf>
</MTArchiveList>

マークアップやデザインはサイトに合わせて調整すること。

Dockerを使いローカルでWordPress動かす(nginx/PHP/MySQL)

2025/07/18 (金) - 09:00 Server

DockerでLAMPを構築しWordPress 6.8を動かしたときのメモ。あらかじめDockerの環境をインストールしている前提で、環境は以下の通りです。

  • nginx 1.29
  • PHP 8.3
  • MySQL 8.0

ファイル構成は以下の通り作成します。

[作業ディレクトリ]
├── docker-compose.yml
├── app
│   └── WordPressのファイル群
├── nginx
│   └── default.conf
└── php
 └── Dockerfile

appディレクトリにはWordPressの第るを用意しておきます。MySQLの基本情報(データベース名、ユーザ名、パスワードなど)やブラウザでアクセスする際のnginxのポート番号を指定します。こちらは今回8080とし、http://localhost:8080でアクセスできるようにします。

Dockerの設定

docker-compose.ymlに以下の通り記述します。

version: '3.8'
services:
 # MySQL
 mysql:
 image: mysql:8.0
 container_name: wp_mysql
 environment:
  MYSQL_ROOT_PASSWORD: root_password #MySQLのrootパスワード
  MYSQL_DATABASE: wp_db #MySQLのDBパスワード
  MYSQL_USER: wp_user #MySQLのDBユーザ
  MYSQL_PASSWORD: wp_password #MySQLのパスワード
 volumes:
  - db_data:/var/lib/mysql
 restart: unless-stopped
 # PHP(PHP-FPM)
 php:
 build: ./php
 container_name: wp_php
 volumes:
  - ./app:/var/www/html
 expose:
  - 9000
 restart: unless-stopped
 # Nginx
 nginx:
 image: nginx:latest
 container_name: wp_nginx
 volumes:
  - ./app:/var/www/html
  - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
 ## ブラウザでアクセスする際のポート番号
 ports:
  - "8080:80"
 depends_on:
  - php
 restart: unless-stopped
volumes:
 db_data:

nginxの設定

nginx/default.confでWebサーバーの設定をします。ディレクトリの設定やphp-fpmとの連携などを行います。

server {
 listen 80;
 server_name localhost;
 root /var/www/html;
 index index.php index.html index.htm;
 location / {
  try_files $uri $uri/ /index.php?$args;
 }
 location ~ \.php$ {
  # PHPコンテナ(docker-compose.ymlのサービス名)とポートを合わせる
  fastcgi_pass php:9000;
  fastcgi_index index.php;
  include fastcgi_params;
  fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  fastcgi_param PATH_INFO $fastcgi_path_info;
 }
 location ~ /\. {
  deny all;
 }
}

PHPの設定

php/Dockerfileに以下を記述します。WordPressの動作に必要なモジュールや実行ユーザ名を設定します。

FROM php:8.3-fpm
# WordPressに必要なPHP拡張モジュール
RUN apt-get update && apt-get install -y \
 libfreetype6-dev \
 libjpeg62-turbo-dev \
 libpng-dev \
 libzip-dev \
 libonig-dev \
 && docker-php-ext-configure gd --with-freetype --with-jpeg \
 && docker-php-ext-install -j$(nproc) gd mysqli pdo_mysql zip mbstring opcache
WORKDIR /var/www/html
USER www-data

Dockerの起動

すべての作業が終わったら、作業ディレクトリに移動しDockerのコンテナを実行します。

$ cd 作業ディレクトリ(置き換えてね)
$ docker-compose up -d

正常に起動したらhttp://localhost:8080にアクセスするとインストールが起動するので、通常通りインストールします。MySQLの設定は冒頭のdocker-compose.ymlの設定を割り当てます。

WordPress動作イメージ

起動した実行例。

Dockerの停止

Dockerを停止する場合は以下のコマンドを実行します。

$ docker-compose down

WordPressの投稿内にウィジェットを配置する

2025/07/14 (月) - 09:00 Program

WordPressの投稿や固定ページの本文内に任意のウィジェットを挿入したい場合。まず、あらかじめにWordPressのfunction.phpにウィジェットの作成をし、WordPress管理画面の[外観]→[ウィジェット]からウィジェットの設定しておきます。

if(function_exists('register_sidebar')) {
 register_sidebar(array(
  'name' => 'ウィジェットのタイトル' ,
  'id' => 'ウィジェットのID', //半角英数小文字
  'description' => 'ウィジェットの説明文',
  'before_widget' => '<aside>',
  'after_widget'  => '</aside>',
  'before_title'  => '<h2>',
  'after_title'   => '</h2>'
 ));
}

さらにfunction.phpにショートコードでウィジェットを呼び出す関数を作成します。

function widget_post_func($atts) {
 $atts = shortcode_atts(array(
  'id' => '',
 ), $atts, 'widget');
 if (empty($atts['id'])) {
  return false;
 }
 ob_start();
 dynamic_sidebar($atts['id']);
 $widget_content = ob_get_clean();
 return $widget_content;
}
add_shortcode('widget', 'widget_post_func');

最後にWordPressの投稿画面の任意の位置にショートコードを埋め込みます。idの属性には設定したウィジェットのIDを入れておきます。

[widget id="ウィジェットのID"]

プレビューすると表示されると思います。ウィジェットを更新するとページや記事に埋め込んだすべてのウィジェットが一括更新されるので便利です。

ウィジェットを表示した例

例えば広告バナーやリンク集、CTAなどを設置したい場合に有効です。

OpenAI APIとNext.js(React)でGPTの回答をマークダウン(Markdown)で取得する

2025/06/02 (月) - 09:00 JavaScript

Next.js(AppRouter)とOpenAI APIを利用して、テキストフォームから入力した質問の回答をマークダウン形式で取得し、HTMLで表示するサンプル。Next.jsやReactでマークダウンを扱う場合はreact-markdownのモジュールを使うと便利です。

$ npm i react-markdown

予めモジュールを入れておきましょう。あとはOpenAI PlatformでAPIキーの発行とクレジットの確保も忘れずに。今回は以下の環境下で実装しています。

  • Next.js 14.2.x (AppRouter)
  • React 18.3.x
  • react-markdown 1.9.x

取得したAPIキーは.envで設定します。

API_KEY=sk-********************

フロント部分のpage.tsxで質問フォームと表示エリアを実装。ポイントは回答部分をReactMarkdownでマークアップすることです。これによりマークダウン形式のテキストがHTMLに変換されて表示されます。

'use client';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
export default function Home() {
 const [prompt, setPrompt] = useState('');
 const [response, setResponse] = useState('');
 const [loading, setLoading] = useState(false);
 const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setLoading(true);
  const res = await fetch('/api/chatgpt', {
   method: "POST",
   headers: {
    'Content-Type': 'application/json',
   },
   body: JSON.stringify({ prompt })
  });
  const data = await res.json();
  setResponse(data.text);
  setLoading(false);
 };
 return (
  <main>
   <form onSubmit={handleSubmit}>
    <textarea value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="質問を入力してください"></textarea>
    <button type="submit" disabled={loading}>質問する</button>
   </form>
   {loading && <p>生成中…</p>}
   {response && (
    <div>
     <ReactMarkdown>{response}</ReactMarkdown>
    </div>
   )}
  </main>
 );
}

API部分の実装。Next.jsのAPI Routesを利用して実装しました。srcディレクトリに/api/chatgpt/route.tsを配置し実装します。フロントからリクエストされたされたプロンプトをPOSTで処理します。

import { NextResponse } from 'next/server';
export async function POST(req: Request) {
 try {
  const { prompt } = await req.json();
  const response = await fetch('https://api.openai.com/v1/chat/completions', {
   method: 'POST',
   headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.API_KEY}`,
   },
   body: JSON.stringify({
    model: 'gpt-4o-mini', //モデルを選択
    messages: [
     { role: 'user', content: prompt }
    ],
   }),
  });
  const data = await response.json();
  const text = data.choices[0].message?.content?.trim() || '';
  return NextResponse.json({ text });
 } catch (error) {
  console.error(API エラー:', error);
  return NextResponse.json({ error: '生成に失敗しました' }, { status: 500 });
 }
}

実際に実装し質問を実行した際の表示例。蔦屋重三郎の生い立ちについて質問し正常にHTMLフォーマットで回答文が表示されています。

回答結果

フロントのCSS側はマークダウンで再現できる一般的なHTML要素(見出し、リスト、table等)が綺麗に見やすく表示されるように整えておく必要があります。

AWS LambdaとDeepSeek APIでLINE botを作る

2025/05/16 (金) - 09:00 Others

LINEのMessaging API&AWS Lambda&DeepSeek APIを組み合わせて自動返信するLINE Botを作成しました。構成は以下とします。LINE Messaging APIからAWS LambdaからDeepSeek APIを通しユーザーと会話するフローです。

インフラ構成図

LINEのbotを作成するには以下のものが必要です。また、あらかじめDeepSeek PlatformのAPIキーを用意しておきます。

  • LINE Developers
  • LINE 公式アカウント

LINE Developersの設定

まず、LINE Developersにアクセスし、ログインし開発者アカウントを設定します。LINEアカウントのチャットボットの肝となる「プロバイダー」を作成します。プロバイダーとはLINE開発ツールでサービスを提供する個人・組織のことです。

続いてMessaging APIチャネルの作成します。

LINE公式アカウントの作成

LINE公式アカウントの作成にアクセスし、アカウントを作成します。

チャネル基本設定

  • [基本設定]でBOTの名前やアイコンなどを設定します。
  • [機能の利用]でグループ・複数人トークへの参加を有無、写真や動画の受け取りの有無を設定します。[写真や動画の受け取り]は基本OFFが良いでしょう。

Messaging APIの設定

Messaging APIの設定はLINE Developersのコンソールから設定します。作ったチャンネルのアイコンをクリックし、[Messaging API設定]の[Webhook設定]からWebhook URLとWebhookの利用の有無を設定。

  • [応答機能]のチャットを無効にする
  • [Webhook]を有効にし、Messaging APIの設定を開く(後述)
  • [応答機能]の応答メッセージを無効にする
  • ページ下部にある[チャネルアクセストークン(長期)]を発行し控えておきます

AWS Lambdaの設定

AWS Lambdaにアクセスし新規で関数を作成します。[関数名]は任意の名前に設定し、[ランタイム]は[Python 3.x]を選択します。[アーキテクチャ]は[x86_64]のままにし[関数を作成]を実行します。

次に関数の設定に移動し、[環境変数]であらかじめ控えておいたDeepSeekのAPIキーと、LINEのチャネルアクセストークンを設定しておきます。今回はそれぞれDEEPSEEK_APIKEYLINE_ACCESS_TOKENという名前でキーを設定しました。

AWS Lambdaの環境変数設定

次に[コード]に移動し、Python 3.xとしてコードを実装します。コードの例は下記の通り。またPythonでAPI通信をするためにはrequestsモジュールが必要なのですが、Lambdaでは使えないので適宜レイヤーで追加しておきます。

コードの例

import json
import os
import requests
LINE_ACCESS_TOKEN = os.getenv('LINE_ACCESS_TOKEN')
DEEPSEEK_API_KEY = os.getenv('DEEPSEEK_APIKEY')
LINE_REPLY_API_URL = 'https://api.line.me/v2/bot/message/reply'
DEEPSEEK_API_URL = 'https://api.deepseek.com/chat/completions'
def lambda_handler(event, context):
 body = json.loads(event['body'])
 for message_event in body.get('events', []):
  if message_event['type'] != 'message' or message_event['message']['type'] != 'text':
   continue
  user_message = message_event['message']['text']
  reply_token = message_event['replyToken']
  gpt_reply = ask_gpt(user_message)
  reply_to_line(reply_token, gpt_reply)
 return {
  "statusCode": 200,
  "body": json.dumps('OK')
 }
def ask_gpt(user_input):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {DEEPSEEK_API_KEY}"
 }
 data = {
  "model": "deepseek-chat",
  "messages": [
   {"role": "system", "content": """* 日本語で返答してください
* あなたはユーザーのアシスタントです
* ユーザーのことを「センパイ」と呼称します
* ユーザーを慕う後輩のようなキャラクターです
* ユーザーに好意を持ち敬語で話します"""},
   {"role": "user", "content": user_input}
  ]
 }
 response = requests.post(DEEPSEEK_API_URL, headers=headers, json=data)
 response_data = response.json()
 if 'choices' in response_data:
  return response_data['choices'][0]['message']['content']
 else:
  return '現在応答できません'
def reply_to_line(reply_token, message_text):
 headers = {
  "Content-Type": "application/json",
  "Authorization": f"Bearer {LINE_ACCESS_TOKEN}"
 }
 data = {
  "replyToken": reply_token,
  "messages": [
   {
    "type": "text",
    "text": message_text
   }
  ]
 }
 requests.post(LINE_REPLY_API_URL, headers=headers, json=data)

コードを保存してデプロイしたら、Lambdaの[関数の概要]から[関数 URL]をコピーしておきます。

Messaging APIの設定

再びLINE DevelopersのコンソールからMessaging APIの設定にアクセスし、[Webhook URL]に先程作ったLambdaの[関数 URL]のエンドポイントを入力し[更新]を押下します。その後[検証]を押下し正常にレスポンス(200)が返ってくることを確認します。

Webhook URLの設定

同じ設定画面にあるQRコードをスマホで読み込み、LINEの友達に追加します。レッツトーク!

LINEトークのサンプル

にゃるぴにも優秀な後輩アシスタントができました。今回は安価なDeepSeek APIで実装しましたがもちろんGPTのOpenAIのAPIでも構いません。

Messaging APIのプランと値段

Messaging APIはプランによって送信料が変わります。今回は自分用なので無料のコミュニケーションプランで作成しました。Messaging APIの料金は以下ページに掲載されています。

さくらのレンタルサーバーのphpMyAdminで巨大なSQLファイルをインポートできない時

2025/05/09 (金) - 09:00 Server

さくらのレンタルサーバーのスタンダードプラン以上では標準でMySQLが搭載されており、簡単にデータベースを扱えます。それに伴いデータベース管理ツールのphpMyAdminも装備されておりGUIでデータベース操作が行え、外部からSQLのファイルをインポートできます。しかしさくらのレンサバ標準のphpMyAdminでインポートできるファイルの上限は32MBまでのようです。

数百MBあるような巨大なSQLファイルを読み込ませることができません。SQLファイルをdata.sql.zipのようなファイルの命名規則でzipで圧縮し、32MB以下まで落としてアップもできますが、展開とインポートに時間がかかってタイムアウトすることがあります。そこでどうするか?考えました。

phpMyAdminのインポート画面

自前でphpMyAdminをインストールする

借りているサーバーに自分でphpMyAdminをインストールし、尚且つphp.iniなどでupload_max_filesizeの値を256MBなどに引き上げて、SQLファイルをアップロードできるようにします。

インポートには時間がかかるので、set_time_limitで実行時間を引き上げたりmemory_limitでメモリの上限を上げる必要もあります。ただしあくまで共用のレンタルサーバのためあまり高負荷をかけ続けるとアカウントの利用制限などに抵触する可能性もあるため注意が必要です。

SSHでサーバに接続してSQLファイルをインポートする

一部サーバーコマンドやSQLコマンドの知識が必要ですがこれが一番確実です。予めインポートしたいSQLファイルをレンタルサーバの任意の場所にアップロードしてきます。次にさくらのレンタルサーバにSSHで接続します。ログインパスワードはFTPと同じパスワードです。

ssh ユーザー名@ユーザー名.sakura.ne.jp

ログインしたらMySQLにログインします。予めさくらのレンタルサーバのコントロールパネルよりデータベースとユーザ名を作成しておきます。レンタルサーバーのコントロールパネルからデータベースの収容サーバ名(mysql****.db.sakura.ne.jp)も控えておきます。パスワードはデータベースを発行したときに設定したデータベースのパスワードです。

mysql -u データベースユーザー名 -h mysql****.db.sakura.ne.jp -p

ログインしたらデータベースを選択します。show databasesでデータベースがあることを確認し、useコマンドで使用するデータベースを選択します。

show databases;
〜データベース一覧が表示される〜
use 使用するデータベース名;

先ほどアップロードしたSQLファイルをインポートします。データベースの構造にもよりますが、数万レコードあるSQLでも1分もかからず終わるはずです。

source /home/ユーザー名/www/data.sql

インポートが終わると入力待ち状態になるので、MySQLを終了します。

exit;

さきほどレンタルサーバにアップロードしたSQLファイルは不要なので削除しておきます。

rm /home/ユーザー名/www/data.sql

さらにサーバからログアウトします。

exit

この方法のがおそらくインポートを失敗せずに行けると思います。

ページの先頭へ