This commit is contained in:
2025-12-01 12:12:30 +08:00
parent 527373853e
commit 133c326fb1
6 changed files with 720 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Api\ai;
use App\BaseApp\BaseController;
use App\Service\common\ai\xunfei\XunFeiAiService;
use App\Service\RoleService;
use Exception;
use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
class AiController extends BaseController
{
//
public function __construct()
{
parent::__construct();
$this->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']]),
'生成成功'
);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Service\common\ai;
class PromptService
{
/**
* 格式化药品信息
*/
public 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). '}';
}
/**
* 格式化患者信息
*/
public 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);
}
public function genPrescriptionPrompt(array $userInfo, array $drugs): string
{
$patientInfo = $this->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;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Service\common\ai\xunfei;
use App\BaseApp\BaseService;
use App\Service\common\ai\xunfei\client\XunFeiAiClientV1;
use App\Service\common\ai\xunfei\client\XunFeiAiClientV2;
use Exception;
use Illuminate\Support\Facades\Log;
class XunFeiAiService extends BaseService
{
protected array $optionField = ['id', 'drug_name'];
private $client;
public function __construct()
{
$this->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;
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace App\Service\common\ai\xunfei\client;
use App\BaseApp\BaseClient;
use App\Service\common\ai\PromptService;
class XunFeiAiClientV1 extends BaseClient
{
// private $appId = '0970a6eb';
// private $apiSecret = 'MjVjZjk2ODM4NjAwOWM1MDVjZTU1MjQ2';
// private $apiKey = '578150544693a7c2108306db2b63cdcd';
// private $apiPassword = 'Bearer qlODxEjzXOVIIdIfyVZl:WxLxhqESNqMmOjskDVwt';
private $apiPassword = 'Bearer nZurRXizItjgjRmPkcRu:MiJrBykLPPEqNDHsGhCl';
public function __construct()
{
parent::__construct();
// $this->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' => '响应解析完成'
];
}
}

View File

@@ -0,0 +1,258 @@
<?php
namespace App\Service\common\ai\xunfei\client;
use App\BaseApp\BaseClient;
use App\Service\common\ai\PromptService;
class XunFeiAiClientV2 extends BaseClient
{
// private $appId = '0970a6eb';
// private $apiSecret = 'MjVjZjk2ODM4NjAwOWM1MDVjZTU1MjQ2';
// private $apiKey = '578150544693a7c2108306db2b63cdcd';
private $promptService;
private $apiPassword = 'Bearer qlODxEjzXOVIIdIfyVZl:WxLxhqESNqMmOjskDVwt';
// private $apiPassword = 'Bearer nZurRXizItjgjRmPkcRu:MiJrBykLPPEqNDHsGhCl';
public function __construct()
{
parent::__construct();
$this->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' => '响应解析完成'
];
}
}

View File

@@ -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 () {