Files
yiliao/backend/services/deepseek_health_service.py

481 lines
17 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
DeepSeek 健康评估与建议生成服务
用于生成"整体健康状况""功能性健康建议"内容
优化:优先使用模板中已有的项目解释,只有模板中没有的项目才调用 DeepSeek 生成
"""
import os
import json
import requests
from pathlib import Path
from typing import List, Dict, Any
class DeepSeekHealthService:
"""DeepSeek 健康内容生成服务"""
def __init__(self, api_key: str = None):
self.api_key = api_key or os.getenv("DEEPSEEK_API_KEY", "")
self.api_url = "https://api.deepseek.com/v1/chat/completions"
# 加载模板中的解释
self.template_explanations = self._load_template_explanations()
def _load_template_explanations(self) -> Dict[str, Dict[str, str]]:
"""加载模板中已有的项目解释"""
explanations_file = Path(__file__).parent.parent / "template_explanations.json"
if explanations_file.exists():
try:
with open(explanations_file, 'r', encoding='utf-8') as f:
explanations = json.load(f)
print(f" ✓ 已加载 {len(explanations)} 个模板解释")
return explanations
except Exception as e:
print(f" ⚠️ 加载模板解释失败: {e}")
return {}
def get_template_explanation(self, abb: str) -> Dict[str, str]:
"""
获取模板中的项目解释
Args:
abb: 项目缩写
Returns:
{"clinical_en": "...", "clinical_cn": "..."} 或空字典
"""
# 尝试多种匹配方式
abb_upper = abb.upper().strip()
# 直接匹配
if abb_upper in self.template_explanations:
return self.template_explanations[abb_upper]
# 去除特殊字符后匹配
abb_clean = ''.join(c for c in abb_upper if c.isalnum())
for key, value in self.template_explanations.items():
key_clean = ''.join(c for c in key if c.isalnum())
if abb_clean == key_clean:
return value
return {}
def is_available(self) -> bool:
"""检查服务是否可用"""
return bool(self.api_key)
def call_deepseek(self, prompt: str) -> str:
"""调用 DeepSeek API"""
if not self.api_key:
raise ValueError("未配置 DEEPSEEK_API_KEY")
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
data = {
"model": "deepseek-chat",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.1,
"max_tokens": 8000
}
response = requests.post(
self.api_url,
headers=headers,
json=data,
timeout=120
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
def collect_abnormal_items(self, analysis: Dict[str, Any]) -> List[Dict[str, str]]:
"""
从分析结果中收集异常项
Args:
analysis: LLM 分析结果,包含 abnormal_items 字段
Returns:
异常项列表
"""
abnormal_items = []
# 从 abnormal_items 字段提取
raw_items = analysis.get("abnormal_items", [])
for item in raw_items:
if isinstance(item, dict):
abnormal_items.append({
"name": item.get("name", ""),
"abb": item.get("abb", item.get("name", "")),
"result": str(item.get("result", item.get("value", ""))),
"reference": item.get("reference", ""),
"unit": item.get("unit", ""),
"level": item.get("level", ""),
"point": "" if item.get("level") == "high" else ("" if item.get("level") == "low" else "")
})
elif isinstance(item, str):
# 如果是字符串格式,尝试解析
abnormal_items.append({
"name": item,
"abb": "",
"result": "",
"reference": "",
"unit": "",
"level": "",
"point": ""
})
return abnormal_items
def get_item_explanations(self, abnormal_items: List[Dict[str, str]]) -> Dict[str, Dict[str, str]]:
"""
为异常项获取解释(优先使用模板中的解释,缺失的才调用 DeepSeek 生成)
Args:
abnormal_items: 异常项列表
Returns:
{
"ABB": {"clinical_en": "...", "clinical_cn": "..."},
...
}
"""
explanations = {}
items_need_generation = []
print("\n 📋 检查模板中的项目解释...")
for item in abnormal_items:
abb = item.get("abb", "").upper().strip()
name = item.get("name", "")
if not abb:
continue
# 尝试从模板获取解释
template_exp = self.get_template_explanation(abb)
if template_exp and template_exp.get("clinical_en") and template_exp.get("clinical_cn"):
explanations[abb] = template_exp
print(f"{abb}: 使用模板解释")
else:
items_need_generation.append(item)
print(f"{abb}: 需要生成解释")
# 如果有需要生成的项目,调用 DeepSeek
if items_need_generation and self.api_key:
print(f"\n 🤖 调用 DeepSeek 为 {len(items_need_generation)} 个项目生成解释...")
generated = self._generate_missing_explanations(items_need_generation)
explanations.update(generated)
return explanations
def _generate_missing_explanations(self, items: List[Dict[str, str]]) -> Dict[str, Dict[str, str]]:
"""
调用 DeepSeek 为缺失解释的项目生成临床意义
Args:
items: 需要生成解释的项目列表
Returns:
生成的解释字典
"""
if not items:
return {}
# 构建项目描述
items_desc = []
for item in items:
desc = f"- {item['abb']}: {item['name']}"
if item.get('result'):
desc += f", 结果: {item['result']}"
if item.get('unit'):
desc += f" {item['unit']}"
if item.get('reference'):
desc += f", 参考范围: {item['reference']}"
items_desc.append(desc)
prompt = f"""你是一位医学检验专家,请为以下医疗检测项目生成临床意义解释。
## 需要解释的项目:
{chr(10).join(items_desc)}
## 要求:
1. 为每个项目提供英文和中文的临床意义解释
2. 解释应包含:该指标的作用、正常范围的意义、异常时可能的原因
3. 语言专业但易于理解
4. 每个解释约50-100字
## 输出格式JSON
```json
{{
"ABB1": {{
"clinical_en": "English clinical significance...",
"clinical_cn": "中文临床意义..."
}},
"ABB2": {{
"clinical_en": "...",
"clinical_cn": "..."
}}
}}
```
只返回JSON不要其他说明。"""
try:
response = self.call_deepseek(prompt)
# 解析 JSON
if "```json" in response:
response = response.split("```json")[1].split("```")[0]
elif "```" in response:
response = response.split("```")[1].split("```")[0]
result = json.loads(response.strip())
print(f" ✓ 成功生成 {len(result)} 个项目的解释")
return result
except Exception as e:
print(f" ✗ 生成解释失败: {e}")
return {}
def generate_health_assessment(self, abnormal_items: List[Dict[str, str]]) -> Dict[str, Any]:
"""
生成"整体健康状况"评估内容
Args:
abnormal_items: 异常项列表
Returns:
包含多个小节的健康评估内容
"""
if not self.api_key or not abnormal_items:
return {"sections": []}
# 构建异常项描述
abnormal_desc = []
for item in abnormal_items:
direction = "偏高" if item.get("point") in ["", "H", ""] or item.get("level") == "high" else "偏低"
desc = f"- {item['name']}"
if item.get('abb'):
desc += f" ({item['abb']})"
desc += f": {item['result']}"
if item.get('unit'):
desc += f" {item['unit']}"
desc += f" ({direction}"
if item.get('reference'):
desc += f", 参考范围: {item['reference']}"
desc += ")"
abnormal_desc.append(desc)
prompt = f"""你是一位功能医学专家,请根据以下所有异常检测指标,撰写"整体健康状况评估"的内容。
## 异常指标:
{chr(10).join(abnormal_desc)}
## 要求:
1. 根据异常指标的类型,自动分成合适的小节(如血液学、内分泌、免疫、代谢等,根据实际异常项决定)
2. 每个小节包含英文和中文两个版本
3. 从功能医学和整体健康角度分析
4. 解释可能的原因和健康影响
5. 语言专业但易于理解
6. 每个小节的每个语言版本约150-250字
## 输出格式JSON
```json
{{
"sections": [
{{
"title_en": "(I) Section Title in English",
"title_cn": "(一)中文小节标题",
"content_en": "English analysis content...",
"content_cn": "中文分析内容..."
}}
]
}}
```
只返回JSON不要其他说明。根据实际异常项情况决定分几个小节不要硬套固定模板。"""
try:
response = self.call_deepseek(prompt)
# 解析 JSON
if "```json" in response:
response = response.split("```json")[1].split("```")[0]
elif "```" in response:
response = response.split("```")[1].split("```")[0]
result = json.loads(response.strip())
print(f" ✓ 生成健康评估内容,共 {len(result.get('sections', []))} 个小节")
return result
except Exception as e:
print(f" ✗ 生成健康评估内容失败: {e}")
return {"sections": []}
def generate_health_advice(self, abnormal_items: List[Dict[str, str]]) -> Dict[str, Any]:
"""
生成"功能性健康建议"内容
Args:
abnormal_items: 异常项列表
Returns:
包含5个固定小节的健康建议内容
"""
if not self.api_key or not abnormal_items:
return {"sections": []}
# 异常项描述
abnormal_desc = []
for item in abnormal_items:
direction = "偏高" if item.get("point") in ["", "H", ""] or item.get("level") == "high" else "偏低"
desc = f"- {item['name']}"
if item.get('abb'):
desc += f" ({item['abb']})"
desc += f": {item['result']}"
if item.get('unit'):
desc += f" {item['unit']}"
desc += f" ({direction})"
abnormal_desc.append(desc)
prompt = f"""你是一位功能医学专家,请根据以下异常检测指标,撰写"功能医学健康建议"的内容。
## 异常指标:
{chr(10).join(abnormal_desc)}
## 要求:
1. 必须包含以下5个固定小节按顺序
- Nutrition Intervention 营养干预
- Exercise Intervention 运动干预
- Sleep & Stress Management 睡眠与压力管理
- Lifestyle Adjustment 生活方式调整
- Long-term Follow-up Plan 长期随访计划
2. 每个小节针对这些异常指标提供具体、可执行的建议
3. 从功能医学角度出发,强调预防和整体调理
4. 每个小节包含3-5条具体建议措施
5. 语言专业但易于理解
6. 分别提供英文和中文版本
7. 每个小节的每个语言版本约200-300字
## 输出格式JSON
```json
{{
"sections": [
{{
"title_en": "Nutrition Intervention",
"title_cn": "营养干预",
"content_en": "English nutrition advice...",
"content_cn": "中文营养建议..."
}},
{{
"title_en": "Exercise Intervention",
"title_cn": "运动干预",
"content_en": "...",
"content_cn": "..."
}},
{{
"title_en": "Sleep & Stress Management",
"title_cn": "睡眠与压力管理",
"content_en": "...",
"content_cn": "..."
}},
{{
"title_en": "Lifestyle Adjustment",
"title_cn": "生活方式调整",
"content_en": "...",
"content_cn": "..."
}},
{{
"title_en": "Long-term Follow-up Plan",
"title_cn": "长期随访计划",
"content_en": "...",
"content_cn": "..."
}}
]
}}
```
只返回JSON不要其他说明。"""
try:
response = self.call_deepseek(prompt)
# 解析 JSON
if "```json" in response:
response = response.split("```json")[1].split("```")[0]
elif "```" in response:
response = response.split("```")[1].split("```")[0]
result = json.loads(response.strip())
print(f" ✓ 生成健康建议内容,共 {len(result.get('sections', []))} 个小节")
return result
except Exception as e:
print(f" ✗ 生成健康建议内容失败: {e}")
return {"sections": []}
def generate_health_content(self, analysis: Dict[str, Any]) -> Dict[str, Any]:
"""
生成完整的健康评估和建议内容
优化:优先使用模板中已有的项目解释,只有模板中没有的项目才调用 DeepSeek 生成
Args:
analysis: LLM 分析结果
Returns:
包含 health_assessment, health_advice, item_explanations 的字典
"""
if not self.is_available():
print(" ⚠️ DeepSeek API Key 未配置,跳过健康内容生成")
return {}
print("\n============================================================")
print("DeepSeek 健康内容生成")
print("============================================================")
# 收集异常项
print("\n 📝 正在收集异常项...")
abnormal_items = self.collect_abnormal_items(analysis)
if not abnormal_items:
print(" 没有检测到异常项目,跳过内容生成")
return {}
print(f" 发现 {len(abnormal_items)} 个异常项目:")
for item in abnormal_items[:10]:
direction = "" if item.get("level") == "high" or item.get("point") == "" else ""
print(f" - {item['name']}: {item['result']} {direction}")
if len(abnormal_items) > 10:
print(f" ... 等共 {len(abnormal_items)}")
# 获取项目解释(优先使用模板,缺失的才生成)
item_explanations = self.get_item_explanations(abnormal_items)
# 统计使用情况
template_count = sum(1 for abb in item_explanations if self.get_template_explanation(abb))
generated_count = len(item_explanations) - template_count
print(f"\n 📊 解释来源统计: 模板 {template_count} 个, DeepSeek生成 {generated_count}")
# 生成健康评估
print("\n 🤖 正在调用 DeepSeek 生成整体健康状况...")
health_assessment = self.generate_health_assessment(abnormal_items)
# 生成健康建议
print("\n 🤖 正在调用 DeepSeek 生成功能性健康建议...")
health_advice = self.generate_health_advice(abnormal_items)
print("\n ✓ 健康内容生成完成")
return {
"health_assessment": health_assessment,
"health_advice": health_advice,
"abnormal_items": abnormal_items,
"item_explanations": item_explanations # 包含每个项目的解释
}