更新
This commit is contained in:
@@ -22,6 +22,7 @@ use app\adminapi\logic\tcm\DiagnosisLogic;
|
||||
use app\adminapi\logic\tcm\PatientAiReportLogic;
|
||||
use app\adminapi\logic\tcm\TrackingNoteLogic;
|
||||
use app\adminapi\service\AssistantSseProtocol;
|
||||
use app\adminapi\service\AssistantSseTiming;
|
||||
use app\adminapi\validate\tcm\DiagnosisValidate;
|
||||
use app\common\model\Order;
|
||||
use app\common\model\WechatChatRecord;
|
||||
@@ -930,6 +931,7 @@ class DiagnosisController extends BaseAdminController
|
||||
*/
|
||||
public function aiAssistantStream()
|
||||
{
|
||||
$timing = new AssistantSseTiming();
|
||||
// 登录由全局中间件完成;请求校验、旧助手权限与 DataScope 必须全部
|
||||
// 在任何 SSE header / start 事件之前完成,失败时仍返回标准 JSON。
|
||||
$params = (new DiagnosisValidate())->post()->goCheck('aiAssistant');
|
||||
@@ -938,17 +940,18 @@ class DiagnosisController extends BaseAdminController
|
||||
(string) $params['task'],
|
||||
(string) ($params['prompt'] ?? ''),
|
||||
$this->adminId,
|
||||
$this->adminInfo
|
||||
$this->adminInfo,
|
||||
$timing
|
||||
);
|
||||
if ($prepared === null) {
|
||||
return $this->fail(DiagnosisAiLogic::getError());
|
||||
}
|
||||
|
||||
$this->runAssistantSse($prepared);
|
||||
$this->runAssistantSse($prepared, $timing);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $prepared */
|
||||
private function runAssistantSse(array $prepared): void
|
||||
private function runAssistantSse(array $prepared, AssistantSseTiming $timing): void
|
||||
{
|
||||
while (ob_get_level() > 0) {
|
||||
ob_end_clean();
|
||||
@@ -984,7 +987,7 @@ class DiagnosisController extends BaseAdminController
|
||||
return !connection_aborted();
|
||||
};
|
||||
|
||||
$emit('start', [
|
||||
$startEmitted = $emit('start', [
|
||||
'task' => (string) ($prepared['task'] ?? ''),
|
||||
'model_key' => (string) ($prepared['profile'] ?? ''),
|
||||
'diagnosis_id' => (int) ($prepared['diagnosis_id'] ?? 0),
|
||||
@@ -995,14 +998,37 @@ class DiagnosisController extends BaseAdminController
|
||||
: [],
|
||||
'message' => '已连接,正在生成…',
|
||||
]);
|
||||
$sseStartedAt = hrtime(true);
|
||||
$firstDeltaMs = null;
|
||||
$timing->log('sse_start', ['status' => $startEmitted ? 'ok' : 'disconnected']);
|
||||
|
||||
try {
|
||||
$result = DiagnosisAiLogic::streamPreparedAssistant(
|
||||
$prepared,
|
||||
static fn (string $delta): bool => $emit('delta', ['text' => $delta]),
|
||||
static fn (): bool => connection_aborted() === 1
|
||||
static function (string $delta) use ($emit, $timing, $sseStartedAt, &$firstDeltaMs): bool {
|
||||
$observedAtMs = $firstDeltaMs === null && $delta !== ''
|
||||
? AssistantSseTiming::elapsedMs($sseStartedAt)
|
||||
: null;
|
||||
$accepted = $emit('delta', ['text' => $delta]);
|
||||
if ($accepted && $observedAtMs !== null) {
|
||||
$firstDeltaMs = $observedAtMs;
|
||||
$timing->log('first_delta', [
|
||||
'duration_ms' => $firstDeltaMs,
|
||||
'delta_bytes' => strlen($delta),
|
||||
]);
|
||||
}
|
||||
return $accepted;
|
||||
},
|
||||
static fn (): bool => connection_aborted() === 1,
|
||||
$timing
|
||||
);
|
||||
if (connection_aborted()) {
|
||||
$timing->log('sse_done', [
|
||||
'duration_ms' => AssistantSseTiming::elapsedMs($sseStartedAt),
|
||||
'first_delta_observed' => $firstDeltaMs !== null,
|
||||
'first_delta_ms' => $firstDeltaMs,
|
||||
'status' => 'disconnected',
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
if ($result === null) {
|
||||
@@ -1013,14 +1039,22 @@ class DiagnosisController extends BaseAdminController
|
||||
} else {
|
||||
$emit('done', $result);
|
||||
}
|
||||
$timing->log('sse_done', [
|
||||
'duration_ms' => AssistantSseTiming::elapsedMs($sseStartedAt),
|
||||
'first_delta_observed' => $firstDeltaMs !== null,
|
||||
'first_delta_ms' => $firstDeltaMs,
|
||||
'status' => $result === null ? 'error' : 'ok',
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
\think\facade\Log::warning('diagnosis ai assistant sse failed ' . json_encode([
|
||||
'diagnosis_id' => (int) ($prepared['diagnosis_id'] ?? 0),
|
||||
'profile' => (string) ($prepared['profile'] ?? ''),
|
||||
'task' => (string) ($prepared['task'] ?? ''),
|
||||
'admin_id' => (int) ($prepared['admin_id'] ?? 0),
|
||||
'exception_class' => get_class($e),
|
||||
'trace_id' => $timing->id(),
|
||||
], JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE));
|
||||
$timing->log('sse_done', [
|
||||
'duration_ms' => AssistantSseTiming::elapsedMs($sseStartedAt),
|
||||
'first_delta_observed' => $firstDeltaMs !== null,
|
||||
'first_delta_ms' => $firstDeltaMs,
|
||||
'status' => 'exception',
|
||||
]);
|
||||
$emit('error', [
|
||||
'code' => 'AI_ASSISTANT_FAILED',
|
||||
'message' => 'AI 助手暂时不可用,请稍后重试',
|
||||
|
||||
Reference in New Issue
Block a user