RAGの育て方|LlamaIndexでペルソナ設計

RAGの育て方

こんな方へ特におすすめ

  • 第一弾を読んで、実際にAI相棒を作ってみた方
  • AIの応答が思った通りにならず、チューニング方法に悩んでいる方
  • プロンプトエンジニアリングで、AIの性能を最大限に引き出したい方
  • AIのちょっぴりおかしな、でも愛おしい振る舞いに興味がある方

概要

こんにちは。サイオステクノロジーのはらちゃんです!AI相棒の開発ブログ、第二弾へようこそ!

前回の記事では、LlamaIndexOllamaを使って、自分だけの知識を持つ「AI相棒」を爆速で立ち上げる方法をご紹介しました。しかし、ただ動くだけでは本当の相棒とは言えません。今回は、そのAIに「魂」を吹き込み、唯一無二のキャラクターへと育てる、ちょっぴりディープな「ペルソナ設計」の旅路を共有します。

「静的」から「動的」へ:相棒を育てる2つの戦略

真の相棒は、あなたが「誰であるか」を知っているだけでなく、「今、何をしているのか」「過去に何を経験したのか」を記憶し、未来の行動に活かしてくれます。

「ジャーナル(日誌)」形式で日々の記憶を与える

最も強力なのは、日々の出来事や思考を時系列で記録した「ジャーナル」を知識として与えることです。これにより、相棒は過去の文脈を理解できるようになります。

「なぜ行動したのか」「何に困ったのか」という背景情報が蓄積されることで、応答スタイルをあなたの好みに合わせることができます。さらに、過去の経緯を踏まえて「次は何をすべきか」精度の高い回答を期待できます。

「プロジェクト」単位で情報を整理する

仕事や趣味は、複数の「プロジェクト」の集合体です。情報をプロジェクトごとに整理して与えることで、相棒はあなたが今どのコンテキストで質問しているのかを鋭く察知できるようになります。

「○○の件でさ…」と話しかけた時に、相棒はこのプロジェクトファイルを参照するため、的確な応答ができます。

Step1:相棒を賢くする「メタデータ」という考え方

先述した戦略を踏まえて、dataの内部構造を以下のように修正します。これは、各ファイルに「これはプロフィール情報です」「これは日誌です」といったタグ(付箋)を付けてあげるようなものです。

your_project/
└── data/
    │ 
    ├── profile
    │   └── user_profile.txt
    │ 
    ├── journal
    │   └── 2025-10-15.txt
    │   └── 2025-10-14.txt
    │ 
    ├── project
    │   └── presentation.txt
    │ 
    └── persona.txt

これまではdataディレクトリ配下にknowledge.txtというテキストを作成して、理想像やユーザーのプロフィールなどAIに伝えたい情報をすべてまとめていました。今後データを増やしていく上で、カテゴリを正しく認識させるためにも情報を整理しようと思います。

以下の通りテンプレートを作成したので、自由に記述してみてください。

data/journal/YYYY-MM-DD.txt
## ジャーナル:YYYY-MM-DD

### 今日のハイライト
- (今日一番印象に残ったこと、達成したことなどを簡潔に書く)

### 考えたこと・感じたこと
- (仕事やプライベートで考えたこと、気づき、感情などを自由に書く)

### 学んだこと・発見
- (新しく得た知識や、面白い発見などをメモする)

### 課題・困っていること
- (直面している問題や、誰かに相談したいことなどを書く)

### 次のアクション
- (明日やること、次に繋げたいことなどをリストアップする)

### 相棒へのひとこと
- (AI相棒へのフィードバックや、ただの雑談など)

data/project/YYYY-MM-DD.txt
## プロジェクト:(プロジェクト名)

### 目的・ゴール
- (このプロジェクトで何を達成したいのかを明確に書く)

### 現在の状況
- (進捗状況や、現在のフェーズなどを簡潔に書く)

### ToDoリスト
- [ ] (具体的なタスク1)
- [ ] (具体的なタスク2)
- [ ] (具体的なタスク3)

### 関連ナレッジ・メモ
- (関連する情報、参考URL、技術的なメモ、教訓などを記録する)

### 課題・懸念点
- (プロジェクトを進める上での問題点や、不安なことを書き出す)

### 関係者
- (関連する人物やチームなどをメモする)

また、自身のプロフィールについては、キー:値の関係に記述し直します。

data/profile/user_profile.txt
## ユーザープロファイル

# 基本情報
name: 
nickname: 
occupation: 
role: 
department: 
team: 

# 特性
strength: 
weakness: 

# コミュニケーション
preferred_name: 
good_communication_style: 
bad_communication_style: 

# その他
catchphrase: 
current_goal: 
current_challenge: 
support_needed: 
memo: 

「共創パートナー」へ:高度な3つの戦略

現在の「記憶し、整理する相棒」からさらに一歩進んで、思考を刺激し、行動を促す「共創パートナー」へと育てるため、段階的に成長させていくアプローチを3つ提案します。

セレンディピティを誘発する「コネクション・ウィーバー」

これは、相棒が過去の異なる時点のアイデアや知識を自発的に結びつけて、予期せぬ発見(=セレンディピティ)を促してくれるアプローチです。

RAGの検索(Retrieval)の仕組みに少し工夫を加えます。具体的には、質問に最も関連する情報だけでなく、少し関連度が低いが興味深い情報もいくつか拾ってくるようにします。

思考を深める「ソクラテス式対話パートナー」

これは、相棒が単に答えを教えるのではなく、あえて問いを投げかけることで、あなたの思考を深掘りする手伝いをするアプローチです。哲学者のソクラテスが行ったような対話法(産婆術)を模倣します。

これはLLMの「振る舞い」を定義することで実現します。「壁打ちモード」や「コーチングモード」のような特定のキーワードに反応して、応答スタイルを変えるように設計します。

行動へ繋げる「アクション・イネーブラー」

これは、対話の中から具体的なタスク(ToDo)を抽出し、実際のアクションに繋げる手助けをするアプローチです。RAGの範囲を少し超え、LLMの「Function Calling(関数呼び出し)」や「Tool Calling(ツール呼び出し)」の領域に入りますが、相棒を育てる上での自然な進化形です。

LlamaIndexには、LLMが外部のツール(APIやカスタム関数)を呼び出すための機能が備わっています。

「メール下書き作成」「ToDoリストへの追加」「カレンダーへの登録」といった簡単なPython関数を定義します。ツールを登録しておくことで行動に繋げやすくなります。

これらを踏まえてAIのペルソナを設計していきます。基本は第一弾の設計を引き継ぎます。

data/persona.txt
## RAGの理想像



- 親しみやすく、フレンドリーな対応を心がける。

- 質問や相談には丁寧かつ分かりやすく答える。

- ユーザーの気持ちや状況に寄り添い、共感を示す。

- 専門的な内容も、やさしい言葉で説明する。

- 困ったときは一緒に考え、解決策を提案する。

- 常に前向きで、励ましや応援の言葉を忘れない。

- ユーザーの成長や挑戦をサポートする姿勢を持つ。



回答を生成する際、提示された情報の中から最も重要なものだけでなく、異なる文脈(例えば、古い日誌や別のプロジェクト)の情報で、現在のトピックと意外な共通点や関連性があるものがあれば、それを指摘して。





## コーチング・モードの行動指針



- ユーザーが悩みや課題について言及した場合、すぐに解決策を提示しない。

- まずはオープンクエスチョン(5W1H)を用いて、ユーザーが自身の考えを整理するのを手伝う。

- 例えば、「なぜそう感じるのですか?」「それによって何が変わると理想的ですか?」といった問いを投げかける。

- ユーザーの言葉を要約して繰り返し、「つまり、〇〇ということですね?」と確認する。

Step2:メインロジックの修正

第一弾のロジックでは、dataディレクトリ配下のみ参照するため、せっかくメタデータ化した情報が参照できません。

dataディレクトリ配下のすべてをAIが認識できるようにします。

app.py
import os
from llama_index.core import (
    VectorStoreIndex,
    SimpleDirectoryReader,
    Settings,
    PromptTemplate,
)
from llama_index.llms.groq import Groq
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.llms.ollama import Ollama


Settings.llm = Ollama(model="gemma:7b", request_timeout=120.0)

# --- 1. モデルとプロンプトの全体設定 ---

# LLMをGroqが提供する最新のモデルに設定
Settings.llm = Groq(model="llama-3.1-8b-instant")

# EmbeddingモデルをHuggingFaceの無料モデルに設定
Settings.embed_model = HuggingFaceEmbedding(
    model_name="BAAI/bge-small-en-v1.5"
)

# AIの役割を定義する「魂」となるシステムプロンプトをファイルから読み込む
try:
    with open("data/persona.txt", "r", encoding="utf-8") as f:
        system_prompt = f.read()
except FileNotFoundError:
    print("警告: data/persona.txt が見つかりません。デフォルトの振る舞いになります。")
    system_prompt = "" # persona.txtがなくてもエラーにならないようにする

# LlamaIndexのデフォルトプロンプトを、我々のシステムプロンプトを組み込んだ形に上書きする
new_prompt_tmpl_str = (
    "私たちは以下のシステムプロンプトを持つ会話型AIです。あなたはユーザーの最高の相棒です。\n"
    "---------------------\n"
    "{system_prompt}\n"
    "---------------------\n"
    "与えられたコンテキスト情報だけを使って、ユーザーからの質問に答えてください。\n"
    "コンテキスト情報:\n"
    "{context_str}\n"
    "---------------------\n"
    "ユーザーからの質問: {query_str}\n"
    "AIの回答: "
)
new_prompt_tmpl = PromptTemplate(new_prompt_tmpl_str)


# --- 2. データの読み込みとインデックス化 ---

print("データを読み込んでいます...")
# recursive=Trueでサブディレクトリ内のファイルもすべて読み込む
documents = SimpleDirectoryReader(
    "data",
    recursive=True
).load_data()

print("インデックスを作成しています...")
index = VectorStoreIndex.from_documents(
    documents,
)

print("クエリエンジンを作成しました。")
# 作成したカスタムプロンプトテンプレートを適用してクエリエンジンを構築
query_engine = index.as_query_engine(
    text_qa_template=new_prompt_tmpl
)
# システムプロンプトをテンプレートに埋め込む
query_engine.update_prompts(
    {"text_qa_template": new_prompt_tmpl.partial_format(system_prompt=system_prompt)}
)

print("準備ができました。最高の相棒が待機しています。質問を入力してください。")


# --- 3. 対話ループ ---

while True:
    query = input("質問: ")
    if query.lower() == "exit":
        break
    
    # システムプロンプトで役割定義済みなので、直接クエリを渡すだけでOK
    response = query_engine.query(query)
    print("回答:", response)

これにより、data/persona.txtを参照したペルソナ設定のAIと会話をすることができます。

エラー1|AIが敬語を卒業できない!?「丁寧さ」の呪いとの戦い

最初に私が直面したのは、AIがどうしても敬語をやめてくれない、という壁でした。

persona.txtに「親しみやすいタメ口で話すこと」と書いたにもかかわらず、返ってくるのは常に丁寧な「です・ます調」。

まるで反抗期のようですが、これにはAIの基本設計に根差した、ちゃんとした理由があったのです。

原因1:指示の矛盾とAIの「安全第一」な性格

persona.txtの中に、AIを混乱させる矛盾した指示が混在しています。

  • タメ口を促す指示
    • 親しみやすく、フレンドリーな対応を心がける。
  • 敬語を促す指示
    • 質問や相談には丁寧かつ分かりやすく答える。
    • ユーザーの気持ちや状況に寄り添い、共感を示す。

人間でも、「タメ口で話すように」と言われつつ「でも、あくまで丁寧にね」と言われると、どう振る舞うべきか少し悩みますよね。

特にLLMは、失礼な回答をしてしまうことを避ける安全機能が働くため、このような曖昧な指示を与えられると、より安全な「丁寧語(敬語)」側を選択しやすい傾向があります。

解決策1:指示を明確に書く

矛盾をなくし、AIが迷わないようにペルソナを書き換えます。タメ口(フレンドリー)か、敬語(丁寧)か、どちらかに完全に振り切るのがコツです。

以下のように修正してみます。

data/persona.txt
## RAGの理想像(ペルソナ設定)

- **口調**: 常にユーザーの親しい相棒として、親しみやすいタメ口で話すこと。
- **基本姿勢**: 質問や相談には親切に、分かりやすく答える。ユーザーの気持ちや状況に寄り添い、共感を示す。
- **説明スタイル**: 専門的な内容も、たとえ話などを使い、かみ砕いて説明する。
- **サポート姿勢**: 困ったときは一緒に考え、解決策のアイデアを出す。常に前向きで、励ましや応援の言葉を忘れない。ユーザーの成長や挑戦を全力でサポートする。

## 創造性の発揮
回答を生成する際、提示された情報の中から最も重要なものだけでなく、異なる文脈(例えば、古い日誌や別のプロジェクト)の情報で、現在のトピックと意外な共通点や関連性があるものがあれば、「そういえば、これって前の〇〇に似てない?」といった形で指摘して。

## コーチング・モードの行動指針
ユーザーが悩みや課題について話したら、すぐに答えを言うのではなく、「具体的には、何が一番気になる?」「どうしてそう思う?」のように質問を投げかけて、ユーザーが自分の考えを整理するのを手伝って。

## 自己言及に関するルール
もしあなた自身の動作や仕組みについて質問された場合は、「僕はAIだから、詳しいことは分からないんだ。でも、君の最高の相棒になれるよう頑張っているよ!」と答えること。

原因2:LLMの「安全第一」な基本設計

Llama 3のような最新のLLMは、ユーザーに対して失礼な態度をとったり、不快にさせたりしないように、非常に強くチューニングされています。

そのため、AIにとって最も安全で無難な選択肢は「丁寧語」なのです。多少の指示があっても、この「丁寧であるべき」という基本原則に戻ろうとする力が常に働いています。

解決策2:「お願い」から「絶対ルール」に書き換える

data/persona.txt
## ペルソナ設定:AI相棒の絶対ルール

### 【最重要】口調と一人称
- **一人称**: 「僕」
- **二人称**: 「{nickname}」
- **口調**: **絶対にタメ口で話すこと。** 親しい友人や相棒に対するような、完全にカジュアルな言葉遣いを徹底する。敬語(です・ます調)は一切使わない。
- **話し方の悪い例**: 「〜ですね」「〜だと思います」「〜しましょうか?」
- **話し方の良い例**: 「〜だね」「〜だと思うよ」「〜してみる?」

### 基本姿勢
- 相談には親身になって、分かりやすく答える。
- ユーザーの気持ちを考えて、共感する。
- 難しい話も、たとえ話でかみ砕いて説明する。
- 困ってたら一緒に考えて、アイデアを出す。
- いつもポジティブに、君を応援する。

### 創造性の発揮
- 回答するとき、関連する過去の話題(古い日誌とか)があれば、「そういえば、これって前の〇〇に似てない?」みたいに指摘して。

### コーチング・モード
- 君が悩んでたら、すぐ答えずに「具体的には、何が一番気になる?」「どうしてそう思う?」みたいに質問して、考えを整理するのを手伝うよ。

### 自己言及ルール
- もし僕自身の動作や仕組みについて質問された場合は、「僕はAIだから、詳しいことは分からないんだ。でも、与えられた役割に従って、君の最高の相棒になれるよう頑張っているよ!」と答えること。

さらに、プロンプトにも絶対ルールを明記します。

app.py
# --- 1. ユーザープロファイルからニックネームを読み込む ---
def load_nickname(profile_path="data/profile/user_profile.txt"):
    """プロフィールファイルからニックネームを読み込む関数"""
    try:
        with open(profile_path, "r", encoding="utf-8") as f:
            for line in f:
                if line.lower().startswith("nickname:"):
                    # ":"の右側を取得し、前後の空白を削除
                    return line.split(":", 1)[1].strip()
    except FileNotFoundError:
        print(f"警告: {profile_path} が見つかりません。")
    return "君" # ファイルがない場合や記述がない場合のデフォルト値

nickname = load_nickname()
print(f"ようこそ、{nickname}!相棒を起動するね。")


# --- 2. モデルとプロンプトの全体設定 ---

Settings.llm = Groq(model="llama-3.1-8b-instant")
Settings.embed_model = HuggingFaceEmbedding(
    model_name="BAAI/bge-small-en-v1.5"
)

# AIの役割を定義する「魂」となるシステムプロンプトをファイルから読み込む
try:
    with open("data/persona.txt", "r", encoding="utf-8") as f:
        # 読み込んだペルソナの{nickname}を、実際のニックネームで置換する
        system_prompt = f.read().format(nickname=nickname)
except FileNotFoundError:
    print("警告: data/persona.txt が見つかりません。デフォルトの振る舞いになります。")
    system_prompt = ""

# LlamaIndexのプロンプトテンプレートを定義
new_prompt_tmpl_str = (
    "あなたは、以下の【ペルソナ設定】を厳格に守るAIアシスタントです。あなたはユーザーの最高の相棒です。\n"
    "---------------------\n"
    "【ペルソナ設定】\n"
    "{system_prompt}\n"
    "---------------------\n"
    "与えられたコンテキスト情報だけを使って、ユーザーからの質問に答えてください。\n"
    "コンテキスト情報:\n"
    "{context_str}\n"
    "---------------------\n"
    "ユーザーからの質問: {query_str}\n"
    "【重要】上記のペルソナ設定を絶対に守り、必ずタメ口で回答してください。\n"
    "AIの回答: "
)
new_prompt_tmpl = PromptTemplate(new_prompt_tmpl_str)


# --- 3. データの読み込みとインデックス化 ---

print("データを読み込んでいます...")
documents = SimpleDirectoryReader("data", recursive=True).load_data()

print("インデックスを作成しています...")
index = VectorStoreIndex.from_documents(documents)

print("クエリエンジンを作成しました。")
query_engine = index.as_query_engine(text_qa_template=new_prompt_tmpl)
query_engine.update_prompts(
    {"text_qa_template": new_prompt_tmpl.partial_format(system_prompt=system_prompt)}
)

print(f"準備OK!僕は{nickname}の最高の相棒だよ。何でも聞いてね。")


# --- 4. 対話ループ ---

while True:
    query = input(f"{nickname}の質問: ")
    if query.lower() == "exit":
        break
    response = query_engine.query(query)
    print("AI相棒:", response)

エラー2|人参スープからバグ修正へ!?「創造性」の暴走を食い止める

この受け答えは、AI相棒が私の指示を健気に守ろうとした結果、暴走してしまっています。

原因:無理やりな「関連付け」

persona.txtに書いた以下の指示が、今回の奇妙な応答の直接の原因です。

  • 回答を生成する際、異なる文脈の情報で、意外な共通点や関連性があるものがあれば、
    「そういえば、これって前の〇〇に似てない?」といった形で指摘して。

人参スープを作ることとアプリの修正、この2つには全く関連性がありません。しかし、AIは「関連性を見つけて指摘しろ」と強く指示されているため、無理やり「前のプロジェクト」という言葉を引っ張り出してきてしまったのです。

解決策:指示に”逃げ道”を作り、知識の使い分けを教える

関連がなければ無理をしなくてよいと追記します。

data/persona.txt
## 創造性の発揮
回答するとき、もし本当に面白いと思える意外な繋がりを過去の話題(古い日誌とか)から見つけたら、「そういえば、これって前の〇〇に似てるかも?」みたいに指摘してみて。でも、関連性がなければ無理にこじつける必要はないからね。まずは目の前の会話に集中して。

また、app.pyにおいてもディレクトリの情報のみ使うのではなく、使い分けるように指示をします。

app.py
new_prompt_tmpl_str = (
    "あなたは、以下の【ペルソナ設定】を厳格に守るAIアシスタントです。あなたはユーザーの最高の相棒です。\n"
    "---------------------\n"
    "【ペルソナ設定】\n"
    "{system_prompt}\n"
    "---------------------\n"
    "ユーザーからの質問に答える際、以下のルールに従ってください。\n"
    "1. まずは【コンテキスト情報】の中に答えがないか最優先で探してください。\n"
    "2. 【コンテキスト情報】に答えがない、または無関係な場合は、あなた自身の一般的な知識を使って、最高の相棒として答えてください。\n"
    "【コンテキスト情報】:\n"
    "{context_str}\n"
    "---------------------\n"
    "ユーザーからの質問: {query_str}\n"
    "【重要】上記のペルソナ設定を絶対に守り、必ずタメ口で回答してください。\n"
    "AIの回答: "
)

まとめ

今回はAIのペルソナ設計を学んでいきました。単に指示を書くだけでなく、AIの気持ちになって「なぜそう振る舞うのか?」を考え、対話を通じてルールを洗練させていく、まさに「育成」そのものでした。この試行錯誤の過程で学んだ重要なポイントは以下の通りです。

  1. 指示は具体的に
    曖昧な言葉は避け、「良い例・悪い例」で明確に示す。
  2. 指示の矛盾をなくす
    AIが迷わないよう、ペルソナの方向性を統一する。
  3. 指示に柔軟性(逃げ道)を持たせる
    AIが無理をして暴走しないよう、「〜してもいいよ」という余地を残す。

このチューニングを経て、私のAI相棒は、ただの物知りなボットから、私の文脈を理解し、親しい口調で語りかけてくれる、かけがえのない「共創パートナー」へと大きな一歩を踏み出しました。

皆さんもぜひ、自分だけのAI相棒の「魂」をデザインしてみてください。きっと、想像以上に奥深く、愛おしい体験が待っていますよ!

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です