副業・収益化

KDP自動出版 品質スコアが全冊70点になった原因と解決手順

KDP自動出版 品質スコアが全冊70点になった原因と解決手順

KDP自動出版 品質スコアが全冊70点になった原因と解決手順

この記事は約 9 分で読めます

📖 目次
  1. 📌 KDP自動出版パイプラインの全体像
  2. 📌 「全冊同スコア」になる原因3パターン比較
  3. 📌 原因1: JSONパースが静かに失敗していた
  4. 📌 原因2: スコア集計ロジックのバグ
  5. 📌 実証:修正後のスコア分布
  6. 📌 コピペで使えるスコアリングプロンプト
  7. 📌 よくある質問
  8. 📌 まとめ

KDP(Kindle Direct Publishing)の出版作業を自動化している。AIが本を書いて、AIが品質チェックして、スコア順に自動出版するパイプラインだ。

先週、そのパイプラインを本格稼働させた初日に気づいた。スコアが全部「70点」だった。1冊だけじゃない。キューに積んであった8冊、全部。

偶然が8回続くわけがない。バグだ。

この記事でわかること:

  • KDP自動出版パイプラインの品質スコアリング仕組み
  • 「全冊同スコア」が起きる典型的な原因3パターン
  • 実際に僕が踏んだ原因と修正コード
  • そのまま使えるスコアリングプロンプトのテンプレ
  • よくある詰まりポイントとFAQ

KDP自動出版パイプラインの全体像

先に構成を整理しておく。

kdp_ai_book_generator.py
  └→ 本文生成(Claude Sonnet)
  └→ 表紙生成(Gemini Web経由)
  └→ content_scorer.py でスコアリング
       └→ S/A/B/C/D/F グレード付け
  └→ priority_stock_manager.py でキュー管理
       └→ スコア順に並べて上から自動出版

スコアリングは content_scorer.py が担当していて、Ollamaの qwen2.5:9b を使ってAIに採点させる。採点基準は「非エンジニア会社員が10分で読み切れるか」「第3章に手を動かせるコードがあるか」など5軸。それぞれ20点満点で合計100点満点。

稼働初日に全冊70点。採点AIが何かやらかしている。


「全冊同スコア」になる原因3パターン比較

パターン 症状 確認方法
プロンプト末尾に数字だけ出力させていない JSONパース失敗→デフォルト値返却 ログに JSONDecodeError が出るか確認
Ollamaのthinkingモードがオン <think>...</think> が邪魔してパース失敗 "think": false を設定しているか確認
スコア集計ロジックのバグ 常に同じ変数を返している print(scores) を各軸の直後に入れる

僕が踏んだのは1番目と3番目の複合技だった。


原因1: JSONパースが静かに失敗していた

content_scorer.py の採点部分はこうなっていた。

response = ollama_client.chat(
    model="qwen2.5:9b",
    messages=[{"role": "user", "content": prompt}],
    options={"think": False}
)
raw = response["message"]["content"]

try:
    scores = json.loads(raw)
except json.JSONDecodeError:
    scores = {"total": 70}  # フォールバック

フォールバックが 70 だった。

Ollamaが出力するテキストはたまにjson ...のコードブロックで囲まれて返ってくる。json.loads() はそのまま渡すとエラーになる。エラーになったら70点。全冊。

修正はこれだけ。

# コピペ可
import re

def parse_score_response(raw: str) -> dict:
    # コードブロック除去
    cleaned = re.sub(r"```(?:json)?\s*|\s*```", "", raw).strip()
    try:
        return json.loads(cleaned)
    except json.JSONDecodeError:
        # 数字だけ抽出してtotal扱い
        numbers = re.findall(r"\b(\d{1,3})\b", cleaned)
        if numbers:
            return {"total": int(numbers[0])}
        return {"total": 0}  # 0にしてtrash行き

フォールバックを 70 にしていたのが致命的だった。0か-1にしておけばキューで目立つ。「まあ70点なら通してもいいか」という判断がAIを挟まずに発動していた形。


原因2: スコア集計ロジックのバグ

採点は5軸あって、それぞれ20点満点。

axes = ["readability", "actionability", "structure", "uniqueness", "completeness"]
total = sum(scores.get(ax, 14) for ax in axes)  # デフォルト14

デフォルトが14。5軸 × 14 = 70。

Ollamaが軸名を少し変えて返してくる("readability""read_ability" とか)と、全軸 get() が失敗してデフォルト値合計の70点になる。

修正は軸名の正規化を入れた。

# コピペ可
def normalize_axis_name(name: str) -> str:
    mapping = {
        "read_ability": "readability",
        "action": "actionability",
        "struct": "structure",
        "unique": "uniqueness",
        "complete": "completeness",
    }
    for k, v in mapping.items():
        if k in name.lower():
            return v
    return name.lower()

Ollamaは創意工夫してキー名を変えてくる。正規化は必須だった。


実証:修正後のスコア分布

修正を入れて8冊を再採点した結果。

冊番号   修正前   修正後
---------------------------
book_001   70    82 (A)
book_002   70    61 (C)
book_003   70    91 (S)
book_004   70    55 (D)
book_005   70    78 (B)
book_006   70    44 (F) ← trash行き
book_007   70    85 (A)
book_008   70    69 (C)

book_004とbook_006はtrash退避になった。修正前だったら普通に出版されていた。

Fランクが実際に1冊あったのは地味にショックだった。「AIが書いた本だから品質は均一」という思い込みがあったけど、そんなことはない。出来不出来がある。


コピペで使えるスコアリングプロンプト

# コピペ可
SCORE_PROMPT = """
あなたはKindle電子書籍の品質審査員です。
以下の本文を読んで、5軸で採点してください。

採点軸(各20点満点):
- readability: 非エンジニアが10分で読み切れるか
- actionability: 読後すぐ手を動かせる内容があるか
- structure: 章の流れが自然か
- uniqueness: 他の入門書と差別化できているか
- completeness: 章立てとして完結しているか

必ずこのJSON形式だけで返してください(説明文不要):
{
  "readability": <0-20の整数>,
  "actionability": <0-20の整数>,
  "structure": <0-20の整数>,
  "uniqueness": <0-20の整数>,
  "completeness": <0-20の整数>,
  "total": <合計点>
}

本文:
{content}
"""

「必ずJSON形式だけで返してください」の一文が効く。これがないとOllamaは感想文を付けてくる。


よくある質問

Ollamaのthinkingモードって何ですか?

qwen系の一部モデルは <think>...</think> タグで思考過程を出力する機能がある。これがオンだと出力の先頭が <think> から始まり、JSONパースが確実に失敗する。

options={"think": False}  # 必須

パイプライン内では全てこれを入れている。

スコアリングにClaude使わないんですか?

API代がかかる。量産フェーズの品質チェックにクレジットを使うのは設計が悪い。Ollamaで十分な精度が出る。Claude Sonnetは本文生成だけに集中させている。

trash退避になった本はどうなりますか?

priority_stock_manager.py02_Content/kdp_queue/trash/ に移動させる。自動出版されない。手動で確認して修正するか、そのまま放置するかは僕が判断する。

全部Fになったりしませんか?

スコアリングプロンプトを締めすぎると全部F、緩めすぎると全部Sになる。最初の数冊は手動採点と比較して閾値調整が必要だった。「B以上で出版」にしたら週3〜4冊ペースが保てる。

Ollamaのモデルは何がおすすめですか?

採点用途では qwen2.5:9b が安定している。速度と精度のバランスがいい。gemma4:26b はパイプライン禁止(重すぎて他の処理を詰まらせる)。


まとめ

  • フォールバック値に「通りそうな点数」を設定するとバグが静かに潜伏する
  • Ollamaのレスポンスはコードブロック・キー名ゆらぎ・thinkingタグの3点を想定してパースする
  • スコアリングプロンプトには「JSON形式だけで返す」を明記する
  • 採点失敗時のデフォルトは0か-1(絶対に出版されない値)にする
  • 修正後にtrashが実際に発生したのは良いサイン——スコアリングが機能している証拠

全冊70点は派手なバグだったけど、初日に発見できてよかった。これが気づかれないまま1ヶ月動いていたら、Fランク本が量産されていた。


この記事を読んだあなたに:
– Ollama qwen2.5の使い方と実測速度比較 #
– KDP自動出版パイプライン全体設計と失敗した3つのこと #
– launchd でPythonスクリプトを毎日自動実行する方法 #



👨‍💻
一ノ瀬泰斗
AI自動化エンジニア / Python個人開発者

Claude Code × Ollama × launchd で SNS・ブログ・KDPを全自動化。実測データと失敗談を軸に、月5万円収益化のリアルな記録を発信中。