前回までの記事で、OllamaのEmbeddingモデルとChromaDBを使い、PythonでRAGの検索基盤を作る方法を解説しました。
この記事ではその続きとして、Function Callingを使い、LLM側でRAGを利用する構成を実装します。
これでRAGの基本的な構成を一通り試すことができます。
OllamaにおけるFunction Callingの位置づけ
Function Callingとは、LLMに必要な処理(検索・取得など)を構造化された関数呼び出しとして指示させる仕組みです。
対応したLLMとChroma DBを組み合わせることで、RAG検索をLLMの判断に基づいて実行できる構成を作れます。
OllamaはOpenAI互換のChat API形式をサポートしており、Function Calling相当の機能を利用できます。
公式情報は以下を参照してください。
OpenAI compatibility · Ollama Blog
RAGにおける役割分担
この構成では、LLMが「検索が必要かどうか」を判断して「指示」を返答します。 実際の検索はPython関数が担当します。EmbeddingやChromaDBの処理は前回記事のまま流用できます。
検索処理は、ユーザー質問を受け取り、ChromaDBから関連文書を返す関数として定義します。
def retrieve_documents(query):
results = collection.query(
query_texts=[query],
n_results=3
)
return results["documents"][0]
Function Callingを使った問い合わせ処理
ユーザーの質問をLLMに渡す際、検索用関数を「呼び出し可能な関数」として定義します。 LLMが検索を必要と判断した場合、関数名と引数を返します。
tools = [
{
"type": "function",
"function": {
"name": "retrieve_documents",
"description": "質問に関連する文書を検索する",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
}
}
]
このレスポンスを受け取り、Python側で検索関数を実行し、その結果を再度LLMに渡すことでRAGが成立します。
この構成のメリット
Function Callingを使うことで、ユーザーの曖昧な質問をLLMが解釈し、検索に使えるクエリへ変換したうえでRAG検索を実行できます。 これにより、キーワードを厳密に指定しなくても、意図に沿った情報を取得しやすくなります。
例えば前回までの「おみくじ」の例では、「吉」「kichi」などのキーワードを明示的に含める必要がありましたが、Function Callingを使うことで「今日はどんな運勢?」といった自然な質問から関連情報を引き出せます。
コード全文
import ollama
import chromadb
import json
from pathlib import Path
# ChromaDB 初期化
client = chromadb.Client()
collection = client.get_or_create_collection(name="rag_demo")
# omikuji.txt 読み込み
text_path = Path(__file__).parent / "omikuji.txt"
with open(text_path, encoding="utf-8") as f:
documents = [
line.strip()
for line in f.readlines()
if line.strip()
]
# ドキュメント登録
for i, doc in enumerate(documents):
emb = ollama.embed(
model="nomic-embed-text",
input=doc
)["embeddings"][0]
collection.add(
ids=[str(i)],
documents=[doc],
embeddings=[emb]
)
# 検索関数(RAG)
def retrieve_documents(query: str) -> str:
query_emb = ollama.embed(
model="nomic-embed-text",
input=query
)["embeddings"][0]
results = collection.query(
query_embeddings=[query_emb],
n_results=3
)
return "\n".join(results["documents"][0])
# Function 定義(LLMに渡す)
tools = [
{
"type": "function",
"function": {
"name": "retrieve_documents",
"description": "おみくじ文書から占い結果の参考情報を検索する",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "占い内容を判断するための検索クエリ"
}
},
"required": ["query"]
}
}
}
]
# System プロンプト(占い師調・検索強制)
system_prompt = """
あなたは神社のおみくじを読み上げる占い師です。
口調は丁寧で落ち着いた和風の語り口にしてください。
占い結果は必ず
「大吉・中吉・小吉・吉・凶」
のいずれか一つを選んでください。
回答は以下の構成で出力してください。
・最初に【運勢】として結果を明示する
・次に【内容】として運勢の意味を説明する
・最後に【ひとこと助言】を添える
回答を作成する前に、必ず retrieve_documents 関数を使って
おみくじ文を検索してください。
検索結果に含まれる内容のみを根拠にして回答し、
推測や創作で内容を補ってはいけません。
"""
# ユーザー入力
user_question = "今日の運勢を占って"
# 1回目:LLMに検索判断させる
response = ollama.chat(
model="llama3.1:8b",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_question}
],
tools=tools
)
message = response["message"]
# Function Calling 判定
if "tool_calls" not in message:
raise RuntimeError("LLMが検索を実行しませんでした")
tool_call = message["tool_calls"][0]
args = tool_call["function"]["arguments"]
# 検索実行(アプリ側)
context = retrieve_documents(args["query"])
# 2回目:検索結果を渡して回答生成
final_response = ollama.chat(
model="llama3.1:8b",
messages=[
{"role": "system", "content": system_prompt},
{"role": "system", "content": f"以下は検索結果です:\n{context}"},
{
"role": "user",
"content": (
"上記の検索結果のみを根拠にして、"
"神社のおみくじを読み上げる占い師として、"
"今日の運勢を占ってください。"
)
}
]
)
print(final_response["message"]["content"])
実行例(毎回変わります)
【運勢】小吉
今日は無理をせず、できることを一つずつ片付けると安心感が生まれます。
【内容】これから物事に取り組む際には大きな目標や急進的な動きよりも、小さな実行可能なステップを優先しましょう。小さいご利益でも気持ちよく進めば、成功へとつながるかもしれません。
【ひとこと助言】気をつけるポイントは「無理をせず」です。気力や時間が足りないと感じたら、その場を引き返すことも大切な時です。
まとめ
この記事では、Ollama+ChromaDB構成を前提に、Function Callingを使ってRAG検索を組み込む最小構成を解説しました。
ユーザーの曖昧な質問をLLMが解釈し、検索に適したクエリに変換したうえでRAG検索を実行するため、キーワードを厳密に指定しなくても、意図に沿った情報を取得できるようになります。
次回は、この構成を対話ループとして発展させる実装例を解説します。



