この記事で分かること・前提
この記事は、問い合わせの多くが「よくある質問」に当てはまる会社の担当者に向けて、ChatGPT APIを使った問い合わせ自動返答を自社のFAQで試せる状態にするまでの手順をまとめたものです。
- 01受信問い合わせの文章(Web/LINE)
- 02照合登録したFAQの範囲に当たるか
- 03生成システムプロンプト+FAQで回答を作る
- 04引き継ぎ範囲外・交渉・苦情は担当者へ● 人が確認
- 05返信自動回答を返す。文言は管理画面で直す
問い合わせには性質の違いがあり、すべてを自動化できるわけではありません。着手前に自社の問い合わせを次の3タイプに分けておくと、どこから自動化すべきかが見えやすくなります。
| タイプ | 特徴 | 自動化の向き |
|---|---|---|
| FAQ型 | 返品・配送・料金など定型的な質問 | 自動返答が最も効く |
| トリアージ型 | 内容を分類して担当部署に振り分ける | 分類精度があれば有効 |
| エスカレーション型 | クレームや交渉が絡む複雑なケース | 人への橋渡しに限定する |
読み終えたときには、自社のFAQを登録した問い合わせ受付をすぐに試せる状態になり、エスカレーション条件の決め方と、公開後に文言を直しながら運用していく勘所が分かるようにしています。コードは実際に動くものだけを載せ、途中で止まるものは置いていません。
まず動くものを触ってみる:管理画面つき問い合わせ自動返答のサンプル
最初に結論として、自社で公開している問い合わせ自動返答のデモを触ってもらうのが一番早い理解の方法です。
このデモは問い合わせ自動返答(管理画面つき)として公開しており、登録した内容だけで答え、答えられない質問は担当者へ回すという、FAQ型の問い合わせ対応に絞った構成になっています。管理画面の文言を書き換えると、その内容がすぐに回答へ反映される仕組みです。コードだけを読んでいても自社に当てはめるイメージは湧きにくいので、まずは実際の挙動を見てから設計の話に入ります。
- 上記のデモページにアクセスします。
- 登録されていそうな質問、たとえば「返品はできますか」のような定番の質問を入力して送信します。
- 登録済みの内容に沿った回答が返ってくることを確認します。
- 次に、わざと登録されていなさそうな質問(業界特有の専門的な内容など)を入力し、担当者へ回す旨の案内が返ってくることを確認します。
- 同じ意味でも言い回しを変えた質問を数パターン試し、拾える表現と拾えない表現の差を体感します。ここが実際に詰まりやすい箇所で、FAQの文言を完全一致に近い形で登録していると、少し言い回しが変わっただけで「分からない」判定になります。
このステップ5で感じる「拾えない」ケースが、次の章で説明するFAQ設計の話に直結します。登録文言をどう書くかで、同じ仕組みでも答えられる範囲が大きく変わります。
システムプロンプトとFAQ設計で詰まりやすい場所
結論として、システムプロンプトの型は一度決めてしまえば使い回せますが、FAQの管理方法は問い合わせ件数が増えた時点で見直しが必要になります。
システムプロンプトには、最低限次の5つの要素を入れておきます。
- ペルソナ定義:「あなたは〇〇社の問い合わせ対応AIです」のように役割を明示します。
- トーン指定:「丁寧かつ簡潔に、です・ます調で回答してください」と文体を固定します。
- 禁止事項:「価格や契約条件の具体的な数値は回答しないでください」など、答えてはいけない範囲を書きます。
- 不明時の対応:「回答に自信がない場合は担当者に確認する旨を伝えてください」と、分からないときの振る舞いを決めます。
- スコープ外の処理:「サービスと無関係な質問には応答しないでください」と範囲外の質問への対応を定めます。
ここまではよく紹介される内容ですが、実運用で詰まるのはこの先です。FAQをPythonのdict(辞書)で直接コードに書いていると、担当者が回答を直したいだけなのにコードを触る必要が出てきます。これは次のような状態になったらスプレッドシートなど外部管理に移すサインです。
- FAQの項目数が増え、1つのファイルで見通しが悪くなってきた
- コードを書けない担当者が文言を直したい場面が出てきた
- 週に1回以上のペースで文言の修正が発生している
この状態になったら、FAQをGoogle スプレッドシートに移し、起動時またはキャッシュを使って定期的に読み込む形に変えます。具体的な実装は次の章のコードで示します。
ChatGPT APIで会話履歴を保持する実装(コード)
結論として、会話履歴はそのまま貯め続けるとAPIのコストが徐々に増えていくため、最初から履歴を制限する処理を入れておく必要があります。
次のコードは、問い合わせメッセージをGPT-4oに送り、会話の文脈を保ちながら回答するものです。会話が長くなっても送信するメッセージ数を一定に保つよう、履歴を直近の往復数で区切る処理を加えています。
import os
import openai
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
SYSTEM_PROMPT = """
あなたは株式会社サンプルの問い合わせ対応AIです。
以下のルールに従って回答してください。
- 丁寧かつ簡潔に回答する
- 登録された情報以外のことは回答しない
- 回答できない場合は「担当者に確認します」と伝える
"""
MAX_HISTORY_TURNS = 6 # 直近6往復だけ残す
conversation_history = []
def trim_history(history):
max_messages = MAX_HISTORY_TURNS * 2
if len(history) > max_messages:
return history[-max_messages:]
return history
def chat_with_customer(user_message: str) -> str:
conversation_history.append({"role": "user", "content": user_message})
trimmed = trim_history(conversation_history)
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + trimmed
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
temperature=0.3,
max_tokens=1024,
)
assistant_message = response.choices[0].message.content
conversation_history.append({"role": "assistant", "content": assistant_message})
conversation_history[:] = trim_history(conversation_history)
return assistant_message
if __name__ == "__main__":
print("問い合わせチャット開始(終了: quit)")
while True:
user_input = input("顧客: ").strip()
if user_input.lower() == "quit":
break
if not user_input:
continue
print(f"AI: {chat_with_customer(user_input)}\n")
temperature=0.3は回答のばらつきを抑える設定です。0に近いほど同じ質問に対して同じような回答が返りやすくなります。問い合わせ対応では創造性より一貫性が重要なので、0.2から0.4の範囲を目安にします。
履歴を制限せずに運用すると、会話が長引くほど毎回のリクエストに含まれるトークン数が増え、1件あたりのコストが膨らみます。MAX_HISTORY_TURNSで往復数の上限を決めておくことで、長い雑談のようなやり取りになってもコストが際限なく増える事態を防げます。
カテゴリ分類とエスカレーション通知の実装(コード)
結論として、分類の仕組み自体はシンプルですが、FAQをコードに直接書く構成のままだと更新が属人化するため、早い段階でスプレッドシート管理に切り替える準備をしておくと後が楽になります。
次のコードは、問い合わせ文をカテゴリに分類し、FAQに該当すれば自動返答、該当しなければSlackへ通知してエスカレーションする処理です。
import os
import json
import requests
import openai
client = openai.OpenAI(api_key=os.environ["OPENAI_API_KEY"])
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
FAQ = {
"返品・交換": "返品・交換は商品到着後7日以内にお問い合わせフォームよりご連絡ください。",
"配送日数": "ご注文から通常3〜5営業日以内に発送いたします。",
"支払い方法": "クレジットカード・銀行振込・コンビニ払いに対応しております。",
}
CLASSIFY_PROMPT = f"""
以下の問い合わせ文を読み、最も適切なカテゴリを1つだけ返してください。
カテゴリ候補: {list(FAQ.keys()) + ["その他"]}
回答はカテゴリ名のみをJSON形式で返してください。例: {{"category": "返品・交換"}}
"""
def classify_inquiry(message: str) -> str:
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": CLASSIFY_PROMPT},
{"role": "user", "content": message},
],
temperature=0,
max_tokens=64,
response_format={"type": "json_object"},
)
result = json.loads(response.choices[0].message.content)
return result.get("category", "その他")
def send_slack_notification(message: str, category: str) -> None:
payload = {
"text": f"未対応の問い合わせが届きました\nカテゴリ: {category}\n内容: {message}"
}
requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
def handle_inquiry(customer_message: str) -> str:
category = classify_inquiry(customer_message)
if category in FAQ:
return f"【自動返答】{FAQ[category]}"
send_slack_notification(customer_message, category)
return "ご質問ありがとうございます。担当者より折り返しご連絡いたします。"
response_format={"type": "json_object"}は、ChatGPTの出力を必ずJSON形式に固定する指定です。これを入れないと、文章の前後に余計な説明が付いてしまい、後続のjson.loadsでエラーになることがあります。分類の精度を安定させるためにtemperature=0も合わせて設定しています。
上のコードはFAQをdictでそのまま持っていますが、これだと文言を直すたびにコードを書き換えてデプロイし直す必要があります。実運用ではスプレッドシートから読み込む形に変えます。
import gspread
gc = gspread.service_account(filename="credentials.json")
sheet = gc.open("FAQ管理").sheet1
def load_faq_from_sheet() -> dict:
rows = sheet.get_all_records() # [{"category": "返品・交換", "answer": "..."}, ...]
return {row["category"]: row["answer"] for row in rows}
FAQ = load_faq_from_sheet()
この移行でつまずきやすいのは認証周りです。credentials.jsonはGoogle Cloudのサービスアカウントキーで、発行したサービスアカウントのメールアドレスを、対象のスプレッドシートの共有設定に編集者として追加し忘れると、権限エラーで読み込めません。スプレッドシート側の列名(カテゴリ・回答など)とコード側のキー名を一致させておくことも忘れやすい点です。
公開後に起きること:精度・コスト・運用の実測
結論として、小規模な運用でもAPIの実費は数ドル単位で収まりますが、人が結果を確認する工程を残しておかないと誤答に気づけません。
当サイトはCloudflare Pages上で動いており、問い合わせ受付の自動返答自体はPages Functionsで動作しています。これとは別に、記事の収集や更新といった定時処理をWindowsのタスクスケジューラとPythonで動かし、データの保存にはSupabaseを使っています。問い合わせ対応に特化した構成ではありませんが、同じAPIを日常的に使い続けた上での実測値として参考になると考えています。
問い合わせ自動返答そのものに絞ったコストではありませんが、小規模な運用であればAPI利用料が数ドル単位に収まる感覚は持っておいてよいと思います。
金額よりも実際に効いているのは、ログを見て誤分類や想定外の回答に気づく工程を残していることです。エスカレーション通知や実行ログが残る設計にしておけば、答えられていない質問や誤った回答に後から気づけます。この確認を怠ると、FAQに登録した内容と実際の回答がずれたまま気づかれずに放置されることになります。
このあたりの設計思想は、問い合わせ対応の効率化を扱ったHelpfeelのFAQお役立ち情報や、分類の自動化による対応時間削減を扱ったAI顧問ワークスの成果直結AIラボでも共通して触れられている部分です。
次の一歩:自社のFAQで試す/外注する選択肢
結論として、いきなり本番環境に組み込むのではなく、まず小さく試してから広げるのが詰まりにくい進め方です。
- 手元のFAQを5件ほど選び、まずはノーコードツール(Makeなど)でフォーム送信からChatGPT APIへ送るだけの簡単な流れを組んでみます。
- 先に紹介したデモページで、実際の自社の質問文を使って回答の精度を試します。登録していない言い回しで答えられない場合は、FAQの文言をどう書き換えれば拾えるかを確認します。
- 社内で分類やエスカレーションの精度を見ながら運用していく時間が取れない場合は、設計とコードの実装部分を外部に依頼する選択肢も検討します。
GASを使った業務連携を外注するときの切り分けと依頼文はGAS自動化を外注する前に決めておく作業の切り分けと依頼文で扱っています。合わせて読むと、今回のChatGPT API部分と組み合わせたときの見積もりの感覚がつかみやすくなります。ChatGPTで社内問い合わせ対応を効率化するには?【弊社事例あり】にも、社内向けの導入事例が紹介されています。
まずは自社のFAQを5件書き出し、デモページに入力して回答を確認するところから始めてみてください。

OpenAI APIのムダ課金を防ぐ設計とスプレッドシート自動化の実例
ChatGPTを画面で使う段階からAPI化する段階への切り分け方
LINE複数チャンネルの使い分けを同一プロバイダー設計で混乱なく運用する方法