1. Prompt Engineeringとは何か、なぜ重要なのか
1.1 Promptを捉え直す
多くの初心者は、Prompt Engineeringを「AIから良い回答を得るために、良い文章を書くこと」だと考えます。しかしAI Application Engineerには、より深い理解が必要です。
LLMの本質は 条件つき確率生成器 です。前の文脈であるpromptが与えられると、学習済みの確率分布に基づいて、最も起こりやすい後続内容をtokenごとに生成します。したがってPrompt Engineeringの中心的な問いは、モデルの確率分布が期待する出力空間へ偏るように、入力シーケンスをどう構築するか ということです。
分かりやすいたとえを挙げましょう。能力は高いものの、あなたの具体的な要望をまったく知らない新しい同僚へ仕事を頼むとします。指示が曖昧なほど、納品物は期待から大きくずれます。指示が正確で、参考資料が具体的であるほど、期待した結果に近づきます。Prompt Engineeringとは、この「正確なコミュニケーション」のための技術です。
1.2 AIアプリケーション開発におけるPromptの位置
実際のAIアプリケーションのアーキテクチャでは、Promptはユーザーが思いつきで書く一文ではなく、エンジニアリングされたモジュール です。典型的なpromptは次の要素を含みます。
System Prompt(系统指令) → 定义模型的角色、行为边界、输出格式
Context(上下文) → 提供背景信息、相关数据、历史对话
User Input(用户输入) → 实际的问题或任务
Output Specification(输出规范) → 期望的格式、长度、风格
この四つの要素が、LLMへ送る完全なpromptを構成します。AI Application Engineerが書くコードの多くは、実際にはこのpromptを 動的に組み立てる ためのものです。
2. Clear Instructions(明確な指示)
2.1 中心原理
Clear Instructionsは、Prompt Engineeringで最も基礎的かつ重要な技術です。原理は直感的です。モデルへ渡す指示が明確で具体的であるほど、出力の確定性が高まり、ランダムにずれる余地が小さくなります。
確率の観点から見ると、曖昧なpromptでは複数の出力方向が高い確率を持ち、結果を制御できません。明確なpromptは、可能な出力分布を大幅に狭め、生成内容を望む方向へ集中させます。
2.2 実践のための六原則
原則1:役割と立場を指定する(Role Setting)
# ❌ 模糊的 prompt
prompt = "帮我分析这段代码的问题"
# ✅ 清晰的 prompt
prompt = """你是一位拥有10年经验的 Python 后端工程师,专精代码审查。
请从以下维度分析这段代码的问题:
1. 逻辑正确性
2. 性能瓶颈
3. 安全隐患
4. 代码风格与可维护性"""
この原則が有効な理由を理解するには、LLMの本質を思い出す必要があります。LLMは膨大なテキストで学習した 条件つき確率モデル です。学習データには、さまざまな「役割」がさまざまな「場面」で書いた文章が含まれています。医師が診療記録で使う表現、プログラマーがcode reviewで使う言葉、弁護士が法律意見書で使う文体などは、それぞれ異なる テキスト分布 に属します。
promptへ「あなたは経験豊富なPythonエンジニアです」と書くと、実際には モデルの条件つき確率分布を変化させている ことになります。数学的に言えば、モデルが元々計算していたP(output | task)が、P(output | role, task)へ変わります。この二つの分布は大きく異なる可能性があります。
Transformer内部では、具体的に何が起きるのでしょうか。「経験豊富なPythonエンジニア」というtokenがモデルへ入ると、self-attention層でほかのすべてのtokenに対する参照先になります。モデルが新しいtokenを生成するたびに、attention機構はこれらの役割説明tokenを「振り返り」、学習データにある「経験豊富なエンジニアが書いたような」文章パターンへ生成全体を偏らせます。専門用語の選択、分析の深さ、注目する観点なども変わります。
図書館の検索システムで、先に「医学」の分類を選んでから「風邪」を検索することに似ています。分類を選んでも検索語自体は変わりませんが、検索範囲は大きく狭まり、返される結果はより専門的で集中したものになります。役割設定も同じで、モデルの「注意」を学習データ内の特定の役割に関係する知識領域へ導きます。
さらに、この原理は 役割の説明が具体的であるほど効果が高い 理由も説明します。「あなたはエンジニアです」は非常に広い分布を活性化します。一方、「10年の経験を持ち、分散システムを専門とするバックエンドエンジニアです」は、はるかに正確な部分分布を活性化します。条件が多いほど確率分布が集中し、出力の確定性が高まります。
原則2:出力形式を明確にする(Output Format)
# ❌ 模糊的格式要求
prompt = "列出学习 Python 的资源"
# ✅ 精确指定格式
prompt = """请推荐 5 个学习 Python 的在线资源。
对每个资源,请按以下 JSON 格式输出:
{
"name": "资源名称",
"url": "链接",
"level": "beginner | intermediate | advanced",
"focus": "主要学习方向",
"reason": "推荐理由(一句话)"
}
请以 JSON 数组形式返回所有结果。"""
これはAIアプリケーション開発で極めて重要です。コードはモデルの出力を 解析 しなければなりません。出力形式が安定しなければ、parserは頻繁に失敗します。JSON、XML、Markdown表などの構造化形式を指定することは、アプリケーションの安定性を保つ鍵です。
この原則は、LLMの最も中心的な生成機構であるautoregressive generation(自己回帰生成)を利用します。
LLMはtokenを一つずつ生成し、新しいtokenの確率は、それまでに生成されたすべてのtokenに依存します。つまり、モデルが一度ある形式で出力を始めると、後続のtokenはその形式の経路へ「固定」されます。
promptで「JSON形式で出力してください」と指定し、具体的なJSON構造の例を示すと、何が起きるでしょうか。生成開始時の最初のtokenは、高い確率で{になります。JSONは{で始まり、promptでもJSONを明示しているからです。{が生成されると、次に"name"が生成される確率が非常に高くなります。学習データでは、{の後には通常、引用符で囲まれたkeyが続くからです。その後は:、"と続きます。このように、各生成ステップが直前の出力によって制約され、全体の出力は列車のようにJSONの「レール」に沿って進みます。
形式指定が非常に有効なのは、このためです。各ステップで形式を守るよう命令する必要はありません。正しい形式で「出発」させれば、autoregressiveの機構が形式の維持を助けます。
さらに高度な知識として、これは Constrained Decoding(制約つきデコーディング) の理論的な基礎でもあります。OutlinesやGuidanceなどのフレームワークは、デコード時に各ステップで合法なtokenを直接制限します。たとえばJSON構文上、その位置で"または}だけを許し、推論エンジンの層で出力形式を強制します。純粋なprompt指定より確実ですが、原理は共通しており、どちらもautoregressive生成の経路依存性を利用します。
原則3:制約条件を与える(Constraints)
prompt = """请为一款面向日本市场的健康饮品写一段广告文案。
约束条件:
- 字数:100-150个日文字符
- 语调:温暖、亲切,面向30-40岁女性
- 必须包含:产品名"朝のめぐみ"
- 不得包含:与竞品的直接比较、夸大的健康声明
- 格式:一个主标题 + 一段正文"""
ここで利用しているのは、出力空間における確率質量の集中です。
この原理は、確率論における 確率質量(probability mass)の集中と分散 という概念から理解できます。
制約がなければ、モデルの出力確率分布は非常に広い空間へ広がります。「広告文を書いて」という要求なら、10文字から1,000文字までの長さ、正式、ユーモラス、感情的、冷静といった語調、段落だけ、見出しつき、リストつきといった構造の、あらゆる組み合わせが出力候補です。確率質量はこの巨大な空間へ分散し、それぞれの出力方法が少しずつ確率を持つため、結果のランダム性が非常に高くなります。
「100〜150文字、温かく親しみやすい語調、30〜40代の女性向け、製品名を必ず含める」という制約を加えると、条件を満たさない領域から条件を満たす領域へ確率質量を「押し出している」 ことになります。制約に反する出力経路の確率は下がり、満たす経路の確率は上がります。最終的に確率質量がはるかに小さな出力空間へ集中し、出力をより制御しやすく、安定させられます。
attention機構の観点では、「100〜150文字」や「温かく親しみやすい」といった制約内の各要件が強い信号になります。モデルはtokenを生成するたびに、これらの制約tokenを「振り返り」、各ステップの確率計算へ制約を取り込みます。
ここから、制約同士が矛盾するとモデルが「混乱する」 という興味深い帰結が得られます。「簡潔に」と「各観点を詳しく展開する」を同時に要求すると、二つの制約が確率質量を逆方向へ引っ張り、出力が二つの文体の間で揺れます。良い制約を設計するには、各条件が一貫し、矛盾していないことを確認する必要があります。
原則4:区切り記号で内容を分離する(Delimiters)
prompt = """请将以下用三重反引号包裹的用户评论分类为"正面"、"负面"或"中性"。
用户评论:
\```
这家餐厅的拉面味道还不错,但是等了40分钟实在太久了。下次可能会考虑其他店。
\```
请只输出分类结果,不需要解释。"""
ここで利用しているのは、attentionによる構造的境界の認識 です。
この原則は、Transformerが学習過程で身につけた 構造化マーカーの意味に対する理解 を利用します。
LLMの学習データには、HTMLタグ、XMLタグ、Markdown記法、コード内の引用符や括弧など、膨大な構造化テキストが含まれています。モデルは統計的な規則から、構造化マーカーの内側と外側の内容は、意味上異なる役割を持つ ことを学びます。HTMLの<title>タグ内の文章はタイトルであり、<p>タグ内は本文、<code>タグ内はコードです。
promptで<user_input>...</user_input>のようなタグを使うと、モデルがすでに学習した「構造の境界」に関する知識を利用できます。モデルのattention機構はタグを認識し、内部の内容を「指示」ではなく「データ」として扱う傾向を強めます。
通常の引用符や三重バッククォートよりXMLタグのほうが有効なのはなぜでしょうか。学習データの中で、XMLタグが非常に強い「境界信号」を持つからです。XMLの設計思想は、内容とメタ情報を厳密に分離することです。一方、自然言語の引用符は強調にも使われ、境界としての意味が弱くなります。学習データの統計的な規則によって、区切り記号ごとにモデルの「感度」が異なります。
ただし、特に強調すべき点があります。この境界認識は「ソフト」であり、「ハード」ではありません。 括弧が一致しなければエラーになるプログラミング言語の絶対的な構文境界とは異なり、LLMによる区切り記号の理解は確率的です。巧妙な攻撃で境界を突破することは依然として可能です。prompt injectionの記事で、区切り記号は万能の防御ではないと繰り返し説明している理由もここにあります。リスクは低減しますが、なくなりません。
原則5:複雑なタスクを段階に分ける(Step-by-Step Task Decomposition)
prompt = """请按以下步骤分析这篇新闻文章:
步骤 1:用一句话总结文章的核心事件
步骤 2:识别文章中提到的所有人物及其角色
步骤 3:判断文章的情感倾向(正面/负面/中性),并给出依据
步骤 4:基于以上分析,生成 3 个关键标签
文章内容:
---
{article_text}
---
请按步骤编号依次输出结果。"""
ここで利用しているのは、autoregressive生成における中間出力を作業記憶として使うこと です。LLMのautoregressiveな性質により、前の出力が後の生成へ影響します。 先に要約し、次に分析し、最後に判断させると、前段階の出力が後段階のcontextとなり、モデルが段階的に分析を深められます。
この原則はChain-of-Thoughtの原理と密接に関係しますが、さらに基礎的な層では、LLMの重要なアーキテクチャ上の制約、つまり Transformerが一度のforward passで実行できる計算量には限界がある ことを利用しています。
Transformerでは、入力が複数のself-attention層とfeed-forward層を通り、出力になります。各層の計算量は、層数、隠れ次元、注意head数などのハイパーパラメータによって固定されています。したがって、「一ステップ」、つまり一度のforward passで実行できる推論の複雑さには上限があります。
単純なタスクなら一ステップで十分です。しかし「記事を分析する → エンティティを抽出する → 感情を判断する → タグを生成する」のような複雑なタスクは、一度のforward passだけでは足りない場合があります。
promptでタスクを「ステップ1、ステップ2、ステップ3」へ分けると、autoregressive生成の巧妙な性質を利用できます。ステップ1で生成した出力が、ステップ2の入力contextになります。 各ステップの出力は、下書き用紙へ書いた中間結果のように機能し、モデルの「有効な計算深度」を拡張します。
言い換えると、TransformerがN層なら、直接回答する場合はN層の中ですべての推論を終える必要があります。3段階に分け、各段階でN層のforward passを行えば、全体の計算深度は3N層になります。モデルはより多くの「考える余地」を得ます。
これは、段階の順序が重要な理由も説明します。「中心となる出来事を要約する」「人物を特定する」といった情報抽出を前に置き、「感情傾向を判断する」「タグを生成する」といった判断・統合を後に置くべきです。前段階の出力が後段階のcontextになるため、後続段階で参照するには先に情報を抽出しなければなりません。これは人間の認知過程とも一致します。記事を理解する前に、その感情傾向を判断することはできません。
原則6:「しないこと」だけでなく「すること」を示す
# ❌ 只说不做什么
prompt = "回答用户的问题,不要编造信息,不要太长,不要太短"
# ✅ 明确说做什么
prompt = """回答用户的问题。
- 只基于提供的文档内容回答
- 如果文档中没有相关信息,回复"根据现有资料无法回答此问题"
- 回答长度控制在 2-3 个段落
- 在回答末尾引用具体的文档段落作为出处"""
ここで利用しているのは、言語モデルにおける活性化の拡散特性 です。LLMの確率生成機構は、「Xをしないで」より「Yをしてください」のほうを確実に処理します。「象を考えないで」と伝えると、かえって象に関係するtokenの重みが高くなる可能性があります。
背景にある仕組みは非常に繊細です。LLMは否定的な指示を処理するとき、まず否定対象の概念を「活性化」し、その後に抑制しようとします。
モデルが「競合製品に触れないで」という文を処理すると、「競合製品」という概念に関係するtoken表現(embedding)が先に活性化されます。競合製品に触れないという指示を理解するには、まず競合製品が何かを「理解」する必要があるからです。しかしattention機構で表現が一度活性化されると、すでに高いattentionの重みを得ているため、その後の生成で競合製品に関係する内容をかえって生成しやすくなります。
この現象は、人間心理学における「白熊効果」(Ironic Process Theory)と驚くほど似ています。「白熊を考えない」と自分に言い聞かせると、かえって白熊の姿が頭へ浮かびます。LLMと人間の脳の仕組みはまったく異なりますが、結果は似ています。否定的な指示の中心概念がモデルに「記憶」されます。
一方、「提供された文書の内容だけに基づいて答える」という肯定的な指示なら、モデルは「文書内容」と「回答」という概念を活性化します。先に活性化してから抑制する矛盾がなく、attentionを正しい方向へ導けます。
実践上、prompt設計では 「否定的な禁止」を「肯定的な誘導」へ置き換える べきです。「情報を捏造しない」を「既知の事実だけに基づいて答える」へ、「長すぎない」を「2〜3段落に収める」へ、「専門用語を使わない」を「高校生が理解できる言葉で説明する」へ変えます。書き換えるたびに、モデルのattentionを 望まない方向 から 望む方向 へ導き直せます。
まとめ
六つの原則が利用する共通の基盤を一文でまとめると、いずれもTransformerのattention分布とautoregressive生成の確率経路を操作している ということです。
役割設定と制約条件はattention機構を通じて確率分布全体を偏らせます。出力形式はautoregressive生成の経路固定効果を利用します。区切り記号は学習データから得た構造境界の認識能力を使います。タスクの分割は中間出力を拡張作業記憶として利用します。肯定的な指示は意味活性化の方向性を利用します。切り口は異なりますが、最終的には同じこと、つまりモデルの確率分布を望む出力空間へ集中させています。
2.3 コードにおけるClear Instructionsの実践
実際の開発では、これらの原則をテンプレートとして構成します。
def build_analysis_prompt(user_text: str, language: str = "zh") -> str:
"""
构建一个结构化的文本分析 prompt。
注意:这就是 AI Application Engineer 的日常工作之一 ——
将 prompt engineering 原则封装成可复用的函数。
"""
system_prompt = f"""你是一位专业的文本分析助手。
你的任务是对用户提供的文本进行结构化分析。
输出语言:{"中文" if language == "zh" else "English"}"""
user_prompt = f"""请分析以下文本,按指定的 JSON 格式输出结果。
待分析文本:
---
{user_text}
---
输出格式:
{{
"summary": "一句话摘要",
"sentiment": "positive | negative | neutral",
"confidence": 0.0-1.0,
"key_entities": ["实体1", "实体2"],
"topics": ["话题1", "话题2"]
}}
要求:
1. summary 不超过 50 个字
2. confidence 为你对 sentiment 判断的置信度
3. key_entities 最多提取 5 个
4. 只输出 JSON,不要添加任何其他文字"""
return system_prompt, user_prompt
3. Few-Shot Prompting(少数例プロンプティング)
3.1 中心原理
Few-shot promptingは、prompt内でモデルへ複数の「入力→出力」の例を示し、例から 求めるパターンを推定 させ、そのパターンを新しい入力へ適用させます。
この技術の理論的な基礎は、2020年のGPT-3論文『Language Models are Few-Shot Learners』にあります。大規模言語モデルはfine-tuningを行わなくても、prompt内で少数の例を示すだけで新しいタスクを「学習」できることが発見されました。この能力を In-Context Learning(文脈内学習) と呼びます。
なぜ機能するのでしょうか。Transformerのattention機構から考えると、モデルは新しい入力を処理するとき、self-attentionによって前方の例へ「注意」を向けます。例の入力と出力の間にある形式、文体、論理規則などの対応パターンを見つけ、新しい出力を生成するときに再現します。本質的にfew-shot promptingは、attention機構によるパターン一致と転移 を利用しています。
3.2 Shot数による分類
Zero-shot: 不给示例,直接给任务指令
One-shot: 给 1 个示例
Few-shot: 给 2-5 个示例(最常用)
Many-shot: 给更多示例(通常 10+ 个,在 context window 足够大时使用)
3.3 実践例:感情分析
# Zero-shot(零样本)
zero_shot_prompt = """判断以下评论的情感倾向。
评论:这家店的服务态度真的很差,等了一个小时才上菜。
情感:"""
# 模型可能输出"负面",也可能输出"消极"、"不好"、"negative" —— 格式不可控
# Few-shot(少样本)
few_shot_prompt = """判断评论的情感倾向。
评论:今天天气真好,心情特别愉快!
情感:positive
评论:这个产品质量一般般,没什么特别的。
情感:neutral
评论:快递太慢了,包装还破了,非常失望。
情感:negative
评论:这家店的服务态度真的很差,等了一个小时才上菜。
情感:"""
# 模型现在非常明确地知道:输出只能是 positive / neutral / negative
違いが分かるでしょうか。Few-shotの三つの例は、複数の情報を同時に伝えています。出力候補はpositive、neutral、negativeの三つだけであること、出力は中国語の説明ではなく英語のラベルであること、それぞれのラベルがどの程度の感情に対応するか、という情報です。こうした「規則」を文章で説明しなくても、モデルは例から自動的に推定します。
3.4 Few-Shotの高度なテクニック
テクニック1:例の数より多様性が重要
# ❌ 差的 few-shot:示例太相似
bad_examples = """
输入:苹果 → 类别:水果
输入:香蕉 → 类别:水果
输入:橙子 → 类别:水果
"""
# ✅ 好的 few-shot:覆盖不同类别和边界情况
good_examples = """
输入:苹果 → 类别:水果
输入:胡萝卜 → 类别:蔬菜
输入:三文鱼 → 类别:海鲜
输入:番茄 → 类别:蔬菜(注:虽然有时被归为水果,但在烹饪分类中属于蔬菜)
"""
「トマト」の例が非常に重要です。モデルが 境界事例 をどう扱うべきかを示し、注釈による説明を追加できることも実演しています。
テクニック2:例の順序が結果に影響する
研究によれば、few-shotの例の順序は出力へ大きく影響します。通常は、現在の入力に最も近い例を最後、つまり実際の質問に最も近い位置へ置くことが推奨されます。LLMのattention機構は、recency biasによって近くにある内容へ強い注意を向けるからです。
テクニック3:コードで例を動的に選ぶ
import numpy as np
from typing import List, Tuple
class DynamicFewShotSelector:
"""
动态 few-shot 示例选择器。
核心思路:根据用户输入,从示例库中选择最相关的示例。
这是 AI 应用开发中的高频模式 —— 不要硬编码示例,要动态检索。
"""
def __init__(self, examples: List[Tuple[str, str]], embedding_func):
"""
参数:
examples: [(input_text, output_text), ...] 示例库
embedding_func: 将文本转为向量的函数
"""
self.examples = examples
self.embedding_func = embedding_func
# 预计算所有示例的 embedding
self.example_embeddings = [
embedding_func(inp) for inp, _ in examples
]
def select(self, query: str, k: int = 3) -> List[Tuple[str, str]]:
"""
选出与 query 最相似的 k 个示例。
使用余弦相似度作为相似性度量。
"""
query_embedding = self.embedding_func(query)
# 计算 query 与每个示例的余弦相似度
similarities = []
for emb in self.example_embeddings:
cos_sim = np.dot(query_embedding, emb) / (
np.linalg.norm(query_embedding) * np.linalg.norm(emb)
)
similarities.append(cos_sim)
# 取 top-k 最相似的示例
top_k_indices = np.argsort(similarities)[-k:]
# 返回示例,注意:最相似的放在最后(利用 recency bias)
return [self.examples[i] for i in top_k_indices]
def build_prompt(self, query: str, k: int = 3) -> str:
"""组装完整的 few-shot prompt"""
selected = self.select(query, k)
prompt_parts = []
for inp, out in selected:
prompt_parts.append(f"输入:{inp}\n输出:{out}")
prompt_parts.append(f"输入:{query}\n输出:")
return "\n\n".join(prompt_parts)
このパターンは Dynamic Few-Shot Selection と呼ばれ、RAG(Retrieval-Augmented Generation)の応用の一つです。本番環境の例ライブラリには数千件の例が含まれる可能性があり、各リクエストで最も関連する3〜5件をfew-shotの例として動的に選びます。
3.5 Few-Shotの限界
Few-shotは万能ではなく、明確な限界があります。第一にtokenを消費します。各例がcontext windowの領域を使い、API料金も増加させます。第二に、複雑な推論タスクへの効果は限定的です。タスクが複数段階の推論を必要とする場合、入力と出力だけの例では足りず、モデルは推論過程を見る必要があります。これがChain-of-Thoughtの解決する問題です。第三に、例の品質が極めて重要です。誤った例はモデルを誤った方向へ「教えて」しまいます。
4. Chain-of-Thought(思考の連鎖)
4.1 中心原理
Chain-of-Thought(CoT)promptingは、Google BrainのJason Weiらが2022年に提案した技術です。中心となる考え方は非常に単純で、同時に非常に効果的です。最終回答を出す前に、中間の推論段階をモデルへ出力させます。
なぜ効果が上がるのでしょうか。ここでもLLMのautoregressiveな本質に戻ります。LLMはtokenを一つずつ生成し、新しいtokenはそれ以前に生成されたすべてのtokenへ依存します。直接回答するとき、モデルはすべての推論を「一ステップ」、つまりニューラルネットワーク内部の一度のforward passで終えなければなりません。しかしモデルが一ステップで実行できる計算量には限界があります。
先に推論段階を書かせると、その内容が後続生成のcontextになり、「外部作業記憶」に相当する役割を果たします。モデルは一度に一つの部分問題へ集中し、結果を書き出して、次のステップの入力として利用できます。人間が数学の問題を解くとき、下書き用紙へ途中式を書くことに似ています。
4.2 基本的なCoTの例
# ❌ 不使用 CoT(直接要答案)
prompt_no_cot = """
餐厅账单是 180 元,需要加 10% 的服务费,三个人平分。
每人需要付多少钱?
"""
# 模型可能直接给出答案,但复杂数学问题时容易出错
# ✅ 使用 CoT(要求展示推理过程)
prompt_with_cot = """
餐厅账单是 180 元,需要加 10% 的服务费,三个人平分。
每人需要付多少钱?
请一步一步地思考这个问题,展示你的推理过程,最后给出答案。
"""
# 模型输出:
# 1. 原始账单:180 元
# 2. 服务费:180 × 10% = 18 元
# 3. 总计:180 + 18 = 198 元
# 4. 每人:198 ÷ 3 = 66 元
# 答案:每人需要付 66 元
4.3 CoTを実現する三つの方法
方法1:Zero-Shot CoT(最も単純)
prompt末尾へ、効果的な一文を追加するだけです。
prompt = f"""
{your_question}
Let's think step by step.
"""
「Let’s think step by step」は、2022年の論文『Large Language Models are Zero-Shot Reasoners』で、有効なzero-shot CoTのトリガーとして検証されました。なぜ有効なのでしょうか。教材、フォーラムの解答、論文など、学習データに含まれる大量の推論文章が、同様の段階的な推論パターンを持つからです。この一文が、それらのパターンに対するモデルの想起を促します。
方法2:Few-Shot CoT(例と組み合わせる)
prompt = """请解决以下数学问题。
问题:小明有 5 个苹果,他给了小红 2 个,然后妈妈又给了他 3 个。小明现在有几个苹果?
推理过程:
- 初始:小明有 5 个苹果
- 给了小红 2 个后:5 - 2 = 3 个
- 妈妈给了 3 个后:3 + 3 = 6 个
答案:6 个
问题:一个图书馆有 3 层,每层有 4 个书架,每个书架有 5 层隔板,每层隔板放 8 本书。图书馆一共有多少本书?
推理过程:
- 每个书架的书:5 层 × 8 本 = 40 本
- 每层楼的书:4 个书架 × 40 本 = 160 本
- 整个图书馆:3 层 × 160 本 = 480 本
答案:480 本
问题:{new_question}
推理过程:"""
ここでは例が「入力→回答」だけでなく、完全な推論の経路も示しています。通常のfew-shotより、推論の方法と形式 という重要な情報が一つ増えています。
方法3:Structured CoT(エンジニアリングされたCoT)
AIアプリケーション開発では、モデルの推論過程を構造化し、解析可能にする必要がよくあります。
prompt = """你是一个医疗分诊助手。根据患者描述,进行初步分诊评估。
请严格按以下结构输出你的分析:
<reasoning>
<symptom_extraction>
[从描述中提取关键症状,列出每个症状]
</symptom_extraction>
<severity_assessment>
[评估每个症状的严重程度:轻微/中等/严重]
</severity_assessment>
<possible_conditions>
[基于症状组合,列出可能的病症,按可能性从高到低排列]
</possible_conditions>
<urgency_decision>
[综合判断:常规就诊/尽快就诊/紧急就医]
</urgency_decision>
</reasoning>
<final_answer>
[分诊建议(简洁版)]
</final_answer>
患者描述:{patient_description}"""
この構造化CoTには、コードからreasoningとfinal_answerを個別に解析できるという価値があります。推論過程を監査・デバッグ用のログへ保存し、エンドユーザーにはfinal_answerだけを表示できます。
4.4 CoTのコード実践:推論チェーンを持つ分析器を構築する
import json
import re
from dataclasses import dataclass
from typing import Optional
@dataclass
class ReasoningResult:
"""封装 CoT 输出的数据结构"""
reasoning_steps: list[str] # 推理步骤
final_answer: str # 最终答案
confidence: float # 置信度
raw_output: str # 模型原始输出
class ChainOfThoughtAnalyzer:
"""
一个带有 Chain-of-Thought 推理能力的分析器。
演示了如何在 AI 应用中工程化地使用 CoT。
"""
def __init__(self, llm_client, model: str = "claude-sonnet-4-20250514"):
self.client = llm_client
self.model = model
def analyze(self, question: str) -> ReasoningResult:
"""
对问题进行 CoT 分析。
关键设计:将推理过程和最终答案分离,方便下游处理。
"""
prompt = self._build_cot_prompt(question)
response = self.client.messages.create(
model=self.model,
max_tokens=2000,
messages=[
{"role": "system", "content": self._system_prompt()},
{"role": "user", "content": prompt}
]
)
raw_output = response.content[0].text
return self._parse_response(raw_output)
def _system_prompt(self) -> str:
return """你是一个严谨的分析助手。
在回答任何问题之前,你必须先展示完整的推理过程。
你的输出必须严格遵循指定的格式。"""
def _build_cot_prompt(self, question: str) -> str:
return f"""请分析以下问题。
问题:{question}
请按以下格式输出:
<reasoning>
步骤1: [第一步分析]
步骤2: [第二步分析]
...(根据需要添加更多步骤)
</reasoning>
<confidence>
[0.0 到 1.0 之间的数字,表示你对答案的置信度]
</confidence>
<answer>
[最终答案]
</answer>"""
def _parse_response(self, raw: str) -> ReasoningResult:
"""
解析模型的结构化输出。
这就是为什么我们需要固定格式 —— 为了可靠地解析。
"""
# 提取推理步骤
reasoning_match = re.search(
r'<reasoning>(.*?)</reasoning>', raw, re.DOTALL
)
reasoning_text = reasoning_match.group(1).strip() if reasoning_match else ""
steps = [
line.strip() for line in reasoning_text.split('\n')
if line.strip() and line.strip().startswith('步骤')
]
# 提取置信度
conf_match = re.search(
r'<confidence>(.*?)</confidence>', raw, re.DOTALL
)
try:
confidence = float(conf_match.group(1).strip()) if conf_match else 0.5
except ValueError:
confidence = 0.5
# 提取最终答案
answer_match = re.search(
r'<answer>(.*?)</answer>', raw, re.DOTALL
)
final_answer = answer_match.group(1).strip() if answer_match else raw
return ReasoningResult(
reasoning_steps=steps,
final_answer=final_answer,
confidence=confidence,
raw_output=raw
)
4.5 CoTを使うべき場面
すべての場面にCoTが必要なわけではありません。複数段階の推論を必要とする数学・論理問題、複数の要素を総合して判断する意思決定、複雑な文章理解と推論、医療、法律、金融など説明可能性が必要なアプリケーションに最も適しています。
一方、単純な事実検索(「フランスの首都はどこか」)、創作やコンテンツ生成、単純な形式変換や翻訳では、CoTが余分になる場合があります。このような場面でCoTを使うとtokenを浪費し、コストと遅延を増やしますが、品質は大きく向上しません。
5. 三つの技術を組み合わせる
実際のAIアプリケーションでは、この三つの技術をほぼ常に組み合わせて使います。次の総合例は、「顧客メール自動返信システム」のprompt設計を示しています。
def build_email_reply_prompt(
customer_email: str,
customer_history: str,
product_info: str
) -> tuple[str, str]:
"""
综合运用 Clear Instructions + Few-Shot + CoT 的实战示例。
场景:电商客服自动回复系统。
"""
system_prompt = """你是"TechShop"的高级客服代表。
你的目标是:准确理解客户问题,提供有帮助的回复,维护品牌形象。
核心规则:
1. 始终保持礼貌和专业
2. 如果涉及退款,需要先验证订单信息
3. 如果问题超出你的处理范围,引导客户联系人工客服
4. 回复长度控制在 100-200 字""" # ← Clear Instructions
user_prompt = f"""请根据客户邮件生成回复。
=== 客户历史 ===
{customer_history}
=== 产品信息 ===
{product_info}
=== 参考示例 ===
【示例1】
客户邮件:我上周买的蓝牙耳机左耳没声音了,能换一个吗?
思考过程:
- 问题类型:产品质量问题
- 购买时间:一周内,在保修期
- 处理方式:应该提供换货服务
- 需要信息:订单号,以便查询
回复:您好!很抱歉听到您的耳机出现了问题。一周内的产品质量问题我们可以为您免费更换。麻烦您提供一下订单号,我会尽快为您安排换货流程。如有其他问题,随时联系我们!
【示例2】
客户邮件:你们能不能把我的订单地址改成大阪市?我下周要出差。
思考过程:
- 问题类型:订单修改(地址变更)
- 关键因素:需要确认订单是否已发货
- 处理方式:如果未发货可以修改,已发货需要拦截或转寄
- 需要信息:订单号,以便查询发货状态
回复:您好!地址变更没问题,不过需要先确认您的订单发货状态。请提供您的订单号,如果还未发货我们可以直接修改地址;如果已发货,我会帮您联系物流进行转寄。
=== 当前客户邮件 ===
{customer_email}
请先进行思考分析,然后生成回复。按以下格式输出:
思考过程:
[你的分析]
回复:
[给客户的回复]""" # ← Few-Shot + CoT 组合
return system_prompt, user_prompt
この例では、Clear Instructionsが役割、規則、制約を定義します。Few-Shotは異なる場面を扱う二つの例を示し、期待する分析方法と返信文体を伝えます。CoTは返信より先に分析することを求め、根拠のある返信を生成させます。三つが協調して、信頼できる本番品質のpromptを構成します。
6. 重要ポイントのまとめ
Clear Instructions は、「モデルが何を求められているか理解しているか」という問題を解決します。正確な指示と制約によってモデルの出力空間を狭め、すべてのprompt engineeringの基礎になります。
Few-Shot は、「モデルがどのような結果を求められているか理解しているか」という問題を解決します。例によって期待するパターンを示し、LLMのin-context learning能力でそのパターンを「模倣」させます。
Chain-of-Thought は、「モデルが十分に考えられるか」という問題を解決します。推論過程を外部化して「考える余地」を与え、複雑な推論タスクの精度を大きく向上させます。
AI Application Engineerには、具体的な場面に応じてこの三技術を柔軟に組み合わせ、品質、コスト、遅延の最適なバランスを見つけることが求められます。