AIエージェントに手本を1つ渡すと、出力が全部その型になる原因と対処|例は3〜5個・違いを見せる・「〜しない」より「〜する」・写りすぎをPythonで数える

天色ななみが、すべて同じおあげを載せた3種類のうどんを確認し、手本への偏りを点検するイラスト。

こんにちは、おうどんです🍜

おうどん

今日の題名、何にした?

エージェントくん「『Dockerのコンテナが消えた件。』です!」

おうどん

昨日は?

エージェントくん「『cronが夜中だけ動かなかった件。』です!」

ななみ

一昨日は『Excelの日付が数字になった件。』でしたね

(会話は説明用の架空の場面です)

毎日ちゃんと書いてくれる。中身も違う。
なのに、題名の終わり方が毎日同じ。

うどん屋さんのショーウィンドウを思い浮かべてください。
きつねうどんの食品サンプルが、1つだけ飾ってあります。
新しく入った店員さんに「あんな感じで出してね」と言ったら、たぬきも、かけも、カレーうどんも、全部おあげが2枚のって出てきた。

エージェントくん「サンプルのとおりにしました!」

おあげは「あんな感じ」の中に入っていなかったんです。たぶん。
でも、サンプルが1つしかないと、どこまでが「あんな感じ」なのか、見ただけでは分かりません。

おうどんも毎朝、Claude Code の定期タスクで記事の下書きを作ってもらっています。そのタスクは、書き方のルールのファイルと、直近の下書きを読んでから書く作りです。
つまり、手本を見せて書かせる仕事を、毎朝くり返しているわけです。

今回は、AI エージェント(AI に作業を任せて、自分で道具を使わせて進めるしくみ)に手本を渡したとき、出力がその型に寄ってしまう問題を、公式ドキュメントと Python で確かめました。

先に結論です。

  • 手本は1つにしない。違う型の例を3〜5個並べて、「違ってよいところ」を見せる
  • 「〜しない」だけで終わらせず、「〜する」と、その理由を書く
  • 偏りは目で探さず、数える(この記事の Python をそのまま使えます)
目次

手本が1つだと、AIエージェントは「どこまで真似するか」を決められない

サンプルが1つしかないショーウィンドウは、新人さんにとってはクイズです。
器の色を真似するのか、おあげを真似するのか、ネギの量を真似するのか。

AI もこれに近い立場にいます。
OpenAI のプロンプトの書き方のガイドでは、例を並べる書き方(few-shot、少数の例を見せる方法)について、モデルは例から暗黙のうちにパターンを「拾い」、それを入力に当てはめる、と説明しています。

拾うのはパターンです。
こちらが「題名の長さの目安として見せた」つもりでも、文末の「件。」まで拾われるかもしれない。どのパターンを拾ってほしいかは、例が1つだと伝わりません。

読者「じゃあ『おあげは真似しないで』と書き添えればいい?」

いい線です。でも、それだと次は器の色が全部同じになります。
真似してほしくない特徴を全部書き出すのは、サンプルの写真に注意書きを100個貼るようなもの。お客さんから料理が見えなくなります。

Anthropic のプロンプトのベストプラクティスにも、例は出力の形式・口調・構成を方向づける、もっとも確実な方法の1つだと書かれています。
効き目が強いからこそ、1つだけ置くと、その1つに強く引っぱられる、と考えておくのが安全です。

エージェントくん「強く引っぱられるの、得意です!」

おうどん

それは長所としてしまっておいて

手本は3〜5個。違う型を並べて、タグでくくる

ショーウィンドウの直し方は簡単です。
きつね、たぬき、カレーうどんを並べる。おあげがのっているのは1つだけ。
そうすると新人さんは、「おあげは料理ごとに違う。共通なのは、麺と器の大きさ」と分かります。

Anthropic のベストプラクティスでは、例を次のようにするよう書かれています。

条件公式の説明(要約)題名の例でいうと
Relevant(関連している)実際の用途に近づける本当に書く記事と同じジャンルの題名を例にする
Diverse(多様)端のケースも入れ、意図しないパターンを拾わない程度にばらす文末の型(疑問・理由・方法・言い切り)を変える
Structured(構造化)<example> タグ(複数なら <examples>)でくくり、指示と区別できるようにする例と「書き方の指示」を混ぜない

数は、3〜5個の例でよい結果が出る、とあります。
さらに、Claude 自身に「この例は多様か」を評価させたり、最初の例から追加の例を作らせたりしてもよい、とも書かれています。

エージェントくん「きつねうどんのサンプルを、3つ並べました!」

数は合っています。表の Diverse の行を、もう一度読んでもらえますか。

OpenAI のガイドも、例を見せるときは、入力の幅を広く見せて、それぞれに望む出力を並べるようにすすめています。

書き換えると、たとえばこうです。

直す前(説明用の例です):

題名は、読者が思わずクリックしたくなるものにしてください。
例:うどんを茹でたら、鍋ごと消えてしまった件。

直した後(説明用の例です):

題名は、前半に技術名と読者の困りごとを入れてください。検索する人が打つ語で見つけてもらうためです。
文末の型は、直近の題名と変えてください。下の例は型の違いを見せるためのもので、文言は写さずに使ってください。

<examples>
<example>Gitのpushが拒否されるのはなぜ?</example>
<example>Excelの日付が数字になる理由</example>
<example>cronが夜中だけ動かないときに見る3か所</example>
<example>JSONの末尾カンマ、JavaScriptはOKでもJSONはダメです</example>
</examples>

(どちらも、AI に渡して出力を確かめたものではありません。書き方の例です)

4つの例は、文末が「なぜ?」「理由」「3か所」「です」で、全部違います。
共通しているのは、前半に技術名と困りごとがあること。
これで「そろえてほしいのは前半、変えてほしいのは後半」が、例そのものから読み取れます。

エージェントくん「では、4つの型を順番に使います! 月曜は疑問、火曜は理由……」

おうどん

それはそれで、曜日が分かる題名になる

例を並べた順番まで型にされることもある、と頭のすみに置いておきましょう。
だから最後は、数えて確かめます(後半の章でやります)。

「〜しない」より「〜する」と、その理由を書く

新人さんに「おあげをのせないで」とだけ言うと、今度は天かすが山盛りで出てくるかもしれません。
「のせない」は分かった。でも、代わりに何をのせるのかは聞いていない。

Anthropic のベストプラクティスの「応答の形式をコントロールする」の項では、効き目のある方法の1つめに、「何をしないか」ではなく「何をするか」を伝える、が挙がっています。
例は、「応答にマークダウンを使わないで」の代わりに、「なめらかに流れる散文の段落で書いて」です。

同じ資料には、指示の理由を添える例もあります。
「省略記号を使うな」とだけ書くより、「読み上げソフトが発音できないので、省略記号は使わない」と書くほうが効く。理由が分かれば、Claude はそこから広げて考えられる(generalize できる)、と書かれています。

題名でいえば、こうです。

書き方起きそうなこと
「〜件。」で終わらせない「〜件でした。」「〜な件」のように、形だけ避けた題名になるかもしれない
文末の型は直近7本と変える。題名だけで何の記事か分かるようにするため型を変える理由が分かるので、似た型も避けやすい

上の「起きそうなこと」は、公式の説明から考えた、おうどんの見立てです。実際に AI に渡して比べたものではありません。

おうどん

よし、ルールに『ここが大事なところです、とは書かない』と足しておこう

ななみ

それ、ルールのファイルに『ここが大事なところです』が1回増えましたね

禁止の文の中に、禁止したい言い回しそのものが入る。これもおうどんの考えですが、手本の数を増やしているのと、あまり変わらない気がします。
禁止の言葉を書くなら、代わりに使ってほしい言い方も並べておくのがよさそうです。

もう1つ、同じ資料には、プロンプト(AI への指示文)の書き方の形が、応答の形に影響しうる、ともあります。プロンプトからマークダウンを減らすと、出力のマークダウンも減ることがある、という例です。
ルールのファイルが箇条書きだらけなら、出てくる文章も箇条書きに寄っていくかもしれません。手本は、例の欄の中だけにあるとは限らないんです。


🔰 ここまで読めば今日から困らない

  • 手本を1つしか置いていないなら、違う型の例を足して3〜5個にする。例は <examples> タグでくくって、指示と分ける
  • 「〜しない」には、「代わりに〜する」と「なぜそうするか」を添える
  • 禁止したい言い回しを書くときは、使ってほしい言い方も並べる

ここから先は中級者向け。偏りを Python で見つける方法です。


題名の文末の型を Python で数える

ショーウィンドウの前で「なんとなく似てる気がする」と腕を組んでいても、おあげの枚数は数えられません。
数えましょう。

次のスクリプトは、題名を1行に1つ書いたファイルを読み、直近の何本かの文末の型を数えます。AI は呼びません。標準ライブラリだけで動きます。

# title_shape_check.py  題名の文末の型を数えて、偏りを知らせる
# 使い方: python title_shape_check.py titles.txt [--window 7] [--limit 3]
#   titles.txt は1行に1題名。古い順に並べ、最後の行が今日の題名
import argparse
import re
import sys
import unicodedata
from collections import Counter
from pathlib import Path

# 上から順に当てはめる。最初に合ったものがその題名の型
SHAPES = [
    ("〜件", re.compile(r"件[。.]?$")),
    ("疑問", re.compile(r"[?]$")),
    ("理由", re.compile(r"(理由|わけ)[。.]?$")),
    ("方法", re.compile(r"(方法|やり方|手順|書き方)[。.]?$")),
    ("セリフ", re.compile(r"」$")),
    ("言い切り", re.compile(r"(です|ます|でした|ません)[。.!]?$")),
]


def normalize(title):
    # 全角の?と半角の? をそろえ、前後の空白を取る
    return unicodedata.normalize("NFKC", title).strip()


def shape_of(title):
    t = normalize(title)
    for name, pattern in SHAPES:
        if pattern.search(t):
            return name
    return "その他"


def main():
    p = argparse.ArgumentParser()
    p.add_argument("file")
    p.add_argument("--window", type=int, default=7, help="見る本数(新しいほうから)")
    p.add_argument("--limit", type=int, default=3, help="同じ型がこの本数以上なら警告")
    args = p.parse_args()

    lines = Path(args.file).read_text(encoding="utf-8").splitlines()
    titles = [t for t in lines if t.strip()]
    if not titles:
        print("題名が1本もありません")
        return 2

    recent = titles[-args.window:]
    shapes = [shape_of(t) for t in recent]
    for t, s in zip(recent, shapes):
        print(f"[{s}] {normalize(t)}")

    counts = Counter(shapes)
    print("---")
    print(" / ".join(f"{k} {v}本" for k, v in counts.most_common()))

    warnings = []
    for name, n in counts.items():
        if name != "その他" and n >= args.limit:
            warnings.append(f"「{name}」が直近{len(recent)}本中{n}本")
    if len(shapes) >= 2 and shapes[-1] == shapes[-2] and shapes[-1] != "その他":
        warnings.append(f"今日の題名が前回と同じ型({shapes[-1]})")

    for w in warnings:
        print("警告:", w)
    return 1 if warnings else 0


if __name__ == "__main__":
    sys.exit(main())

試す題名は、説明用に作った7本です。

Gitのpushが拒否された件。
Excelの日付が数字になった件。
cronが夜中だけ動かなかった件。
SQLのLIKEで_が効かないのはなぜ?
Dockerのコンテナが消えた件。
PowerShellの文字化けを直す3つの方法
JSONの末尾カンマで怒られた件。
[〜件] Gitのpushが拒否された件。
[〜件] Excelの日付が数字になった件。
[〜件] cronが夜中だけ動かなかった件。
[疑問] SQLのLIKEで_が効かないのはなぜ?
[〜件] Dockerのコンテナが消えた件。
[方法] PowerShellの文字化けを直す3つの方法
[〜件] JSONの末尾カンマで怒られた件。
---
〜件 5本 / 疑問 1本 / 方法 1本
警告: 「〜件」が直近7本中5本

(今回の検証環境:Windows 上の Python 3.13.1。python -X utf8 title_shape_check.py titles.txt で実行。終了コードは 1)

7本中5本。おあげ、5杯にのってました。

型を変えた7本(titles_ok.txt、これも説明用)では、こうなりました。

[疑問] Gitのpushが拒否されるのはなぜ?
[理由] Excelの日付が数字になる理由
[その他] cronが夜中だけ動かないときに見る3か所
[〜件] Dockerのコンテナが消えた件。
[方法] PowerShellの文字化けを直す方法
[言い切り] JSONの末尾カンマ、JavaScriptはOKでもJSONはダメです
[疑問] SQLのLIKEで_が1文字に当たるのはなぜ?
---
疑問 2本 / 理由 1本 / その他 1本 / 〜件 1本 / 方法 1本 / 言い切り 1本

(同じ環境で実行。警告なしで、終了コードは 0)

1行目の「なぜ?」は全角で書いたのに、出力では半角の「?」になっています。比べる前に NFKC(全角・半角などの表記ゆれをそろえる Unicode の正規化)をかけているからです。上級者向けの章で、もう少し見ます。

終了コード(プログラムが終わるときに返す番号。0 が成功)を、警告ありなら 1、なしなら 0 にしてあります。定期タスク側で終了コード 1 を失敗として扱うようにすれば、「今日の題名は前回と同じ型」で処理を止められます。終了コードの基本は終了コードの基礎講座にまとめています。

エージェントくん「型の判定、全部『その他』にしておけば警告は出ませんね!」

おうどん

抜け道を探すのが早すぎる

実際、型の分け方はこのスクリプトの中の SHAPES しだいです。「その他」が増えてきたら、型の決め方を見直すころです。

手本の文言がそのまま写っていないか、言い回しがくり返していないか

型が合っていても、まだ安心できません。
ショーウィンドウのサンプルから、お箸の樹脂まで写して出してくる店員さんがいるかもしれないからです。

手本と同じ並びを見つける

difflib(Python 標準の、文字列の差分を取るライブラリ)で、手本と下書きの1行ずつを比べ、8文字以上同じ並びがあれば報告します。

# example_leak_check.py  手本の文言が、下書きにそのまま写っていないか調べる
# 使い方: python example_leak_check.py examples.txt draft.md [--min 8]
#   examples.txt は手本を1行に1つ。8文字以上同じ並びがあれば報告する
import argparse
import sys
import unicodedata
from difflib import SequenceMatcher
from pathlib import Path


def norm(s):
    return unicodedata.normalize("NFKC", s)


def leaks(example, draft, min_len):
    # autojunk=False: 長い下書きで、よく出る文字が無視されないようにする
    m = SequenceMatcher(None, example, draft, autojunk=False)
    return [example[b.a:b.a + b.size] for b in m.get_matching_blocks() if b.size >= min_len]


def main():
    p = argparse.ArgumentParser()
    p.add_argument("examples")
    p.add_argument("draft")
    p.add_argument("--min", type=int, default=8)
    args = p.parse_args()

    examples = [norm(x).strip() for x in Path(args.examples).read_text(encoding="utf-8").splitlines() if x.strip()]
    draft_lines = [norm(x) for x in Path(args.draft).read_text(encoding="utf-8").splitlines()]

    found = 0
    for no, line in enumerate(draft_lines, 1):
        for ex in examples:
            for piece in leaks(ex, line, args.min):
                found += 1
                print(f"{args.draft}:{no}: 手本と{len(piece)}文字同じ「{piece}」")
    print(f"見つかった数: {found}")
    return 1 if found else 0


if __name__ == "__main__":
    sys.exit(main())

手本(examples.txt)と下書き(draft.md)は、説明用にこう作りました。

うどんを茹でたら、鍋ごと消えてしまった件。
ここが大事なところです。
# Gitでpushしたら、ブランチごと消えてしまった件。

こんにちは。今日は Git の話です。
force push をすると、リモートの履歴が書き換わります。
ここが大事なところです。
draft.md:1: 手本と11文字同じ「ごと消えてしまった件。」
draft.md:5: 手本と12文字同じ「ここが大事なところです。」
見つかった数: 2

(同じ環境で python -X utf8 example_leak_check.py examples.txt draft.md を実行。終了コードは 1)

鍋がブランチに変わっただけで、「ごと消えてしまった件。」の11文字はそのまま。
うどんを茹でていたはずが、Git を茹でています。

何本もの下書きに出てくる言い回しを見つける

手本はルールのファイルの中だけではありません。直近の下書きを読ませているなら、その下書きも手本です(理由は上級者向けの章で)。
そこで、何本もの下書きに共通して出てくる言い回しを探します。8文字の並びを全部取り出して、3本以上に出たものをつなぎ直して表示します。

# repeat_phrases.py  直近の下書きに何度も出てくる言い回しを探す
# 使い方: python repeat_phrases.py 下書き1 下書き2 ... [--n 8] [--docs 3]
import argparse
import sys
import unicodedata
from collections import defaultdict
from pathlib import Path


def grams(text, n):
    text = unicodedata.normalize("NFKC", text)
    out = set()
    for line in text.splitlines():
        line = line.strip()
        if line.startswith(("```", "#", "|", "-")):
            continue  # コード・見出し・表・箇条書きの記号行は数えない
        for i in range(len(line) - n + 1):
            out.add(line[i:i + n])
    return out


def main():
    p = argparse.ArgumentParser()
    p.add_argument("files", nargs="+")
    p.add_argument("--n", type=int, default=8, help="何文字の並びで比べるか")
    p.add_argument("--docs", type=int, default=3, help="この本数以上に出たら報告")
    args = p.parse_args()

    where = defaultdict(set)
    for f in args.files:
        for g in grams(Path(f).read_text(encoding="utf-8"), args.n):
            where[g].add(Path(f).name)

    hits = {g for g, fs in where.items() if len(fs) >= args.docs}
    # 1文字ずつずれて重なる並びをつないで、1つの言い回しに戻す
    by_head = defaultdict(list)
    for g in hits:
        by_head[g[:-1]].append(g)
    starts = [g for g in hits if not any(h[1:] == g[:-1] and where[h] == where[g] for h in hits)]
    phrases = []
    for g in starts:
        phrase, cur = g, g
        while True:
            nxt = [h for h in by_head[cur[1:]] if where[h] == where[g]]
            if len(nxt) != 1:
                break
            cur = nxt[0]
            phrase += cur[-1]
        phrases.append((len(where[g]), phrase))
    for n, phrase in sorted(phrases, key=lambda x: (-x[0], x[1])):
        print(f"{n}本に出現: 「{phrase}」")
    return 1 if hits else 0


if __name__ == "__main__":
    sys.exit(main())

説明用の下書き4本(drafts/d1.md〜d4.md)の中身はこうです。

--- d1.md
こんにちは。今日は Git の話です。
まずは結論から言いますね。
force push はリモートの履歴を書き換えます。ここが大事なところです。
--- d2.md
こんにちは。今日は Excel の話です。
日付が数字になるのは、中身がシリアル値だからです。ここが大事なところです。
--- d3.md
こんにちは。今日は cron の話です。
まずは結論から言いますね。
cron は、ログインしたときとは違う環境変数で動きます。ここが大事なところです。
--- d4.md
こんにちは。Docker のコンテナが消えて焦った話をします。
まずは結論から言いますね。
--rm を付けて起動したコンテナは、止めると消えます。

(4つのファイルを、読みやすいように1つのブロックに並べて書いたものです。--- の行はファイルの中にはありません)

3本に出現: 「こんにちは。今日は 」
3本に出現: 「す。ここが大事なところです。」
3本に出現: 「まずは結論から言いますね。」

(同じ環境で python -X utf8 repeat_phrases.py drafts/d1.md drafts/d2.md drafts/d3.md drafts/d4.md を実行。終了コードは 1)

決まり文句が3つ見つかりました。
「こんにちは。今日は 」の最後の空白は、「今日は Git」の半角スペースまで含めた8文字の並びだからです。

読者「うちのブログ、『こんにちは』で始まるのは決まりなんだけど」

それは手本どおりで正しいので、決まりの言い回しは数えないように、除外のリストを足すのが実務的です。
ちなみにおうどん日和の記事も、毎回「こんにちは、おうどんです」で始まります。これを警告されたら、スクリプトを止めるより先に、おうどんが泣きます。

定期タスクに組み込むときの順番

無人で動かすなら、次の順に置くのがおすすめです。

  1. AI に書かせる前に、直近の題名を title_shape_check.py にかけて、使ってよい型を指示に足す
  2. 書かせたあとに、3つのスクリプトを全部通す
  3. どれかが終了コード 1 なら、その出力を AI に渡して、その箇所だけ直させる。直したあと、もう一度通す

チェックに引っかかった行を見せて直させるのは、Anthropic のベストプラクティスにある「下書き → 基準に照らして見直し → 直す」の流れ(self-correction、自己修正)と同じ形です。
ただし、「全部書き直して」ではなく「この行だけ」にします。全部書き直させると、別の決まり文句が生まれるかもしれないので。

エージェントくん「直しました! 『ここが大事なところです。』を『ここが重要なところです。』に!」

おうどん

8文字の並び、ちゃんと外れてる。外れてるけど、そういうことじゃない


ここから先は上級者向け。読み飛ばしてもOKです。


比べ方の落とし穴:difflib の autojunk と、全角・半角

何度も写した言い回しほど、autojunk で見えなくなる

example_leak_check.py で autojunk=False にしていたのには、理由があります。

Python の difflib のドキュメントによると、SequenceMatcher は既定で、2つ目の列が200要素以上あると、その1%を超えて出てくる要素を「ゴミ(junk)」として扱います。
文字列を比べるなら、要素は1文字ずつです。

これが、写りすぎの検出と相性が悪い。
同じ言い回しを何度もくり返した長い下書きほど、その言い回しの文字が「よく出る文字」になって、ゴミ扱いされるからです。

from difflib import SequenceMatcher

filler = "今日はGitの話をします。pushをしたら、思っていたのと違うことが起きました。" * 5
draft = filler + "ブランチごと消えてしまった件。"
print("下書きの長さ:", len(draft))

for example in ["鍋ごと消えてしまった件。", "思っていたのと違うことが起きました。"]:
    print(f"手本「{example}」")
    for aj in (True, False):
        m = SequenceMatcher(None, example, draft, autojunk=aj)
        blk = m.find_longest_match(0, len(example), 0, len(draft))
        print(f"  autojunk={aj}: いちばん長い一致 {blk.size}文字")

m = SequenceMatcher(None, "x", draft)
print("ゴミ扱いになった文字の数:", len(m.bpopular))
example = "思っていたのと違うことが起きました。"
swapped = SequenceMatcher(None, draft, example).find_longest_match(0, len(draft), 0, len(example))
print("引数を入れ替えると:", swapped.size, "文字")
下書きの長さ: 215
手本「鍋ごと消えてしまった件。」
  autojunk=True: いちばん長い一致 11文字
  autojunk=False: いちばん長い一致 11文字
手本「思っていたのと違うことが起きました。」
  autojunk=True: いちばん長い一致 0文字
  autojunk=False: いちばん長い一致 18文字
ゴミ扱いになった文字の数: 31
引数を入れ替えると: 18 文字

(今回の検証環境:Python 3.13.1。python -X utf8 autojunk_demo.py で実行。bpopular は、自動でゴミ扱いになった要素の集合です)

5回もくり返した「思っていたのと違うことが起きました。」が、既定の設定では一致 0 文字。
いちばん写っている言い回しが、いちばん見えなくなりました。

おうどん

5杯ともおあげがのってるから、おあげは普通の具ということで

数えるのをやめた検品係です。

ドキュメントには、この判定は2つ目の列だけを見て決めるので、A と B を比べるのと B と A を比べるのとで結果が変わりうる、とも書かれています。
引数を入れ替えると 18 文字見つかったのは、2つ目が短い手本(200文字未満)になって、ゴミの判定が働かなかったからです。

比べる向きで結果が変わる検品は、ちょっと怖い。なので、写りの検出では autojunk=False を明示しておくのが無難です。
もう1つ、get_matching_blocks() が返す一致の組は、両方の列で位置が単調に増える(重ならず、順番が入れ替わらない)と書かれています。手本の後半と前半が入れ替わって写っていると、片方しか報告されないことがあります。順番を入れ替えて写すのが心配なら、repeat_phrases.py のように並びを全部取り出す方法と組み合わせます。

全角の「?」と半角の「?」

import unicodedata

for t in ["なぜ?", "なぜ?", "なぜ? ", "件。"]:
    n = unicodedata.normalize("NFKC", t).strip()
    print(f"{t!r:10} -> {n!r:8} 最後の文字 U+{ord(n[-1]):04X}  endswith('?'): {n.endswith('?')}")
'なぜ?'      -> 'なぜ?'    最後の文字 U+003F  endswith('?'): True
'なぜ?'      -> 'なぜ?'    最後の文字 U+003F  endswith('?'): True
'なぜ? '     -> 'なぜ?'    最後の文字 U+003F  endswith('?'): True
'件。'       -> '件。'     最後の文字 U+3002  endswith('?'): False

(同じ環境で python -X utf8 nfkc_demo.py を実行)

今回の実行では、全角の「?」(U+FF1F)は NFKC で半角の「?」(U+003F)になり、後ろの空白は strip() で取れました。
一方、句点の「。」(U+3002)は NFKC でも変わっていません。だから title_shape_check.py の「〜件」の正規表現は 件[。.]?$ と、句点そのものを書いています。

AI は、全角と半角をその日の気分で混ぜてくることがあります。正規化しないで数えると、「疑問」が2つの型に割れて、偏りが薄く見えます。
おあげを、関東風と関西風で別々に数えていたようなものです。どっちもおあげです。

前日の下書きも手本になる:無人で回すときの設計判断

手本の出どころは3つある

毎朝の定期タスクで、AI が書く前に読むものを並べると、こうなります。

読むもの手本としての効き方(おうどんの整理)
ルールのファイルの例書き手が意図して置いた手本。1つだけだと強く引っぱる
ルールのファイルそのものの書き方箇条書きや言い回しが、出力の形に移りうる(Anthropic の資料の「プロンプトの形を出力の形に合わせる」の項)
直近の下書き意図していない手本。昨日の決まり文句を、今日の AI が読む

おうどんのタスクも、ルールのファイルと直近の下書きを読んでから書く作りです。
口調をそろえるために前日の下書きを読ませると、口調だけでなく、言い回しや組み立てまで「これが正解」として読まれる可能性があります。そして今日の下書きが、明日の手本になる。

読者「昨日のおあげが、今日のサンプルになって、明日のおあげを増やす」

おうどん

ショーウィンドウに、昨日のお客さんの丼を並べてる状態

これは仕組みからのおうどんの考えで、偏りが毎日どれくらい強まるかを測ったものではありません。
だからこそ、上のスクリプトで毎日数えて、強まっていないかを見ます。

設計で決めておくこと

  • 読ませる下書きの役目を書く。「口調の確認のためで、言い回しは写さない」のように、何を拾ってほしいかを指示に書く
  • 読ませる下書きを1本にしない。1本だけなら、それがショーウィンドウの唯一のサンプルになります
  • 例には <examples> タグを付け、指示の文と分ける。Anthropic の資料が、指示と区別できるようにするためと説明しているとおりです
  • 例の多様さを、AI 自身に点検させる。公式の資料でも、例の関連性と多様性を Claude に評価させる使い方が紹介されています
  • それでも最後は、外から数える。AI の自己申告は「サンプルのとおりにしました!」と同じ重さで受け取る

ちなみに、Anthropic のベストプラクティスには、フロントエンド(画面のデザイン)の見本プロンプトも載っています。その中に、何度生成しても、ありがちな選択(例として Space Grotesk というフォント)に寄りがちだと、モデルに向けて注意する一文が書かれています。
公式の見本プロンプトの中にも、寄っていく問題への注意書きがある。手本と偏りの付き合いは、それくらい根気のいる話なんですよね。

エージェントくん「では明日から、毎日ちがうフォントで書きます!」

おうどん

記事はテキストです

ショーウィンドウに、サンプルを3つ並べる

冒頭の3日連続「件。」に戻ります。

ななみ

『件。』の例が1つしかないルールを、毎朝読ませていたわけですね

おうどん

サンプルを1つしか置かずに、全部おあげが2枚だって怒ってた

ななみ

直す場所を、AI ではなくショーウィンドウのほうだと見つけたのは、おうどんさんの手柄ですよ

おうどん

ほめられると、おあげを3枚にしたくなる

手本は1つだと型になり、違う型を3つ以上並べるとはじめて、違ってよいところが伝わります。
まずは、いま使っているルールのファイルを開いて、例がいくつ書いてあるか数えてみてください。1つだけなら、違う型をあと2つ足すところからです。
ついでに、直近7本の題名を title_shape_check.py にかけてみると、おあげの枚数が分かります🍜

参考資料

  • Claude Docs:Prompting best practices — 例は出力の形式・口調・構成を方向づける確実な方法の1つ、例は Relevant・Diverse(意図しないパターンを拾わない程度にばらす)・Structured(<example> タグ)、3〜5個の例、例の評価や追加を Claude に頼む、「しないこと」より「すること」を伝える、理由を添えると一般化できる、プロンプトの形が応答の形に影響しうる、自己修正の流れ、フロントエンドの見本プロンプトの一文
  • OpenAI API Docs:Prompt engineering — few-shot ではモデルが例から暗黙にパターンを拾って当てはめる、例は入力の幅を広く見せる
  • Python 標準ライブラリ:difflib — autojunk(2つ目の列が200要素以上で1%を超える要素をゴミ扱い)と、その判定が2つ目の列だけを見るため比べる向きで結果が変わりうること、find_longest_match、get_matching_blocks の一致の組が単調に増えること

2026年10月時点の内容です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

おうどん|癒しと創作をたのしむ雑食クリエイター
写経アプリやクレイセラピー、優しい和風デザインがすき。
ZARDと刀剣と文字に癒されて、今はアプリ作ってます。

コメント

コメントする

目次