目錄
說明
前言
需求
流程說明
表結構說明
整體流程?
百度智能云
注冊和實名認證
創建應用
費用說明
大模型API說明
集成大模型
設計Prompt
上傳體檢報告
讀取PDF內容
功能實現
智能評測
抽取大模型工具
功能實現
總結
說明
AI體檢報告解讀、病例小結或者類似的業務可以參考,完整DEMO不提供,只提供思路。
前言
在上家公司寫過AI體檢報告解讀和AI精準就醫推薦需求,不過那時候是調用分公司AI研發部的
API,估計他們也是調用大模型,后臺使用的是JeecgBoot管理數據,例如你有這個權益卡,這個
權益卡包含這個服務你就可以使用這個服務。
這次自己對接千帆大模型使用OSS上傳體檢報告,apache.pdfbox識別pdf內容,設置號Prompt,
調用千帆大模型對體檢報告進行分析。
需求
輸入如下信息,提前準備好老人體檢報告(PDF格式)點擊確認按鈕以后,會使用AI對老人的健康
報告進行評估。
下面是健康評估的詳情頁面,使用AI分析后的結果頁,給了很多數據,可以讓護理員或銷售來查看
健康狀況,進一步更好的服務老人或者給老人推薦一些護理服務。
?
流程說明
表結構說明
建表語句:
CREATE TABLE "health_assessment" ("id" bigint NOT NULL AUTO_INCREMENT COMMENT '主鍵',"elder_name" varchar(255) DEFAULT NULL COMMENT '老人姓名',"id_card" varchar(255) DEFAULT NULL COMMENT '身份證號',"birth_date" datetime DEFAULT NULL COMMENT '出生日期',"age" int DEFAULT NULL COMMENT '年齡',"gender" int DEFAULT NULL COMMENT '性別(0:男,1:女)',"health_score" varchar(255) DEFAULT NULL COMMENT '健康評分',"risk_level" varchar(255) DEFAULT NULL COMMENT '嚴重危險(健康, 提示, 風險, 危險, 嚴重危險)',"suggestion_for_admission" int DEFAULT NULL COMMENT '是否建議入住(0:建議,1:不建議)',"nursing_level_name" varchar(255) DEFAULT NULL COMMENT '推薦護理等級',"admission_status" int DEFAULT NULL COMMENT '入住情況(0:已入住,1:未入住)',"total_check_date" varchar(64) DEFAULT NULL COMMENT '總檢日期',"physical_exam_institution" varchar(255) DEFAULT NULL COMMENT '體檢機構',"physical_report_url" varchar(255) DEFAULT NULL COMMENT '體檢報告URL鏈接',"assessment_time" datetime DEFAULT NULL COMMENT '評估時間',"report_summary" text COMMENT '報告總結',"disease_risk" text COMMENT '疾病風險',"abnormal_analysis" text COMMENT '異常分析',"system_score" varchar(255) DEFAULT NULL COMMENT '健康系統分值',"create_by" varchar(255) DEFAULT NULL COMMENT '創建者',"create_time" datetime DEFAULT NULL COMMENT '創建時間',"update_by" varchar(255) DEFAULT NULL COMMENT '更新者',"update_time" datetime DEFAULT NULL COMMENT '更新時間',"remark" text COMMENT '備注',PRIMARY KEY ("id")
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='健康評估表';
整體流程?
- 上傳PDF文件存儲到OSS?
- 讀取PDF文件內容 存儲到Redis key為身份證號 value為pdf識別內容
- 點擊確定 根據身份證從Redis獲取pdf識別內容
- 結合Prompt+pdf內容 調用大模型 返回JSON結果 接收
百度智能云
注冊和實名認證
- 注冊地址:https://qianfan.cloud.baidu.com/
需要實名認證,不然大模型調用不了,實名認證自己認證一下。?
創建應用
實名認證后,需要創建一個應用,獲取到這個應用的API Key和Secret Key。
地址:千帆大模型服務與開發平臺ModelBuilder
進入管理平臺后,找到應用接入,創建新的應用,只有創建了應用,后面才能讓大模型來綁定應用
使用。
?輸入應用的名稱,和應用的描述點擊確認即可。
獲取到AppId、API Key、Secret Key后續項目中調用API需要用到。
?
費用說明
記得充錢!!!! 調用大模型需要花費TOKEN TOKEN是需要錢的!!!!
充錢才能使你變的強大!!!
大模型API說明
我們的需求中,prompt是比較多的,低版本都不支持大量token使用,所以采用ERNIE-4.0-8K-
Preview。
官方地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/nluv1jxlp
ERNIE 4.0是百度自研的旗艦級超大規模?語?模型,相較ERNIE 3.5實現了模型能力全面升級,
廣泛適用于各領域復雜任務場景;支持自動對接百度搜索插件,保障問答信息時效,支持5K
tokens輸入+2K tokens輸出。
-
對話模型,一次請求就是一次對話
-
提供了java的sdk調用,地址:https://github.com/baidubce/bce-qianfan-sdk/tree/main/java
-
支持單輪對話、多輪對話、流式
-
部分關鍵的參數:?
名稱 | 類型 | 必填 | 描述 |
messages | List[dict] | 是 | 對話信息, message中的content總長度和system字段總內容不能超過20000個字符,且不能超過5120 tokens |
model | string | 是 | 模型名稱,用于指定平臺支持預置服務的模型,說明:該字段為固定值ERNIE-4.0-8K-Preview(必須開通付費) |
temperature | float | 否 | 大模型的采樣參數, (1)較高的數值會使輸出更加隨機,而較低的數值會使其更加集中和確定 (2)默認0.8,范圍 (0, 1.0],不能為0 |
max_output_tokens | int | 否 | 指定模型最大輸出token數,說明: (1)如果設置此參數,范圍[2, 2048] (2)如果不設置此參數,最大輸出token數為1024 |
response_format | string | 否 | 指定響應內容的格式,說明: (1)可選值: · json_object:以json格式返回,可能出現不滿足效果情況 · text:以文本格式返回 (2)如果不填寫參數response_format值,默認為text |
集成大模型
導入依賴:
<dependency><groupId>com.baidubce</groupId><artifactId>qianfan</artifactId><version>0.1.1</version>
</dependency>
編寫一個main測試方法:
public class AIModelTest {private static final String prompt = "你能幫我分析一份完整的體檢報告嗎?";public static void main(String[] args) {/*** 第一個參數:認證類型,固定選擇 Auth.TYPE_OAUTH* 第二個參數:accessKeyId,從百度云控制臺創建的應用里可以找到* 第三個參數:accessKeySecret,從百度云控制臺創建的應用里可以找到*/Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, "xEO9h4cswlghfdfdUiYpkNt", "T68lkk7XuyfgfdgfdWtCQcFCVkd2HnZuKH");ChatResponse response = qianfan.chatCompletion().model("ERNIE-4.0-8K-Preview") // 模型名稱,要選擇自己開通付費的模型.addMessage("user", prompt) // 聊天內容,可以設置多個,每個消息包含role(角色,user表示用戶,assistant表示模型),content(消息內容)
// .temperature(0.7) // 采樣參數,取值范圍(0,1]
// .maxOutputTokens(2000) // 模型輸出最大長度,取值范圍[2, 2048]
// .responseFormat("json_object") // 模型輸出格式,取值范圍:text(文本)、json_object(JSON對象).execute();String result = response.getResult();System.out.println(result);}
}
結果輸出:
設計Prompt
設計之后的Prompt提示詞,這里涉及了大量的專業名詞和健康指標,實際開發中需要找產品經理
協助
請以一個專業醫生的視角來分析這份體檢報告,報告中包含了一些異常數據,我需要您對這些數據進行解讀,并給出相應的健康建議。 體檢內容如下: 內容略.... 要求: 1. 提取體檢報告中的“總檢日期”; 2. 通過臨床醫學、疾病風險評估模型和數據智能分析,給該用戶的風險等級和健康指數給出結果。風險等級分為:健康、提示、風險、危險、嚴重危險。健康指數范圍為0至100分; 3. 根據用戶身體各項指標數據,詳細說明該用戶各項風險等級的占比是多少,最多保留兩位小數。結論格式:該用戶健康占比20.00%,提示占比20.00%,風險占比20%,危險占比20%,嚴重危險占比20%; 4. 對于體檢報告有異常數據,請列出(異常數據的結論、體檢項目名稱、檢查結果、參考值、單位、異常解讀、建議)這8字段。解讀異常數據,解決這些數據可能代表的健康問題或風險。分析可能的原因,包括但不限于生活習慣、飲食習慣、遺傳因素等。基于這些異常數據和可能的原因,請給出具體的健康建議,包括飲食調整、運動建議、生活方式改變以及是否需要進一步檢查或治療等。 結論格式:異常數據的結論:肥胖,體檢項目名稱:體重指數BMI,檢查結果:29.2,參考值>24,單位:-。異常解讀:體重超標包括超重與肥胖。體重指數(BMI)=體重(kg)/身?(m)的平?,BMI≥24為超重,BMI≥28為肥胖;男性腰圍≥90cm和?性腰圍≥85cm為腹型肥胖。體重超標是?種由多因素(如遺傳、進?油脂較多、運動少、疾病等)引起的慢性代謝性疾病,尤其是肥胖,已經被世界衛?組織列為導致疾病負擔的??危險因素之?。AI建議:采取綜合措施預防和控制體重,積極改變?活?式,宜低脂、低糖、?纖維素膳?,多?果蔬及菌藻類?物,增加有氧運動。若有相關疾病(如?脂異常、??壓、糖尿病等)應積極治療。 5. 根據這個體檢報告的內容,分別是給人體的8大系統打分,每項滿分為100分,8大系統分別為:呼吸系統、消化系統、內分泌系統、免疫系統、循環系統、泌尿系統、運動系統、感官系統 6. 給體檢報告做一個總結,總結格式:體檢報告中尿蛋?、癌胚抗原、?沉、空腹?糖、總膽固醇、?油三酯、低密度脂蛋?膽固醇、?清載脂蛋?B、動脈硬化指數、?細胞、平均紅細胞體積、平均?紅蛋?共12項指標提示異常,尿液常規共1項指標處于臨界值,?脂、?液常規、尿液常規、糖類抗原、?清酶類等共43項指標提示正常,綜合這些臨床指標和數據分析:腎臟、肝膽、?腦?管存在隱患,其中?腦?管有“?危”?險;腎臟部位有“中危”?險;肝膽部位有“低危”?險。 輸出要求: 最后,將以上結果輸出為JSON格式,不要包含其他的文字說明,所有的返回結果都是json,詳細格式如下: { "totalCheckDate": "YYYY-MM-DD", "healthAssessment": { "riskLevel": "healthy/caution/risk/danger/severeDanger", "healthIndex": XX.XX }, "riskDistribution": { "healthy": XX.XX, "caution": XX.XX, "risk": XX.XX, "danger": XX.XX, "severeDanger": XX.XX }, "abnormalData": [ { "conclusion": "異常數據的結論", "examinationItem": "體檢項目名稱", "result": "檢查結果", "referenceValue": "參考值", "unit": "單位", "interpret":"對于異常的結論進一步詳細的說明", "advice":"針對于這一項的異常,給出一些健康的建議" } ], "systemScore": { "breathingSystem": XX, "digestiveSystem": XX, "endocrineSystem": XX, "immuneSystem": XX, "circulatorySystem": XX, "urinarySystem": XX, "motionSystem": XX, "senseSystem": XX }, "summarize": "體檢報告的總結" }
上傳體檢報告
點擊上傳體檢報告后,需要將體檢報告上傳到阿里云OSS然后返回OSS鏈接,同時識別PDF文件
內容存儲到Redis,點擊確認的時候,根據身份證號從Redis獲取PDF內容,組合Prompt調用大模
型返回JSON數據接收。
讀取PDF內容
使用Apache PDFBox來識別PDF內容。
導入依賴:
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.24</version>
</dependency>
創建一個PDFUtil工具類,接收一個文件輸入流,讀取PDF文件內容,返回String PDF內容。
public class PDFUtil {public static String pdfToString(InputStream inputStream) {PDDocument document = null;try {// 加載PDF文檔document = PDDocument.load(inputStream);// 創建一個PDFTextStripper實例來提取文本PDFTextStripper pdfStripper = new PDFTextStripper();// 從PDF文檔中提取文本String text = pdfStripper.getText(document);return text;} catch (IOException e) {e.printStackTrace();} finally {// 關閉PDF文檔if (document != null) {try {document.close();inputStream.close();} catch (IOException e) {e.printStackTrace();}}}return null;}
}
測試:
public class PDFUtilTest {public static void main(String[] args) throws FileNotFoundException {FileInputStream fileInputStream = new FileInputStream("E:\\tmp\\體檢報告-劉愛國-男-69歲.pdf");String result = PDFUtil.pdfToString(fileInputStream);System.out.println(result);}
}
功能實現
OSSAliyunFileStorageService是上傳文件至OSS.
@Autowired
private OSSAliyunFileStorageService fileStorageService;@Autowired
private RedisTemplate<String,String> redisTemplate;/*** 通用上傳請求(單個)*/
@ApiOperation("健康文檔上傳")
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file, String idCardNo) throws Exception
{try{//生成一個名字,保證不重復,唯一String originalFilename = file.getOriginalFilename();String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));String url = fileStorageService.store(fileName, file.getInputStream());AjaxResult ajax = AjaxResult.success();ajax.put("url", url);ajax.put("fileName", url);ajax.put("newFileName", FileUtils.getName(fileName));ajax.put("originalFilename", file.getOriginalFilename());//PDF文件內容讀取為字符串String content = PDFUtil.pdfToString(file.getInputStream());//臨時存儲到redis中redisTemplate.opsForHash().put("healthReport", idCardNo, content);return ajax;}catch (Exception e){return AjaxResult.error(e.getMessage());}
}
智能評測
抽取大模型工具
application.yml定義配置:
baidu:accessKey: xEO9h4csw9zWlUiYpkNtsecretKey: T67Xuyx9JoCQcFCVkd2HnZuKHqianfanModel: ERNIE-4.0-8K-Preview
定義properties類方便讀取配置:
@Data
@Configuration
@ConfigurationProperties(prefix = "baidu")
public class BaiduAIProperties {private String accessKey;private String secretKey;private String qianfanModel;}
模型工具類,傳入組裝好的Prompt,返回String JSON。
@Component
@Slf4j
public class AIModelInvoker {@Autowiredprivate BaiduAIProperties baiduAIProperties;public String qianfanInvoker(String prompt) {System.out.println(prompt);Qianfan qianfan = new Qianfan(Auth.TYPE_OAUTH, baiduAIProperties.getAccessKey(), baiduAIProperties.getSecretKey());ChatResponse response = qianfan.chatCompletion().model(baiduAIProperties.getQianfanModel()).addMessage("user", prompt).temperature(0.7).maxOutputTokens(2000).responseFormat("json_object").execute();String result = response.getResult();System.out.println(result);return result;}}
功能實現
根據大模型返回的結果數據,返回的是一個JSON,調用大模型的時候已經指定返回的格式是
JSON,然后提取JSON數據,根據返回的結果判斷。
例如前端頁面的柱狀圖、餅圖數據這些都是模型返回,然后渲染的。
@Autowired
private AIModelInvoker aIModelInvoker;/*** 新增健康評估** @param healthAssessmentDto 健康評估* @return 結果*/
@Override
public Long insertHealthAssessment(HealthAssessmentDto healthAssessmentDto) {try {//組裝prompt,然后調用千帆大模型來分析數據String prompt = getPrompt(healthAssessmentDto);String result = aIModelInvoker.qianfanInvoker(prompt);if (StringUtils.isEmpty(result)) {throw new BaseException("AI分析失敗");}//解析數據HealthReportVo healthReportVo = JSONUtil.toBean(result, HealthReportVo.class);//保存數據Long id = insertHealthReport(healthReportVo, healthAssessmentDto);return id;} catch (BaseException e) {throw new BaseException("AI分析失敗");}
}/*** 插入健康報告** @param healthReportVo* @return*/
private Long insertHealthReport(HealthReportVo healthReportVo, HealthAssessmentDto healthAssessmentDto) {HealthAssessment healthAssessment = new HealthAssessment();//總檢日期healthAssessment.setTotalCheckDate(healthReportVo.getTotalCheckDate());healthAssessment.setElderName(healthAssessmentDto.getElderName());String idCard = healthAssessmentDto.getIdCard();healthAssessment.setIdCard(idCard);healthAssessment.setBirthDate(IDCardUtils.getBirthDateByIdCard(idCard));healthAssessment.setAge(IDCardUtils.getAgeByIdCard(idCard));healthAssessment.setGender(IDCardUtils.getGenderFromIdCard(idCard));double healthScore = healthReportVo.getHealthAssessment().getHealthIndex();healthAssessment.setHealthScore(String.valueOf(healthScore));healthAssessment.setRiskLevel(healthReportVo.getHealthAssessment().getRiskLevel());//是否建議入住healthAssessment.setSuggestionForAdmission(getSuggestionForAdmission(healthScore));healthAssessment.setNursingLevelName(getNursingLevelName(healthScore));healthAssessment.setAdmissionStatus(0);healthAssessment.setTotalCheckDate(healthReportVo.getTotalCheckDate());healthAssessment.setPhysicalExamInstitution(healthAssessmentDto.getPhysicalExamInstitution());healthAssessment.setPhysicalReportUrl(healthAssessmentDto.getPhysicalReportUrl());healthAssessment.setAssessmentTime(LocalDateTime.now());healthAssessment.setReportSummary(healthReportVo.getSummarize());healthAssessment.setDiseaseRisk(JSONUtil.toJsonStr(healthReportVo.getRiskDistribution()));healthAssessment.setAbnormalAnalysis(JSONUtil.toJsonStr(healthReportVo.getAbnormalData()));healthAssessment.setSystemScore(JSONUtil.toJsonStr(healthReportVo.getSystemScore()));save(healthAssessment);return healthAssessment.getId();
}/*** 計算護理等級** @param healthScore* @return*/
private String getNursingLevelName(double healthScore) {//處理邊界if (healthScore < 0 || healthScore > 100) {throw new IllegalArgumentException("健康評分必須在0到100之間");}if(healthScore >= 60 && healthScore < 70){return "三級護理等級";}else if (healthScore >= 70 && healthScore < 80){return "二級護理等級";}else if (healthScore >= 80 && healthScore < 90){return "一級護理等級";}else if (healthScore >= 90){return "特級護理等級";}return "無";}/*** 是否建議入住** @param healthScore* @return*/
private Integer getSuggestionForAdmission(double healthScore) {if (healthScore >= 60) {return 0;}return 1;}@Autowired
private RedisTemplate<String, String> redisTemplate;/*** 獲取prompt** @param healthAssessmentDto* @return*/
private String getPrompt(HealthAssessmentDto healthAssessmentDto) {//獲取文件中的內容String content = (String) redisTemplate.opsForHash().get("healthReport", healthAssessmentDto.getIdCard());//判斷是否為空if (StringUtils.isEmpty(content)) {throw new BaseException("文件提取內容失敗,請重新上傳提交報告");}String prompt = "請以一個專業醫生的視角來分析這份體檢報告,報告中包含了一些異常數據,我需要您對這些數據進行解讀,并給出相應的健康建議。\n" +"體檢內容如下:\n" +content + "\n" +"\n" +"要求:\n" +"1. 提取體檢報告中的“總檢日期”;\n" +"2. 通過臨床醫學、疾病風險評估模型和數據智能分析,給該用戶的風險等級和健康指數給出結果。風險等級分為:健康、提示、風險、危險、嚴重危險。健康指數范圍為0至100分;\n" +"3. 根據用戶身體各項指標數據,詳細說明該用戶各項風險等級的占比是多少,最多保留兩位小數。結論格式:該用戶健康占比20.00%,提示占比20.00%,風險占比20%,危險占比20%,嚴重危險占比20%;\n" +"4. 對于體檢報告有異常數據,請列出(異常數據的結論、體檢項目名稱、檢查結果、參考值、單位、異常解讀、建議)這8字段。解讀異常數據,解決這些數據可能代表的健康問題或風險。分析可能的原因,包括但不限于生活習慣、飲食習慣、遺傳因素等。基于這些異常數據和可能的原因,請給出具體的健康建議,包括飲食調整、運動建議、生活方式改變以及是否需要進一步檢查或治療等。\n" +"結論格式:異常數據的結論:肥胖,體檢項目名稱:體重指數BMI,檢查結果:29.2,參考值>24,單位:-。異常解讀:體重超標包括超重與肥胖。體重指數(BMI)=體重(kg)/身?(m)的平?,BMI≥24為超重,BMI≥28為肥胖;男性腰圍≥90cm和?性腰圍≥85cm為腹型肥胖。體重超標是?種由多因素(如遺傳、進?油脂較多、運動少、疾病等)引起的慢性代謝性疾病,尤其是肥胖,已經被世界衛?組織列為導致疾病負擔的??危險因素之?。AI建議:采取綜合措施預防和控制體重,積極改變?活?式,宜低脂、低糖、?纖維素膳?,多?果蔬及菌藻類?物,增加有氧運動。若有相關疾病(如?脂異常、??壓、糖尿病等)應積極治療。\n" +"5. 根據這個體檢報告的內容,分別是給人體的8大系統打分,每項滿分為100分,8大系統分別為:呼吸系統、消化系統、內分泌系統、免疫系統、循環系統、泌尿系統、運動系統、感官系統\n" +"6. 給體檢報告做一個總結,總結格式:體檢報告中尿蛋?、癌胚抗原、?沉、空腹?糖、總膽固醇、?油三酯、低密度脂蛋?膽固醇、?清載脂蛋?B、動脈硬化指數、?細胞、平均紅細胞體積、平均?紅蛋?共12項指標提示異常,尿液常規共1項指標處于臨界值,?脂、?液常規、尿液常規、糖類抗原、?清酶類等共43項指標提示正常,綜合這些臨床指標和數據分析:腎臟、肝膽、?腦?管存在隱患,其中?腦?管有“?危”?險;腎臟部位有“中危”?險;肝膽部位有“低危”?險。\n" +"\n" +"輸出要求:\n" +"最后,將以上結果輸出為JSON格式,不要包含其他的文字說明,所有的返回結果都是json,詳細格式如下:\n" +"\n" +"{\n" +" \"totalCheckDate\": \"YYYY-MM-DD\",\n" +" \"healthAssessment\": {\n" +" \"riskLevel\": \"healthy/caution/risk/danger/severeDanger\",\n" +" \"healthIndex\": XX.XX\n" +" },\n" +" \"riskDistribution\": {\n" +" \"healthy\": XX.XX,\n" +" \"caution\": XX.XX,\n" +" \"risk\": XX.XX,\n" +" \"danger\": XX.XX,\n" +" \"severeDanger\": XX.XX\n" +" },\n" +" \"abnormalData\": [\n" +" {\n" +" \"conclusion\": \"異常數據的結論\",\n" +" \"examinationItem\": \"體檢項目名稱\",\n" +" \"result\": \"檢查結果\",\n" +" \"referenceValue\": \"參考值\",\n" +" \"unit\": \"單位\",\n" +" \"interpret\":\"對于異常的結論進一步詳細的說明\",\n" +" \"advice\":\"針對于這一項的異常,給出一些健康的建議\"\n" +" }\n" +" ],\n" +" \"systemScore\": {\n" +" \"breathingSystem\": XX,\n" +" \"digestiveSystem\": XX,\n" +" \"endocrineSystem\": XX,\n" +" \"immuneSystem\": XX,\n" +" \"circulatorySystem\": XX,\n" +" \"urinarySystem\": XX,\n" +" \"motionSystem\": XX,\n" +" \"senseSystem\": XX\n" +" },\n" +" \"summarize\": \"體檢報告的總結\"\n" +"}";return prompt;
}
總結
主要是設置好Prompt然后結合PDF內容調用大模型,然后返回JSON數據解析組裝,好像一些大模
型直接使用OSS鏈接也可以,上傳PDF后key為身份證號 value為PDF內容 (其實OSS鏈接也可
以)點擊確認根據身份證號從Redis獲取PDF內容,組裝Prompt調用大模型,流程不復雜,理解了
其實就很容易,后續類似調用大模型需求也大概這種流程。