Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -32,18 +32,15 @@ def get_extraction_config():
|
|
| 32 |
]
|
| 33 |
return prompt_description, examples
|
| 34 |
|
| 35 |
-
# --- 2. Gradio 的核心处理函数 (
|
| 36 |
-
# 新增了对 PDF 文件处理的支持
|
| 37 |
def process_input_and_visualize(input_text, input_file):
|
| 38 |
"""
|
| 39 |
-
接收文本或文件输入,解析内容,调用 LangExtract 处理,并返回结果
|
| 40 |
"""
|
| 41 |
source_text = ""
|
| 42 |
# 优先处理上传的文件
|
| 43 |
if input_file is not None:
|
| 44 |
try:
|
| 45 |
-
# 使用 fitz (PyMuPDF) 打开 PDF 文件
|
| 46 |
-
# input_file.name 会提供 Gradio 保存的临时文件路径
|
| 47 |
with fitz.open(input_file.name) as doc:
|
| 48 |
for page in doc:
|
| 49 |
source_text += page.get_text()
|
|
@@ -59,40 +56,41 @@ def process_input_and_visualize(input_text, input_file):
|
|
| 59 |
# --- LangExtract 核心提取逻辑 ---
|
| 60 |
prompt, examples = get_extraction_config()
|
| 61 |
|
| 62 |
-
#
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
text=source_text,
|
| 67 |
-
extractions=[
|
| 68 |
-
# 注意:这些硬编码的位置仅适用于特定的示例文本
|
| 69 |
-
Extraction(extraction_class='药物', extraction_text='Lisinopril', attributes={'medication_group': 'Lisinopril'}, char_interval=CharInterval(start_pos=28, end_pos=38)),
|
| 70 |
-
Extraction(extraction_class='药物', extraction_text='Metformin', attributes={'medication_group': 'Metformin'}, char_interval=CharInterval(start_pos=43, end_pos=52)),
|
| 71 |
-
]
|
| 72 |
-
)
|
| 73 |
-
# --- 模拟结束 ---
|
| 74 |
|
| 75 |
-
# --- 真实的 LangExtract 调用
|
| 76 |
-
#
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
# 1. 准备命名实体识别 (NER) 的高亮文本输出
|
| 88 |
highlighted_text = []
|
| 89 |
last_pos = 0
|
|
|
|
| 90 |
sorted_extractions = sorted(result.extractions, key=lambda e: e.char_interval.start_pos)
|
| 91 |
for entity in sorted_extractions:
|
| 92 |
start, end = entity.char_interval.start_pos, entity.char_interval.end_pos
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
| 96 |
highlighted_text.append((source_text[last_pos:], None))
|
| 97 |
|
| 98 |
# 2. 准备关系提取 (RE) 的结构化 Markdown 输出
|
|
@@ -102,12 +100,15 @@ def process_input_and_visualize(input_text, input_file):
|
|
| 102 |
medication_groups.setdefault(group_name, []).append(extraction)
|
| 103 |
|
| 104 |
structured_output = "### 结构化提取结果\n\n"
|
| 105 |
-
|
| 106 |
-
structured_output +=
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
structured_output += f"
|
| 110 |
-
|
|
|
|
|
|
|
|
|
|
| 111 |
|
| 112 |
# 3. 生成并保存交互式可视化文件
|
| 113 |
session_id = str(uuid.uuid4())
|
|
@@ -127,26 +128,18 @@ def process_input_and_visualize(input_text, input_file):
|
|
| 127 |
return highlighted_text, structured_output, html_path, html_path, jsonl_path
|
| 128 |
|
| 129 |
|
| 130 |
-
# --- 3. 创建 Gradio 应用界面 (
|
| 131 |
with gr.Blocks(theme=gr.themes.Soft(), title="药物信息提取器") as demo:
|
| 132 |
-
gr.Markdown("# ⚕️
|
| 133 |
gr.Markdown("一个基于大型语言模型的智能工具,可从**临床文本**或 **PDF 文件**中自动提取药物、剂量等信息,并进行结构化关联。")
|
| 134 |
|
| 135 |
with gr.Row():
|
| 136 |
-
# 左侧为输入区,使用 Tab 切换
|
| 137 |
with gr.Column(scale=1):
|
| 138 |
with gr.Tabs():
|
| 139 |
with gr.TabItem("📄 临床文本输入"):
|
| 140 |
-
input_textbox = gr.Textbox(
|
| 141 |
-
lines=15,
|
| 142 |
-
label="粘贴临床笔记",
|
| 143 |
-
placeholder=textwrap.dedent("""请在此处粘贴文本...""")
|
| 144 |
-
)
|
| 145 |
with gr.TabItem("📁 上传PDF文件"):
|
| 146 |
-
input_file_uploader = gr.File(
|
| 147 |
-
label="选择一个 PDF 文件进行分析",
|
| 148 |
-
file_types=['.pdf']
|
| 149 |
-
)
|
| 150 |
|
| 151 |
submit_btn = gr.Button("🧠 提取信息", variant="primary")
|
| 152 |
gr.Examples(
|
|
@@ -154,35 +147,29 @@ with gr.Blocks(theme=gr.themes.Soft(), title="药物信息提取器") as demo:
|
|
| 154 |
"The patient was prescribed Lisinopril and Metformin last month.\nHe takes the Lisinopril 10mg daily for hypertension, but often misses his Metformin 500mg dose which should be taken twice daily for diabetes.",
|
| 155 |
"Patient took 400 mg PO Ibuprofen q4h for two days for a headache.",
|
| 156 |
],
|
| 157 |
-
inputs=input_textbox,
|
| 158 |
label="示例文本 (点击自动填充到上方文本框)"
|
| 159 |
)
|
| 160 |
|
| 161 |
-
# 右侧为输出区
|
| 162 |
with gr.Column(scale=2):
|
| 163 |
with gr.Tabs():
|
| 164 |
with gr.TabItem("📊 总览 (NER & RE)"):
|
| 165 |
gr.Markdown("### 命名实体识别 (NER) - 文本高亮")
|
| 166 |
output_highlight = gr.HighlightedText(label="实体高亮显示", color_map={"药物": "#FF6347", "剂量": "#FFA500", "频率": "#32CD32", "病症": "#4169E1"})
|
| 167 |
output_structured = gr.Markdown(label="结构化关系")
|
| 168 |
-
|
| 169 |
with gr.TabItem("🌐 交互式可视化"):
|
| 170 |
gr.Markdown("### 交互式可视化图表")
|
| 171 |
output_html_viewer = gr.HTML(label="交互式图表 (可缩放和筛选)")
|
| 172 |
-
|
| 173 |
with gr.TabItem("📁 文件下载"):
|
| 174 |
gr.Markdown("### 下载提取结果")
|
| 175 |
download_html = gr.File(label="下载交互式 HTML 文件")
|
| 176 |
download_jsonl = gr.File(label="下载 JSONL 数据文件")
|
| 177 |
|
| 178 |
-
# 设置按钮的点击事件
|
| 179 |
-
# 输入现在是文本框和文件上传器
|
| 180 |
submit_btn.click(
|
| 181 |
fn=process_input_and_visualize,
|
| 182 |
inputs=[input_textbox, input_file_uploader],
|
| 183 |
outputs=[output_highlight, output_structured, output_html_viewer, download_html, download_jsonl]
|
| 184 |
).then(
|
| 185 |
-
# 任务完成后,清空输入框和文件上传器,准备下一次输入
|
| 186 |
lambda: (None, None),
|
| 187 |
inputs=None,
|
| 188 |
outputs=[input_textbox, input_file_uploader]
|
|
|
|
| 32 |
]
|
| 33 |
return prompt_description, examples
|
| 34 |
|
| 35 |
+
# --- 2. Gradio 的核心处理函数 (生产级版本) ---
|
|
|
|
| 36 |
def process_input_and_visualize(input_text, input_file):
|
| 37 |
"""
|
| 38 |
+
接收文本或文件输入,解析内容,调用 LangExtract 的高级功能进行处理,并返回结果。
|
| 39 |
"""
|
| 40 |
source_text = ""
|
| 41 |
# 优先处理上传的文件
|
| 42 |
if input_file is not None:
|
| 43 |
try:
|
|
|
|
|
|
|
| 44 |
with fitz.open(input_file.name) as doc:
|
| 45 |
for page in doc:
|
| 46 |
source_text += page.get_text()
|
|
|
|
| 56 |
# --- LangExtract 核心提取逻辑 ---
|
| 57 |
prompt, examples = get_extraction_config()
|
| 58 |
|
| 59 |
+
# 从环境变量中获取 API Key (在 Hugging Face 中必须设置为 Secret)
|
| 60 |
+
api_key = os.environ.get("LANGEXTRACT_API_KEY")
|
| 61 |
+
if not api_key:
|
| 62 |
+
raise gr.Error("错误:服务器未配置 LANGEXTRACT_API_KEY。请联系管理员在 Hugging Face Space 的 Secrets 中添加它。")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
+
# --- 真实的、带高级参数的 LangExtract 调用 ---
|
| 65 |
+
# 这里我们移除了所有模拟代码,直接调用 API
|
| 66 |
+
try:
|
| 67 |
+
result = lx.extract(
|
| 68 |
+
text_or_documents=source_text,
|
| 69 |
+
prompt_description=prompt,
|
| 70 |
+
examples=examples,
|
| 71 |
+
model_id="gemini-2.5-pro", # 使用强大的模型以获得最佳效果
|
| 72 |
+
api_key=api_key,
|
| 73 |
+
# --- 智能处理长文本的关键参数 ---
|
| 74 |
+
max_workers=10, # 启用并行处理以加速,设置10个工作线程
|
| 75 |
+
extraction_passes=2, # 执行两次提取以提高召回率 (可以设为3以获得更高精度,但会更慢)
|
| 76 |
+
max_char_buffer=1500 # 设置文本块的最大字符数,用于智能分块
|
| 77 |
+
)
|
| 78 |
+
except Exception as e:
|
| 79 |
+
# 捕获API调用或其他处理中可能发生的错误
|
| 80 |
+
raise gr.Error(f"信息提取过程中发生错误: {e}")
|
| 81 |
|
| 82 |
# 1. 准备命名实体识别 (NER) 的高亮文本输出
|
| 83 |
highlighted_text = []
|
| 84 |
last_pos = 0
|
| 85 |
+
# 确保实体按位置排序,以便正确高亮显示
|
| 86 |
sorted_extractions = sorted(result.extractions, key=lambda e: e.char_interval.start_pos)
|
| 87 |
for entity in sorted_extractions:
|
| 88 |
start, end = entity.char_interval.start_pos, entity.char_interval.end_pos
|
| 89 |
+
# 检查位置是否有效,防止解析错误
|
| 90 |
+
if start >= last_pos and end <= len(source_text):
|
| 91 |
+
highlighted_text.append((source_text[last_pos:start], None))
|
| 92 |
+
highlighted_text.append((entity.extraction_text, entity.extraction_class))
|
| 93 |
+
last_pos = end
|
| 94 |
highlighted_text.append((source_text[last_pos:], None))
|
| 95 |
|
| 96 |
# 2. 准备关系提取 (RE) 的结构化 Markdown 输出
|
|
|
|
| 100 |
medication_groups.setdefault(group_name, []).append(extraction)
|
| 101 |
|
| 102 |
structured_output = "### 结构化提取结果\n\n"
|
| 103 |
+
if not medication_groups:
|
| 104 |
+
structured_output += "未提取到任何药物信息。"
|
| 105 |
+
else:
|
| 106 |
+
for med_name, extractions in medication_groups.items():
|
| 107 |
+
structured_output += f"#### 药物组: {med_name}\n"
|
| 108 |
+
for extraction in sorted(extractions, key=lambda e: e.char_interval.start_pos):
|
| 109 |
+
pos_info = f" (位置: {extraction.char_interval.start_pos}-{extraction.char_interval.end_pos})"
|
| 110 |
+
structured_output += f"- **{extraction.extraction_class}**: {extraction.extraction_text}{pos_info}\n"
|
| 111 |
+
structured_output += "\n"
|
| 112 |
|
| 113 |
# 3. 生成并保存交互式可视化文件
|
| 114 |
session_id = str(uuid.uuid4())
|
|
|
|
| 128 |
return highlighted_text, structured_output, html_path, html_path, jsonl_path
|
| 129 |
|
| 130 |
|
| 131 |
+
# --- 3. 创建 Gradio 应用界面 (保持不变) ---
|
| 132 |
with gr.Blocks(theme=gr.themes.Soft(), title="药物信息提取器") as demo:
|
| 133 |
+
gr.Markdown("# ⚕️ 基于大模型的药物信息提取器")
|
| 134 |
gr.Markdown("一个基于大型语言模型的智能工具,可从**临床文本**或 **PDF 文件**中自动提取药物、剂量等信息,并进行结构化关联。")
|
| 135 |
|
| 136 |
with gr.Row():
|
|
|
|
| 137 |
with gr.Column(scale=1):
|
| 138 |
with gr.Tabs():
|
| 139 |
with gr.TabItem("📄 临床文本输入"):
|
| 140 |
+
input_textbox = gr.Textbox(lines=15, label="粘贴临床笔记", placeholder="请在此处粘贴文本...")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
with gr.TabItem("📁 上传PDF文件"):
|
| 142 |
+
input_file_uploader = gr.File(label="选择一个 PDF 文件进行分析", file_types=['.pdf'])
|
|
|
|
|
|
|
|
|
|
| 143 |
|
| 144 |
submit_btn = gr.Button("🧠 提取信息", variant="primary")
|
| 145 |
gr.Examples(
|
|
|
|
| 147 |
"The patient was prescribed Lisinopril and Metformin last month.\nHe takes the Lisinopril 10mg daily for hypertension, but often misses his Metformin 500mg dose which should be taken twice daily for diabetes.",
|
| 148 |
"Patient took 400 mg PO Ibuprofen q4h for two days for a headache.",
|
| 149 |
],
|
| 150 |
+
inputs=input_textbox,
|
| 151 |
label="示例文本 (点击自动填充到上方文本框)"
|
| 152 |
)
|
| 153 |
|
|
|
|
| 154 |
with gr.Column(scale=2):
|
| 155 |
with gr.Tabs():
|
| 156 |
with gr.TabItem("📊 总览 (NER & RE)"):
|
| 157 |
gr.Markdown("### 命名实体识别 (NER) - 文本高亮")
|
| 158 |
output_highlight = gr.HighlightedText(label="实体高亮显示", color_map={"药物": "#FF6347", "剂量": "#FFA500", "频率": "#32CD32", "病症": "#4169E1"})
|
| 159 |
output_structured = gr.Markdown(label="结构化关系")
|
|
|
|
| 160 |
with gr.TabItem("🌐 交互式可视化"):
|
| 161 |
gr.Markdown("### 交互式可视化图表")
|
| 162 |
output_html_viewer = gr.HTML(label="交互式图表 (可缩放和筛选)")
|
|
|
|
| 163 |
with gr.TabItem("📁 文件下载"):
|
| 164 |
gr.Markdown("### 下载提取结果")
|
| 165 |
download_html = gr.File(label="下载交互式 HTML 文件")
|
| 166 |
download_jsonl = gr.File(label="下载 JSONL 数据文件")
|
| 167 |
|
|
|
|
|
|
|
| 168 |
submit_btn.click(
|
| 169 |
fn=process_input_and_visualize,
|
| 170 |
inputs=[input_textbox, input_file_uploader],
|
| 171 |
outputs=[output_highlight, output_structured, output_html_viewer, download_html, download_jsonl]
|
| 172 |
).then(
|
|
|
|
| 173 |
lambda: (None, None),
|
| 174 |
inputs=None,
|
| 175 |
outputs=[input_textbox, input_file_uploader]
|