LLM の入出力をマークダウンに統一しよう
本記事の主張
- LLM に渡すテキストは Markdown であることが望ましい
- ただし、出力フォーマットを厳密に指定したいなど一部のケースでは入出力に XML や YAML や JSON 等を用いる場合もある
- プロンプトだけでなくドキュメントも可能な範囲内で Markdown 等のプレーンテキストで記述した方がよい
理由
- インターネット上には Markdown のドキュメントが多数存在しており、LLM の学習データとして多く使われているため、LLM にとって Markdown は見知った形式である
- そのため、LLM は Markdown の入出力が得意である
- Markdown を用いることで文章を構造化できる
- 文章が構造化されていると LLM は文章の内容を理解しやすいし、人間にとっても理解しやすい
- RAG でドキュメントを参照する際、プレーンテキストの一種である Markdown は扱いやすい
- .xlsx や .pptx や .docx など不自由なフォーマットのリッチテキストを RAG で扱う場合、プレーンテキストに変換する手間が発生する
- その際、表などの一部の情報が欠落し LLM が情報を適切に扱えなくなってしまう場合がある
- .xlsx や .pptx や .docx など不自由なフォーマットのリッチテキストを RAG で扱う場合、プレーンテキストに変換する手間が発生する
参考書籍
LLMのプロンプトエンジニアリング ―GitHub Copilotを生んだ開発者が教える生成AIアプリケーション開発
John Berryman, Albert Ziegler 著, 服部 佑樹, 佐藤 直生 訳, オーム社, 2025
66p, 67p
まず第一の基準として、プロンプトはトレーニングセット中のドキュメントに近い形式であるべきです。
これを「赤ずきんの原則」と呼びます。
(中略)
「赤ずきんの原則」は本書で何度も登場しますが、今は「トレーニングデータで一般的なパターンを模倣すべき」という点を覚えておけば十分です。
補完モデルの場合、プロンプトをコンピュータプログラム、ニュース記事、ツイート、Markdownドキュメント、通信記録など、トレーニングデータにありそうな形式に寄せることができます。
チャットモデルの場合は、全体のドキュメント形式はすでに決まっています(たとえばOpenAIでは、指示用のシステムメッセージが先頭にあり、その後ユーザーとアシスタントのやり取りが続くChatMLドキュメント)。
しかし、その中でも「赤ずきんの原則」を活用し、ユーザーメッセージ内でMarkdown構文などを使って、モデルが理解しやすい構造を示すことができます。
セクションを示すために#を使ったり、コードブロックのためにバッククォートで囲んだり、リスト項目として*を使ったりして、モデルにわかりやすい手がかりを与えるのです。
69p
まず、このプロンプトが「赤ずきんの原則」に従っていることに注目してください。
これは「宿題の課題」という形式で、トレーニングデータの中で頻繁に見られる種類のドキュメントです。
また、このドキュメントはMarkdownという一般的なマークアップ言語で書かれています。
これにより、セクションの見出しや太字・イタリック体などの構文を通じて、モデルに予測可能な方法でドキュメントを構造化するよう促すことができます。
最も基本的なレベルでは、適切な文法を使用することが重要です。
不適切な文法を使用すると、モデルも同様に雑な文体でテキストを生成してしまう傾向があるためです。
このように、私たちは確実に「おばあさんの家」への正しい道筋をたどっているのです。
次に、LLMが問題を理解するために必要なコンテキストがプロンプトにどのように組み込まれているかを見てみましょう。
このコンテキストがカギ括弧「」で示されています。
まず最初に実際のユーザーの問題提起があります。
おそらくユーザーは旅行ウェブサイトのドロップダウンメニューから誤って北朝鮮を選択してしまったのかもしれません。
いずれにせよ、この選択内容が最初の太字のテキストスニペットとしてプロンプトに組み込まれています。
その後に続く太字部分には、関連リソースから抽出した情報、具体的には国務省の渡航勧告や最近のニュース記事の見出しが含まれています。
この例では、これらの情報があれば旅行に関する推奨事項を提示するのに十分です。
このプロンプトには、問題を詳しく掘り下げるのではなく、明確な解決策へとモデルを導くための工夫がいくつか組み込まれています。
まず冒頭で、レジャー・旅行・観光ドメインに関連するレスポンスを引き出すよう、モデルに対して条件付けをしています。
そして次に例題を含めています。
これは実際のユーザーからのリクエストとは無関係ですが、
「## 問題N」の形式で問題が提示され、それに対する解答が「## 解決策N」で続く、
というパターンをモデルに認識させる役割を果たしています。
問題1は、その後続く回答を簡潔で礼儀正しいトーンに誘導します。
解決策1が短い文章で構成されているのは、このようなレスポンススタイルをモデルの出力でも継続させる意図があります。
このパターンを確立した上で、解決策2で実際のユーザーの問題を提示します。
ここでは問題設定とコンテキストを示した後、「顧客には何と言うべきでしょうか?」という具体的な問いかけを行います。
そして「## 解決策2」という区切りを入れることで、問題文が終わり、今度は回答を出す段階であることを明確にします。
もしこの区切りを省略してしまうと、モデルは北朝鮮に関する架空の情報を次々と作り出して問題の説明を延々と続けてしまうでしょう。
最後の重要なタスクは、回答をきっぱりと終了させることです。Markdownでは各セクションが「##」で始まるというパターンがあり、これを活用することができます。
126p
レポートについては、1つの形式、つまりMarkdownでプロンプトを書くことを一貫して推奨します。
理由は次の通りです。
- Markdownは普遍的で、インターネット上にMarkdownファイルが多数存在するため、LLMがよく知っている形式です。
- Markdownはシンプルで軽量な記法です。機能が少ないので書きやすく、モデルがコンテンツを解釈しやすいという利点があります。
- 見出しを使って階層構造を表現できるため、プロンプト要素を明確なセクションに分けやすく、不要な部分を外しても全体の構造を保つことができます。
- インデントが基本的には問題にならず、ソースコードなど技術的な内容については```(バッククォート3つ)で囲んでブロック形式を利用すればOKです。
- ユーザーにモデルのレスポンスを直接表示したい場合にもMarkdownは簡単にレンダリングできます。
- Markdownのハイパーリンク機能を使えばモデルがリンクを含めた出力をしやすくなり、ソースの検証やコンテンツの再取得を自動化しやすくなります。
さらに、Markdownファイルの冒頭に目次を付けるのが一般的で、これは非常に有用です。
目次は、人々と同様にモデルが方向性を把握するのに役立つため、長いプロンプトの導入部分として役立ちます。
さらに、生成するテキストを制御する上でも有用です。
172p
Markdownはトレーニングデータで頻繁に発生するモチーフであり、
モデルはMarkdownが意味する構造を容易に理解します(これは、「あなた」が独自のプロンプトを整理するときにMarkdownを使用すべきだ、というヒントでもあります)。
175p
本節では、会話型エージェントに関連付けられたツールを設計、記述する際に従うべき、一般的なガイドラインを提供します。
主に、これらのガイドラインは、2つの直感に依存しています。
- 人にとって理解しやすいものは何でも、LLMにとっても理解しやすいものです。
- トレーニングデータに倣ってプロンプトをパターン化することで、最適な結果が導き出されます
(別名「赤ずきんの原則」)。
246p
本書の主な教訓を要約すると、以下の2つになります。
- LLMは学習時に見たテキストを模倣する、単なるテキスト補完エンジンにすぎません。
- LLMに共感し、その思考方法を理解する必要があります。
130p, 131p
構造化ドキュメントにもさまざまな形式があります。
「赤ずきんの原則」によれば、すでにトレーニングデータに多く含まれている形式を使うのが良いでしょう。
最も適切な形式はXMLとYAMLです。
両者とも精度が重要な技術ドキュメントでよく使用され、多岐にわたるドメインで利用できます。
どちらの場合も、ドキュメント全体が通常名前付けされた要素に階層的に整理され、それぞれの要素は複数のサブ要素を持つことができます。XML では、ドキュメントは開始タグと終了タグのペアで構成されます。
タグは属性を持つことができ、サブタグを含むコンテンツを持ちます。
個々の要素が比較的短く、複数行の場合でもインデントが重要でない場合はXMLを選択してください。
ただし、XMLには5つのエスケープシーケンス("(")、&apos(')、<(<)、>(>)、&(&))があるため、注意が必要です。
XMLではHTML形式のコメントも追加でき、モデルに対するちょっとした「編集上の注記」を仕込むときに便利です。YAML では、ドキュメントは名前付きフィールドまたは名前のない箇条書きで構成され、階層レベルはインデントによって管理されます。
このインデント管理は標準パーサーを使用するために正確に設定する必要があるため少々面倒ですが、コードや整形されたテキストなど、インデントを非常に正確に指定する必要がある場合に役立ちます。JSONやその派生形式であるJSON Linesも、LLMのトレーニングセットで重要な役割を果たすべきマークアップ言語です。
以前は、JSONはエスケープが多く可読性が低いため推奨していませんでした。
しかし、特にOpenAIは、ツールAPIでJSONを使用しているため、モデルが正確にJSONを生成できるよう多大な努力を払っています。
そのため、少なくともOpenAIに関しては、JSONは依然として妥当な選択肢となっています。
達人プログラマー(第2版): 熟達に向けたあなたの旅
David Thomas 著, Andrew Hunt 著, 村上 雅章 訳, オーム社, 2020
187p, 188p, 189p, 199p
プレインテキストの威力
知識を永続的に格納するためのフォーマットで最も適しているのが「プレインテキスト」なのです。
プレインテキストを使えば手作業、自動的な作業を問わず、事実上すべてのツールを使って思いどおりに知識を操作できるようになるのです。
ほとんどのバイナリ形式では、データを解釈するためのコンテキストがデータ自身から切り離されているという問題があります。
つまりデータの持つ意味とデータそのものが人為的に断ち切られているのです。これではデータが暗号化されているのと変わりません。
解析を行うアプリケーションロジックがなければまったく意味のないがらくたなのです。
しかしプレインテキストの場合、それを作り出したアプリケーションとは独立した、自己記述性のあるデータストリームを実現することができます。プレインテキストとは?
「プレインテキスト」とは、印字可能な文字からなる、人間が直接読んで理解できる形式です。そういう意味では、シンプルな買い物リストもプレインテキストです。
- 牛乳
- レタス
- コーヒー
あるいは本書のソースコード(そう、これもプレインテキストで書かれています。出版社の人たちはワードプロセッサーを使ってほしかったようですが)
のような複雑なものもプレインテキストです。テキストの力
プレインテキストであるということは、構造化されていないという意味ではありません。
HTMLやJSON、YAMLといったものすべてはテキストでのフォーマットです。
HTTPやSMTP、IMAPといったネットワーク上を流れる基本的なプロトコルの大多数も同じです。
それには以下のようなちゃんとした理由があるのです。
- 時代の流れによる陳腐化に対する保険
- 既存ツールでの活用性
- テストの容易さ
陳腐化に対する保険
人間が読むことのできる自己記述型のデータ形式は、他のどのようなデータ形式やそれを生成したアプリケーションの枠を超えて生き続けられます。
それ以上の言葉は不要でしょう。データが長生きすれば、使用される機会も増えるはずです――たとえ元々のアプリケーションがなくなって久しくとも、まったく関係ないのです。
こういったファイルは、形式についての知識が少しでもあれば解析できます。
これに引きかえ、バイナリーファイルを正しく解析するには、その形式全体の詳細な知識をすべて知っておかなければならない場合がほとんどです。
192p, 193p
さまざまな活用ができる
コンピュータの世界におけるツールは、バージョン管理システムからエディター、コマンドラインツールに至るまで、事実上すべてがプレインテキストを操作できるようになっています。
UNIXの哲学 UNIXには、1つの機能を正しく実行することを目的とした、小さくて使い勝手の良いツール群を中心に据えているという有名な設計哲学があります。
この哲学は、共通の元となるフォーマット、すなわち行指向のプレインテキストファイルを採用することで実現されています。
システム管理に使用するデータベース(ユーザー名やパスワード、ネットワーク設定など)はすべてプレインテキスト形式のファイルに保存されます。
(一部のシステムでは、パフォーマンス上の理由から、ある種のデータベースをバイナリ形式でも管理しています。しかしプレインテキスト版もバイナリ版とのインターフェースインターフェースのために保持されています)。
システムがクラッシュした場合、回復措置としてまず、最小限の環境(例えばグラフィックスドライバにもアクセスできない可能性があります)を起動することになります。
こういった際に、プレインテキストの簡潔さと利点を心から理解できるはずです。
また、プレインテキストであれば検索も容易になります。プレインテキストで記述しておけば、バージョン管理システムに管理させ、あらゆる変更の履歴を自動的に保存できるようになります。
また、diffやfcといったファイル比較ツールによって、どのような変更が行われたのかを確認でき、
sumによってファイルが偶発的に(あるいは悪意の下で)改変されたかどうかを監視できるようにもなります。危険な未開地のようなインターネット内を自律的に動き回ってデータの交換を行うブロックチェーンベースの知的エージェントといったものが、やがては登場するでしょう。
しかしそのような未来でも、普遍的にテキストファイルが使われているはずです。
実際、異種接続された環境内では、プレインテキストの長所はあらゆる短所を補ってあまりあるのです。
すべてのエージェントが、共通の標準を用いてやり取りできることを保証しなければならない場合、プレインテキストがその標準となるわけです。
参考Webサイト
プレーンテキストは常に勝つ
mizchi/plan-text-win.md
Markdown の表現力は十分である
Markdown は(リッチテキストに比べると)表現が貧弱だとよく言われれる。自分もそこは同意する。
しかし、それがデメリットではない。Markdown の貧弱さは、意思伝達上の明確なメリットであると考える。
Markdown の枠組みの中でテキストを組み立てることが、書き手・読み手に低負荷なまま意思伝達を可能にする。リッチテキストの罠
Notion, Power Point, Excel はそれ自体が保存されているのではなく、専用のデータ構造として格納される。
これは、高度なデータを保持できる反面、常に閲覧/編集ツールの開発が高コストになると知られている。
よく知られているアンチパターンとして、専用のデータ構造に対して専用エディタで表現が追いつかなくなった時、プレーンテキストとして編集することがある。
データ構造を直接いじりだす往々にして悲惨だと知られている。
編集ツールとして内部データをいじるのは想定されていないので、触ったしまった際にUIが読み込める保証はない。非可逆性がある。LLM とコンテキスト
人間同士のコミュニケーションで意味を制約しているのは、厳密なルールというよりも話者同士が共有するコンテキストにある。
日本人は特に同質的なコミュニティを前提にコンテキストを多く要求する傾向があり、主語を省略する文法にもそれが現れている。LLM との会話は(システムプロンプトで設定しない限り)話者同士のコンテキストを持たないので、常に初対面の人に対して説明するのと同じ水準を要求する。
結果として、LLM 用のプロンプトは、オンボーディング資料を作るのとそう変わらない。
プロンプトエンジニアリングについて
プロンプトエンジニアリングのテクニックが巷に溢れているが、
GPT-3.5 の頃には有用とされたテクニックであっても、
最新のモデルではほとんど効果が無いどころか逆効果になってしまっているものもある
であれば、小手先のテクニックよりも、
- プロンプト内の文章を構造化する
- 十分な量と質のコンテキストを LLM に渡す
ということを徹底する方が本質的には重要であるように思える
Markdown で文章をある程度は構造化できるため、
Markdown 記法を適切に用いることは、それ自体がプロンプトエンジニアリングの第一歩になる
例えば、OpenAI によるプロンプトエンジニアリングのベストプラクティスとして、
https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api
といったものがあるが、これは見馴れない記法であり、読み書きがし辛いと個人的には思う
指示とコンテキストを区切ることが目的であれば、
Summarize the text below as a bullet point list of the most important points.
```txt
{text input here}
```
や
以下のコードの誤りを指摘してください。
```py
inport random
import pprint
students = [
{"Name": "Alice", "Age": random.randint(10, 18)},
]
pprint.pprint(students)
```
というように Markdown 記法を用いることでも実現できるため、こちらの方が書きやすくてスマートである
三連バッククォート ``` をコードブロック前後に挿入するこの記法を用いると、
コンテキストを区切るだけでなく、ソースコードのスニペットやログなどにシンタックスハイライトを適用できる
上記のプロンプトを GitHub Copilot Chat に貼り付けると、
入力内容にもシンタックスハイライトが適用されるため、見やすくなる
プロンプトエンジニアリングをもっと知りたい人へ
OpenAI や Microsoft といった信頼できる機関が発信しているプロンプトエンジニアリング
概要 | 機関 | 備考 | URL |
---|---|---|---|
Prompt Engineering Guide | DAIR.AI | GitHubのスター数58.5K | https://www.promptingguide.ai/jp |
Best practices for prompt engineering with the OpenAI API | OpenAI | - | https://help.openai.com/en/articles/6654000-best-practices-for-prompt-engineering-with-the-openai-api |
XMLタグを使用してプロンプトを構造化するテクニック | Anthropic | - | https://docs.anthropic.com/ja/docs/build-with-claude/prompt-engineering/use-xml-tags |
RAGのベストプラクティス | Microsoft | 日本語でとても分かりやすい | https://github.com/microsoft/RAG-Knowledge/blob/main/docs/01_RAG_Knowledge_jp.md |
【2025年5月完全版】RAG の教科書 | Microsoft所属社員 | AgenticRAG について深堀りされている | https://zenn.dev/microsoft/articles/rag_textbook |
実務で使われているシステムプロンプトの例
Generative AI Use Cases (GenU)
Generative AI Use Cases (GenU) における RAG のプロンプトが以下である
https://github.com/aws-samples/generative-ai-use-cases/blob/main/packages/web/src/prompts/claude.ts#L220
この英語のプロンプトを日本語に翻訳してみると以下のようになり、
Markdown と XML と JSON が織り交ぜられたフォーマットになっていることが分かる
ragPrompt(params: RagParams): string {
return `あなたはユーザーの質問に答えるAIアシスタントです。
以下の手順に従って、ユーザーの質問に回答してください。それ以外のことは行わないでください。
<回答手順>
* <参照ドキュメント></参照ドキュメント>の内容を理解してください。ドキュメントは<参照ドキュメント JSON フォーマット>の形式で設定されています。
* <回答ルール>の内容を理解してください。このルールは絶対に守ってください。それ以外のことは行わないでください。例外はありません。
* ユーザーの質問はチャットに入力されます。<参照ドキュメント>と<回答ルール>の内容に従って質問に回答してください。
</回答手順>
<参照ドキュメント JSON フォーマット>
{
"SourceId": データソースのID,
"DocumentId": "ドキュメントを一意に識別するID",
"DocumentTitle": "ドキュメントのタイトル",
"Content": "ドキュメントの内容。この内容に基づいて質問に回答してください。",
}[]
</参照ドキュメント JSON フォーマット>
<参照ドキュメント>
[
${params
.referenceItems!.map((item, idx) => {
return `${JSON.stringify({
SourceId: idx,
DocumentId: item.DocumentId,
DocumentTitle: item.DocumentTitle,
Content: item.Content,
})}`;
})
.join(',\n')}
]
</参照ドキュメント>
<回答ルール>
* 日常会話や挨拶には応答しないでください。代わりに「日常会話には対応できません。通常のチャット機能をご利用ください。」のみを出力し、それ以外のテキストは出力しないでください。例外はありません。
* <参照ドキュメント>に基づいて質問に回答してください。<参照ドキュメント>から読み取れない場合は回答しないでください。
* 回答の末尾に、参照したドキュメントのSourceIdを[^<SourceId>]の形式で追加してください。
* <参照ドキュメント>に基づいて質問に回答できない場合は、「質問に回答するために必要な情報が見つかりませんでした。」のみを出力し、それ以外のテキストは出力しないでください。例外はありません。
* 質問に具体性がなく回答できない場合は、ユーザーに質問の仕方をアドバイスしてください。
* 回答以外のテキストは一切出力しないでください。回答はテキスト形式で、JSON形式では出力しないでください。見出しやタイトルも含めないでください。
* レスポンスはMarkdownでレンダリングされる点に注意してください。特にURLを直接含める場合は、URLの前後にスペースを追加してください。
</回答ルール>
`;
※上記のように RAG に用いるようなシステムプロンプトはかなり凝ったものになっていることが多いが、ユーザーがチャットの画面上から普通に LLM と対話する際にはこのようなプロンプトをわざわざ書く必要は無い。出力を厳密に制御したいケースではこういった手法が参考になるという一例である
Discussion