From 133c326fb19e1be60f61949526d63fa028d6c6fe Mon Sep 17 00:00:00 2001 From: lq Date: Mon, 1 Dec 2025 12:12:30 +0800 Subject: [PATCH] =?UTF-8?q?ai=E5=AF=BB=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/ai/AiController.php | 41 +++ app/Service/common/ai/PromptService.php | 121 ++++++++ .../common/ai/xunfei/XunFeiAiService.php | 43 +++ .../ai/xunfei/client/XunFeiAiClientV1.php | 248 +++++++++++++++++ .../ai/xunfei/client/XunFeiAiClientV2.php | 258 ++++++++++++++++++ routes/api.php | 9 + 6 files changed, 720 insertions(+) create mode 100755 app/Http/Controllers/Api/ai/AiController.php create mode 100644 app/Service/common/ai/PromptService.php create mode 100755 app/Service/common/ai/xunfei/XunFeiAiService.php create mode 100755 app/Service/common/ai/xunfei/client/XunFeiAiClientV1.php create mode 100755 app/Service/common/ai/xunfei/client/XunFeiAiClientV2.php diff --git a/app/Http/Controllers/Api/ai/AiController.php b/app/Http/Controllers/Api/ai/AiController.php new file mode 100755 index 00000000..fc3ad8c0 --- /dev/null +++ b/app/Http/Controllers/Api/ai/AiController.php @@ -0,0 +1,41 @@ +insertField = ['name', 'value', 'desc']; + $this->updateField = ['id', 'name', 'value', 'desc']; + $this->service = XunFeiAiService::getInstance(); + } + + public function genSingV1() + { + $this->insertField = ['time', 'term', 'version']; + $param = $this->checkRequiredFields(request()->post()); + + $versionMap = [ + 'fortune' => 1, + 'wellness' => 2 + ]; + if (!isset($versionMap[$param['version']])) { + return jerr('版本号错误'); + } + return jok( + $this->service->genSign($param['time'], $param['term'], $versionMap[$param['version']]), + '生成成功' + ); + } +} diff --git a/app/Service/common/ai/PromptService.php b/app/Service/common/ai/PromptService.php new file mode 100644 index 00000000..cb3309f4 --- /dev/null +++ b/app/Service/common/ai/PromptService.php @@ -0,0 +1,121 @@ +formatPatientInfo($userInfo); + $drugsInfo = $this->formatDrugsInfo($drugs); + + $drugsInfo = base64_encode($drugsInfo); + $prompt = "你是一名专业的医生,请根据以下患者信息和可用药品,开具合理的处方: + ## 患者信息: + {$patientInfo} + ## 可用药品列表(base64_encode后的): + {$drugsInfo} + ## 请按照以下JSON格式返回处方建议: + { + \"ai_diagnosis\": \"AI诊断描述\", + \"ai_advice\": \"AI医嘱\", + \"medication_advice\": [ + { +// \"index_id\": \"药品ID\", + \"drug_id\": \"药品ID\", + \"drug_name\": \"药品名称\", + \"single_dosage\": \"单次用量\", + } + ], + \"frequency_description\": \"用药频次说明\", + \"treatment_period\": \"治疗周期说明\", + \"frequency\": \"每天次数(数字格式)\", + \"days\": \"用药天数(数字格式)\" + } + ## 要求: + 1. 诊断要基于患者年龄、病史和当前症状 + 2. 用药建议要合理,考虑药品适应症和患者情况,只能选择我给出的药品列表中的药品 + 3. 单次用量采用数字类型单位都是g + 4. 频次要合理(如:3,就是一天三次的意思) + 5. 用药天数要根据病情严重程度确定"; + + return $prompt; + } + + // 求签提示词封装 + public function genSignPrompt($toDay, $dayDesc = '寻常日'): string + { + if (empty($dayDesc)) { + $dayDesc = '寻常日'; + } + // toDay是日期yyyy-mm-dd + // dayDesc是节气默认寻常日 + $prompt = "今天是{$toDay},{$dayDesc}。请模仿隐世高人,为我求签。包含【签文】(七言绝句)、【解曰】(禅意指点)。"; + return $prompt; + } + + // 求签提示词封装 + public function genSignPrompt2($toDay, $dayDesc = '寻常日'): string + { + // toDay是日期yyyy-mm-dd + // dayDesc是节气默认寻常日 + $prompt = "今天是{$toDay},{$dayDesc}。请模仿老中医,根据这个季节和临近的节气,给出现代人的养生方。包含饮食、起居。要用一段或几段描述。切记不要使用md格式,请使用纯文本格式。并且语气要有仙风道骨的感觉"; + return $prompt; + } +} diff --git a/app/Service/common/ai/xunfei/XunFeiAiService.php b/app/Service/common/ai/xunfei/XunFeiAiService.php new file mode 100755 index 00000000..ea4f3541 --- /dev/null +++ b/app/Service/common/ai/xunfei/XunFeiAiService.php @@ -0,0 +1,43 @@ +isAuth = false; + parent::__construct(); +// $this->client = XunFeiAiClientV2::getInstance(); + $this->client = XunFeiAiClientV1::getInstance(); + } + + public function genSign(string $time, $dayDesc, $version = 1) + { + // 构建提示词 + $prompt = $this->client->genSignPrompt($time, $dayDesc, $version); +// $prompt = $this->client->buildPrescriptionPrompt($userInfo, $drugModel->toArray()); + + // 发送请求(模拟) + $requestResult = $this->client->sendRequest($prompt, 'text'); + if ($requestResult['code'] !== 0) { + throw new Exception('AI请求失败: ' . ($requestResult['message'] ?? '未知错误')); + } + + // 解析响应(模拟) + $response = $this->client->parseResponse($requestResult); + + return $response; + } + +} diff --git a/app/Service/common/ai/xunfei/client/XunFeiAiClientV1.php b/app/Service/common/ai/xunfei/client/XunFeiAiClientV1.php new file mode 100755 index 00000000..89347827 --- /dev/null +++ b/app/Service/common/ai/xunfei/client/XunFeiAiClientV1.php @@ -0,0 +1,248 @@ +baseUrl = 'https://spark-api-open.xf-yun.com/v2/chat/completions'; + $this->baseUrl = 'https://spark-api-open.xf-yun.com/v1/chat/completions'; + $this->headers['Authorization'] = $this->apiPassword; + $this->promptService = new PromptService(); + } + + + public function genSignPrompt(string $time, $dayDesc, $version = 1) + { + if (empty($time)) { + $time = get_time(true, 'Y-m-d'); + } + + if ($version == 1) { + $prompt = $this->promptService->genSignPrompt($time, $dayDesc); + } else { + $prompt = $this->promptService->genSignPrompt2($time, $dayDesc); + } + return $prompt. '\n 要求:不使用Markdown,纯文本输出,不要出现有关于Markdown的语法,分段清晰。'; + } + + /** + * 患者画像分析 - 构建提示词 + */ + public function buildPatientProfilePrompt(array $userInfo, array $prescriptionHistory, string $diagnosis): string + { + $patientInfo = $this->formatPatientInfo($userInfo); + $historyInfo = $this->formatPrescriptionHistory($prescriptionHistory); + + $prompt = "你是一名医疗数据分析专家,请根据以下患者信息分析患者画像: + + 患者基本信息: + {$patientInfo} + + 诊断信息:{$diagnosis} + + 处方历史: + {$historyInfo} + + 请生成一个完整的患者画像分析报告,包含以下部分: + 1. 患者基本信息总结 + 2. 健康状况评估 + 3. 用药习惯分析 + 4. 疾病风险预测 + 5. 个性化医疗建议 + + 请将分析结果以HTML格式返回,确保可以直接在iframe中展示。HTML结构要完整,包含必要的CSS样式。"; + + return $prompt; + } + + /** + * 用户增长分析 - 构建提示词 + */ + public function buildUserGrowthPrompt(string $type, array $growthData): string + { + $typeMap = [ + 0 => '天', + 1 => '周', + 2 => '月', + 3 => '年' + ]; + + $periodType = $typeMap[$type] ?? '未知周期'; + $dataSummary = $this->formatGrowthData($growthData); + + $prompt = "你是一名数据分析专家,请根据以下用户增长数据进行分析和预测: + + 统计周期:按{$periodType}统计 + 增长数据: + {$dataSummary} + + 请完成以下分析: + 1. 当前增长趋势分析(上升/下降/平稳) + 2. 增长模式识别(季节性、周期性等) + 3. 未来3个{$periodType}的用户增长预测 + 4. 增长建议和优化策略 + + 请将分析结果和预测以HTML格式返回,包含图表说明和趋势可视化(使用纯HTML+CSS实现简单的数据可视化),确保可以直接在iframe中展示。需要包含完整的HTML结构。"; + + return $prompt; + } + + /** + * 格式化患者信息 + */ + private function formatPatientInfo(array $userInfo): string + { + $info = []; + $info[] = "年龄:" . ($userInfo['age'] ?? '未知'); + $info[] = "性别:" . ($userInfo['gender'] ?? '未知'); + $info[] = "诊断:" . ($userInfo['diagnosis'] ?? '未提供'); + + if (!empty($userInfo['medical_history'])) { + $info[] = "患病历史:" . (is_array($userInfo['medical_history']) ? + implode('、', $userInfo['medical_history']) : $userInfo['medical_history']); + } + + if (!empty($userInfo['treatment_history'])) { + $info[] = "就诊历史:" . (is_array($userInfo['treatment_history']) ? + implode('、', $userInfo['treatment_history']) : $userInfo['treatment_history']); + } + + if (!empty($userInfo['symptoms'])) { + $info[] = "症状:" . (is_array($userInfo['symptoms']) ? + implode('、', $userInfo['symptoms']) : $userInfo['symptoms']); + } + + return implode("\n", $info); + } + + /** + * 格式化药品信息 + */ + private function formatDrugsInfo(array $drugs): string + { + $drugList = []; + foreach ($drugs as $drug) { +// $drugInfo = "{index_id: {$drug['index_id']}, 药品ID: {$drug['drug_id']}, '名称: {$drug['drug_name']}', 单价: '{$drug['price']}'}"; +// $drugInfo = "index_id: {$drug['index_id']}, 药品ID: {$drug['drug_id']}, '名称: {$drug['drug_name']}'"; + $drugInfo = "ID:{$drug['drug_id']},名称:{$drug['drug_name']}"; + + if (!empty($drug['indications']?? '')) { + $drugInfo .= ", 适应症: {$drug['indications']}"; + } + + if (!empty($drug['contraindications']?? '')) { + $drugInfo .= ", 禁忌: {$drug['contraindications']}"; + } + + $drugList[] = $drugInfo; + } + + return implode("\n", $drugList); +// return '{'. implode(",", $drugList). '}'; + } + + /** + * 格式化处方历史 + */ + private function formatPrescriptionHistory(array $prescriptionHistory): string + { + if (empty($prescriptionHistory)) { + return "无处方历史记录"; + } + + $historyList = []; + foreach ($prescriptionHistory as $index => $history) { + $historyInfo = "处方" . ($index + 1) . ": "; + $historyInfo .= "日期: " . ($history['date'] ?? '未知') . ", "; + $historyInfo .= "诊断: " . ($history['diagnosis'] ?? '未知') . ", "; + $historyInfo .= "药品: " . (is_array($history['drugs']) ? + implode('、', $history['drugs']) : ($history['drugs'] ?? '无')); + + $historyList[] = $historyInfo; + } + + return implode("\n", $historyList); + } + + /** + * 格式化增长数据 + */ + private function formatGrowthData(array $growthData): string + { + $dataList = []; + foreach ($growthData as $period => $count) { + $dataList[] = "{$period}: {$count} 用户"; + } + + return implode("\n", $dataList); + } + + /** + * 发送请求到讯飞星火API(模拟实现) + */ + public function sendRequest(string $prompt, string $type): array + { + if (empty($type)) { + $type = 'json_object'; + } + // 这里构建实际的API请求参数 + $requestData = [ + 'model' => 'lite', + 'user' => 'user_123456', + 'messages' => [ + [ + 'role' => 'system', + 'content' => $prompt +// 'content' => '你好,返回格式要用json:{id: xxxx, content: 你好}' + ] + ], + 'temperature' => 0.7, + 'response_format' => [ + 'type' => $type + ], + ]; + $result = $this->init()->postJson('', $requestData); + + if (!array_key_exists('body', $result)) { + $this->utils->errorThrow('OldShopApi Http请求失败: '. json_encode($result)); + } + if ($result['body']['code'] !== 0) { + $this->utils->errorThrow('OldShopApi Http请求失败: '. $result['body']['errmsg']); + } + return $result['body']; + } + + /** + * 解析AI响应(模拟实现) + */ + public function parseResponse(array $apiResponse): array + { + // 在实际实现中,这里会解析讯飞星火的API响应 +// 替换 $apiResponse['choices'][0]['message']['content']的```json + $content = $apiResponse['choices'][0]['message']['content']; +// $content = str_replace('\"', '"', $content); +// $content = str_replace(PHP_EOL, '', $content); +// $content = str_replace(' ', '', $content); +// $content = str_replace(' ', '', $content); +// $content = str_replace('```json', '', $content); +// $content = str_replace('```', '', $content); +// $content = json_decode($content, true); + // 现在返回模拟数据 + return [ + 'success' => true, + 'data' => $content, + 'message' => '响应解析完成' + ]; + } +} diff --git a/app/Service/common/ai/xunfei/client/XunFeiAiClientV2.php b/app/Service/common/ai/xunfei/client/XunFeiAiClientV2.php new file mode 100755 index 00000000..4a81f44a --- /dev/null +++ b/app/Service/common/ai/xunfei/client/XunFeiAiClientV2.php @@ -0,0 +1,258 @@ +baseUrl = 'https://spark-api-open.xf-yun.com/v2/chat/completions'; +// $this->baseUrl = 'https://spark-api-open.xf-yun.com/v1/chat/completions'; + $this->headers['Authorization'] = $this->apiPassword; + $this->promptService = new PromptService(); + } + + /** + * 开处方功能 - 构建提示词 + */ + public function buildPrescriptionPrompt(array $userInfo, array $drugs): string + { + $patientInfo = $this->promptService->formatPatientInfo($userInfo); + $drugsInfo = $this->promptService->formatDrugsInfo($drugs); + + $prompt = "你是一名专业的医生,请根据以下患者信息和可用药品,开具合理的处方: + ## 患者信息: + {$patientInfo} + ## 可用药品列表: + {$drugsInfo} + ## 请按照以下JSON格式返回处方建议: + { + \"ai_diagnosis\": \"AI诊断描述\", + \"ai_diagnosis\": \"AI医嘱\", + \"medication_advice\": [ + { + \"index_id\": \"药品ID\", + \"drug_id\": \"药品ID\", + \"drug_name\": \"药品名称\", + \"single_dosage\": \"单次用量(g)\", + } + ], + \"frequency_description\": \"用药频次说明\", + \"treatment_period\": \"治疗周期说明\", + \"frequency\": \"每天次数\", + \"days\": \"用药天数\" + } + ## 要求: + 1. 诊断要基于患者年龄、病史和当前症状 + 2. 用药建议要合理,考虑药品适应症和患者情况 + 3. 单次用量要明确(如:1片、10ml等) + 4. 频次要合理(如:一天3次) + 5. 用药天数要根据病情严重程度确定"; + + return $prompt; + } + + /** + * 患者画像分析 - 构建提示词 + */ + public function buildPatientProfilePrompt(array $userInfo, array $prescriptionHistory, string $diagnosis): string + { + $patientInfo = $this->formatPatientInfo($userInfo); + $historyInfo = $this->formatPrescriptionHistory($prescriptionHistory); + + $prompt = "你是一名医疗数据分析专家,请根据以下患者信息分析患者画像: + + 患者基本信息: + {$patientInfo} + + 诊断信息:{$diagnosis} + + 处方历史: + {$historyInfo} + + 请生成一个完整的患者画像分析报告,包含以下部分: + 1. 患者基本信息总结 + 2. 健康状况评估 + 3. 用药习惯分析 + 4. 疾病风险预测 + 5. 个性化医疗建议 + + 请将分析结果以HTML格式返回,确保可以直接在iframe中展示。HTML结构要完整,包含必要的CSS样式。"; + + return $prompt; + } + + /** + * 用户增长分析 - 构建提示词 + */ + public function buildUserGrowthPrompt(string $type, array $growthData): string + { + $typeMap = [ + 0 => '天', + 1 => '周', + 2 => '月', + 3 => '年' + ]; + + $periodType = $typeMap[$type] ?? '未知周期'; + $dataSummary = $this->formatGrowthData($growthData); + + $prompt = "你是一名数据分析专家,请根据以下用户增长数据进行分析和预测: + + 统计周期:按{$periodType}统计 + 增长数据: + {$dataSummary} + + 请完成以下分析: + 1. 当前增长趋势分析(上升/下降/平稳) + 2. 增长模式识别(季节性、周期性等) + 3. 未来3个{$periodType}的用户增长预测 + 4. 增长建议和优化策略 + + 请将分析结果和预测以HTML格式返回,包含图表说明和趋势可视化(使用纯HTML+CSS实现简单的数据可视化),确保可以直接在iframe中展示。需要包含完整的HTML结构。"; + + return $prompt; + } + + /** + * 格式化患者信息 + */ + private function formatPatientInfo(array $userInfo): string + { + $info = []; + $info[] = "年龄:" . ($userInfo['age'] ?? '未知'); + $info[] = "性别:" . ($userInfo['gender'] ?? '未知'); + $info[] = "诊断:" . ($userInfo['diagnosis'] ?? '未提供'); + + if (!empty($userInfo['medical_history'])) { + $info[] = "患病历史:" . (is_array($userInfo['medical_history']) ? + implode('、', $userInfo['medical_history']) : $userInfo['medical_history']); + } + + if (!empty($userInfo['treatment_history'])) { + $info[] = "就诊历史:" . (is_array($userInfo['treatment_history']) ? + implode('、', $userInfo['treatment_history']) : $userInfo['treatment_history']); + } + + if (!empty($userInfo['symptoms'])) { + $info[] = "症状:" . (is_array($userInfo['symptoms']) ? + implode('、', $userInfo['symptoms']) : $userInfo['symptoms']); + } + + return implode("\n", $info); + } + + /** + * 格式化药品信息 + */ + private function formatDrugsInfo(array $drugs): string + { + $drugList = []; + foreach ($drugs as $drug) { + $drugInfo = "{index_id: {$drug['index_id']}, 药品ID: {$drug['drug_id']}, '名称: {$drug['drug_name']}', 单价: '{$drug['price']}'}"; +// $drugInfo = "index_id: {$drug['index_id']}, 药品ID: {$drug['drug_id']}, '名称: {$drug['drug_name']}'"; +// $drugInfo = "ID:{$drug['drug_id']},名称:{$drug['drug_name']}"; + + if (!empty($drug['indications']?? '')) { + $drugInfo .= ", 适应症: {$drug['indications']}"; + } + + if (!empty($drug['contraindications']?? '')) { + $drugInfo .= ", 禁忌: {$drug['contraindications']}"; + } + + $drugList[] = $drugInfo; + } + + return implode("\n", $drugList); +// return '{'. implode(",", $drugList). '}'; + } + + /** + * 格式化处方历史 + */ + private function formatPrescriptionHistory(array $prescriptionHistory): string + { + if (empty($prescriptionHistory)) { + return "无处方历史记录"; + } + + $historyList = []; + foreach ($prescriptionHistory as $index => $history) { + $historyInfo = "处方" . ($index + 1) . ": "; + $historyInfo .= "日期: " . ($history['date'] ?? '未知') . ", "; + $historyInfo .= "诊断: " . ($history['diagnosis'] ?? '未知') . ", "; + $historyInfo .= "药品: " . (is_array($history['drugs']) ? + implode('、', $history['drugs']) : ($history['drugs'] ?? '无')); + + $historyList[] = $historyInfo; + } + + return implode("\n", $historyList); + } + + /** + * 格式化增长数据 + */ + private function formatGrowthData(array $growthData): string + { + $dataList = []; + foreach ($growthData as $period => $count) { + $dataList[] = "{$period}: {$count} 用户"; + } + + return implode("\n", $dataList); + } + + /** + * 发送请求到讯飞星火API(模拟实现) + */ + public function sendRequest(string $prompt): array + { + // 这里构建实际的API请求参数 + $requestData = [ + 'model' => 'spark-x', + 'messages' => [ + [ + 'role' => 'user', + 'content' => $prompt + ] + ], + 'temperature' => 0.7, + ]; + $result = $this->init()->postJson('', $requestData); + + ds($result); + if (!array_key_exists('body', $result)) { + $this->utils->errorThrow('OldShopApi Http请求失败: '. json_encode($result)); + } + if ($result['body']['code'] !== 0) { + $this->utils->errorThrow('OldShopApi Http请求失败: '. $result['body']['errmsg']); + } + return $result['body']; + } + + /** + * 解析AI响应(模拟实现) + */ + public function parseResponse(array $apiResponse): array + { + // 在实际实现中,这里会解析讯飞星火的API响应 + // 现在返回模拟数据 + return [ + 'success' => true, + 'data' => $apiResponse, + 'message' => '响应解析完成' + ]; + } +} diff --git a/routes/api.php b/routes/api.php index f095c400..16ffb39f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -11,6 +11,14 @@ Route::get('/', function () { Route::post('/sql/test-connection', [SqlInstallController::class, 'testConnection']); Route::post('/sql/start-installation', [SqlInstallController::class, 'startInstallation']); + +///** +// * 自动注册路由 +// */ +//UtilsService::class::getInstance()->autoRouteRegister([ +// 'ai' => \App\Http\Controllers\Api\ai\AiController::class, // ai功能暂时不用登录 +// // 不需要登录的路由生成地址 +//]); /* * -----------------------------------以下注释不要删除-------------------------------- * 不需要登录的路由生成地址 @@ -24,6 +32,7 @@ UtilsService::class::getInstance()->autoRouteRegister([ '' => \App\Http\Controllers\Api\LoginController::class, // 登录控制器 'code' => \App\Http\Controllers\core\CodeGenerationController::class, // 代码生成控制器 // 不需要登录的路由生成地址 + 'ai' => \App\Http\Controllers\Api\ai\AiController::class, // ai功能暂时不用登录 ]); Route::group([], function () {