ルールベースとLLMを融合した、安全性と精度を両立する医薬品推奨システムのアーキテクチャと実装について
単純なLLMベースの推奨では、安全性と正確性に課題があるため、ルールベースとLLMを組み合わせたハイブリッド推奨システムを採用しました。この記事では、システムのアーキテクチャと実装の詳細について解説します。
システムは以下の2段階で推奨を行います:
# スコアリングの主要な流れ(簡略化)
def calculate_recommendation_score(candidate, nlu_result, user_info):
# 1. 基本スコア
base_score = 0.0
# 2. 効能特異性スコア
efficacy_score = calculate_efficacy_specificity_score(candidate, nlu_result)
# 3. 副作用リスクスコア
side_effect_score = calculate_side_effect_risk_score(candidate, user_info)
# 4. 相互作用リスクスコア
interaction_score = calculate_interaction_risk_score(candidate, user_info)
# 5. 用法簡便性スコア
convenience_score = calculate_usage_convenience_score(candidate)
# 6. 調整スコア(症状特異性、年齢制限など)
adjustment_score = calculate_adjustment_score(candidate, nlu_result, user_info)
# 最終スコア
final_score = base_score + efficacy_score + side_effect_score + \
interaction_score + convenience_score + adjustment_score
return final_score
効能特異性スコアは、医薬品の効能効果が症状に特化しているほど高スコアになります。
def calculate_efficacy_specificity_score(candidate, nlu_result):
"""
効能特異性スコアを計算
医薬品の効能効果が症状に特化しているほど高スコア
"""
efficacy_text = candidate.get('efficacy', '')
symptoms = nlu_result.get("symptoms", [])
# 症状名のリストを作成
symptom_names = [s.get('name', '') for s in symptoms]
# 効能テキストを正規化
normalized_efficacy = normalize_text(efficacy_text)
# 症状とのマッチング(単語境界を考慮)
match_count = 0
for symptom_name in symptom_names:
normalized_symptom = normalize_text(symptom_name)
if is_word_match(normalized_symptom, normalized_efficacy):
match_count += 1
# 特異性比率を計算
specificity_ratio = match_count / len(symptom_names) if symptom_names else 0.0
return specificity_ratio
症状の様々な表現パターンに対応するため、同義語マッピングを実装しました:
symptom_synonyms = {
"たん": ["たん", "痰", "タン", "たんが出る", "痰が出る", "喀痰", "咳痰",
"のどにからむ", "喉に絡む", "からむ", "絡む", "ゼロゼロ", "ゼーゼー"],
"せき": ["せき", "咳", "セキ", "せきが出る", "咳が出る", "咳嗽", "咳込む"],
# ... その他の症状
}
短単語(2文字以下)の誤検知を防止するため、ブラックリストによる局所判定を実装:
TANN_FALSE_POSITIVE_BLACKLIST = [
"簡単", "かんたん", "カンタン",
"負担", "ふたん", "フタン",
"短期間", "たんきかん", "タンキカン",
# ... その他のブラックリスト
]
def is_word_match(token, text, blacklist=None):
"""
単語境界を考慮したマッチング(ブラックリストチェック統合版)
"""
# ブラックリストチェック(短単語の場合・局所判定)
if blacklist and len(token) <= 2:
# 座標計算による局所判定
# ...
# 単語境界チェック
# ...
単一症状時に複合薬(総合感冒薬など)にペナルティを適用し、特化薬を優先します:
# 単一症状時の推奨ロジック
if len(symptom_names) == 1:
symptom = symptom_names[0]
if symptom == "発熱":
# 解熱鎮痛薬を優先
if "総合風邪薬" in medicine_type:
penalty = -0.5 # 総合風邪薬にはペナルティ
elif symptom == "のどの痛み":
# 外用薬(のど)を最優先
if "外用薬(のど)" in medicine_type:
bonus = +0.15
elif "総合風邪薬" in medicine_type:
penalty = -0.15
カロナールA、ロキソニンS、タイレノールAを第一選択として推奨するため、ボーナススコアを設定:
MAJOR_ANALGESIC_MEDICINES = [
"カロナールA", "タイレノールA", "ロキソニンS"
]
# 主要解熱鎮痛薬のボーナス
if product_name in MAJOR_ANALGESIC_MEDICINES:
if "頭痛" in symptoms or "発熱" in symptoms:
bonus = +0.8 # カロナールA/タイレノールA
elif "筋肉痛" in symptoms:
bonus = +0.6 # ロキソニンS
LLMは以下の場合のみ使用します:
def select_symptoms_via_gpt(user_text, conversation_history):
"""
ChatGPT APIを使用して症状を抽出
ルールベースNLUの信頼度が低い場合のみ実行
"""
if nlu_confidence >= 0.3 and len(symptoms) > 0:
return symptoms # ルールベースの結果を使用
# ChatGPT APIを呼び出し
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[
{"role": "system", "content": "症状抽出プロンプト"},
{"role": "user", "content": user_text}
]
)
return extract_symptoms_from_response(response)
全候補に対して詳細なスコアリングを行うのではなく、まず簡易スコアリングで候補を絞り込み、その後詳細スコアリングを実行:
# 第1段階: 簡易スコアリング(高速)
candidates = get_candidate_medicines(nlu_result, medicine_df)
candidates = filter_by_efficacy_symptom_match(candidates, nlu_result)
# 第2段階: 詳細スコアリング(高精度)
for candidate in candidates:
score = calculate_recommendation_score(candidate, nlu_result, user_info)
candidate['final_score'] = score
同一入力に対するLLMトリアージ結果をキャッシュし、パフォーマンスを向上:
_triage_cache = {}
def get_triage_result(user_text):
cache_key = hash(user_text)
if cache_key in _triage_cache:
return _triage_cache[cache_key]
result = llm_triage(user_text)
_triage_cache[cache_key] = result
return result
原因: 症状名と効能テキストの表記ゆれ(全角・半角、ひらがな・カタカナ)
解決策:
normalize_text()関数でNFKC正規化・小文字化原因: 短単語の部分マッチング
解決策:
原因: 症状特異性ペナルティが不十分
解決策:
ハイブリッド推奨システムを実装する際、ルールベースとLLMのバランスを取ることが最も難しい課題でした。
LLMだけに頼ると、安全性に課題があります。一方、ルールベースだけでは、ユーザーの多様な表現に対応できません。
解決策:
医療情報システムとして、推奨の根拠を説明できることが重要です。ルールベースのアルゴリズムにより、なぜその医薬品が推奨されたのかを明確に説明できます。
アルゴリズムの改善は、継続的なプロセスです。ユーザーフィードバックを基に、スコアリングのパラメータを調整し、推奨精度を向上させています。
ハイブリッド推奨システムにより、安全性と精度を両立した医薬品推奨を実現しました。ルールベースの透明性とLLMの柔軟性を組み合わせることで、より適切な推奨が可能になりました。
「なぜこの結果になったか」を説明できる設計 - この信念を胸に、今後もより多くの相談事例データを蓄積し、アルゴリズムの改善を続けていきます。