This commit is contained in:
wangjinlei
2026-04-16 13:30:31 +08:00
parent a2338340f6
commit ae221e6be6
8 changed files with 838 additions and 91 deletions

View File

@@ -0,0 +1,108 @@
<?php
namespace app\common;
/**
* 使用本地 chat/completions 接口,从 affiliation 文本推断国家。
*
* 输出约定:返回数组 ['code' => '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];
}
}