| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- """
- CrossRef 期刊论文检索工具
- 通过 CrossRef REST API 检索已发表的学术期刊论文。
- CrossRef 是学术出版物的 DOI 注册机构,覆盖 1.5 亿+ 记录。
- API 文档: https://api.crossref.org/
- """
- import urllib.request
- import urllib.parse
- import urllib.error
- import json
- from typing import Dict, Any, List
- from hello_agents.tools import Tool, ToolParameter, ToolResponse, ToolStatus
- class CrossRefSearchTool(Tool):
- """CrossRef 期刊论文检索工具
- 通过 CrossRef REST API 检索正式发表的期刊论文、会议论文、书籍等。
- 覆盖 1.5 亿+ 学术作品,拥有最完整的期刊论文元数据(DOI、ISSN、页码等)。
- 特别适合检索正式发表的期刊论文和获取引用元数据。
- """
- BASE_URL = "https://api.crossref.org/works"
- def __init__(self):
- super().__init__(
- name="crossref_search",
- description="通过 CrossRef API 检索正式发表的期刊论文和会议论文。"
- "覆盖 1.5 亿+ 记录,拥有最完整的引用元数据(DOI、期刊名、"
- "卷号、页码等)。特别适合按 DOI 查找论文或检索特定期刊的文献。"
- "当需要精确的引用信息时使用此工具。"
- )
- def run(self, parameters: Dict[str, Any]) -> ToolResponse:
- keyword = parameters.get("keyword", "")
- author = parameters.get("author", "")
- doi = parameters.get("doi", "")
- journal = parameters.get("journal", "")
- max_results = min(parameters.get("max_results", 5), 20)
- year_from = parameters.get("year_from", "")
- year_to = parameters.get("year_to", "")
- # DOI 精确查询(最高效)
- if doi:
- url = f"{self.BASE_URL}/{urllib.parse.quote(doi.strip(), safe='')}"
- else:
- if not keyword and not author and not journal:
- return ToolResponse.error(
- code="INVALID_PARAM",
- message="请提供关键词(keyword)、作者(author)、DOI(doi)或期刊名(journal)"
- )
- # 构建过滤条件
- filters = []
- if year_from or year_to:
- f = f"from-pub-date:{year_from or '1900'}"
- if year_to:
- f += f",until-pub-date:{year_to}"
- filters.append(f)
- # 查询字段
- query_parts = []
- if keyword:
- query_parts.append(keyword.strip())
- if author:
- query_parts.append(author.strip())
- if journal:
- query_parts.append(journal.strip())
- params = {
- "query": " ".join(query_parts),
- "rows": str(max_results),
- }
- if filters:
- params["filter"] = ",".join(filters)
- url = f"{self.BASE_URL}?{urllib.parse.urlencode(params)}"
- try:
- req = urllib.request.Request(
- url,
- headers={
- "User-Agent": "PaperAssistant/1.0 (mailto:1793636425@qq.com)",
- "Accept": "application/json"
- }
- )
- with urllib.request.urlopen(req, timeout=20) as resp:
- data = json.loads(resp.read().decode("utf-8"))
- # 解析结果
- if doi:
- # 单篇论文查询
- msg = data.get("message", {})
- items = [msg] if msg else []
- total = len(items)
- else:
- msg = data.get("message", {})
- items = msg.get("items", [])
- total = msg.get("total-results", 0)
- if not items:
- return ToolResponse.success(
- text=f"在 CrossRef 中未找到匹配的论文。"
- f"{' DOI 可能不正确。' if doi else ' 请尝试更换关键词。'}",
- data={"count": 0, "papers": []}
- )
- # 格式化输出
- lines = [f"找到 {total} 篇论文(显示前 {len(items)} 篇):\n"]
- for i, item in enumerate(items, 1):
- title_list = item.get("title", ["N/A"])
- title = title_list[0] if title_list else "N/A"
- # 作者
- authors = item.get("author", [])
- author_names = []
- for a in authors[:5]:
- given = a.get("given", "")
- family = a.get("family", "")
- if given or family:
- author_names.append(f"{family} {given}".strip())
- authors_str = ", ".join(author_names)
- if len(authors) > 5:
- authors_str += " et al."
- # 发表信息
- published = item.get("published-print", {}) or item.get("published-online", {})
- pub_date = "-".join(str(v) for v in published.get("date-parts", [["?"]])[0]) if published else "N/A"
- # 期刊
- container = item.get("container-title", [])
- venue = container[0] if container else item.get("publisher", "N/A")
- # 引用次数
- ref_count = item.get("is-referenced-by-count", 0)
- item_doi = item.get("DOI", "")
- lines.append(f"### {i}. {title}")
- if authors_str:
- lines.append(f"> 作者: {authors_str}")
- lines.append(f"> 发表: {pub_date} | {venue}")
- lines.append(f"> 引用: {ref_count} 次")
- if item_doi:
- lines.append(f"> DOI: [{item_doi}](https://doi.org/{item_doi})")
- lines.append("")
- lines.append(f"---")
- lines.append(f"*数据来源: CrossRef API*")
- return ToolResponse.success(
- text="\n".join(lines),
- data={"count": len(items), "total": total, "papers": items}
- )
- except urllib.error.HTTPError as e:
- return ToolResponse.error(
- code="NETWORK_ERROR",
- message=f"CrossRef API 请求失败 (HTTP {e.code})"
- )
- except json.JSONDecodeError:
- return ToolResponse.error(
- code="INVALID_FORMAT",
- message="解析 CrossRef 返回数据失败"
- )
- except Exception as e:
- return ToolResponse.error(
- code="INTERNAL_ERROR",
- message=f"CrossRef 检索出错: {str(e)}"
- )
- def get_parameters(self) -> List[ToolParameter]:
- return [
- ToolParameter(name="keyword", type="string",
- description="搜索关键词", required=False),
- ToolParameter(name="author", type="string",
- description="作者姓名", required=False),
- ToolParameter(name="doi", type="string",
- description="DOI 号码(精确查询,优先级最高)", required=False),
- ToolParameter(name="journal", type="string",
- description="期刊名称", required=False),
- ToolParameter(name="year_from", type="string",
- description="起始年份", required=False),
- ToolParameter(name="year_to", type="string",
- description="截止年份", required=False),
- ToolParameter(name="max_results", type="integer",
- description="最大返回结果数(默认5,最大20)", required=False),
- ]
|