モジュール 02 · 第 2 回

例を示す:few-shot プロンプト

ユーザーの書き込みを 4 つに分類します。例なしでは 18 件正解、例を 4 つ示すと 20 件正解。例の選び方、いくつ示すか、どんな副作用があるかを説明します。

  • 約 30 分
  • 難易度:入門
  • 検証:2026-09-14 deepseek-flash

コードと実行結果は実際に動かしたときのまま載せているため、コメントと出力は中国語です。

言葉ではっきり説明しにくい要求もあります。「不具合」と「使い方の質問」の境界はどこにあるのか。ユーザーが「リダイレクトのあと cookie が消えたんですが、私の使い方が間違っていますか?」と聞いたら、どちらに分類するのか。長々と定義を書くこともできますが、いくつか例を示すほうが早いことがよくあります。

モデルに「入力 → 出力」の例をいくつか見せ、それにならってやらせることを few-shot プロンプト(少数ショット)と呼びます。例を示さずにそのままやらせるのは zero-shot(ゼロショット)です。

実験:ユーザーの書き込みを分類する

httpx のメンテナーのもとには毎日さまざまな書き込みが届きます。これをモデルに自動で 4 つに分類させたいとします。不具合、機能提案、使い方の質問、その他です。20 件の書き込みを用意し、それぞれ人手で正しいカテゴリを付けておきました。たとえば次のようなものです。

TESTS = [
    ("用 AsyncClient 并发 100 个请求,程序直接卡死,CPU 占满", "缺陷"),
    ("能不能加一个像 requests 那样的 Session 重试适配器?", "功能建议"),
    ("怎么给单个请求设置不同的超时时间?", "使用问题"),
    ("你们的文档网站打不开了", "其他"),
    # ……一共 20 条,每类 5 条,完整列表见 code/02-prompting/few_shot.py
]

zero-shot のプロンプトでは、分類ルールだけを説明します。

ZERO_SHOT = """把用户留言分成以下四类之一:缺陷、功能建议、使用问题、其他。
只输出类别名称。"""

few-shot のプロンプトでは、後ろに例を 4 つ加えます。各カテゴリに 1 つずつで、この 4 件はどれもテストセットに含まれていません。

FEW_SHOT = ZERO_SHOT + """

例子:
留言:调用 client.close() 之后再发请求没有报错,而是静默返回了旧的响应
类别:缺陷

留言:想要一个参数,能在请求失败时自动打印完整的请求和响应
类别:功能建议

留言:base_url 和 url 拼接的规则是什么?结尾的斜杠有没有影响
类别:使用问题

留言:这个项目和 aiohttp 比哪个更好
类别:其他"""

各書き込みを 留言:{text}\n类别:(書き込み:{text}\nカテゴリ:)の形式でモデルに送り、温度は 0、思考はオフにして、人手の正解と照合します。

def classify(system, text):
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "system", "content": system}, {"role": "user", "content": f"留言:{text}\n类别:"}],
        temperature=0,
        extra_body={"thinking": {"type": "disabled"}},
    )
    return response.choices[0].message.content.strip()

私が実行した結果です。

零样本:答对 18/20,格式不对 0 条 []
    怎么给单个请求设置不同的超时时间?  标注=使用问题  模型=功能建议
    你们的文档网站打不开了  标注=其他  模型=缺陷
少样本:答对 20/20,格式不对 0 条 []

zero-shot はどこで間違えたか

zero-shot が間違えた 2 件を見てみましょう。

「怎么给单个请求设置不同的超时时间?」(リクエストごとに違うタイムアウトを設定するには?)は機能提案に分類されました。モデルはおそらく「ユーザーはリクエストごとにタイムアウトを設定する機能を求めている」と理解したのでしょう。しかしこれは httpx がとっくに対応している使い方で、ユーザーは書き方を知らなかっただけです。

「你们的文档网站打不开了」(ドキュメントのサイトが開けなくなりました)は不具合に分類されました。字面では、サイトが開けないのは確かにバグです。しかし私たちの「不具合」は httpx というライブラリそのものの問題を指していて、ドキュメントのサイトは「その他」に入ります。

どちらの間違いも、モデルが愚かだからではありません。分類基準にあいまいな部分があるのに、私のルールがそれをはっきり書いていなかったのです。例を示すと、モデルは「base_url と url をつなぐルールは何ですか」という例から「使い方を聞いている = 使い方の質問」を、「このプロジェクトと aiohttp ではどちらがいいですか」から「ライブラリそのものと関係ない = その他」を読み取りました。例が私の代わりにルールをはっきりさせてくれたのです。

もちろん、例を使わずにルールをもっと細かく書くこともできます。「『どうやるか』を聞いているものは、新機能を求めているように見えても使い方の質問とする」「httpx ライブラリそのもののふるまいの問題だけを不具合とする」。これも効果があり、トークンも節約できます。実際には両方を一緒に使うことが多く、ルールで骨組みを書き、例で細部を補います。

例の選び方

すべての出力をカバーする。4 つのカテゴリにそれぞれ例を 1 つずつ示します。「不具合」と「使い方の質問」の例だけしか示さなければ、モデルは残りの 2 つのカテゴリをあまり出力しなくなります。

境界上の例を選ぶ。最も価値のある例は、最も典型的なものではなく、最も分類を間違えやすいものです。「close() を呼んだあとにリクエストを送ってもエラーにならない」はよい例です。使い方を聞いているように聞こえますが、実際にはライブラリのふるまいに問題があります。

テストセットと分ける。例をテストセットに含めてはいけません。答えをモデルに教えるのと同じで、正答率が水増しされてしまいます。この実験の 4 つの例は別に書いたものです。

形式を実際の入力とそろえる。例は「留言:……类别:……」の形式で、実際のリクエストも同じ形式を使っています。モデルは例の形式を忠実にまねるので、例の形式がそのままあなたのほしい出力形式になります。この実験では zero-shot でも few-shot でも形式の誤りはありませんでしたが、もっと複雑な出力(たとえば JSON)を求めるときは、形式の正しい例が一つあるだけで形式の問題を大きく減らせます。

いくつ示すか

一般に 3~5 個で十分です。例が多いほど毎回のリクエストの入力が長くなり、費用がかかります。幸い例は固定なので、system メッセージに入れておけばキャッシュにヒットします。

それでも効果が足りなければ、急いで数を増やす前に、例の選び方がよくないのではないかと考えてください。似たような例 10 個より、異なる境界ケースをカバーする例 3 個のほうが効果的です。

例の副作用

例はよく効くので、モデルを誤った方向に引っ張るのも簡単です。

モデルは例のすべてをまねる。例の回答がどれも短ければ、モデルの回答も短くなります。例がどれも中国語なら、英語の書き込みに対しても中国語のカテゴリ名で分類するかもしれません(これは私たちが望んでいたことですが)。まねさせるつもりのなかった特徴まで、まねてしまいます。

モデルが例の内容を写してしまうことがある。生成系のタスク(たとえば商品説明を書く)で、例がどれもコーヒーについて書かれていると、お茶の説明を書くときにもコーヒーの言葉が紛れ込むことがあります。ですから例は多様にし、内容が互いに違うようにします。

順番と比率が影響する。5 つの例のうち 4 つが「不具合」なら、モデルは「不具合」に多く分類しがちになります。カテゴリごとの例の数はできるだけ均等にします。

よくある質問

例は system メッセージに入れるべきか、マルチターンの会話にすべきか? どちらでもかまいません。上のやり方は例を system メッセージに書いています。もう一つの書き方は、例をそれぞれ user と assistant のメッセージの組にして、本当の質問の前に置くものです。モデルが以前にすでに何度かそう答えたかのようにするわけです。後者は形式をまねさせるのに特に効果的ですが、メッセージのリストが長くなり、保守もしにくくなります。私はたいてい system メッセージの書き方から始め、形式に問題が出たら切り替えます。

カテゴリが 30 個などと多く、各カテゴリに例を 1 つずつ示すと長すぎる場合は? まず、カテゴリを 2 階層に分けて、大分類を決めてから小分類を決められないか考えます。あるいは混同しやすいカテゴリにだけ例を付けます。さらに進んだやり方として、モジュール 01 第 5 課の埋め込みを使い、大きな例のライブラリから現在の入力に最も似た例をいくつか探し出して、それだけをプロンプトに入れる方法があります。これは動的 few-shot と呼ばれ、原理はモジュール 04 の RAG と同じです。

この実験で言えないこと

20 件のテストデータは少なすぎます。18 件と 20 件の差は、データを替えれば 19 件と 20 件になるかもしれず、逆転することさえありえます。この実験で言えるのは「few-shot はこの種のタスクで役に立つ」ことの具体例の一つであって、「few-shot で正答率が 10% 上がる」とは言えません。二つのプロンプトを信頼できる形で比べるには、もっと多くのテストデータが必要で、それが第 5 課の内容です。

練習問題

  1. few-shot のプロンプトの例を 2 つに減らして(「不具合」と「使い方の質問」だけを残す)実行し直し、「その他」と「機能提案」の正答率がどう変わるか見てください。
  2. わざとすべて「不具合」の例を 4 つ入れて、モデルが「不具合」に分類しやすくなるか見てください。
  3. 自分でさらに 10 件の書き込みを書いてください。自分でも少し考えないと正しく分類できないようなものをできるだけ選び、テストセットに加えて実行し直します。zero-shot と few-shot の差は大きくなりましたか、小さくなりましたか。
  4. 例を使わず、zero-shot のルールに説明を 2 つ追加して(「使い方を聞いているものは、新機能を求めているように見えても使い方の質問とする」「ドキュメントやコミュニティに関するものはその他とする」)、few-shot と同じ効果が得られるか見てください。

確認テスト

1. few-shot プロンプトの例をテストセットに含めてはいけないのはなぜですか?

例は、それらの入力の正解をモデルに教えるのと同じです。テストセットに同じ入力があれば、モデルは写すだけで正解でき、測った正答率が水増しされて、新しいデータでの本当の成績を反映しなくなります。

2. モデルに 5 つの例を示し、そのうち 4 つが「不具合」でした。どんな問題が起きうるでしょうか?

モデルはより多くの書き込みを「不具合」に分類しがちになります。例の比率が「ほとんどの書き込みは不具合だ」とほのめかすからです。例に含めるカテゴリの数はできるだけ均等にし、すべての出力を少なくとも一つはカバーするべきです。

3. 例を選ぶときは、最も典型的なものと、最も間違えやすいもの、どちらを選ぶべきですか?

最も間違えやすい、境界上にある例を優先します。典型的な例はもともとモデルが正しく分類できるもので、境界上の例こそが、あいまいな部分のルールをモデルに理解させる助けになります。

質問と議論

このレッスンでつまずいたところは、ここで質問してください。他の人の質問に答えるのも歓迎です。

質問で 3 ポイント、回答で 6 ポイント。審査を通過すると公開されます。

議論を読み込んでいます…