Skip to content

Commit 9fc91cd

Browse files
authored
Merge pull request #104 from ZimoLiao/review/v1.5.0-release-readiness
Prepare v1.5.0 release readiness
2 parents 75a23cc + 7a6de38 commit 9fc91cd

16 files changed

Lines changed: 425 additions & 85 deletions

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
77

88
## [Unreleased]
99

10+
## [1.5.0] — 2026-05-24
11+
1012
### Added
1113

1214
- **Line-addressable evidence chunk search** ([#35](https://github.com/ZimoLiao/scholaraio/issues/35)): Added `scholaraio index --chunks` and `scholaraio search --chunk` so agents can build a paper-section chunk index from `paper.md` / `meta.json["toc"]` and retrieve source snippets with paper IDs, section titles, line ranges, and normal search filters such as `--year`, `--journal`, and `--type`.

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ abstract: "Scholar All-In-One — A research infrastructure for AI agents"
55
type: software
66
license: MIT
77
repository-code: "https://github.com/zimoliao/scholaraio"
8-
version: 1.4.0
9-
date-released: "2026-04-27"
8+
version: 1.5.0
9+
date-released: "2026-05-24"
1010
authors:
1111
- family-names: Liao
1212
given-names: Zimo

config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ ingest:
7171
pdf_fallback_order: [auto] # 完全自定义降级链;可写 [pymupdf] 或 [docling,pymupdf],auto=自动检测本机可用解析器
7272
pdf_fallback_auto_detect: true # 是否启用自动检测(建议开启)
7373

74+
# Optional web tools 外部实时网页工具
75+
# Prefer MCP endpoints for agent workflows. API keys, if used, should live in config.local.yaml.
76+
# Agent 工作流优先走 MCP endpoint;如启用认证,key 放 config.local.yaml。
77+
websearch:
78+
transport: mcp
79+
mcp_url: http://127.0.0.1:8765/mcp
80+
api_key: null # optional bearer token -> config.local.yaml or env WEBSEARCH_API_KEY
81+
mcp_tool: search_bing
82+
83+
webextract:
84+
transport: mcp
85+
mcp_url: http://127.0.0.1:8766/mcp
86+
api_key: null # optional bearer token -> config.local.yaml or env WEBEXTRACT_API_KEY
87+
mcp_tool: fetch_url
88+
7489
# Paper2Any external extension Paper2Any 外部增强组件
7590
# ScholarAIO talks to a lightweight MCP sidecar. The upstream OpenDCAI/Paper2Any
7691
# checkout stays outside tracked source, by default under data/runtime/extensions/paper2any/Paper2Any.

docs/getting-started/agent-setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pip install -e ".[full]"
2626
scholaraio setup
2727
```
2828

29-
`scholaraio setup check` is the companion diagnostic command. It reports both the core setup state and optional advanced items such as Semantic Scholar / Zotero API keys. Current setup guidance prefers MinerU first whenever a MinerU path is available.
29+
`scholaraio setup check` is the companion diagnostic command. It reports both the core setup state and optional advanced items such as Semantic Scholar / Zotero API keys, websearch/webextract endpoints, and Paper2Any sidecar readiness. Current setup guidance prefers MinerU first whenever a MinerU path is available.
3030

3131
Then start your agent in the repository root:
3232

docs/getting-started/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ scholaraio setup check
6666
`setup check` is the most complete initial diagnostic surface. It covers:
6767

6868
- core setup items: dependency groups, `config.yaml`, LLM key, MinerU / Docling availability, parser recommendation, Graphviz `dot`, Inkscape, `contact_email`, and directory state
69-
- optional advanced items: Semantic Scholar API key and Zotero API key
69+
- optional advanced items: Semantic Scholar API key, Zotero API key, external websearch/webextract services, and Paper2Any sidecar readiness
7070

7171
Current setup guidance prefers **MinerU first** whenever a MinerU path is available (local service or `mineru-open-api` + token). `Docling` and then PyMuPDF remain the fallback chain when MinerU is not usable or when the user explicitly prefers a lighter parser path.
7272

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "scholaraio"
7-
version = "1.4.0"
7+
version = "1.5.0"
88
description = "Scholar All-In-One — A research infrastructure for AI agents"
99
readme = {file = "README.md", content-type = "text/markdown"}
1010
requires-python = ">=3.10"

scholaraio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""ScholarAIO — A research infrastructure for AI agents."""
22

3-
__version__ = "1.4.0"
3+
__version__ = "1.5.0"

scholaraio/providers/mineru.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,18 @@ def check_server(api_url: str = DEFAULT_API_URL) -> bool:
240240
Returns:
241241
可达返回 ``True``,不可达返回 ``False``。
242242
"""
243+
base_url = api_url.rstrip("/")
243244
try:
244-
resp = requests.get(f"{api_url}/docs", timeout=5)
245-
return resp.status_code == 200
246-
except requests.ConnectionError:
245+
resp = requests.get(f"{base_url}/openapi.json", timeout=5)
246+
if resp.status_code != 200:
247+
return False
248+
try:
249+
data = resp.json()
250+
except ValueError:
251+
return False
252+
paths = data.get("paths", {}) if isinstance(data, dict) else {}
253+
return isinstance(paths, dict) and PARSE_ENDPOINT in paths
254+
except requests.RequestException:
247255
return False
248256

249257

scholaraio/providers/paper2any.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,15 @@ def call_paper2any_tool(
5050

5151
def _client(*, cfg: object | None, timeout: int) -> StreamableHttpMcpClient:
5252
return StreamableHttpMcpClient(
53-
_paper2any_mcp_url(cfg),
53+
resolve_paper2any_mcp_url(cfg),
5454
api_key=_paper2any_mcp_api_key(cfg),
5555
client_name="scholaraio-paper2any-client",
5656
timeout=timeout,
5757
)
5858

5959

60-
def _paper2any_mcp_url(cfg: object | None) -> str:
60+
def resolve_paper2any_mcp_url(cfg: object | None = None) -> str:
61+
"""Resolve the Paper2Any MCP endpoint using provider precedence."""
6162
env_url = os.environ.get("PAPER2ANY_MCP_URL", "").strip()
6263
if env_url:
6364
return env_url
@@ -66,6 +67,10 @@ def _paper2any_mcp_url(cfg: object | None) -> str:
6667
return config_url or DEFAULT_PAPER2ANY_MCP_URL
6768

6869

70+
def _paper2any_mcp_url(cfg: object | None) -> str:
71+
return resolve_paper2any_mcp_url(cfg)
72+
73+
6974
def _paper2any_mcp_api_key(cfg: object | None) -> str:
7075
env_key = os.environ.get("PAPER2ANY_MCP_API_KEY", "").strip()
7176
if env_key:

scholaraio/providers/webtools.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _headers(api_key: str) -> dict[str, str]:
5858
return headers
5959

6060

61-
def _load_json_response(req: Request, *, timeout: int, error_prefix: str):
61+
def _load_json_response(req: Request, *, timeout: float, error_prefix: str):
6262
try:
6363
with urlopen(req, timeout=timeout) as response:
6464
raw = response.read().decode("utf-8")
@@ -83,11 +83,11 @@ def _load_json_response(req: Request, *, timeout: int, error_prefix: str):
8383
# ---------------------------------------------------------------------------
8484

8585

86-
def check_websearch_health(base_url: str | None = None) -> dict:
86+
def check_websearch_health(base_url: str | None = None, timeout: float = 5.0) -> dict:
8787
"""Check whether the external web search service is healthy."""
8888
base = _resolve_base_url(base_url, "WEBSEARCH_URL", _DEFAULT_WEBSEARCH_URL)
8989
req = Request(f"{base}/health", method="GET")
90-
return _load_json_response(req, timeout=5, error_prefix="搜索服务健康检查失败")
90+
return _load_json_response(req, timeout=timeout, error_prefix="搜索服务健康检查失败")
9191

9292

9393
def websearch(
@@ -182,7 +182,7 @@ def check_websearch_service(cfg: Config | None = None, timeout: float = 3.0) ->
182182
)
183183
client.list_tools()
184184
else:
185-
check_websearch_health(_get_websearch_base_url(cfg))
185+
check_websearch_health(_get_websearch_base_url(cfg), timeout=timeout)
186186
return True
187187
except Exception as e:
188188
_log.debug("Health check failed: %s", e)
@@ -372,11 +372,11 @@ def search_and_fetch_arxiv(
372372
# ---------------------------------------------------------------------------
373373

374374

375-
def check_webextract_health(base_url: str | None = None) -> dict:
375+
def check_webextract_health(base_url: str | None = None, timeout: float = 5.0) -> dict:
376376
"""Check whether the external web extraction service is healthy."""
377377
base = _resolve_base_url(base_url, "WEBEXTRACT_URL", _DEFAULT_WEBEXTRACT_URL)
378378
req = Request(f"{base}/health", method="GET")
379-
return _load_json_response(req, timeout=5, error_prefix="提取服务健康检查失败")
379+
return _load_json_response(req, timeout=timeout, error_prefix="提取服务健康检查失败")
380380

381381

382382
def webextract(url: str, pdf: bool | None = None, base_url: str | None = None) -> dict:
@@ -474,7 +474,7 @@ def check_webextract_service(cfg: Config | None = None, timeout: float = 3.0) ->
474474
)
475475
client.list_tools()
476476
else:
477-
check_webextract_health(_get_webextract_base_url(cfg))
477+
check_webextract_health(_get_webextract_base_url(cfg), timeout=timeout)
478478
return True
479479
except Exception as e:
480480
_log.debug("Health check failed: %s", e)

0 commit comments

Comments
 (0)