ChatGPTやGitHub Copilot、Claude等のLLMを活用したコード生成ツールが日常的に利用されるようになった。しかし、生成されたコードの品質を体系的に評価し、改善していく取り組みはまだ発展途上です。本記事では、筆者がチームで実践してきたLLMコード生成の品質評価手法とその改善アプローチを紹介します。
OpenAIが公開したHumanEvalは、LLMのコード生成能力を測定する最も有名なベンチマークです。164個のPythonプログラミング問題で構成され、関数のdocstringから正しい実装を生成できるかを評価します。
# HumanEval形式の問題例
def has_close_elements(numbers: list[float], threshold: float) -> bool:
"""Check if in given list of numbers, are any two numbers
closer to each other than given threshold.
>>> has_close_elements([1.0, 2.0, 3.0], 0.5)
False
>>> has_close_elements([1.0, 2.8, 3.0, 4.0], 0.3)
True
"""
# LLMがここを生成する
for i, num1 in enumerate(numbers):
for j, num2 in enumerate(numbers):
if i != j and abs(num1 - num2) < threshold:
return True
return False
ただしHumanEvalは比較的単純な関数レベルの問題が多く、実務でのコード生成品質とは乖離がある点に注意が必要だ。
Googleが公開したMBPPは、約1000問のPython問題を含む。HumanEvalよりも多様な問題を扱うが、やはり単一関数レベルの評価に留まる。
より実践的な評価として注目されているのがSWE-benchです。実際のGitHubリポジトリのIssueを解決できるかを測定します。複数ファイルにまたがる修正が必要な場合もあり、実務に近い評価が可能だ。
汎用ベンチマークだけでは自チームの用途に合った評価はできません。筆者のチームでは独自の評価フレームワークを構築し運用しています。
import json
from dataclasses import dataclass
from typing import Callable
@dataclass
class EvalCase:
"""評価ケースの定義"""
task_id: str
prompt: str
test_cases: list[dict]
expected_patterns: list[str] # コードに含まれるべきパターン
forbidden_patterns: list[str] # 含まれてはいけないパターン
max_complexity: int # 許容される循環的複雑度
@dataclass
class EvalResult:
"""評価結果"""
task_id: str
pass_rate: float # テストケース通過率
pattern_score: float # パターンマッチスコア
complexity_ok: bool # 複雑度が基準内か
security_issues: list[str] # セキュリティ問題
overall_score: float # 総合スコア
この評価フレームワークでは、単にテストが通るかだけでなく、コードの品質面も含めて多角的に評価します。
品質評価の結果を踏まえ、プロンプトを改善することで生成品質を大幅に向上できます。以下は筆者が効果を実感した手法だ。
# 改善前のプロンプト
prompt_basic = "ユーザー認証のミドルウェアを実装してください"
# 改善後:コーディング規約とサンプルコードを含むプロンプト
prompt_improved = """
以下の規約に従って、ユーザー認証ミドルウェアを実装してください。
【コーディング規約】
- 関数には型ヒントを必ず付与
- エラーハンドリングはカスタム例外クラスを使用
- ログは構造化ログ形式
【参考実装(rate limitミドルウェア)】
async def rate_limit_middleware(
request: Request,
call_next: Callable
) -> Response:
client_ip: str = request.client.host
if await is_rate_limited(client_ip):
logger.warning("rate_limited", extra={"ip": client_ip})
raise RateLimitError(f"Rate limit exceeded for {client_ip}")
return await call_next(request)
この形式に倣い、認証ミドルウェアを実装してください。
"""
規約と具体例を示すことで、生成コードの品質は格段に向上します。筆者のチームの計測では、パターン準拠率が42%から87%に改善した。
一度の生成で完璧なコードを期待するのではなく、評価結果をフィードバックとして与え、段階的に改善させるアプローチも有効だ。
def iterative_improve(llm_client, prompt: str, eval_cases: list[EvalCase],
max_iterations: int = 3) -> str:
"""反復的にコード品質を改善する"""
generated_code = llm_client.generate(prompt)
for i in range(max_iterations):
result = evaluate(generated_code, eval_cases)
if result.overall_score >= 0.9:
break
feedback = build_feedback(result)
improved_prompt = f"""
以下のコードに問題があります。修正してください。
【現在のコード】
{generated_code}
【問題点】
{feedback}
修正後のコード全体を出力してください。
"""
generated_code = llm_client.generate(improved_prompt)
return generated_code
LLMコード生成の品質評価を実務に組み込む際に重要なのは、CI/CDパイプラインとの統合です。PRに含まれるLLM生成コードに対して自動的に品質チェックを走らせ、基準を満たさない場合はアラートを出す仕組みが効果的です。
また、チーム固有のコーディングパターンを評価基準に含めることで、プロジェクト全体のコード一貫性を維持できます。LLMは便利なツールだが、その出力を無批判に受け入れるのではなく、体系的な品質ゲートを設けることが、長期的なコードベースの健全性を保つ鍵となります。
LLMによるコード生成は急速に進化していますが、品質評価と改善の仕組みを整備することで、そのポテンシャルを最大限に引き出せる。ベンチマークの理解、独自評価フレームワークの構築、プロンプトの最適化を組み合わせ、チームの生産性を持続的に向上させていきたいです。