第9章 集成AI千帆大模型
學習目標
- 能夠說清楚健康評估模塊在項目中的作用
- 能夠掌握千帆大模型的開通和對接
- 能夠掌握健康評估模塊中的prompt提示詞編寫
- 能夠自主完成健康評估模塊的接口開發
分析設計
需求說明
健康評估是指老人辦理入住前需上傳體檢報告,由AI自動進行分析,并對報告進行總結,同時給出合理的建議;
下圖是健康評估列表頁,展示了經過健康評估的老人列表。
當點擊了上傳體檢報告按鈕之后會彈窗,效果如下,需要輸入信息,需要提前準備好老人的體檢報告(PDF格式)
點擊確定按鈕之后,會使用AI對老人的健康報告進行評估
下圖是健康評估的詳情頁面,是使用AI分析后的結果頁,給了很多的數據,可以讓護理員或銷售人員來查看老人的健康狀況,進一步更好的服務老人或者給老人推薦一些護理服務。
表結構說明
基于需求原型,咱們可以確定健康評估表(health_assessment)如下建表語句:
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='健康評估表';
特別字段說明:
- abnormal_analysis(異常分析) 這個字段是字符串類型,會把異常數據轉換為json進行存儲
接口說明
基于需求說明,在健康評估這個模塊中,共有4個接口需要開發,分別是
- 列表查詢-模板生成
- 上傳體檢報告-手動編寫
- 智能評測-手動編寫
- 查看詳情-模板生成
實現方案
整體實現流程如下:
讀取PDF文件內容
市面有很多工具類,咱們可以直接使用,目前選擇的是Apache PDFBox
Apache PDFBox庫是一個用于處理PDF文檔的開源Java工具。該項目允許創建新的PDF文檔,編輯現有的文檔,以及從文檔中提取內容。
導入對應的依賴,在zzyl-common模塊中導入以下依賴:
<!--pdf工具包-->
<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.24</version>
</dependency>
創建工具類,通過pdf文件來讀取內容,工具類路徑:zzyl-common模塊下的com.zzyl.common.utils.PDFUtil
package com.zzyl.common.utils;import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;import java.io.File;
import java.io.IOException;
import java.io.InputStream;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;}
}
測試:找一個pdf文檔來讀取里面的內容為字符串
package com.nursing.test;import com.zzyl.common.utils.PDFUtil;import java.io.FileInputStream;
import java.io.FileNotFoundException;public class PDFUtilTest {public static void main(String[] args) throws FileNotFoundException {FileInputStream fileInputStream = new FileInputStream("C:\\tmp\\體檢報告-劉愛國-男-69歲.pdf");String result = PDFUtil.pdfToString(fileInputStream);System.out.println(result);}
}
百度智能云AI集成
選擇合適的AI
咱們的需求中,prompt是比較多的,也就是說需要更多的token,對話Chat V2支持的模型列表如下:
https://cloud.baidu.com/doc/WENXINWORKSHOP/s/wm7ltcvgc
經過綜合評估,咱們本次采用的是ERNIE-4.0-8K
官方地址:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/clntwmv7t
ERNIE 4.0是百度自研的旗艦級超大規模?語?模型,相較ERNIE 3.5實現了模型能力全面升級,廣泛適用于各領域復雜任務場景;支持自動對接百度搜索插件,保障問答信息時效,支持5K tokens輸入+2K tokens輸出。
- 對話模型,一次請求就是一次對話
- 提供了java的sdk調用,地址:https://cloud.baidu.com/doc/qianfan-docs/s/nm9l6oc8e
- 支持單輪對話、多輪對話、流式
- 部分關鍵的參數:https://cloud.baidu.com/doc/qianfan-api/s/3m7of64lb
- 返回參數:result可以獲取對話返回的結果
集成準備步驟
1)注冊和實名認證百度智能云
2)創建應用
2.1)訪問千帆大模型,地址:https://qianfan.cloud.baidu.com/
2.2)點擊大模型服務與開發平臺ModelBuilder**,**可以進入到管理平臺
2.3)進入管理平臺后,找到應用接入,點擊創建應用按鈕。
2.4)創建一個新的API Key
3)項目集成
3.1)在父工程pom.xml文件中統一管理依賴的版本:
<properties><openai-java.version>2.8.1</openai-java.version><kotlin.version>1.9.23</kotlin.version><jackson.version>2.16.1</jackson.version><okhttp.version>4.12.0</okhttp.version>
</properties><dependencies><!-- 千帆AI --><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-bom</artifactId><version>${kotlin.version}</version><scope>import</scope><type>pom</type></dependency><!-- Kotlin Standard Library --><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId><version>${kotlin.version}</version></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId><version>${kotlin.version}</version></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId><version>${kotlin.version}</version></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId><version>${kotlin.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jdk8</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.fasterxml.jackson.module</groupId><artifactId>jackson-module-kotlin</artifactId><version>${jackson.version}</version></dependency><dependency><groupId>com.openai</groupId><artifactId>openai-java</artifactId><version>${openai-java.version}</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>${okhttp.version}</version></dependency>
</dependencies>
3.2)在common模塊的pom.xml文件中引入openai-java的依賴,并排除有版本沖突的依賴然后重新引入
<!-- 千帆AI -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId>
</dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jdk8</artifactId>
</dependency><dependency><groupId>com.fasterxml.jackson.datatype</groupId><artifactId>jackson-datatype-jsr310</artifactId>
</dependency><dependency><groupId>com.fasterxml.jackson.module</groupId><artifactId>jackson-module-kotlin</artifactId><exclusions><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId></exclusion></exclusions>
</dependency><!--MyBatisPlus再AI集成中要排除有版本沖突的依賴-->
<!--MyBatisPlus-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><exclusions><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId>
</dependency><dependency><groupId>com.openai</groupId><artifactId>openai-java</artifactId><exclusions><exclusion><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId></exclusion><exclusion><groupId>com.squareup.okio</groupId><artifactId>okio</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId></exclusion><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></exclusion></exclusions>
</dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId>
</dependency><!-- Kotlin Standard Library -->
<dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib</artifactId>
</dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId>
</dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId><exclusions><exclusion><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk7</artifactId></exclusion></exclusions>
</dependency>
3.3)參考官方示例(https://cloud.baidu.com/doc/qianfan-docs/s/nm9l6oc8e)編寫測試方法來學習
package com.zzyl;import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.ResponseFormatJsonObject;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;public class QianfanAIModelTest {public static void main(String[] args) {OpenAIClient client = OpenAIOkHttpClient.builder().apiKey("bce-v3/ALTAK-6TGebWXMraNYDUsJdvwoh/61483266642b4a18108057ce85a900850d4b7715") //將your_APIKey替換為真實值,如何獲取API Key請查看https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Um2wxbaps#步驟二-獲取api-key.baseUrl("https://qianfan.baidubce.com/v2/") //千帆ModelBuilder平臺地址.build();ChatCompletionCreateParams params = ChatCompletionCreateParams.builder().addUserMessage("你是誰?") // 對話messages信息.model("ernie-4.0-8k") // 模型對應的model值,請查看支持的模型列表:https://cloud.baidu.com/doc/WENXINWORKSHOP/s/wm7ltcvgc
// .responseFormat(ChatCompletionCreateParams.ResponseFormat.ofJsonObject(ResponseFormatJsonObject.builder().build())).build();ChatCompletion chatCompletion = client.chat().completions().create(params);System.out.println(chatCompletion.choices().get(0).message().content().orElseGet(() -> ""));}
}
代碼的apiKey要換成自己的
后續再接口開發中就要將AI集成到項目中。
接口開發
先用若依跟據表結構生成基礎代碼,便于后續代碼的改造開發。
該項目主要有兩個接口需要開發,上傳體檢報告和保存智能評測(AI生成評測數據)。
上傳體檢報告
為什么要有單獨的文件上傳接口,因為這個接口還有將數據添加到緩存的步驟。
可以由原先的通用上傳接口改造。
在功能模塊的pom.xml文件下:
<!-- 阿里云OSS -->
<dependency><groupId>com.zzyl</groupId><artifactId>zzyl-oss</artifactId>
</dependency>
工具類要存在common模塊的utils包下:
package com.zzyl.common.utils;import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;import java.io.File;
import java.io.IOException;
import java.io.InputStream;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;}
}
該工具類要生效確保pdf工具包依賴已經引入。
在控制層:
@Autowired
private AliyunOSSOperator aliyunOSSOperator;@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 通用上傳請求(單個)
*/
@ApiOperation("上傳體檢報告")
@PostMapping("/upload")
public AjaxResult uploadFile(MultipartFile file, String idCardNo) throws Exception
{try {// 上傳到OSSString url = aliyunOSSOperator.upload(file.getBytes(), file.getOriginalFilename());AjaxResult ajax = AjaxResult.success();ajax.put("url", url);ajax.put("fileName", url);ajax.put("newFileName", url.substring(url.lastIndexOf("/")));ajax.put("originalFilename", file.getOriginalFilename());// PDF文件內容讀取為字符串String content = PDFUtil.pdfToString(file.getInputStream());// 臨時存儲到redis中redisTemplate.opsForHash().put(CacheConstants.HEALTH_REPORT_ALL_KEY, idCardNo, content);return ajax;} catch (Exception e) {return AjaxResult.error(e.getMessage());}
}
保存智能評測(AI生成評測數據)
設計Prompt
Prompt的構成部分:
-
角色:給 AI 定義一個最匹配任務的角色,比如:「你是一位軟件工程師」「你是一位小學老師」
-
指示:對任務進行描述
-
上下文:給出與任務相關的其它背景信息(尤其在多輪交互中)
-
例子:必要時給出舉例,[實踐證明例子對輸出正確性有幫助]
-
輸入:任務的輸入信息;在提示詞中明確的標識出輸入
-
輸出:輸出的格式描述,以便后繼模塊自動解析模型的輸出結果,比如(JSON、Java)
設計Prompt提示詞,因為涉及了大量的專業名詞和健康指標,實際開發中需要找產品經理協助
請以一個專業醫生的視角來分析這份體檢報告,報告中包含了一些異常數據,我需要您對這些數據進行解讀,并給出相應的健康建議。 體檢內容如下:內容略....要求: 1. 提取體檢報告中的“總檢日期”; 2. 通過臨床醫學、疾病風險評估模型和數據智能分析,給該用戶的風險等級和健康指數給出結果。風險等級分為:健康、提示、風險、危險、嚴重危險。健康指數范圍為0至100分; 3. 根據用戶身體各項指標數據,詳細說明該用戶各項風險等級的占比是多少,最多保留兩位小數。結論格式:該用戶健康占比20.00%,提示占比20.00%,風險占比20%,危險占比20%,嚴重危險占比20%; 4. 對于體檢報告中的異常數據,請列出(異常數據的結論、體檢項目名稱、檢查結果、參考值、單位、異常解讀、建議)這7字段。解讀異常數據,解決這些數據可能代表的健康問題或風險。分析可能的原因,包括但不限于生活習慣、飲食習慣、遺傳因素等。基于這些異常數據和可能的原因,請給出具體的健康建議,包括飲食調整、運動建議、生活方式改變以及是否需要進一步檢查或治療等。 結論格式:異常數據的結論:肥胖,體檢項目名稱:體重指數BMI,檢查結果:29.2,參考值>24,單位:-。異常解讀:體重超標包括超重與肥胖。體重指數(BMI)=體重(kg)/身?(m)的平?,BMI≥24為超重,BMI≥28為肥胖;男性腰圍≥90cm和?性腰圍≥85cm為腹型肥胖。體重超標是?種由多因素(如遺傳、進?油脂較多、運動少、疾病等)引起的慢性代謝性疾病,尤其是肥胖,已經被世界衛?組織列為導致疾病負擔的??危險因素之?。AI建議:采取綜合措施預防和控制體重,積極改變?活?式,宜低脂、低糖、?纖維素膳?,多?果蔬及菌藻類?物,增加有氧運動。若有相關疾病(如?脂異常、??壓、糖尿病等)應積極治療。 5. 根據這個體檢報告的內容,分別給人體的8大系統打分,每項滿分為100分,8大系統分別為:呼吸系統、消化系統、內分泌系統、免疫系統、循環系統、泌尿系統、運動系統、感官系統 6. 給體檢報告做一個總結,總結格式:體檢報告中尿蛋?、癌胚抗原、?沉、空腹?糖、總膽固醇、?油三酯、低密度脂蛋?膽固醇、?清載脂蛋?B、動脈硬化指數、?細胞、平均紅細胞體積、平均?紅蛋?共12項指標提示異常,尿液常規共1項指標處于臨界值,?脂、?液常規、尿液常規、糖類抗原、?清酶類等共43項指標提示正常,綜合這些臨床指標和數據分析:腎臟、肝膽、?腦?管存在隱患,其中?腦?管有“?危”?險;腎臟部位有“中危”?險;肝膽部位有“低危”?險。輸出要求: 最后,將以上結果輸出為純JSON格式,不要包含其他的文字說明,也不要出現Markdown語法相關的文字,所有的返回結果都是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": "體檢報告的總結" }
功能編寫步驟
當用戶上傳體檢報告之后,點擊確定,就會觸發智能評測的調用。
1)一些可變的參數,在application.yml文件中定義:
# 百度千帆大模型配置
baidu:qianfan:apiKey: bce-v3/ALTAK-6H6k7kuLyTmZPBmOJ2ajC/2520d4ad36847715153ca819932f1d854bb9e71f(換成自己的apiKey)baseUrl: https://qianfan.baidubce.com/v2/model: ernie-4.0-8k
2)在zzyl-common模塊下定義工具類:
2.2)在zzyl-common模塊下定義BaiduAIProperties配置類和AIModelInvoker工具類:
com.zzyl.common.ai.BaiduAIProperties配置類:
package com.zzyl.common.ai;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;@Data
@Configuration
@ConfigurationProperties(prefix = "baidu.qianfan")
public class BaiduAIProperties {private String apiKey;private String baseUrl;private String model;
}
com.zzyl.common.ai.AIModelInvoker工具類:
package com.zzyl.common.ai;import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.ResponseFormatJsonObject;
import com.openai.models.chat.completions.ChatCompletion;
import com.openai.models.chat.completions.ChatCompletionCreateParams;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
@Slf4j
public class AIModelInvoker {@Autowiredprivate BaiduAIProperties baiduAIProperties;public String qianfanInvoker(String prompt) {OpenAIClient client = OpenAIOkHttpClient.builder().apiKey(baiduAIProperties.getApiKey()).baseUrl(baiduAIProperties.getBaseUrl()).build();ChatCompletionCreateParams params = ChatCompletionCreateParams.builder().addUserMessage(prompt).model(baiduAIProperties.getModel()).responseFormat(ChatCompletionCreateParams.ResponseFormat.ofJsonObject(ResponseFormatJsonObject.builder().build())).build();ChatCompletion chatCompletion = client.chat().completions().create(params);return chatCompletion.choices().get(0).message().content().orElseGet(() -> "");}
}
2.2)跟據身份證提取個人信息的IDCardUtils工具類
package com.zzyl.common.utils;import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.format.DateTimeFormatter;public final class IDCardUtils {// 私有構造器,防止實例化private IDCardUtils() {throw new AssertionError("Cannot instantiate static class IDCardUtils");}/*** 根據身份證號提取個人年齡。** @param idCard 身份證號碼* @return 個人年齡,數值類型*/public static int getAgeByIdCard(String idCard) {LocalDate birthDate = extractBirthDate(idCard);if (birthDate != null) {LocalDate currentDate = LocalDate.now();return Period.between(birthDate, currentDate).getYears();}throw new IllegalArgumentException("Invalid ID card number.");}/*** 根據身份證號提取出生日期。** @param idCard 身份證號碼* @return 出生日期,LocalDateTime類型*/public static LocalDateTime getBirthDateByIdCard(String idCard) {String birthDateString = idCard.substring(6, 14); // 身份證號碼中的出生日期部分DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");LocalDate birthDate = LocalDate.parse(birthDateString, formatter);return LocalDateTime.of(birthDate, LocalTime.MIDNIGHT);}/*** 根據身份證號提取性別。** @param idCard 身份證號碼* @return 性別,0表示女性,1表示男性*/public static int getGenderFromIdCard(String idCard) {if (idCard == null || idCard.length() != 18) {throw new IllegalArgumentException("Invalid ID card number");}String genderCode = idCard.substring(16, 17);int gender = Integer.parseInt(genderCode);return gender % 2 == 0 ? 0 : 1;}/*** 內部輔助方法,從身份證號中提取出生日期的LocalDate對象。** @param idCard 身份證號碼* @return 出生日期的LocalDate對象,或null(如果身份證號碼無效)*/private static LocalDate extractBirthDate(String idCard) {if (idCard == null || idCard.length() != 18) {return null;}try {String birthStr = idCard.substring(6, 14);DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");return LocalDate.parse(birthStr, formatter);} catch (Exception e) {return null;}}public static void main(String[] args) {System.out.println(getAgeByIdCard("330103199001011234"));System.out.println(getBirthDateByIdCard("330103199001011234"));}
}
3)改造MVC代碼
3.1)修改IHealthAssessmentService中的insertHealthAssessment方法,把返回值類型替換為Long
/*** 新增健康評估* * @param healthAssessment 健康評估* @return 結果*/
public Long insertHealthAssessment(HealthAssessment healthAssessment);
3.2)實現類中的返回值類型也要改為Long,并且按照思路來編寫業務代碼
最終代碼如下:
@Autowired
private RedisTemplate<String, String> redisTemplate;@Autowired
private AIModelInvoker aiModelInvoker;/*** 新增健康評估* * @param healthAssessment 健康評估* @return 結果*/
@Override
public Long insertHealthAssessment(HealthAssessment healthAssessment)
{// 1.設計Prompt提示詞(需要從Redis中讀取當前身份證號對應的體檢報告)String prompt = getPrompt(healthAssessment.getIdCard());// 2.調用百度千帆大模型,分析體檢報告,獲取分析結果String qianfanResult = aiModelInvoker.qianfanInvoker(prompt);// 3.將分析結果保存到數據庫中,并返回保存的這條記錄的id// 將大模型返回的字符串解析為對象,方便取數據HealthReportVo healthReportVo = JSON.parseObject(qianfanResult, HealthReportVo.class);return saveHealthAssessment(healthReportVo, healthAssessment);
}/*** 保存大模型返回的結果和前端傳遞的老人信息到數據庫* @param healthReportVo 千帆大模型返回的結果* @param healthAssessment 老人基本信息* @return 記錄的id*/
private Long saveHealthAssessment(HealthReportVo healthReportVo, HealthAssessment healthAssessment) {// 老人身份證號String idCard = healthAssessment.getIdCard();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(healthScore >= 60 ? 0 : 1);// 通過健康評分計算一個推薦的護理等級String nursingLevelName = getLevelNameByHealthScore(healthScore);healthAssessment.setNursingLevelName(nursingLevelName);// 統一先設置未入住healthAssessment.setAdmissionStatus(1);// 總檢日期healthAssessment.setTotalCheckDate(healthReportVo.getTotalCheckDate());healthAssessment.setAssessmentTime(LocalDateTime.now());// 報告總結healthAssessment.setReportSummary(healthReportVo.getSummarize());// 疾病風險分布healthAssessment.setDiseaseRisk(JSON.toJSONString(healthReportVo.getRiskDistribution()));// 異常分析healthAssessment.setAbnormalAnalysis(JSON.toJSONString(healthReportVo.getAbnormalData()));// 八大系統評分healthAssessment.setSystemScore(JSON.toJSONString(healthReportVo.getSystemScore()));healthAssessmentMapper.insert(healthAssessment);return healthAssessment.getId();
}private String getLevelNameByHealthScore(double healthScore) {if (healthScore > 100 || healthScore < 0) {throw new BaseException("健康評分值不合法");}if (healthScore >= 90) {return "四級護理等級";} else if (healthScore >= 80) {return "三級護理等級";} else if (healthScore >= 70) {return "二級護理等級";} else if (healthScore >= 60) {return "一級護理等級";} else {return "特級護理等級";}
}/*** 獲取Prompt提示詞* @param idCard 身份證號* @return 提示詞*/
private String getPrompt(String idCard) {// 獲取文件中的內容String content = (String) redisTemplate.opsForHash().get("healthReport", idCard);// 判斷是否為空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. 對于體檢報告中的異常數據,請列出(異常數據的結論、體檢項目名稱、檢查結果、參考值、單位、異常解讀、建議)這7字段。解讀異常數據,解決這些數據可能代表的健康問題或風險。分析可能的原因,包括但不限于生活習慣、飲食習慣、遺傳因素等。基于這些異常數據和可能的原因,請給出具體的健康建議,包括飲食調整、運動建議、生活方式改變以及是否需要進一步檢查或治療等。\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格式,不要包含其他的文字說明,也不要出現Markdown語法相關的文字,所有的返回結果都是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;
}
Vo可跟據Json格式生成并編寫嚴格的提示詞生成。
HealthReportVo值對象:
package com.zzyl.nursing.vo;import lombok.Data;import java.util.List;/*** 體檢報告* @author itheima*/
@Data
public class HealthReportVo {/*** 體檢日期*/private String totalCheckDate;/*** 健康評估*/private HealthAssessmentVo healthAssessment;/*** 風險分布*/private RiskDistributionVo riskDistribution;/*** 異常數據列表*/private List<AbnormalDataVo> abnormalData;/*** 健康系統分值*/private SystemScore systemScore;/*** 綜合總結*/private String summarize;
}
HealthAssessmentVo值對象:
package com.zzyl.nursing.vo;import lombok.Data;/*** 健康評估類* @author itheima*/
@Data
public class HealthAssessmentVo {/*** 健康風險等級*/private String riskLevel;/*** 健康指數*/private double healthIndex;}
AbnormalDataVo值對象:
package com.zzyl.nursing.vo;import lombok.Data;/*** 異常數據類* @author itheima*/
@Data
public class AbnormalDataVo {/*** 結論*/private String conclusion;/*** 檢查項目*/private String examinationItem;/*** 結果*/private String result;/*** 參考值*/private String referenceValue;/*** 單位*/private String unit;/*** 結果解釋*/private String interpret;/*** 建議*/private String advice;}
RiskDistributionVo值對象:
package com.zzyl.nursing.vo;import lombok.Data;/*** 風險分布類* @author itheima*/
@Data
public class RiskDistributionVo {/*** 健康*/private double healthy;/*** 警告*/private double caution;/*** 風險*/private double risk;/*** 危險*/private double danger;/*** 嚴重危險*/private double severeDanger;}
SystemScore值對象:
package com.zzyl.nursing.vo;import lombok.Data;@Data
public class SystemScore {/*** 呼吸系統*/private int breathingSystem;/*** 消化系統*/private int digestiveSystem;/*** 內分泌系統*/private int endocrineSystem;/*** 免疫系統*/private int immuneSystem;/*** 循環系統*/private int circulatorySystem;/*** 泌尿系統*/private int urinarySystem;/*** 感覺系統*/private int motionSystem;/*** 感官系統*/private int senseSystem;
}