'US', 'en_name' => 'United States'],任一可为空。 */ class CountryResolverService { private $chatUrl = ''; private $chatModel = ''; private $apiKey = ''; private $timeout = 60; public function __construct(array $config = []) { if (isset($config['chat_url'])) $this->chatUrl = (string)$config['chat_url']; if (isset($config['chat_model'])) $this->chatModel = (string)$config['chat_model']; if (isset($config['api_key'])) $this->apiKey = (string)$config['api_key']; if (isset($config['timeout'])) $this->timeout = max(5, intval($config['timeout'])); } public function resolve(string $affiliation): array { $affiliation = trim($affiliation); if ($affiliation === '' || $this->chatUrl === '' || $this->chatModel === '') { return []; } $messages = [ [ 'role' => 'system', 'content' => 'You extract the country from an academic affiliation string. Reply ONLY with minified JSON.', ], [ 'role' => 'user', 'content' => "Affiliation:\n" . $affiliation . "\n\nReturn JSON with keys:\n- code: ISO 3166-1 alpha-3 (preferred)\n- en_name: English country name (optional)\nIf uncertain, return {\"code\":\"\",\"en_name\":\"\"}.", ], ]; $payload = [ 'model' => $this->chatModel, 'temperature' => 0, 'messages' => $messages, ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $this->chatUrl); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload, JSON_UNESCAPED_UNICODE)); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, min(10, $this->timeout)); curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout); $headers = [ 'Content-Type: application/json', ]; if ($this->apiKey !== '') { $headers[] = 'Authorization: Bearer ' . $this->apiKey; } curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); $raw = curl_exec($ch); if ($raw === false) { curl_close($ch); return []; } $httpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE)); curl_close($ch); if ($httpCode < 200 || $httpCode >= 300) { return []; } $data = json_decode($raw, true); if (!is_array($data)) return []; // 兼容 OpenAI chat/completions 结构:choices[0].message.content $content = ''; if (isset($data['choices'][0]['message']['content'])) { $content = (string)$data['choices'][0]['message']['content']; } elseif (isset($data['content'])) { $content = (string)$data['content']; } $content = trim($content); if ($content === '') return []; // 尝试提取 JSON(允许模型包裹 ```json) if (preg_match('/\{.*\}/s', $content, $m)) { $content = $m[0]; } $obj = json_decode($content, true); if (!is_array($obj)) return []; $code = isset($obj['code']) ? strtoupper(trim((string)$obj['code'])) : ''; $enName = isset($obj['en_name']) ? trim((string)$obj['en_name']) : ''; // 防止模型乱回长段文本 if (strlen($code) > 8) $code = ''; if (strlen($enName) > 128) $enName = mb_substr($enName, 0, 128); return ['code' => $code, 'en_name' => $enName]; } }