This commit is contained in:
Your Name
2026-08-18 14:08:38 +08:00
parent 8b9df1154c
commit bc1228a310
77 changed files with 10763 additions and 1181 deletions
@@ -4,13 +4,12 @@ declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\common\cache\AdminAuthCache;
use app\common\logic\BaseLogic;
use app\common\model\auth\AdminRole;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\DiagnosisAiReport;
use app\common\service\DataScope\DataScopeService;
use app\common\service\DifyChatService;
use app\common\cache\AdminAuthCache;
use app\adminapi\logic\firstvisit\MyPatientLogic;
use app\common\logic\BaseLogic;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\DiagnosisAiReport;
use app\common\service\DifyChatService;
use think\facade\Db;
use think\facade\Log;
@@ -201,6 +200,44 @@ class DiagnosisAiLogic extends BaseLogic
string $prompt,
int $adminId,
array $adminInfo
): ?array {
$prepared = self::prepareAssistant($diagnosisId, $task, $prompt, $adminId, $adminInfo);
if ($prepared === null) {
return null;
}
try {
$result = DifyChatService::chat(
$prepared['profile'],
$prepared['inputs'],
$prepared['query'],
$prepared['user']
);
} catch (\Throwable $e) {
self::logAssistantFailure($diagnosisId, $prepared['profile'], $adminId, $e);
self::setError('AI 助手暂时不可用,请稍后重试');
return null;
}
return self::formatAssistantResult($prepared, $result);
}
/**
* 在 SSE headers 发出前完成参数、权限、DataScope、病例和模型选择预检。
* 返回值只供同一请求内的流执行使用,绝不能直接序列化给客户端。
*
* @param array<string,mixed> $adminInfo
* @return array{
* diagnosis_id:int,profile:string,model_name:string,model_label:string,task:string,
* inputs:array<string,mixed>,query:string,user:string,admin_id:int
* }|null
*/
public static function prepareAssistant(
int $diagnosisId,
string $task,
string $prompt,
int $adminId,
array $adminInfo
): ?array {
$diagnosis = self::loadAuthorizedDiagnosis(
$diagnosisId,
@@ -239,28 +276,63 @@ class DiagnosisAiLogic extends BaseLogic
return null;
}
return [
'diagnosis_id' => $diagnosisId,
'profile' => $profile,
'model_name' => $model,
'model_label' => $modelLabel,
'task' => $task,
'inputs' => self::buildUpstreamInputs(
$context,
'病例问诊助手',
self::ASSISTANT_PROMPT_VERSION
),
'query' => self::buildAssistantPrompt($context, $task, $prompt),
'user' => 'admin-diagnosis-assistant-' . $adminId,
'admin_id' => $adminId,
];
}
/**
* @param array<string,mixed> $prepared prepareAssistant() 的内部返回值
* @param callable(string):mixed $onDelta
* @param callable():bool|null $shouldAbort
* @return array{answer:string,model_key:string,model_label:string,model_name:string,task:string}|null
*/
public static function streamPreparedAssistant(
array $prepared,
callable $onDelta,
?callable $shouldAbort = null
): ?array {
$diagnosisId = (int) ($prepared['diagnosis_id'] ?? 0);
$profile = (string) ($prepared['profile'] ?? '');
$adminId = (int) ($prepared['admin_id'] ?? 0);
try {
$result = DifyChatService::chat(
$result = DifyChatService::streamChat(
$profile,
self::buildUpstreamInputs(
$context,
'病例问诊助手',
self::ASSISTANT_PROMPT_VERSION
),
self::buildAssistantPrompt($context, $task, $prompt),
'admin-diagnosis-assistant-' . $adminId
is_array($prepared['inputs'] ?? null) ? $prepared['inputs'] : [],
(string) ($prepared['query'] ?? ''),
(string) ($prepared['user'] ?? ''),
$onDelta,
$shouldAbort
);
} catch (\Throwable $e) {
Log::warning('diagnosis ai assistant upstream call failed', [
'diagnosis_id' => $diagnosisId,
'profile' => $profile,
'admin_id' => $adminId,
'exception_class' => get_class($e),
]);
self::logAssistantFailure($diagnosisId, $profile, $adminId, $e);
self::setError('AI 助手暂时不可用,请稍后重试');
return null;
}
return self::formatAssistantResult($prepared, $result);
}
/**
* @param array<string,mixed> $prepared
* @param array<string,mixed> $result
* @return array{answer:string,model_key:string,model_label:string,model_name:string,task:string}|null
*/
private static function formatAssistantResult(array $prepared, array $result): ?array
{
if (empty($result['ok'])) {
self::setError((string) ($result['error'] ?? 'AI 助手暂时不可用,请稍后重试'));
return null;
@@ -273,13 +345,27 @@ class DiagnosisAiLogic extends BaseLogic
return [
'answer' => $content,
'model_key' => $profile,
'model_label' => $modelLabel,
'model_name' => $model,
'task' => $task,
'model_key' => (string) ($prepared['profile'] ?? ''),
'model_label' => (string) ($prepared['model_label'] ?? ''),
'model_name' => (string) ($prepared['model_name'] ?? ''),
'task' => (string) ($prepared['task'] ?? ''),
];
}
private static function logAssistantFailure(
int $diagnosisId,
string $profile,
int $adminId,
\Throwable $exception
): void {
Log::warning('diagnosis ai assistant upstream call failed', [
'diagnosis_id' => $diagnosisId,
'profile' => $profile,
'admin_id' => $adminId,
'exception_class' => get_class($exception),
]);
}
/**
* 接诊台结构化 AI 智能分析。每次只调用客户端白名单键对应的服务端模型,
* 上游失败或响应不符合契约时直接失败,不构造本地伪分析。
@@ -604,28 +690,10 @@ class DiagnosisAiLogic extends BaseLogic
return null;
}
$accessQuery = Diagnosis::where('id', $id)->whereNull('delete_time');
$isRoot = !empty($adminInfo['root']) && (int) $adminInfo['root'] === 1;
if (!$isRoot) {
$roleIds = array_map('intval', AdminRole::where('admin_id', $adminId)->column('role_id'));
if (in_array(2, $roleIds, true)) {
$accessQuery->where('assistant_id', $adminId);
}
if (DataScopeService::isEnabled()) {
$visibleIds = DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
if ($visibleIds === []) {
self::setError('诊单不存在或无权访问');
return null;
}
if (is_array($visibleIds)) {
$accessQuery->whereIn('assistant_id', $visibleIds);
}
}
if (!MyPatientLogic::canAccessDiagnosis($id, $adminId, $adminInfo)) {
self::setError('诊单不存在或无权访问');
return null;
}
if (!$accessQuery->find()) {
self::setError('诊单不存在或无权访问');
return null;
}
$diagnosis = DiagnosisLogic::detail(['id' => $id], $adminInfo);
if ($diagnosis === [] || empty($diagnosis['id'])) {