useLlm = (bool)$config['use_llm']; } } /** * 主入口:根据作者原文(可选 CrossRef type)识别类型 * * @param string $referContent 作者提供的原始参考文献字符串 * @param string $crossrefType CrossRef 返回的 type(可选) * @return array ['type' => string, 'confidence' => float, 'source' => string] */ public function classify($referContent, $crossrefType = '') { $text = trim((string)$referContent); // 1. CrossRef type 优先 $byCrossref = $this->mapCrossrefType($crossrefType); if ($byCrossref !== '') { return ['type' => $byCrossref, 'confidence' => 0.95, 'source' => 'crossref']; } if ($text === '') { return ['type' => self::TYPE_OTHER, 'confidence' => 0.0, 'source' => 'fallback']; } // 2. 规则启发式 $byRule = $this->classifyByRule($text); if ($byRule['type'] !== self::TYPE_OTHER && $byRule['confidence'] >= 0.8) { return ['type' => $byRule['type'], 'confidence' => $byRule['confidence'], 'source' => 'rule']; } // 3. 大模型兜底 if ($this->useLlm) { $byLlm = $this->classifyByLlm($text); if ($byLlm !== '') { return ['type' => $byLlm, 'confidence' => 0.7, 'source' => 'llm']; } } // 规则给出的弱结果(若有)优先于纯兜底 if ($byRule['type'] !== self::TYPE_OTHER) { return ['type' => $byRule['type'], 'confidence' => $byRule['confidence'], 'source' => 'rule']; } // 4. 兜底 return ['type' => self::TYPE_OTHER, 'confidence' => 0.0, 'source' => 'fallback']; } /** * CrossRef type → 内部枚举映射;未命中返回空串 */ public function mapCrossrefType($crossrefType) { $t = strtolower(trim((string)$crossrefType)); if ($t === '') { return ''; } $map = [ // 期刊 'journal-article' => self::TYPE_JOURNAL, 'journal' => self::TYPE_JOURNAL, 'journal-volume' => self::TYPE_JOURNAL, 'journal-issue' => self::TYPE_JOURNAL, // 图书 'book' => self::TYPE_BOOK, 'book-chapter' => self::TYPE_BOOK, 'book-part' => self::TYPE_BOOK, 'book-section' => self::TYPE_BOOK, 'book-set' => self::TYPE_BOOK, 'book-series' => self::TYPE_BOOK, 'book-track' => self::TYPE_BOOK, 'reference-book' => self::TYPE_BOOK, 'edited-book' => self::TYPE_BOOK, 'monograph' => self::TYPE_BOOK, // 会议 'proceedings-article' => self::TYPE_CONFERENCE, 'proceedings' => self::TYPE_CONFERENCE, 'proceedings-series' => self::TYPE_CONFERENCE, // 学位论文 'dissertation' => self::TYPE_THESIS, // 在线/预印本 'posted-content' => self::TYPE_WEB, ]; return isset($map[$t]) ? $map[$t] : ''; } /** * 英文规则启发式:按「最独特 → 最普通」顺序判定 * * @return array ['type' => string, 'confidence' => float] */ public function classifyByRule($text) { $hasDoi = (bool)preg_match('/\bdoi:\s*10\./i', $text) || (bool)preg_match('#doi\.org/#i', $text); $hasUrl = (bool)preg_match('#https?://#i', $text); // 期刊卷期页结构,如 2020;382(8):727-733 或 2020;10:100 $hasJournalVol = (bool)preg_match('/\b(19|20)\d{2}\s*[;:]\s*\d+\s*(\(\d+\))?\s*:\s*[A-Za-z]?\d+/', $text); // 1) 学位论文 if (preg_match('/\[(ph\.?d\.?|master(\'s)?|doctoral|masters)?\s*(thesis|dissertation)\]/i', $text) || preg_match('/\b(ph\.?d\.?|master\'?s|doctoral|doctorate)\b[^.]{0,40}\b(thesis|dissertation)\b/i', $text) || preg_match('/\b(thesis|dissertation)\b/i', $text)) { return ['type' => self::TYPE_THESIS, 'confidence' => 0.9]; } // 2) 会议论文 if (preg_match('/\bproceedings\b/i', $text) || preg_match('/\bin:\s*proc\b/i', $text) || preg_match('/\b(conference|symposium|workshop|congress)\b/i', $text) || preg_match('/\bannual meeting\b/i', $text)) { return ['type' => self::TYPE_CONFERENCE, 'confidence' => 0.85]; } // 3) 网页 / 在线资源 if (preg_match('/\[(internet|online)\]/i', $text) || preg_match('/\baccessed\b/i', $text) || preg_match('/\bavailable\s+(from|at)\b/i', $text) || preg_match('/\bcited\s+(19|20)\d{2}/i', $text) || ($hasUrl && !$hasDoi && !$hasJournalVol)) { return ['type' => self::TYPE_WEB, 'confidence' => 0.8]; } // 4) 图书 if (preg_match('/\b\d+(st|nd|rd|th)\s+ed(ition)?\.?/i', $text) || preg_match('/\bisbn\b/i', $text) || preg_match('/\b(press|publisher|publishing house)\b/i', $text) || preg_match('/[A-Z][A-Za-z .]+:\s*[A-Z][A-Za-z .&]+;\s*(19|20)\d{2}/', $text)) { // 期刊卷期结构更强时不判为书 if (!$hasJournalVol) { return ['type' => self::TYPE_BOOK, 'confidence' => 0.8]; } } // 5) 期刊 if ($hasJournalVol || $hasDoi || preg_match('/\bvol\.?\s*\d+/i', $text) || preg_match('/\bpp?\.\s*\d+/i', $text)) { return ['type' => self::TYPE_JOURNAL, 'confidence' => $hasJournalVol ? 0.85 : 0.6]; } return ['type' => self::TYPE_OTHER, 'confidence' => 0.0]; } /** * 大模型兜底:只返回枚举值之一,失败返回空串 */ public function classifyByLlm($text) { try { $llm = new LLMService(); $system = 'You are a bibliography classifier. Classify the reference into exactly one type. ' . 'Allowed types: journal, book, conference, thesis, web, other. ' . 'journal=journal article; book=book or book chapter; conference=conference/proceedings paper; ' . 'thesis=dissertation/thesis; web=website or online resource; other=none of the above. ' . 'Respond with ONLY a JSON object: {"type":""}. No explanation.'; $user = "Reference:\n" . mb_substr($text, 0, 2000); $content = $llm->requestChat([ ['role' => 'system', 'content' => $system], ['role' => 'user', 'content' => $user], ], 0); if ($content === null || $content === '') { return ''; } $parsed = $llm->parseJsonResponse($content); $type = ''; if (is_array($parsed) && isset($parsed['type'])) { $type = strtolower(trim((string)$parsed['type'])); } else { // 兜底:直接从文本里找枚举词 if (preg_match('/\b(journal|book|conference|thesis|web|other)\b/i', $content, $m)) { $type = strtolower($m[1]); } } $allowed = [ self::TYPE_JOURNAL, self::TYPE_BOOK, self::TYPE_CONFERENCE, self::TYPE_THESIS, self::TYPE_WEB, self::TYPE_OTHER, ]; return in_array($type, $allowed, true) ? $type : ''; } catch (\Throwable $e) { return ''; } } }