第7章:LLM開発

更新日:2025年12月9日

本章では、大規模言語モデル(LLM)を活用したアプリケーション開発を解説する。OpenAI/Anthropic APIの効率的な利用法、RAG(Retrieval-Augmented Generation)によるドメイン知識の活用、LangChain等を用いたエージェント開発、LoRA/QLoRAによるファインチューニング、vLLM/Ollamaによるローカル推論について学ぶ。LLMは急速に進化しており、適切な技術選択が重要である。

1. LLM API活用

1.1 OpenAI/Anthropic

主要なLLMプロバイダのAPI利用方法を解説する。Table 1に主要プロバイダの比較を示す。

Table 1. LLMプロバイダ比較(2025年時点)

プロバイダ 主要モデル 特徴
OpenAI GPT-4o, o1, o3 汎用性高、エコシステム充実
Anthropic Claude 3.5/4 長文対応、安全性重視
Google Gemini 2.0 マルチモーダル、長コンテキスト
Mistral Mistral Large 欧州拠点、オープンモデルも提供

1.1.1 OpenAI API

from openai import OpenAI
import os

client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])

# 基本的なChat Completion
response = client.chat.completions.create(
    model='gpt-4o',
    messages=[
        {'role': 'system', 'content': 'あなたは親切なアシスタントです。'},
        {'role': 'user', 'content': 'Pythonの特徴を3つ教えてください。'}
    ],
    temperature=0.7,
    max_tokens=1000,
)

print(response.choices[0].message.content)

# Structured Output(JSON Mode)
response = client.chat.completions.create(
    model='gpt-4o',
    messages=[
        {'role': 'user', 'content': '東京の観光スポットを3つJSON形式で返してください。'}
    ],
    response_format={'type': 'json_object'},
)

import json
data = json.loads(response.choices[0].message.content)

1.1.2 Anthropic API

from anthropic import Anthropic

client = Anthropic(api_key=os.environ['ANTHROPIC_API_KEY'])

response = client.messages.create(
    model='claude-sonnet-4-20250514',
    max_tokens=1024,
    system='あなたは経験豊富なPythonエンジニアです。',
    messages=[
        {'role': 'user', 'content': 'async/awaitのベストプラクティスを教えてください。'}
    ],
)

print(response.content[0].text)

1.2 ストリーミング

長い応答をリアルタイムで表示するためのストリーミング実装。

# OpenAI ストリーミング
from openai import OpenAI

client = OpenAI()

stream = client.chat.completions.create(
    model='gpt-4o',
    messages=[{'role': 'user', 'content': '長い物語を書いてください。'}],
    stream=True,
)

for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end='', flush=True)

# Anthropic ストリーミング
from anthropic import Anthropic

client = Anthropic()

with client.messages.stream(
    model='claude-sonnet-4-20250514',
    max_tokens=1024,
    messages=[{'role': 'user', 'content': '長い物語を書いてください。'}],
) as stream:
    for text in stream.text_stream:
        print(text, end='', flush=True)

# FastAPIでのストリーミングエンドポイント
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()

async def generate_stream(prompt: str):
    stream = client.chat.completions.create(
        model='gpt-4o',
        messages=[{'role': 'user', 'content': prompt}],
        stream=True,
    )
    for chunk in stream:
        if content := chunk.choices[0].delta.content:
            yield f"data: {content}\n\n"

@app.get("/stream")
async def stream_response(prompt: str):
    return StreamingResponse(
        generate_stream(prompt),
        media_type="text/event-stream"
    )

2. RAG実装

RAG(Retrieval-Augmented Generation)は、外部知識をLLMに注入する手法である[1]。ドメイン固有の質問応答システム構築に有効。

Fig. 1にRAGのアーキテクチャを示す。

2.1 ベクトルストアの構築

from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, TextLoader

# ドキュメントの読み込み
loader = DirectoryLoader(
    './docs/',
    glob='**/*.md',
    loader_cls=TextLoader,
)
documents = loader.load()

# チャンク分割
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=['\n\n', '\n', '。', '、', ' '],
)
chunks = text_splitter.split_documents(documents)

# Embeddingとベクトルストア作成
embeddings = OpenAIEmbeddings(model='text-embedding-3-small')
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory='./chroma_db',
)

# 検索テスト
results = vectorstore.similarity_search('Pythonの型ヒントとは', k=3)
for doc in results:
    print(doc.page_content[:200])

2.2 RAGチェーンの構築

from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

# プロンプトテンプレート
template = """以下のコンテキストを参考にして質問に答えてください。
コンテキストに情報がない場合は、「情報が見つかりません」と回答してください。

コンテキスト:
{context}

質問: {question}

回答:"""

prompt = PromptTemplate(
    template=template,
    input_variables=['context', 'question'],
)

# RAGチェーン
llm = ChatOpenAI(model='gpt-4o', temperature=0)
retriever = vectorstore.as_retriever(search_kwargs={'k': 5})

qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type='stuff',
    retriever=retriever,
    chain_type_kwargs={'prompt': prompt},
    return_source_documents=True,
)

# 質問応答
result = qa_chain.invoke({'query': 'Pythonの型ヒントの利点は?'})
print(result['result'])
print('---ソース---')
for doc in result['source_documents']:
    print(f"- {doc.metadata.get('source', 'unknown')}")

Table 2. ベクトルストアの比較

ベクトルストア 特徴 用途
Chroma 軽量、ローカル、簡単 プロトタイプ、小規模
Pinecone マネージド、スケーラブル 本番環境、大規模
Weaviate ハイブリッド検索、GraphQL 複雑なクエリ
Qdrant 高性能、フィルタリング 高速検索必須
pgvector PostgreSQL拡張 既存DB活用

3. エージェント開発

LLMエージェントは、ツールを使用して自律的にタスクを遂行するシステムである[2]。

3.1 LangChain Agent

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.tools import tool
from langchain import hub

# カスタムツールの定義
@tool
def search_database(query: str) -> str:
    """データベースを検索して情報を取得します。"""
    # 実際のDB検索ロジック
    return f"検索結果: {query}に関する情報..."

@tool
def calculate(expression: str) -> str:
    """数式を計算します。"""
    try:
        result = eval(expression)  # 本番では安全な評価器を使用
        return str(result)
    except Exception as e:
        return f"計算エラー: {e}"

@tool
def get_current_weather(location: str) -> str:
    """指定した場所の現在の天気を取得します。"""
    # 実際のAPI呼び出し
    return f"{location}の天気: 晴れ、気温20度"

# エージェントの作成
llm = ChatOpenAI(model='gpt-4o', temperature=0)
tools = [search_database, calculate, get_current_weather]

# プロンプトテンプレート(hub から取得)
prompt = hub.pull('hwchase17/openai-tools-agent')

agent = create_openai_tools_agent(llm, tools, prompt)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    max_iterations=5,
)

# 実行
result = agent_executor.invoke({
    'input': '東京の天気を調べて、気温を華氏に変換してください。'
})
print(result['output'])

3.2 Function Calling(Tool Use):OpenAI/Anthropic のネイティブ機能。

from openai import OpenAI
import json

client = OpenAI()

# ツール定義
tools = [
    {
        'type': 'function',
        'function': {
            'name': 'get_weather',
            'description': '指定した場所の天気を取得',
            'parameters': {
                'type': 'object',
                'properties': {
                    'location': {
                        'type': 'string',
                        'description': '都市名(例:東京)',
                    },
                    'unit': {
                        'type': 'string',
                        'enum': ['celsius', 'fahrenheit'],
                    },
                },
                'required': ['location'],
            },
        },
    },
]

# ツール呼び出しを含むリクエスト
response = client.chat.completions.create(
    model='gpt-4o',
    messages=[{'role': 'user', 'content': '東京の天気は?'}],
    tools=tools,
    tool_choice='auto',
)

# ツール呼び出しの処理
message = response.choices[0].message
if message.tool_calls:
    for tool_call in message.tool_calls:
        function_name = tool_call.function.name
        arguments = json.loads(tool_call.function.arguments)
        print(f"ツール呼び出し: {function_name}({arguments})")

        # ツール実行(実際の実装)
        if function_name == 'get_weather':
            tool_result = '晴れ、20度'

        # 結果を含めて再度リクエスト
        follow_up = client.chat.completions.create(
            model='gpt-4o',
            messages=[
                {'role': 'user', 'content': '東京の天気は?'},
                message,
                {
                    'role': 'tool',
                    'tool_call_id': tool_call.id,
                    'content': tool_result,
                },
            ],
        )
        print(follow_up.choices[0].message.content)

Fig. 2にエージェントの動作フローを示す。

4. ファインチューニング

ドメイン特化や特定タスクへの適応にはファインチューニングが有効である[3]。

4.1 LoRA(Low-Rank Adaptation):パラメータ効率的なファインチューニング手法。

from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments
from peft import LoraConfig, get_peft_model, TaskType
from datasets import load_dataset
from trl import SFTTrainer

# ベースモデルの読み込み
model_name = 'meta-llama/Llama-3.1-8B'
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.bfloat16,
    device_map='auto',
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token

# LoRA設定
lora_config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    r=16,                    # LoRAのランク
    lora_alpha=32,           # スケーリング係数
    lora_dropout=0.1,
    target_modules=['q_proj', 'v_proj', 'k_proj', 'o_proj'],
)

# PEFTモデル作成
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# trainable params: 6,553,600 || all params: 8,030,261,248 || trainable%: 0.082

# データセット準備
dataset = load_dataset('json', data_files='training_data.jsonl')

# トレーニング設定
training_args = TrainingArguments(
    output_dir='./lora_output',
    num_train_epochs=3,
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    learning_rate=2e-4,
    bf16=True,
    logging_steps=10,
    save_strategy='epoch',
)

# SFTTrainerによる学習
trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset['train'],
    tokenizer=tokenizer,
    dataset_text_field='text',
    max_seq_length=2048,
)

trainer.train()

4.2 QLoRA:量子化と組み合わせたメモリ効率の良い手法。

from transformers import BitsAndBytesConfig
import torch

# 4bit量子化設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type='nf4',
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True,
)

# 量子化モデルの読み込み
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map='auto',
)

# LoRA適用(以降は同様)
model = get_peft_model(model, lora_config)

Table 3. ファインチューニング手法の比較

手法 VRAM要件(8B) 学習速度 性能
Full Fine-tuning 〜80GB 遅い 最高
LoRA 〜24GB 速い 高い
QLoRA(4bit) 〜12GB 中程度 やや高い

5. ローカルLLM運用

プライバシー要件やコスト削減のため、ローカルでLLMを運用するケースが増えている。

5.1 vLLM:高速推論エンジン[4]。PagedAttentionによりスループットを最大化。

# vLLMのインストール
# pip install vllm

from vllm import LLM, SamplingParams

# モデルの読み込み
llm = LLM(
    model='meta-llama/Llama-3.1-8B-Instruct',
    tensor_parallel_size=1,  # GPU数
    gpu_memory_utilization=0.9,
)

# サンプリングパラメータ
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
)

# バッチ推論
prompts = [
    'Pythonの利点は何ですか?',
    '機械学習とは何ですか?',
    'RESTful APIの設計原則を説明してください。',
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    print(f"Prompt: {output.prompt}")
    print(f"Output: {output.outputs[0].text}\n")

# OpenAI互換サーバーとして起動
# python -m vllm.entrypoints.openai.api_server \
#     --model meta-llama/Llama-3.1-8B-Instruct \
#     --port 8000

5.2 Ollama:シンプルなローカルLLM実行環境[5]。

# Ollamaのインストールと実行
# curl -fsSL https://ollama.com/install.sh | sh
# ollama pull llama3.1:8b
# ollama serve

import requests

# REST API経由で呼び出し
response = requests.post(
    'http://localhost:11434/api/generate',
    json={
        'model': 'llama3.1:8b',
        'prompt': 'Pythonの特徴を教えてください。',
        'stream': False,
    }
)
print(response.json()['response'])

# OpenAI互換クライアントで利用
from openai import OpenAI

client = OpenAI(
    base_url='http://localhost:11434/v1',
    api_key='ollama',  # ダミー値
)

response = client.chat.completions.create(
    model='llama3.1:8b',
    messages=[{'role': 'user', 'content': 'Hello!'}],
)
print(response.choices[0].message.content)

Table 4. ローカル推論エンジンの比較

エンジン 特徴 用途
vLLM 高スループット、本番向け 高負荷サービス
Ollama シンプル、セットアップ容易 開発、個人利用
llama.cpp CPU対応、量子化 エッジ、低リソース
TGI HuggingFace製、本番向け HFモデル運用

References

[1] P. Lewis et al., "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks," NeurIPS 2020.

[2] LangChain, "LangChain Documentation," python.langchain.com, 2024.

[3] E. J. Hu et al., "LoRA: Low-Rank Adaptation of Large Language Models," ICLR 2022.

[4] W. Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention," SOSP 2023.

[5] Ollama, "Ollama Documentation," ollama.com, 2024.

免責事項
本コンテンツは2025年12月時点の情報に基づいて作成されている。LLM分野は急速に進化しており、APIやツールの仕様は頻繁に変更される。最新の情報は各プロバイダの公式ドキュメントを参照されたい。

← 前章:機械学習フレームワーク次章:パフォーマンス最適化 →