options[$option] = $value; return true; } function curl_exec(\stdClass $handle): string|bool { $GLOBALS['clinicalChatPayloads'][] = json_decode($handle->options[CURLOPT_POSTFIELDS], true); if (isset($handle->options[CURLOPT_WRITEFUNCTION])) { $write = $handle->options[CURLOPT_WRITEFUNCTION]; $write($handle, "data: {\"choices\":[{\"delta\":{\"content\":\"offline stream\"}}]}\n\n"); return true; } return '{"choices":[{"message":{"content":"offline reply"}}]}'; } function curl_errno(\stdClass $handle): int { return 0; } function curl_error(\stdClass $handle): string { return ''; } function curl_getinfo(\stdClass $handle, int $option): int { return 200; } function curl_close(\stdClass $handle): void {} } namespace { require dirname(__DIR__) . '/vendor/autoload.php'; use app\common\service\AiChatService; use app\common\service\NihaixiaClinicalSkill; function chatSkillExpect(bool $condition, string $message): void { if (!$condition) { fwrite(STDERR, "FAIL: {$message}\n"); exit(1); } } $clinicalChatConfig = [ 'enable' => true, 'api_key' => 'offline-fixture', 'base_url' => 'https://ai.example.test', 'model' => 'offline-model', ]; $clinicalChatPayloads = []; $messages = [ ['role' => 'system', 'content' => '只输出 JSON'], ['role' => 'user', 'content' => '分析该患者症状'], ]; $blocking = AiChatService::chat($messages); chatSkillExpect($blocking['ok'] === true, 'blocking chat succeeds offline'); $deltas = []; $streaming = AiChatService::streamChat($messages, static function (string $delta) use (&$deltas): void { $deltas[] = $delta; }); chatSkillExpect($streaming['ok'] === true && $deltas === ['offline stream'], 'streaming chat succeeds offline'); chatSkillExpect(count($clinicalChatPayloads) === 2, 'both transports sent one request'); foreach ($clinicalChatPayloads as $payload) { $wireMessages = $payload['messages']; chatSkillExpect(str_contains($wireMessages[0]['content'], NihaixiaClinicalSkill::VERSION), 'wire request includes the clinical skill'); chatSkillExpect($wireMessages[1] === $messages[0] && $wireMessages[2] === $messages[1], 'original format and patient question are preserved'); } echo "AiChat clinical skill transport contract passed.\n"; }