This commit is contained in:
Your Name
2026-08-27 14:23:23 +08:00
parent b5b14516a1
commit 2fa8492c56
27 changed files with 3832 additions and 1435 deletions
@@ -18,6 +18,9 @@ use think\facade\Log;
*/
class DiagnosisAiLogic extends BaseLogic
{
/** @var string Safe machine-readable code for the current assistant request. */
private static $assistantErrorCode = 'AI_ASSISTANT_FAILED';
private const PROMPT_VERSION = 'patient-context-case-explain-v2';
private const ASSISTANT_PROMPT_VERSION = 'patient-context-assistant-v2';
@@ -354,6 +357,7 @@ class DiagnosisAiLogic extends BaseLogic
int $adminId,
array $adminInfo
): ?array {
self::$assistantErrorCode = 'AI_ASSISTANT_FAILED';
$diagnosis = self::loadAuthorizedDiagnosis(
$diagnosisId,
$adminId,
@@ -433,6 +437,14 @@ class DiagnosisAiLogic extends BaseLogic
$diagnosisId = (int) ($prepared['diagnosis_id'] ?? 0);
$profile = (string) ($prepared['profile'] ?? '');
$adminId = (int) ($prepared['admin_id'] ?? 0);
$deliveredDelta = false;
$forwardDelta = static function (string $delta) use (&$deliveredDelta, $onDelta) {
$accepted = $onDelta($delta);
if ($accepted !== false) {
$deliveredDelta = true;
}
return $accepted;
};
try {
$result = DifyChatService::streamChat(
@@ -440,7 +452,7 @@ class DiagnosisAiLogic extends BaseLogic
is_array($prepared['inputs'] ?? null) ? $prepared['inputs'] : [],
(string) ($prepared['query'] ?? ''),
(string) ($prepared['user'] ?? ''),
$onDelta,
$forwardDelta,
$shouldAbort,
is_array($prepared['files'] ?? null) ? $prepared['files'] : []
);
@@ -452,6 +464,7 @@ class DiagnosisAiLogic extends BaseLogic
$e,
(string) ($prepared['task'] ?? '')
);
self::$assistantErrorCode = 'UPSTREAM_UNAVAILABLE';
self::setError('AI 助手暂时不可用,请稍后重试');
return null;
}
@@ -464,6 +477,47 @@ class DiagnosisAiLogic extends BaseLogic
(string) ($prepared['task'] ?? ''),
is_array($result) ? $result : []
);
// Some Dify-compatible gateways accept blocking chat but reject or
// incompletely terminate streaming responses. Before any delta has
// reached the doctor it is safe to make one blocking compatibility
// attempt; after a delta, retrying could duplicate clinical text.
$streamErrorCode = strtoupper(trim((string) ($result['error_code'] ?? '')));
if (
!$deliveredDelta
&& in_array(
$streamErrorCode,
['UPSTREAM_REJECTED', 'INCOMPLETE_RESPONSE', 'EMPTY_RESPONSE'],
true
)
) {
try {
$result = DifyChatService::chat(
$profile,
is_array($prepared['inputs'] ?? null) ? $prepared['inputs'] : [],
(string) ($prepared['query'] ?? ''),
(string) ($prepared['user'] ?? ''),
is_array($prepared['files'] ?? null) ? $prepared['files'] : []
);
} catch (\Throwable $e) {
self::logAssistantFailure(
$diagnosisId,
$profile,
$adminId,
$e,
(string) ($prepared['task'] ?? '')
);
}
if (empty($result['ok'])) {
self::logAssistantUpstreamError(
$diagnosisId,
$profile,
$adminId,
(string) ($prepared['task'] ?? ''),
is_array($result) ? $result : []
);
}
}
}
return self::formatAssistantResult($prepared, $result);
@@ -480,6 +534,7 @@ class DiagnosisAiLogic extends BaseLogic
// 附带上游错误码,让医生反馈时管理员能直接定位是配置、体积还是上游拒绝。
$message = (string) ($result['error'] ?? 'AI 助手暂时不可用,请稍后重试');
$errorCode = trim((string) ($result['error_code'] ?? ''));
self::$assistantErrorCode = self::normaliseAssistantErrorCode($errorCode);
if ($errorCode !== '') {
$message .= '' . $errorCode . '';
}
@@ -488,6 +543,7 @@ class DiagnosisAiLogic extends BaseLogic
}
$content = self::cleanText($result['content'] ?? '', self::MAX_REPORT_LENGTH, true);
if ($content === '') {
self::$assistantErrorCode = 'EMPTY_RESPONSE';
self::setError('AI 助手未返回内容,请重试');
return null;
}
@@ -520,6 +576,20 @@ class DiagnosisAiLogic extends BaseLogic
return $payload;
}
/** Return a safe code for the current SSE terminal error event. */
public static function getAssistantErrorCode(): string
{
return self::normaliseAssistantErrorCode(self::$assistantErrorCode);
}
private static function normaliseAssistantErrorCode(string $code): string
{
$code = strtoupper(trim($code));
return preg_match('/^[A-Z][A-Z0-9_]{2,63}$/', $code) === 1
? $code
: 'AI_ASSISTANT_FAILED';
}
/** @return array<string,mixed>|null */
private static function parsePrescriptionDraft(string $content): ?array
{