Spaces:
Running
Running
高橋慧
commited on
Commit
·
5e2744f
1
Parent(s):
7a3c9e9
stage3
Browse files- OpenAITools/__init__.py +29 -0
- README.md +83 -17
- app.py +382 -193
- requirements.txt +28 -3
OpenAITools/__init__.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# OpenAITools package
|
2 |
+
# 臨床試験適格性評価システム用のAIツール群
|
3 |
+
|
4 |
+
__version__ = "1.0.0"
|
5 |
+
__author__ = "ClinicalTrialV2 Team"
|
6 |
+
|
7 |
+
# 主要クラスのインポート
|
8 |
+
try:
|
9 |
+
from .FetchTools import fetch_clinical_trials, fetch_clinical_trials_jp
|
10 |
+
from .CrinicalTrialTools import (
|
11 |
+
SimpleClinicalTrialAgent,
|
12 |
+
GraderAgent,
|
13 |
+
LLMTranslator,
|
14 |
+
generate_ex_question_English,
|
15 |
+
generate_ex_question
|
16 |
+
)
|
17 |
+
print("✅ OpenAITools モジュールが正常にロードされました")
|
18 |
+
except ImportError as e:
|
19 |
+
print(f"⚠️ OpenAITools モジュールの一部でインポートエラー: {e}")
|
20 |
+
|
21 |
+
__all__ = [
|
22 |
+
'fetch_clinical_trials',
|
23 |
+
'fetch_clinical_trials_jp',
|
24 |
+
'SimpleClinicalTrialAgent',
|
25 |
+
'GraderAgent',
|
26 |
+
'LLMTranslator',
|
27 |
+
'generate_ex_question_English',
|
28 |
+
'generate_ex_question'
|
29 |
+
]
|
README.md
CHANGED
@@ -10,31 +10,97 @@ pinned: false
|
|
10 |
license: mit
|
11 |
---
|
12 |
|
13 |
-
#
|
14 |
|
15 |
このアプリケーションは患者情報に基づいて適切な臨床試験を見つけ、AIエージェントが適格性を自動評価するシステムです。
|
16 |
|
17 |
-
##
|
18 |
|
19 |
-
|
20 |
-
-
|
21 |
-
-
|
22 |
-
-
|
23 |
|
24 |
-
|
|
|
|
|
|
|
25 |
|
26 |
-
|
|
|
|
|
|
|
27 |
|
28 |
-
|
29 |
-
- `OPENAI_API_KEY`: OpenAIのAPIキー(オプション)
|
30 |
|
31 |
-
|
|
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
|
38 |
-
|
|
|
|
|
39 |
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
license: mit
|
11 |
---
|
12 |
|
13 |
+
# 🏥 臨床試験適格性評価システム(完全版)
|
14 |
|
15 |
このアプリケーションは患者情報に基づいて適切な臨床試験を見つけ、AIエージェントが適格性を自動評価するシステムです。
|
16 |
|
17 |
+
## ✨ 主要機能
|
18 |
|
19 |
+
### 🔍 リアルタイム臨床試験検索
|
20 |
+
- **ClinicalTrials.gov API**: 日本で実施中の最新臨床試験を自動検索
|
21 |
+
- **多言語対応**: 日本語入力 → 英語検索 → 日本語結果
|
22 |
+
- **条件絞り込み**: 腫瘍タイプ、募集状況、実施地域での自動フィルタリング
|
23 |
|
24 |
+
### 🤖 AI適格性評価システム
|
25 |
+
- **LangChain + Groq**: 高速で正確な自然言語処理
|
26 |
+
- **3段階評価**: Yes(適格)/ No(不適格)/ Unclear(要検討)
|
27 |
+
- **詳細判断理由**: AI が各試験への適格性を詳細に説明
|
28 |
|
29 |
+
### 💾 データ管理機能
|
30 |
+
- **インタラクティブフィルタリング**: 適格性レベルでの結果絞り込み
|
31 |
+
- **CSV エクスポート**: フィルタ済み/全データのダウンロード
|
32 |
+
- **リアルタイム更新**: 最新の臨床試験情報を常時取得
|
33 |
|
34 |
+
## 🚀 使用方法
|
|
|
35 |
|
36 |
+
### 1. 環境設定
|
37 |
+
**Settings → Variables and secrets** で以下のAPIキーを設定:
|
38 |
|
39 |
+
```
|
40 |
+
GROQ_API_KEY: あなたのGroq APIキー(必須)
|
41 |
+
OPENAI_API_KEY: あなたのOpenAI APIキー(オプション)
|
42 |
+
```
|
43 |
|
44 |
+
### 2. 患者情報入力
|
45 |
+
- **基本情報**: 年齢、性別、腫瘍タイプ
|
46 |
+
- **詳細情報**: 遺伝子変異、測定可能腫瘍、生検可能性
|
47 |
|
48 |
+
### 3. 検索・評価実行
|
49 |
+
- 「🔍 Generate Clinical Trials Data (Real API)」をクリック
|
50 |
+
- AIが自動的に適格性を評価
|
51 |
+
|
52 |
+
### 4. 結果の活用
|
53 |
+
- **フィルタリング**: ✅適格 / ❌不適格 / ❓要検討 別に表示
|
54 |
+
- **詳細確認**: 各試験の判断理由を確認
|
55 |
+
- **データ保存**: 必要に応じてCSVでダウンロード
|
56 |
+
|
57 |
+
## 🛠️ 技術スタック
|
58 |
+
|
59 |
+
### フロントエンド
|
60 |
+
- **Gradio 4.36.1**: 直感的なWebインターフェース
|
61 |
+
- **Pandas**: データ処理・表示
|
62 |
+
|
63 |
+
### AI/機械学習
|
64 |
+
- **LangChain**: AIエージェント構築フレームワーク
|
65 |
+
- **Groq API**: 高速LLM推論(Llama3-70B)
|
66 |
+
- **OpenAI API**: 補完的なLLM機能(オプション)
|
67 |
+
|
68 |
+
### データソース
|
69 |
+
- **ClinicalTrials.gov API**: 国際的な臨床試験データベース
|
70 |
+
- **リアルタイム検索**: 最新の募集状況を反映
|
71 |
+
|
72 |
+
## 📊 システム動作モード
|
73 |
+
|
74 |
+
### ✅ 完全版モード(API連携有効)
|
75 |
+
- 環境変数が正しく設定されている場合
|
76 |
+
- 実際のClinicalTrials.govからリアルタイムデータ取得
|
77 |
+
- AI による適格性自動評価
|
78 |
+
|
79 |
+
### ⚠️ 制限モード(API制限あり)
|
80 |
+
- 一部の環境変数が未設定の場合
|
81 |
+
- 基本的な検索機能のみ利用可能
|
82 |
+
|
83 |
+
### 🔧 軽量版モード(サンプルデータ)
|
84 |
+
- 依存関係エラーが発生した場合
|
85 |
+
- サンプルデータでの機能デモンストレーション
|
86 |
+
|
87 |
+
## 🔐 プライバシー・セキュリティ
|
88 |
+
|
89 |
+
- **患者情報**: ローカル処理のみ、サーバーに保存されません
|
90 |
+
- **API通信**: HTTPS暗号化通信
|
91 |
+
- **データ匿名化**: 個人識別情報は使用されません
|
92 |
+
|
93 |
+
## 📝 ライセンス
|
94 |
+
|
95 |
+
MIT License - 学術・商用利用可能
|
96 |
+
|
97 |
+
## 🙋♂️ サポート
|
98 |
+
|
99 |
+
問題が発生した場合:
|
100 |
+
1. **ログ確認**: Spaces の Logs タブでエラー詳細を確認
|
101 |
+
2. **環境変数**: API キーが正しく設定されているか確認
|
102 |
+
3. **リフレッシュ**: ページを再読み込みしてリトライ
|
103 |
+
|
104 |
+
---
|
105 |
+
|
106 |
+
*このシステムは研究・教育目的で開発されています。実際の臨床決定には専門医にご相談ください。*
|
app.py
CHANGED
@@ -1,6 +1,22 @@
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
2 |
import os
|
3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
# 環境変数チェック
|
5 |
def check_environment():
|
6 |
"""環境変数をチェックし、不足している場合は警告"""
|
@@ -12,220 +28,393 @@ def check_environment():
|
|
12 |
if not os.getenv("OPENAI_API_KEY"):
|
13 |
missing_vars.append("OPENAI_API_KEY")
|
14 |
|
15 |
-
|
|
|
|
|
|
|
|
|
16 |
|
17 |
-
#
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
try:
|
21 |
# 入力検証
|
22 |
if not all([age, sex, tumor_type]):
|
23 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
-
#
|
26 |
-
|
27 |
-
|
28 |
-
'NCTID': 'NCT12345678',
|
29 |
-
'AgentGrade': 'yes',
|
30 |
-
'Title': f'Clinical Trial for {tumor_type} in {sex} patients',
|
31 |
-
'AgentJudgment': f'{age}歳{sex}の{tumor_type}患者は参加可能です',
|
32 |
-
'Japanese_Locations': 'Tokyo',
|
33 |
-
'Cancer': tumor_type,
|
34 |
-
'Gene_Mutation': gene_mutation,
|
35 |
-
'Measurable': measurable,
|
36 |
-
'Biopsiable': biopsiable
|
37 |
-
},
|
38 |
-
{
|
39 |
-
'NCTID': 'NCT87654321',
|
40 |
-
'AgentGrade': 'no',
|
41 |
-
'Title': f'Alternative treatment for {tumor_type}',
|
42 |
-
'AgentJudgment': f'{age}歳{sex}の{tumor_type}患者は参加できません',
|
43 |
-
'Japanese_Locations': 'Osaka',
|
44 |
-
'Cancer': tumor_type,
|
45 |
-
'Gene_Mutation': gene_mutation,
|
46 |
-
'Measurable': measurable,
|
47 |
-
'Biopsiable': biopsiable
|
48 |
-
},
|
49 |
-
{
|
50 |
-
'NCTID': 'NCT11111111',
|
51 |
-
'AgentGrade': 'unclear',
|
52 |
-
'Title': f'Experimental therapy for {tumor_type} with {gene_mutation}',
|
53 |
-
'AgentJudgment': f'{age}歳{sex}の{tumor_type}患者の参加は不明確です',
|
54 |
-
'Japanese_Locations': 'Kyoto',
|
55 |
-
'Cancer': tumor_type,
|
56 |
-
'Gene_Mutation': gene_mutation,
|
57 |
-
'Measurable': measurable,
|
58 |
-
'Biopsiable': biopsiable
|
59 |
-
}
|
60 |
-
]
|
61 |
|
62 |
-
|
|
|
63 |
|
64 |
except Exception as e:
|
65 |
-
print(f"
|
66 |
-
|
|
|
67 |
|
68 |
-
#
|
69 |
-
def
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
try:
|
75 |
-
if
|
76 |
-
return
|
77 |
-
|
|
|
|
|
78 |
except Exception as e:
|
79 |
-
print(f"
|
80 |
-
return
|
81 |
-
|
82 |
-
# データを表形式に変換
|
83 |
-
def data_to_table(data):
|
84 |
-
"""データを表形式に変換"""
|
85 |
-
if not data:
|
86 |
-
return []
|
87 |
-
|
88 |
-
# ヘッダー行
|
89 |
-
headers = ['NCTID', 'Grade', 'Title', 'Judgment', 'Location', 'Cancer']
|
90 |
-
|
91 |
-
# データ行
|
92 |
-
rows = []
|
93 |
-
for item in data:
|
94 |
-
row = [
|
95 |
-
item.get('NCTID', ''),
|
96 |
-
item.get('AgentGrade', ''),
|
97 |
-
item.get('Title', ''),
|
98 |
-
item.get('AgentJudgment', ''),
|
99 |
-
item.get('Japanese_Locations', ''),
|
100 |
-
item.get('Cancer', '')
|
101 |
-
]
|
102 |
-
rows.append(row)
|
103 |
-
|
104 |
-
return [headers] + rows
|
105 |
|
106 |
# Gradioインターフェースの作成
|
107 |
-
|
108 |
-
|
109 |
|
110 |
-
|
111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
-
|
114 |
-
|
115 |
-
gr.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
else:
|
117 |
-
gr.
|
118 |
-
|
119 |
-
|
120 |
-
gr.
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
biopsiable_input = gr.Dropdown(choices=["有り", "無し", "不明"], label="Biopsiable Tumor", value="有り")
|
133 |
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
# 結果をHTMLテーブルに変換
|
149 |
-
def create_html_table(data):
|
150 |
-
if not data:
|
151 |
-
return "<p>データがありません</p>"
|
152 |
-
|
153 |
-
html = "<table style='width:100%; border-collapse: collapse;'>"
|
154 |
-
html += "<tr style='background-color: #f0f0f0;'>"
|
155 |
-
html += "<th style='border: 1px solid #ddd; padding: 8px;'>NCTID</th>"
|
156 |
-
html += "<th style='border: 1px solid #ddd; padding: 8px;'>Grade</th>"
|
157 |
-
html += "<th style='border: 1px solid #ddd; padding: 8px;'>Title</th>"
|
158 |
-
html += "<th style='border: 1px solid #ddd; padding: 8px;'>Judgment</th>"
|
159 |
-
html += "<th style='border: 1px solid #ddd; padding: 8px;'>Location</th>"
|
160 |
-
html += "</tr>"
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
'unclear': '#fff3cd'
|
167 |
-
}.get(item.get('AgentGrade', ''), '#ffffff')
|
168 |
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
return
|
179 |
-
|
180 |
-
# イベントハンドリング
|
181 |
-
def update_data(age, sex, tumor_type, gene_mutation, measurable, biopsiable):
|
182 |
-
data = generate_sample_data(age, sex, tumor_type, gene_mutation, measurable, biopsiable)
|
183 |
-
html_table = create_html_table(data)
|
184 |
-
return html_table, data
|
185 |
-
|
186 |
-
def filter_and_show(data, grade):
|
187 |
-
filtered_data = filter_data(data, grade)
|
188 |
-
html_table = create_html_table(filtered_data)
|
189 |
-
return html_table
|
190 |
-
|
191 |
-
# ボタンイベント
|
192 |
-
generate_button.click(
|
193 |
-
fn=update_data,
|
194 |
-
inputs=[age_input, sex_input, tumor_type_input, gene_mutation_input, measurable_input, biopsiable_input],
|
195 |
-
outputs=[results_html, data_state]
|
196 |
-
)
|
197 |
-
|
198 |
-
all_button.click(
|
199 |
-
fn=lambda data: filter_and_show(data, "all"),
|
200 |
-
inputs=[data_state],
|
201 |
-
outputs=[results_html]
|
202 |
-
)
|
203 |
-
|
204 |
-
yes_button.click(
|
205 |
-
fn=lambda data: filter_and_show(data, "yes"),
|
206 |
-
inputs=[data_state],
|
207 |
-
outputs=[results_html]
|
208 |
-
)
|
209 |
-
|
210 |
-
no_button.click(
|
211 |
-
fn=lambda data: filter_and_show(data, "no"),
|
212 |
-
inputs=[data_state],
|
213 |
-
outputs=[results_html]
|
214 |
-
)
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
|
|
|
|
|
|
|
|
221 |
|
222 |
-
|
223 |
-
|
224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
225 |
|
226 |
-
|
|
|
|
|
227 |
|
228 |
-
# アプリケーション起動
|
229 |
if __name__ == "__main__":
|
230 |
-
demo
|
231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
import time
|
4 |
+
import traceback
|
5 |
import os
|
6 |
|
7 |
+
# 完全版のimportエラー対策
|
8 |
+
try:
|
9 |
+
from OpenAITools.FetchTools import fetch_clinical_trials
|
10 |
+
from langchain_openai import ChatOpenAI
|
11 |
+
from langchain_groq import ChatGroq
|
12 |
+
from OpenAITools.CrinicalTrialTools import SimpleClinicalTrialAgent, GraderAgent, LLMTranslator, generate_ex_question_English
|
13 |
+
FULL_VERSION = True
|
14 |
+
print("✅ 完全版モジュールが正常にロードされました")
|
15 |
+
except ImportError as e:
|
16 |
+
print(f"⚠️ 完全版モジュールのインポートに失敗: {e}")
|
17 |
+
print("軽量版モードで動作します")
|
18 |
+
FULL_VERSION = False
|
19 |
+
|
20 |
# 環境変数チェック
|
21 |
def check_environment():
|
22 |
"""環境変数をチェックし、不足している場合は警告"""
|
|
|
28 |
if not os.getenv("OPENAI_API_KEY"):
|
29 |
missing_vars.append("OPENAI_API_KEY")
|
30 |
|
31 |
+
if missing_vars:
|
32 |
+
print(f"⚠️ 環境変数が設定されていません: {', '.join(missing_vars)}")
|
33 |
+
print("一部の機能が制限される可能性があります。")
|
34 |
+
|
35 |
+
return len(missing_vars) == 0
|
36 |
|
37 |
+
# 環境変数チェック実行
|
38 |
+
env_ok = check_environment()
|
39 |
+
|
40 |
+
# モデルとエージェントの安全な初期化
|
41 |
+
def safe_init_agents():
|
42 |
+
"""エージェントを安全に初期化"""
|
43 |
+
if not FULL_VERSION:
|
44 |
+
return None, None, None
|
45 |
+
|
46 |
+
try:
|
47 |
+
groq = ChatGroq(model_name="llama3-70b-8192", temperature=0)
|
48 |
+
translator = LLMTranslator(groq)
|
49 |
+
criteria_agent = SimpleClinicalTrialAgent(groq)
|
50 |
+
grader_agent = GraderAgent(groq)
|
51 |
+
print("✅ AIエージェントが正常に初期化されました")
|
52 |
+
return translator, criteria_agent, grader_agent
|
53 |
+
except Exception as e:
|
54 |
+
print(f"❌ エージェント初期化エラー: {e}")
|
55 |
+
return None, None, None
|
56 |
+
|
57 |
+
# エージェント初期化
|
58 |
+
translator, CriteriaCheckAgent, grader_agent = safe_init_agents()
|
59 |
+
|
60 |
+
# エラーハンドリング付きでエージェント評価を実行する関数
|
61 |
+
def evaluate_with_retry(agent, criteria, question, max_retries=3):
|
62 |
+
"""エラーハンドリング付きでエージェント評価を実行"""
|
63 |
+
if agent is None:
|
64 |
+
return "評価エラー: エージェントが初期化されていません。API keyを確認してください。"
|
65 |
+
|
66 |
+
for attempt in range(max_retries):
|
67 |
+
try:
|
68 |
+
return agent.evaluate_eligibility(criteria, question)
|
69 |
+
except Exception as e:
|
70 |
+
if "missing variables" in str(e):
|
71 |
+
print(f"プロンプトテンプレートエラー (試行 {attempt + 1}/{max_retries}): {e}")
|
72 |
+
return "評価エラー: プロンプトテンプレートの設定に問題があります"
|
73 |
+
elif "no healthy upstream" in str(e) or "InternalServerError" in str(e):
|
74 |
+
print(f"Groqサーバーエラー (試行 {attempt + 1}/{max_retries}): {e}")
|
75 |
+
if attempt < max_retries - 1:
|
76 |
+
time.sleep(2)
|
77 |
+
continue
|
78 |
+
else:
|
79 |
+
return "評価エラー: サーバーに接続できませんでした"
|
80 |
+
elif "API key" in str(e) or "authentication" in str(e).lower():
|
81 |
+
return "評価エラー: API keyが無効または設定されていません"
|
82 |
+
else:
|
83 |
+
print(f"予期しないエラー (試行 {attempt + 1}/{max_retries}): {e}")
|
84 |
+
if attempt < max_retries - 1:
|
85 |
+
time.sleep(1)
|
86 |
+
continue
|
87 |
+
else:
|
88 |
+
return f"評価エラー: {str(e)}"
|
89 |
+
return "評価エラー: 最大リトライ回数に達しました"
|
90 |
+
|
91 |
+
def evaluate_grade_with_retry(agent, judgment, max_retries=3):
|
92 |
+
"""エラーハンドリング付きでグレード評価を実行"""
|
93 |
+
if agent is None:
|
94 |
+
return "unclear"
|
95 |
+
|
96 |
+
for attempt in range(max_retries):
|
97 |
+
try:
|
98 |
+
return agent.evaluate_eligibility(judgment)
|
99 |
+
except Exception as e:
|
100 |
+
if "no healthy upstream" in str(e) or "InternalServerError" in str(e):
|
101 |
+
print(f"Groqサーバーエラー (グレード評価 - 試行 {attempt + 1}/{max_retries}): {e}")
|
102 |
+
if attempt < max_retries - 1:
|
103 |
+
time.sleep(2)
|
104 |
+
continue
|
105 |
+
else:
|
106 |
+
return "unclear"
|
107 |
+
elif "API key" in str(e) or "authentication" in str(e).lower():
|
108 |
+
return "unclear"
|
109 |
+
else:
|
110 |
+
print(f"予期しないエラー (グレード評価 - 試行 {attempt + 1}/{max_retries}): {e}")
|
111 |
+
if attempt < max_retries - 1:
|
112 |
+
time.sleep(1)
|
113 |
+
continue
|
114 |
+
else:
|
115 |
+
return "unclear"
|
116 |
+
return "unclear"
|
117 |
+
|
118 |
+
# 軽量版データ生成関数
|
119 |
+
def generate_sample_dataframe(age, sex, tumor_type, GeneMutation, Meseable, Biopsiable):
|
120 |
+
"""サンプルデータを生成(軽量版)"""
|
121 |
+
try:
|
122 |
+
if not all([age, sex, tumor_type]):
|
123 |
+
return pd.DataFrame()
|
124 |
+
|
125 |
+
sample_data = {
|
126 |
+
'NCTID': ['NCT12345678', 'NCT87654321', 'NCT11111111'],
|
127 |
+
'AgentGrade': ['yes', 'no', 'unclear'],
|
128 |
+
'Title': [
|
129 |
+
f'Clinical Trial for {tumor_type} in {sex} patients',
|
130 |
+
f'Alternative treatment for {tumor_type}',
|
131 |
+
f'Experimental therapy for {tumor_type} with {GeneMutation}'
|
132 |
+
],
|
133 |
+
'AgentJudgment': [
|
134 |
+
f'{age}歳{sex}の{tumor_type}患者は参加可能です。詳細な検査結果により最終判断が必要です。',
|
135 |
+
f'{age}歳{sex}の{tumor_type}患者は年齢制限により参加できません。',
|
136 |
+
f'{age}歳{sex}の{tumor_type}患者の参加は追加情報が必要で不明確です。'
|
137 |
+
],
|
138 |
+
'Japanes Locations': ['Tokyo, Osaka', 'Kyoto, Fukuoka', 'Nagoya, Sendai'],
|
139 |
+
'Primary Completion Date': ['2025-12-31', '2026-06-30', '2025-09-15'],
|
140 |
+
'Cancer': [tumor_type, tumor_type, tumor_type],
|
141 |
+
'Summary': [
|
142 |
+
f'Phase II study evaluating new treatment for {tumor_type}',
|
143 |
+
f'Comparative study of standard vs experimental therapy for {tumor_type}',
|
144 |
+
f'Early phase trial testing combination therapy for {tumor_type}'
|
145 |
+
],
|
146 |
+
'Eligibility Criteria': [
|
147 |
+
f'Age 18-75, confirmed {tumor_type}, adequate organ function',
|
148 |
+
f'Age 20-65, {tumor_type} with specific mutations, ECOG 0-1',
|
149 |
+
f'Age 18-80, advanced {tumor_type}, previous treatment failure'
|
150 |
+
]
|
151 |
+
}
|
152 |
+
|
153 |
+
return pd.DataFrame(sample_data)
|
154 |
+
|
155 |
+
except Exception as e:
|
156 |
+
print(f"サンプルデータ生成エラー: {e}")
|
157 |
+
return pd.DataFrame()
|
158 |
+
|
159 |
+
# 完全版データ生成関数
|
160 |
+
def generate_full_dataframe(age, sex, tumor_type, GeneMutation, Meseable, Biopsiable):
|
161 |
+
"""完全版のデータ生成(実際のAPI使用)"""
|
162 |
try:
|
163 |
# 入力検証
|
164 |
if not all([age, sex, tumor_type]):
|
165 |
+
return pd.DataFrame()
|
166 |
+
|
167 |
+
# 日本語の腫瘍タイプを英語に翻訳
|
168 |
+
try:
|
169 |
+
if translator is not None:
|
170 |
+
TumorName = translator.translate(tumor_type)
|
171 |
+
print(f"腫瘍タイプ翻訳: {tumor_type} → {TumorName}")
|
172 |
+
else:
|
173 |
+
print("翻訳エージェントが利用できません。元の値を使用します。")
|
174 |
+
TumorName = tumor_type
|
175 |
+
except Exception as e:
|
176 |
+
print(f"翻訳エラー: {e}")
|
177 |
+
TumorName = tumor_type
|
178 |
+
|
179 |
+
# 質問文を生成
|
180 |
+
try:
|
181 |
+
ex_question = generate_ex_question_English(age, sex, TumorName, GeneMutation, Meseable, Biopsiable)
|
182 |
+
print(f"生成された質問: {ex_question}")
|
183 |
+
except Exception as e:
|
184 |
+
print(f"質問生成エラー: {e}")
|
185 |
+
return pd.DataFrame()
|
186 |
+
|
187 |
+
# 臨床試験データの取得
|
188 |
+
try:
|
189 |
+
print(f"臨床試験データを検索中: {TumorName}")
|
190 |
+
df = fetch_clinical_trials(TumorName)
|
191 |
+
if df.empty:
|
192 |
+
print("臨床試験データが見つかりませんでした")
|
193 |
+
return pd.DataFrame()
|
194 |
+
print(f"取得した臨床試験数: {len(df)}")
|
195 |
+
except Exception as e:
|
196 |
+
print(f"臨床試験データ取得エラー: {e}")
|
197 |
+
return pd.DataFrame()
|
198 |
+
|
199 |
+
df['AgentJudgment'] = None
|
200 |
+
df['AgentGrade'] = None
|
201 |
+
|
202 |
+
# 臨床試験の適格性の評価
|
203 |
+
NCTIDs = list(df['NCTID'])
|
204 |
+
|
205 |
+
for i, nct_id in enumerate(NCTIDs):
|
206 |
+
try:
|
207 |
+
print(f"評価中 ({i+1}/{len(NCTIDs)}): {nct_id}")
|
208 |
+
target_criteria = df.loc[df['NCTID'] == nct_id, 'Eligibility Criteria'].values[0]
|
209 |
+
|
210 |
+
# エラーハンドリング付きで評価実行
|
211 |
+
agent_judgment = evaluate_with_retry(CriteriaCheckAgent, target_criteria, ex_question)
|
212 |
+
agent_grade = evaluate_grade_with_retry(grader_agent, agent_judgment)
|
213 |
+
|
214 |
+
# データフレームの更新
|
215 |
+
df.loc[df['NCTID'] == nct_id, 'AgentJudgment'] = agent_judgment
|
216 |
+
df.loc[df['NCTID'] == nct_id, 'AgentGrade'] = agent_grade
|
217 |
+
|
218 |
+
except Exception as e:
|
219 |
+
print(f"NCTID {nct_id} の評価中にエラー: {e}")
|
220 |
+
df.loc[df['NCTID'] == nct_id, 'AgentJudgment'] = f"エラー: {str(e)}"
|
221 |
+
df.loc[df['NCTID'] == nct_id, 'AgentGrade'] = "unclear"
|
222 |
+
|
223 |
+
# 列を指定した順に並び替え
|
224 |
+
columns_order = ['NCTID', 'AgentGrade', 'Title', 'AgentJudgment', 'Japanes Locations',
|
225 |
+
'Primary Completion Date', 'Cancer', 'Summary', 'Eligibility Criteria']
|
226 |
|
227 |
+
# 存在する列のみを選択
|
228 |
+
available_columns = [col for col in columns_order if col in df.columns]
|
229 |
+
df = df[available_columns]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
|
231 |
+
print(f"評価完了。結果: {len(df)} 件")
|
232 |
+
return df
|
233 |
|
234 |
except Exception as e:
|
235 |
+
print(f"完全版データフレーム生成中に予期しないエラー: {e}")
|
236 |
+
traceback.print_exc()
|
237 |
+
return pd.DataFrame()
|
238 |
|
239 |
+
# CSVとして保存しダウンロードする関数
|
240 |
+
def download_filtered_csv(df):
|
241 |
+
try:
|
242 |
+
if df is None or len(df) == 0:
|
243 |
+
return None
|
244 |
+
file_path = "filtered_data.csv"
|
245 |
+
df.to_csv(file_path, index=False)
|
246 |
+
return file_path
|
247 |
+
except Exception as e:
|
248 |
+
print(f"CSV保存エラー: {e}")
|
249 |
+
return None
|
250 |
+
|
251 |
+
def download_full_csv(df):
|
252 |
try:
|
253 |
+
if df is None or len(df) == 0:
|
254 |
+
return None
|
255 |
+
file_path = "full_data.csv"
|
256 |
+
df.to_csv(file_path, index=False)
|
257 |
+
return file_path
|
258 |
except Exception as e:
|
259 |
+
print(f"CSV保存エラー: {e}")
|
260 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
261 |
|
262 |
# Gradioインターフェースの作成
|
263 |
+
with gr.Blocks(title="臨床試験適格性評価", theme=gr.themes.Soft()) as demo:
|
264 |
+
gr.Markdown("## 🏥 臨床試験適格性評価インターフェース")
|
265 |
|
266 |
+
# バージョン情報と状態表示
|
267 |
+
if FULL_VERSION and env_ok:
|
268 |
+
gr.Markdown("✅ **モード**: 完全版(API連携有効)")
|
269 |
+
elif FULL_VERSION and not env_ok:
|
270 |
+
gr.Markdown("⚠️ **モード**: 完全版(API制限あり)")
|
271 |
+
else:
|
272 |
+
gr.Markdown("🔧 **モード**: 軽量版(サンプルデータ)")
|
273 |
+
|
274 |
+
# 環境変数状態の表示
|
275 |
+
if env_ok:
|
276 |
+
gr.Markdown("✅ **API Status**: 全ての環境変数が設定されています")
|
277 |
+
else:
|
278 |
+
gr.Markdown("⚠️ **API Status**: 環境変数が不足しています。Settings → Variables and secrets で設定してください")
|
279 |
+
|
280 |
+
gr.Markdown("💡 **使用方法**: 患者情報を入力してボタンをクリックしてください。完全版では実際のClinicalTrials.govからリアルタイムでデータを取得し、AIが適格性を評価します。")
|
281 |
+
|
282 |
+
# 各種入力フィールド
|
283 |
+
with gr.Row():
|
284 |
+
with gr.Column():
|
285 |
+
age_input = gr.Textbox(label="Age", placeholder="例: 65", value="65")
|
286 |
+
sex_input = gr.Dropdown(choices=["男性", "女性"], label="Sex", value="男性")
|
287 |
+
tumor_type_input = gr.Textbox(label="Tumor Type", placeholder="例: gastric cancer", value="gastric cancer")
|
288 |
|
289 |
+
with gr.Column():
|
290 |
+
gene_mutation_input = gr.Textbox(label="Gene Mutation", placeholder="例: HER2", value="HER2")
|
291 |
+
measurable_input = gr.Dropdown(choices=["有り", "無し", "不明"], label="Measurable Tumor", value="有り")
|
292 |
+
biopsiable_input = gr.Dropdown(choices=["有り", "無し", "不明"], label="Biopsiable Tumor", value="有り")
|
293 |
+
|
294 |
+
# データフレーム表示エリア
|
295 |
+
dataframe_output = gr.DataFrame(
|
296 |
+
label="Clinical Trials Results",
|
297 |
+
interactive=False,
|
298 |
+
wrap=True
|
299 |
+
)
|
300 |
+
|
301 |
+
# 内部状態用の非表示コンポーネント
|
302 |
+
original_df_state = gr.State(value=None)
|
303 |
+
filtered_df_state = gr.State(value=None)
|
304 |
+
|
305 |
+
# ボタン類
|
306 |
+
with gr.Row():
|
307 |
+
if FULL_VERSION and env_ok:
|
308 |
+
generate_button = gr.Button("🔍 Generate Clinical Trials Data (Real API)", variant="primary")
|
309 |
else:
|
310 |
+
generate_button = gr.Button("📋 Generate Sample Data", variant="primary")
|
311 |
+
|
312 |
+
with gr.Row():
|
313 |
+
yes_button = gr.Button("✅ Show Eligible Trials", variant="secondary")
|
314 |
+
no_button = gr.Button("❌ Show Ineligible Trials", variant="secondary")
|
315 |
+
unclear_button = gr.Button("❓ Show Unclear Trials", variant="secondary")
|
316 |
+
all_button = gr.Button("📊 Show All Trials", variant="secondary")
|
317 |
+
|
318 |
+
with gr.Row():
|
319 |
+
download_filtered_button = gr.Button("💾 Download Filtered Data")
|
320 |
+
download_full_button = gr.Button("💾 Download Full Data")
|
321 |
+
|
322 |
+
# ダウンロードファイル
|
323 |
+
download_filtered_output = gr.File(label="Download Filtered Data", visible=False)
|
324 |
+
download_full_output = gr.File(label="Download Full Data", visible=False)
|
|
|
325 |
|
326 |
+
# プログレス表示
|
327 |
+
progress_text = gr.Textbox(label="Processing Status", value="Ready", interactive=False)
|
328 |
+
|
329 |
+
# イベントハンドリング
|
330 |
+
def update_dataframe_and_state(age, sex, tumor_type, gene_mutation, measurable, biopsiable):
|
331 |
+
"""データフレーム生成と状態更新"""
|
332 |
+
try:
|
333 |
+
if FULL_VERSION and env_ok:
|
334 |
+
progress_text.value = "🔍 実際の臨床試験データを検索中..."
|
335 |
+
df = generate_full_dataframe(age, sex, tumor_type, gene_mutation, measurable, biopsiable)
|
336 |
+
else:
|
337 |
+
progress_text.value = "📋 サンプルデータを生成中..."
|
338 |
+
df = generate_sample_dataframe(age, sex, tumor_type, gene_mutation, measurable, biopsiable)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
339 |
|
340 |
+
if len(df) > 0:
|
341 |
+
progress_text.value = f"✅ 完了: {len(df)} 件の臨床試験が見つかりました"
|
342 |
+
else:
|
343 |
+
progress_text.value = "⚠️ 該当する臨床試験が見つかりませんでした"
|
|
|
|
|
344 |
|
345 |
+
return df, df, df, progress_text.value
|
346 |
+
except Exception as e:
|
347 |
+
error_msg = f"❌ エラー: {str(e)}"
|
348 |
+
print(f"データフレーム更新エラー: {e}")
|
349 |
+
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame(), error_msg
|
350 |
+
|
351 |
+
def filter_and_update(original_df, grade):
|
352 |
+
"""フィルタリングと表示更新"""
|
353 |
+
if original_df is None or len(original_df) == 0:
|
354 |
+
return original_df, original_df
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
355 |
|
356 |
+
try:
|
357 |
+
if grade == "all":
|
358 |
+
df_filtered = original_df
|
359 |
+
else:
|
360 |
+
df_filtered = original_df[original_df['AgentGrade'] == grade]
|
361 |
+
return df_filtered, df_filtered
|
362 |
+
except Exception as e:
|
363 |
+
print(f"フィルタリングエラー: {e}")
|
364 |
+
return original_df, original_df
|
365 |
|
366 |
+
# ボタン動作の設定
|
367 |
+
generate_button.click(
|
368 |
+
fn=update_dataframe_and_state,
|
369 |
+
inputs=[age_input, sex_input, tumor_type_input, gene_mutation_input, measurable_input, biopsiable_input],
|
370 |
+
outputs=[dataframe_output, original_df_state, filtered_df_state, progress_text]
|
371 |
+
)
|
372 |
+
|
373 |
+
yes_button.click(
|
374 |
+
fn=lambda df: filter_and_update(df, "yes"),
|
375 |
+
inputs=[original_df_state],
|
376 |
+
outputs=[dataframe_output, filtered_df_state]
|
377 |
+
)
|
378 |
+
|
379 |
+
no_button.click(
|
380 |
+
fn=lambda df: filter_and_update(df, "no"),
|
381 |
+
inputs=[original_df_state],
|
382 |
+
outputs=[dataframe_output, filtered_df_state]
|
383 |
+
)
|
384 |
+
|
385 |
+
unclear_button.click(
|
386 |
+
fn=lambda df: filter_and_update(df, "unclear"),
|
387 |
+
inputs=[original_df_state],
|
388 |
+
outputs=[dataframe_output, filtered_df_state]
|
389 |
+
)
|
390 |
+
|
391 |
+
all_button.click(
|
392 |
+
fn=lambda df: filter_and_update(df, "all"),
|
393 |
+
inputs=[original_df_state],
|
394 |
+
outputs=[dataframe_output, filtered_df_state]
|
395 |
+
)
|
396 |
+
|
397 |
+
download_filtered_button.click(
|
398 |
+
fn=download_filtered_csv,
|
399 |
+
inputs=[filtered_df_state],
|
400 |
+
outputs=[download_filtered_output]
|
401 |
+
)
|
402 |
+
|
403 |
+
download_full_button.click(
|
404 |
+
fn=download_full_csv,
|
405 |
+
inputs=[original_df_state],
|
406 |
+
outputs=[download_full_output]
|
407 |
+
)
|
408 |
|
409 |
+
# フッター情報
|
410 |
+
gr.Markdown("---")
|
411 |
+
gr.Markdown("🔬 **技術情報**: このシステムはClinicalTrials.gov API、LangChain、およびGroq/OpenAI APIを使用しています。")
|
412 |
|
|
|
413 |
if __name__ == "__main__":
|
414 |
+
demo.launch(
|
415 |
+
server_name="0.0.0.0",
|
416 |
+
server_port=7860,
|
417 |
+
share=False,
|
418 |
+
debug=False,
|
419 |
+
show_error=True
|
420 |
+
)
|
requirements.txt
CHANGED
@@ -1,5 +1,30 @@
|
|
1 |
-
|
2 |
-
# requirements.txt を以下に変更
|
3 |
gradio==4.36.1
|
4 |
numpy==1.21.6
|
5 |
-
pandas==1.3.5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Stage 3: 完全版 requirements.txt
|
|
|
2 |
gradio==4.36.1
|
3 |
numpy==1.21.6
|
4 |
+
pandas==1.3.5
|
5 |
+
requests==2.31.0
|
6 |
+
|
7 |
+
# LangChain ecosystem
|
8 |
+
langchain==0.1.20
|
9 |
+
langchain-community==0.0.38
|
10 |
+
langchain-core==0.1.52
|
11 |
+
langchain-openai==0.1.7
|
12 |
+
langchain-groq==0.1.5
|
13 |
+
langchain-text-splitters==0.0.1
|
14 |
+
|
15 |
+
# LLM providers
|
16 |
+
openai==1.12.0
|
17 |
+
groq==0.4.2
|
18 |
+
|
19 |
+
# Database utilities
|
20 |
+
SQLAlchemy==2.0.23
|
21 |
+
|
22 |
+
# Pydantic
|
23 |
+
pydantic==2.5.3
|
24 |
+
|
25 |
+
# Text processing
|
26 |
+
tiktoken==0.5.2
|
27 |
+
|
28 |
+
# Utilities
|
29 |
+
tenacity==8.2.3
|
30 |
+
packaging==23.0.0
|