""" AI service: Report analysis, chat, doctor questions. Uses OpenAI-compatible API (e.g. SiliconFlow). """ import json import logging from typing import Optional from openai import AsyncOpenAI from app.config import settings from app.models.report import Report from app.models.user import User logger = logging.getLogger(__name__) def _get_client() -> AsyncOpenAI: return AsyncOpenAI(api_key=settings.ai_api_key, base_url=settings.ai_base_url) def _build_report_context(report: Report, user: User) -> str: """构建报告上下文给 AI""" lines = [ f"孕妈信息: 年龄{user.age}岁", ] if report.pregnancy_week: lines.append(f"检查时孕周: 第{report.pregnancy_week}周") if report.report_date: lines.append(f"报告日期: {report.report_date}") if user.last_period_date: lines.append(f"末次月经: {user.last_period_date}") if user.height: lines.append(f"身高: {user.height}cm") if user.current_weight: lines.append(f"当前体重: {user.current_weight}kg") if user.blood_type: lines.append(f"血型: {user.blood_type}") if user.medical_history: lines.append(f"病史: {', '.join(user.medical_history)}") lines.append(f"\n报告类型: {report.report_type}") lines.append("\n检查指标:") for item in report.data_items: ref = f", 参考范围: {item.reference_range}" if item.reference_range else "" lines.append(f" - {item.name}: {item.value} {item.unit}{ref}") if report.symptoms: lines.append(f"\n自觉症状: {', '.join(report.symptoms)}") return "\n".join(lines) def _parse_json_response(text: str) -> dict: """从 AI 回复中提取 JSON""" if "```json" in text: text = text.split("```json")[1].split("```")[0] elif "```" in text: text = text.split("```")[1].split("```")[0] return json.loads(text.strip()) async def _chat_complete(system: str, user_content: str, messages: Optional[list] = None, max_tokens: int = 4096) -> str: """统一调用 AI 文本模型""" client = _get_client() msgs = [{"role": "system", "content": system}] if messages: msgs.extend(messages) else: msgs.append({"role": "user", "content": user_content}) try: response = await client.chat.completions.create( model=settings.ai_text_model, max_tokens=max_tokens, temperature=0.7, # 降低温度提高生成速度 messages=msgs, ) except Exception as e: err_msg = str(e) logger.error("AI 调用失败: %s: %s", type(e).__name__, err_msg[:200]) if "auth" in err_msg.lower() or "api key" in err_msg.lower(): raise RuntimeError("AI 服务认证失败,请检查 API Key") from e raise RuntimeError("AI 服务暂时不可用,请稍后重试") from e return response.choices[0].message.content # ─── Report Analysis ─── ANALYSIS_SYSTEM_PROMPT = """你是一位专业、温暖的孕期健康顾问AI。你的任务是解读孕期检查报告,用通俗易懂的语言帮助孕妈理解检查结果。 核心要求: 1. 必须结合孕周来判断指标是否正常(孕期不同阶段的正常范围不同,如血红蛋白在孕中晚期会生理性下降) 2. 将每个指标的测定结果与报告中的参考范围对比,准确判断偏高/偏低/正常 3. 如果参考范围缺失,根据你的医学知识和该孕周的标准范围来判断 4. 用温暖、关怀的语气,避免医学术语恐吓 5. 对每个指标给出白话化的昵称(如血红蛋白→"氧气快递员",双顶径→"宝宝头围",腹围→"宝宝肚围",股骨长→"宝宝腿长") 6. 解释每个指标的含义、对宝宝的影响、行动建议 7. 给出整体评估和风险等级 8. 行动建议按优先级排序 **特别注意:** - 如果是B超报告,重点关注胎儿生长指标(双顶径、腹围、股骨长),即使只有这3个指标也要给出完整的解读 - B超报告的解读要重点说明宝宝的生长发育情况,是否符合孕周标准 - 即使指标数量很少(如只有3个),也要给出详细的解读和建议 请严格按以下JSON格式返回: { "overall_status": "normal/attention/warning", "risk_level": "low/medium/high", "overall_summary": "整体评估文案", "emotional_summary": "情感化总结,如'宝宝发育良好,继续保持'", "categories": [ { "category": "分类名称", "status": "normal/attention/warning", "indicators": [ { "name": "指标名", "nickname": "白话昵称", "value": "值", "status": "normal/high/low", "what_is_it": "通俗解释", "impact": "对宝宝的影响", "suggestion": "行动建议", "recheck_reminder": "复查提醒(可选)" } ] } ], "action_items": [ { "content": "具体建议", "priority": "high/medium/low", "category": "饮食/运动/就医/复查" } ], "tips": ["温馨提示1", "温馨提示2"] }""" async def analyze_report(report: Report, user: User) -> dict: """调用 AI 解读报告""" logger.info("开始AI解读报告 report_id=%s", report.id) context = _build_report_context(report, user) response_text = await _chat_complete( system=ANALYSIS_SYSTEM_PROMPT, user_content=f"请解读以下孕期检查报告:\n\n{context}", ) try: return _parse_json_response(response_text) except (json.JSONDecodeError, IndexError): return { "overall_status": "normal", "risk_level": "low", "overall_summary": response_text, "emotional_summary": "报告解读完成", "categories": [], "action_items": [], "tips": [], } # ─── Chat ─── CHAT_SYSTEM_PROMPT = """你是一位专业、温暖的孕期健康顾问AI。孕妈正在就她的检查报告向你提问。 要求: 1. 用通俗易懂、温暖关怀的语气回答 2. 基于报告数据给出专业建议 3. 遇到严重问题建议及时就医 4. 回答简洁明了,避免过长 以下是孕妈的检查报告信息: {report_context}""" async def chat_with_ai( report: Report, user: User, messages: list[dict], user_message: str ) -> str: """AI 对话追问""" context = _build_report_context(report, user) system = CHAT_SYSTEM_PROMPT.format(report_context=context) conversation = [{"role": "system", "content": system}] for msg in messages[-10:]: conversation.append({"role": msg["role"], "content": msg["content"]}) conversation.append({"role": "user", "content": user_message}) client = _get_client() try: response = await client.chat.completions.create( model=settings.ai_text_model, max_tokens=1024, messages=conversation, ) except Exception as e: logger.error("AI 对话失败: %s", e) raise RuntimeError("AI 对话服务暂时不可用") from e return response.choices[0].message.content # ─── Doctor Questions ─── DOCTOR_QUESTIONS_SYSTEM_PROMPT = """你是一位孕期健康顾问AI。根据检查报告中的异常项,帮孕妈生成3-5条看医生时可以问的问题。 要求: 1. 问题要专业但容易理解 2. 按重要性排序 3. 给出为什么要问这个问题的原因 请严格按以下JSON格式返回: { "questions": [ { "category": "分类", "question": "问医生的话术", "reason": "为什么要问这个", "priority": "high/medium/low" } ] }""" async def generate_doctor_questions(report: Report, user: User) -> dict: """生成问诊清单""" context = _build_report_context(report, user) response_text = await _chat_complete( system=DOCTOR_QUESTIONS_SYSTEM_PROMPT, user_content=f"请根据以下报告生成问诊清单:\n\n{context}", max_tokens=1024, ) try: return _parse_json_response(response_text) except (json.JSONDecodeError, IndexError): return {"questions": []}