目的:
問い合わせ文を入力すると、AIが
カテゴリ・優先度・要約・次アクション・返信文案
をJSONで返すCLIツールを作る。
OpenAI公式では、PythonからOpenAI APIを使うには公式Python SDKをインストールして始める形になっています。また、現在のResponses APIはテキスト入力からテキストやJSON出力を生成でき、Structured OutputsではJSON SchemaやPydanticで構造化出力を扱えます。今回の問い合わせ分類ツールは、この Responses API + Structured Outputs の練習としてちょうどよい題材です。(OpenAI Platform)
まず完成形をイメージする
最初に作るものは、こういうものです。
問い合わせ文を入力
↓
OpenAI APIに送信
↓
AIが分類
↓
JSONで返す
↓
画面表示
↓
outputs/results.jsonl に保存
入力例:
本日10時頃から、管理画面にログインしようとすると500エラーになります。
複数名で同じ事象が発生しており、業務が止まっています。
至急確認をお願いします。
出力例:
{
"category": "障害報告",
"priority": "high",
"summary": "管理画面へのログイン時に500エラーが発生し、複数名の業務に影響している。",
"required_action": "障害としてチケットを起票し、管理画面の認証・サーバーログを確認する。",
"reply_draft": "お問い合わせありがとうございます。管理画面へのログイン時に500エラーが発生している件について、至急確認いたします。追加で、発生時刻・対象ユーザー・画面キャプチャがあればご共有ください。",
"confidence": 0.92,
"reason": "500エラー、複数名影響、業務停止、至急確認という記述があるため、障害報告かつ優先度高と判断した。"
}
最初からWeb画面は不要です。
まずはCLIで動く1ファイルを作ってください。
Day 2〜3の到達目標
Day 2でやること
- プロジェクトを作る
- APIキーを設定する
- サンプル問い合わせを1件分類する
- JSONで結果を表示する
Day 3でやること
- サンプル問い合わせを10件に増やす
- 期待結果とAI結果を比較する
- プロンプトを改善する
- 結果をファイル保存する
- 簡単な評価表を作る
1. プロジェクト有効化
作成済みのプロジェクト(genai-step1)に移動して仮想環境を有効化します。
cd genai-step1
source .venv/bin/activate # Windowsなら .venv\Scripts\activate
プロジェクト構成はこうします。
genai-step1/
.env
01_classify_inquiry.py
data
sample_inquiries.jsonl
outputs/
2. まずは1ファイルで完成させる
01_classify_inquiry.py を作って、以下をそのまま貼ってください。
import json
import os
from datetime import datetime
from enum import Enum
from typing import Optional
from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field
# =========================
# 1. 初期設定
# =========================
load_dotenv()
client = OpenAI()
MODEL = "gpt-5.4"
# =========================
# 2. 出力形式の定義
# =========================
class InquiryCategory(str, Enum):
INCIDENT = "障害報告"
OPERATION_QUESTION = "操作質問"
SPEC_CONFIRMATION = "仕様確認"
IMPROVEMENT_REQUEST = "改善要望"
CONTRACT_BILLING = "契約・料金"
OTHER = "その他"
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class InquiryClassification(BaseModel):
category: InquiryCategory = Field(
description="問い合わせの分類。指定されたカテゴリから選ぶ。"
)
priority: Priority = Field(
description="対応優先度。影響範囲、緊急度、業務停止有無から判断する。"
)
summary: str = Field(
description="問い合わせ内容を1文で要約する。"
)
required_action: str = Field(
description="次に取るべき対応。チケット起票、確認依頼、担当者エスカレーションなど。"
)
reply_draft: str = Field(
description="問い合わせ元に返す一次返信文案。丁寧で簡潔にする。"
)
confidence: float = Field(
ge=0.0,
le=1.0,
description="分類結果への自信度。0.0〜1.0で表す。"
)
reason: str = Field(
description="なぜそのカテゴリ・優先度にしたかの短い理由。"
)
# =========================
# 3. プロンプト
# =========================
SYSTEM_PROMPT = """
あなたは業務システムの問い合わせ一次対応を支援するシステムエンジニアです。
問い合わせ文を読み、以下を判断してください。
分類カテゴリ:
- 障害報告: エラー、停止、使えない、失敗、不具合、業務影響があるもの
- 操作質問: 操作方法、手順、設定方法を知りたいもの
- 仕様確認: 仕様、制限、挙動、項目意味の確認
- 改善要望: 機能追加、UI改善、自動化、項目追加などの要望
- 契約・料金: 請求、契約、ライセンス、料金、見積に関するもの
- その他: 上記に当てはまらないもの
優先度の基準:
- critical: 全社・多数ユーザーで業務停止、重要システム停止、金銭・法令・重大障害の恐れ
- high: 複数ユーザー影響、主要業務に支障、至急対応が必要
- medium: 一部業務に影響、回避策がある、期限が近い
- low: 質問、軽微な確認、急ぎではない要望
注意:
- 情報が不足している場合は、推測で断定しすぎない
- ただし、問い合わせ一次分類として実務的に判断する
- reply_draft では、必要に応じて追加情報の依頼も含める
- 個人情報や機密情報を増やして書かない
"""
# =========================
# 4. 分類処理
# =========================
def classify_inquiry(inquiry_text: str) -> InquiryClassification:
response = client.responses.parse(
model=MODEL,
input=[
{
"role": "system",
"content": SYSTEM_PROMPT,
},
{
"role": "user",
"content": f"以下の問い合わせを分類してください。\n\n{inquiry_text}",
},
],
text_format=InquiryClassification,
)
return response.output_parsed
# =========================
# 5. 結果保存
# =========================
def save_result(input_text: str, result: InquiryClassification) -> None:
os.makedirs("outputs", exist_ok=True)
record = {
"timestamp": datetime.now().isoformat(timespec="seconds"),
"input": input_text,
"result": result.model_dump(mode="json"),
}
with open("outputs/results_classify_inquiry.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
# =========================
# 6. CLI実行
# =========================
def main() -> None:
print("問い合わせ分類ツール")
print("終了する場合は空のままEnterしてください。")
print("-" * 40)
while True:
inquiry_text = input("\n問い合わせ文を入力してください:\n> ").strip()
if not inquiry_text:
print("終了します。")
break
try:
result = classify_inquiry(inquiry_text)
print("\n分類結果:")
print(json.dumps(result.model_dump(mode="json"), ensure_ascii=False, indent=2))
save_result(inquiry_text, result)
print("\n結果を outputs/results_classify_inquiry.jsonl に保存しました。")
except Exception as e:
print("\nエラーが発生しました。")
print(str(e))
if __name__ == "__main__":
main()
公式ドキュメントでは、Python SDKはPydanticを使って構造化レスポンスを定義できると説明されています。ここでは InquiryClassification というPydanticモデルを作り、問い合わせ分類の出力形式を固定しています。Structured Outputsでは、キー名を明確にし、重要な項目に説明を付けることが推奨されているため、Field(description=...) を細かく書いています。(OpenAI Platform)
4. 実行する
python 01_classify_inquiry.py
次の問い合わせを入れてください。
本日10時頃から、管理画面にログインしようとすると500エラーになります。複数名で同じ事象が発生しており、業務が止まっています。至急確認をお願いします。
期待する結果は、だいたいこうです。
{
"category": "障害報告",
"priority": "high",
"summary": "管理画面へのログイン時に500エラーが発生し、複数名の業務に影響している。",
"required_action": "障害としてチケットを起票し、発生時刻、対象ユーザー、サーバーログを確認する。",
"reply_draft": "お問い合わせありがとうございます。管理画面へのログイン時に500エラーが発生している件について、至急確認いたします。発生時刻や対象ユーザー、画面キャプチャがあればご共有ください。",
"confidence": 0.9,
"reason": "500エラー、複数名影響、業務停止、至急確認という記述があるため。"
}
まったく同じでなくて大丈夫です。
大事なのは、カテゴリと優先度が妥当で、JSONとして扱えることです。
5. ここで理解すべきコードのポイント
InquiryCategory
class InquiryCategory(str, Enum):
INCIDENT = "障害報告"
OPERATION_QUESTION = "操作質問"
SPEC_CONFIRMATION = "仕様確認"
IMPROVEMENT_REQUEST = "改善要望"
CONTRACT_BILLING = "契約・料金"
OTHER = "その他"
これは分類カテゴリを固定しています。
AIに自由入力させると、
- 障害
- 不具合
- 障害問い合わせ
- システム障害
- エラー報告
のように表記ブレします。
業務システムに連携するなら、表記ブレは困ります。
だからEnumで固定します。
Priority
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
優先度も固定します。
ここも自由入力にすると、
- 高
- 緊急
- 至急
- High
- 重要
のようにブレます。
業務ツールでは、AIの文章力より、後続処理しやすい安定性が大事です。
confidence
confidence: float = Field(ge=0.0, le=1.0)
分類の自信度です。
これは実務で便利です。
たとえば、
- confidence >= 0.8 → 自動分類候補として採用
- confidence < 0.8 → 人間確認
- confidence < 0.6 → 「要確認」扱い
のようにできます。
reason
reason: str
理由を出させるのは重要です。
理由がないと、後で人間が確認したときに、
なぜhighにしたのか?
が分かりません。
AIの判断をそのまま信じるのではなく、判断理由を人間が確認できる形にするのが実務向きです。
6. Day 2の完成条件
Day 2はここまでできれば合格です。
-
python 01_classify_inquiry.pyで実行できる - 問い合わせ文を入力できる
- JSONが表示される
-
outputs/results_classify_inquiry.jsonlに保存される - カテゴリが指定候補から選ばれる
- 優先度が
low / medium / high / criticalから選ばれる
まだ精度は完璧でなくていいです。
7. Day 3:サンプルを10件作る
次に、data/sample_inquiries.jsonl を作ります。
{"id":"001","text":"本日10時頃から管理画面にログインしようとすると500エラーになります。複数名で同じ事象が発生しており、業務が止まっています。至急確認をお願いします。","expected_category":"障害報告","expected_priority":"high"}
{"id":"002","text":"パスワードを変更したいのですが、どの画面から操作すればよいでしょうか。","expected_category":"操作質問","expected_priority":"low"}
{"id":"003","text":"ユーザー一覧画面のCSV出力に、部署名と最終ログイン日時を追加してほしいです。","expected_category":"改善要望","expected_priority":"medium"}
{"id":"004","text":"請求書に記載されている利用人数が実際より多いようです。契約内容を確認してください。","expected_category":"契約・料金","expected_priority":"medium"}
{"id":"005","text":"商品登録画面で価格を0円にした場合、登録できる仕様でしょうか。","expected_category":"仕様確認","expected_priority":"low"}
{"id":"006","text":"全店舗でPOS連携ができず、売上登録が止まっています。至急復旧してください。","expected_category":"障害報告","expected_priority":"critical"}
{"id":"007","text":"検索条件を保存できるようにしてほしいです。毎回同じ条件を入力するのが手間です。","expected_category":"改善要望","expected_priority":"low"}
{"id":"008","text":"一部ユーザーだけ帳票出力が失敗します。エラーメッセージは権限がありませんと表示されます。","expected_category":"障害報告","expected_priority":"medium"}
{"id":"009","text":"月額プランを年額プランに変更した場合の料金を教えてください。","expected_category":"契約・料金","expected_priority":"low"}
{"id":"010","text":"退職者のアカウントを無効化する手順を教えてください。","expected_category":"操作質問","expected_priority":"medium"}
この10件が最初のテストデータです。
ポイントは、わざと分類が迷うものを入れることです。
たとえば 008 は「権限がありません」と表示されているので操作質問にも見えますが、「帳票出力が失敗する」なので障害報告寄りです。
010 は操作質問ですが、退職者アカウントなのでセキュリティ上やや重要です。だから medium にしています。
こういう判断基準を持つと、プロンプト改善がしやすくなります。
8. 10件をまとめて評価するコードに変える
inquiry_classifier.py の下に、以下の関数を追加してください。
def run_batch_eval(file_path: str = "data/sample_inquiries.jsonl") -> None:
total = 0
category_correct = 0
priority_correct = 0
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
total += 1
sample = json.loads(line)
result = classify_inquiry(sample["text"])
actual_category = result.category.value
actual_priority = result.priority.value
expected_category = sample["expected_category"]
expected_priority = sample["expected_priority"]
is_category_correct = actual_category == expected_category
is_priority_correct = actual_priority == expected_priority
if is_category_correct:
category_correct += 1
if is_priority_correct:
priority_correct += 1
print("=" * 60)
print(f"ID: {sample['id']}")
print(f"入力: {sample['text']}")
print(f"期待カテゴリ: {expected_category}")
print(f"実際カテゴリ: {actual_category}")
print(f"カテゴリ判定: {'OK' if is_category_correct else 'NG'}")
print(f"期待優先度: {expected_priority}")
print(f"実際優先度: {actual_priority}")
print(f"優先度判定: {'OK' if is_priority_correct else 'NG'}")
print(f"理由: {result.reason}")
save_result(sample["text"], result)
print("\n" + "#" * 60)
print("評価結果")
print(f"件数: {total}")
print(f"カテゴリ正解数: {category_correct}/{total}")
print(f"優先度正解数: {priority_correct}/{total}")
print(f"カテゴリ正解率: {category_correct / total:.1%}")
print(f"優先度正解率: {priority_correct / total:.1%}")
さらに、main() の手前に実行モードを少し足します。
元の main() をこれに置き換えてください。
def main() -> None:
print("問い合わせ分類ツール")
print("1: 1件ずつ分類する")
print("2: sample_inquiries.jsonl をまとめて評価する")
mode = input("実行モードを選んでください [1/2]: ").strip()
if mode == "2":
run_batch_eval()
return
print("\n終了する場合は空のままEnterしてください。")
print("-" * 40)
while True:
inquiry_text = input("\n問い合わせ文を入力してください:\n> ").strip()
if not inquiry_text:
print("終了します。")
break
try:
result = classify_inquiry(inquiry_text)
print("\n分類結果:")
print(json.dumps(result.model_dump(mode="json"), ensure_ascii=False, indent=2))
save_result(inquiry_text, result)
print("\n結果を outputs/results_classify_inquiry.jsonl に保存しました。")
except Exception as e:
print("\nエラーが発生しました。")
print(str(e))
これで実行すると、モード選択が出ます。
python 01_classify_inquiry.py
問い合わせ分類ツール
1: 1件ずつ分類する
2: sample_inquiries.jsonl をまとめて評価する
実行モードを選んでください [1/2]:
2 を選ぶと、10件まとめて評価できます。
9. 評価結果をどう見るか
最初の目標はこれです。
| 項目 | 目標 |
|---|---|
| カテゴリ正解率 | 80%以上 |
| 優先度正解率 | 70%以上 |
| JSON崩れ | 0件 |
| 明らかな過剰判断 | 少ない |
| 返信文の違和感 | 少ない |
最初から100%は狙わなくていいです。
むしろ100%になるまでプロンプトをいじりすぎると、他のデータに弱くなります。
仕事で使うなら、重要なのは以下です。
- 障害を操作質問にしない
- critical/highを見落とさない
- 低優先度を過剰にcriticalにしすぎない
- 分からないときに自信度を下げる
- 一次返信文が失礼でない
10. プロンプト改善の具体例
もし critical が出にくい場合は、SYSTEM_PROMPTの優先度基準を強めます。
追加例:
以下の表現がある場合は high または critical を強く検討する:
- 業務が止まっている
- 全員使えない
- 全店舗
- 複数部署
- 本番環境
- 決済できない
- 売上登録できない
- 至急
- 期限が本日
もし何でも 障害報告 になりすぎる場合は、こう足します。
単に「使い方が分からない」「どこから操作するか知りたい」という内容は、エラーや失敗が書かれていない限り、障害報告ではなく操作質問にする。
もし 改善要望 と 仕様確認 が混ざる場合は、こう足します。
「できますか」「仕様ですか」「制限はありますか」は仕様確認。
「できるようにしてほしい」「追加してほしい」「改善してほしい」は改善要望。
もし優先度が高く出すぎる場合は、こう足します。
「至急」と書かれていても、業務停止・多数影響・期限切迫が読み取れない場合は、原則 high ではなく medium とする。
こうやって、誤分類したケースを見ながらルールを足すのが正しい進め方です。
11. Day 3でやるべき改善サイクル
以下を3回まわしてください。
1. 10件を実行する
2. NGだったケースを見る
3. なぜ間違えたか考える
4. SYSTEM_PROMPTにルールを1〜3個だけ追加する
5. 再実行する
注意点は、一度に大量のルールを追加しないことです。
何が効いたのか分からなくなります。
おすすめの記録方法は、memo.md を作ることです。
# 問い合わせ分類ツール 改善メモ
## v1
- 初期プロンプト
- カテゴリ正解率: 8/10
- 優先度正解率: 6/10
## 課題
- 退職者アカウント無効化が low になった
- 全店舗POS連携停止が high になり、criticalにならなかった
## v2で追加したルール
- 全店舗、決済停止、売上登録停止はcriticalを強く検討
- アカウント無効化などセキュリティ関連の操作はmedium以上を検討
## v2結果
- カテゴリ正解率: 9/10
- 優先度正解率: 8/10
このメモはかなり価値があります。
社内PoCにするとき、「プロンプトをどう改善したか」を説明できます。
12. 仕事に近づけるなら項目を追加する
分類が安定してきたら、出力項目を増やします。
おすすめはこの3つです。
missing_info
問い合わせ対応に不足している情報です。
"missing_info": [
"対象ユーザー",
"発生時刻",
"画面キャプチャ",
"再現手順"
]
ticket_title
チケット起票用のタイトルです。
"ticket_title": "管理画面ログイン時に500エラーが発生"
escalation_team
担当チーム候補です。
"escalation_team": "アプリケーション運用チーム"
Pydanticモデルに追加するなら、こうです。
class InquiryClassification(BaseModel):
category: InquiryCategory = Field(description="問い合わせの分類。指定されたカテゴリから選ぶ。")
priority: Priority = Field(description="対応優先度。影響範囲、緊急度、業務停止有無から判断する。")
summary: str = Field(description="問い合わせ内容を1文で要約する。")
required_action: str = Field(description="次に取るべき対応。")
reply_draft: str = Field(description="問い合わせ元に返す一次返信文案。")
confidence: float = Field(ge=0.0, le=1.0, description="分類結果への自信度。")
reason: str = Field(description="判断理由。")
ticket_title: str = Field(description="チケット管理システムに登録する場合のタイトル。")
missing_info: list[str] = Field(description="追加で確認すべき不足情報。なければ空配列。")
escalation_team: Optional[str] = Field(description="エスカレーション先候補。不明ならnull。")
このあたりまでできると、かなり実務っぽくなります。
13. よくあるエラーと対処
エラー1:APIキーが読めない
症状:
OPENAI_API_KEY is missing
確認:
-
.envがプロジェクト直下にあるか -
OPENAI_API_KEY=...の形式になっているか -
.env.txtになっていないか - 仮想環境を有効化しているか
エラー2:ModuleNotFoundError: No module named 'openai'
対処:
source .venv/bin/activate
pip install openai python-dotenv pydantic
Windowsなら:
.venv\Scripts\activate
pip install openai python-dotenv pydantic
エラー3:モデル名で失敗する
MODEL = "gpt-5.5" でエラーになる場合は、利用可能なモデルに変更してください。
たとえば、あなたのAPIアカウントで使えるモデル名に合わせます。
MODEL = "gpt-5"
または、価格を抑えたい検証では軽量モデルにします。
MODEL = "gpt-5-mini"
モデル名は変わることがあるので、ここはOpenAIのモデルページやAPIダッシュボードで確認してください。
エラー4:responses.parse が使えない
OpenAI SDKのバージョンが古い可能性があります。
pip install --upgrade openai
それでも難しい場合は、いったん通常の responses.create とJSON Schema方式に変える方法もあります。
ただ、学習段階ではPydanticで進める方が理解しやすいです。
14. 最低限の完成版コード
上の説明を踏まえると、Day 2〜3の最終版はこの形です。
import json
import os
from datetime import datetime
from enum import Enum
from typing import Optional
from dotenv import load_dotenv
from openai import OpenAI
from pydantic import BaseModel, Field
load_dotenv()
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MODEL = "gpt-5.5"
class InquiryCategory(str, Enum):
INCIDENT = "障害報告"
OPERATION_QUESTION = "操作質問"
SPEC_CONFIRMATION = "仕様確認"
IMPROVEMENT_REQUEST = "改善要望"
CONTRACT_BILLING = "契約・料金"
OTHER = "その他"
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class InquiryClassification(BaseModel):
category: InquiryCategory = Field(description="問い合わせの分類。指定されたカテゴリから選ぶ。")
priority: Priority = Field(description="対応優先度。影響範囲、緊急度、業務停止有無から判断する。")
summary: str = Field(description="問い合わせ内容を1文で要約する。")
required_action: str = Field(description="次に取るべき対応。")
reply_draft: str = Field(description="問い合わせ元に返す一次返信文案。")
confidence: float = Field(ge=0.0, le=1.0, description="分類結果への自信度。")
reason: str = Field(description="判断理由。")
ticket_title: str = Field(description="チケット管理システムに登録する場合のタイトル。")
missing_info: list[str] = Field(description="追加で確認すべき不足情報。なければ空配列。")
escalation_team: Optional[str] = Field(description="エスカレーション先候補。不明ならnull。")
SYSTEM_PROMPT = """
あなたは業務システムの問い合わせ一次対応を支援するシステムエンジニアです。
問い合わせ文を読み、分類・優先度・要約・次アクション・返信文案を作成してください。
分類カテゴリ:
- 障害報告: エラー、停止、使えない、失敗、不具合、業務影響があるもの
- 操作質問: 操作方法、手順、設定方法を知りたいもの
- 仕様確認: 仕様、制限、挙動、項目意味の確認
- 改善要望: 機能追加、UI改善、自動化、項目追加などの要望
- 契約・料金: 請求、契約、ライセンス、料金、見積に関するもの
- その他: 上記に当てはまらないもの
優先度の基準:
- critical: 全社・多数ユーザーで業務停止、重要システム停止、金銭・法令・重大障害の恐れ
- high: 複数ユーザー影響、主要業務に支障、至急対応が必要
- medium: 一部業務に影響、回避策がある、期限が近い
- low: 質問、軽微な確認、急ぎではない要望
判断補足:
- 「できますか」「仕様ですか」「制限はありますか」は仕様確認
- 「できるようにしてほしい」「追加してほしい」「改善してほしい」は改善要望
- エラーや失敗が書かれていない操作手順の質問は操作質問
- 全店舗、全社、決済停止、売上登録停止、業務停止はcriticalを強く検討
- 退職者アカウント無効化などセキュリティに関わる操作はmedium以上を検討
- 「至急」と書かれていても、業務停止・多数影響・期限切迫が読み取れない場合はhighにしすぎない
注意:
- 情報が不足している場合は、推測で断定しすぎない
- 不足情報は missing_info に列挙する
- reply_draft では、必要に応じて追加情報の依頼も含める
- 個人情報や機密情報を増やして書かない
"""
def classify_inquiry(inquiry_text: str) -> InquiryClassification:
response = client.responses.parse(
model=MODEL,
input=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": f"以下の問い合わせを分類してください。\n\n{inquiry_text}"},
],
text_format=InquiryClassification,
)
return response.output_parsed
def save_result(input_text: str, result: InquiryClassification) -> None:
os.makedirs("outputs", exist_ok=True)
record = {
"timestamp": datetime.now().isoformat(timespec="seconds"),
"input": input_text,
"result": result.model_dump(mode="json"),
}
with open("outputs/results.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
def run_batch_eval(file_path: str = "sample_inquiries.jsonl") -> None:
total = 0
category_correct = 0
priority_correct = 0
with open(file_path, "r", encoding="utf-8") as f:
for line in f:
total += 1
sample = json.loads(line)
result = classify_inquiry(sample["text"])
actual_category = result.category.value
actual_priority = result.priority.value
expected_category = sample["expected_category"]
expected_priority = sample["expected_priority"]
is_category_correct = actual_category == expected_category
is_priority_correct = actual_priority == expected_priority
if is_category_correct:
category_correct += 1
if is_priority_correct:
priority_correct += 1
print("=" * 60)
print(f"ID: {sample['id']}")
print(f"入力: {sample['text']}")
print(f"期待カテゴリ: {expected_category}")
print(f"実際カテゴリ: {actual_category}")
print(f"カテゴリ判定: {'OK' if is_category_correct else 'NG'}")
print(f"期待優先度: {expected_priority}")
print(f"実際優先度: {actual_priority}")
print(f"優先度判定: {'OK' if is_priority_correct else 'NG'}")
print(f"理由: {result.reason}")
save_result(sample["text"], result)
print("\n" + "#" * 60)
print("評価結果")
print(f"件数: {total}")
print(f"カテゴリ正解数: {category_correct}/{total}")
print(f"優先度正解数: {priority_correct}/{total}")
print(f"カテゴリ正解率: {category_correct / total:.1%}")
print(f"優先度正解率: {priority_correct / total:.1%}")
def main() -> None:
print("問い合わせ分類ツール")
print("1: 1件ずつ分類する")
print("2: sample_inquiries.jsonl をまとめて評価する")
mode = input("実行モードを選んでください [1/2]: ").strip()
if mode == "2":
run_batch_eval()
return
print("\n終了する場合は空のままEnterしてください。")
print("-" * 40)
while True:
inquiry_text = input("\n問い合わせ文を入力してください:\n> ").strip()
if not inquiry_text:
print("終了します。")
break
try:
result = classify_inquiry(inquiry_text)
print("\n分類結果:")
print(json.dumps(result.model_dump(mode="json"), ensure_ascii=False, indent=2))
save_result(inquiry_text, result)
print("\n結果を outputs/results.jsonl に保存しました。")
except Exception as e:
print("\nエラーが発生しました。")
print(str(e))
if __name__ == "__main__":
main()
15. この課題で本当に学んでほしいこと
問い合わせ分類ツールで学ぶ本質は、APIの呼び出し方だけではありません。
SEとして重要なのはこの5つです。
1. 自然文を業務データに変換する
問い合わせ文は曖昧です。
それを、
{
"category": "...",
"priority": "...",
"required_action": "..."
}
のように、システムで扱える形に変換する。
これが生成AI活用の基本です。
2. 出力を固定する
AIに自由に文章を書かせると、業務システムにはつなぎにくいです。
JSON、Enum、Schemaで縛るのが重要です。
3. 評価データを作る
AIは「動いた」だけではダメです。
10件でもよいので、期待値を作って比較してください。
この習慣があるかどうかで、実務導入のレベルが変わります。
4. 判断理由を残す
AIの分類結果だけではなく、理由を残す。
これはレビュー・監査・改善に効きます。
5. 人間確認を前提にする
このツールは自動対応ツールではなく、最初は一次分類支援ツールです。
おすすめの使い方は、
AIが分類する
↓
人間が確認する
↓
チケット起票や返信に使う
です。
いきなり自動返信まで行くのは危険です。
Day 2〜3の最終チェックリスト
最後に、これを全部満たせば完了です。
- PythonからOpenAI APIを呼べる
- 問い合わせ文を1件分類できる
- JSONで結果が返る
- カテゴリが固定値から選ばれる
- 優先度が固定値から選ばれる
-
summaryが自然 -
required_actionが実務的 -
reply_draftがそのまま下書きに使える -
reasonで判断理由が分かる -
confidenceが出る -
outputs/results.jsonlに保存される - サンプル10件で評価できる
- 誤分類を見てプロンプトを改善できる
ここまでやれば、Day 2〜3としては十分以上です。
次に進む前に、10件のテストデータでカテゴリ80%以上・優先度70%以上を目安にしてください。