feat: extract Daily Paper into an independently packaged plugin (#491)

* feat: extract Daily Paper into a plugin

* fix: satisfy clean-environment quality checks

* fix: address daily paper review feedback
This commit is contained in:
jinliyl 2026-08-26 17:32:46 +08:00 committed by GitHub
parent 1a6b584274
commit 513fb5b7f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
34 changed files with 705 additions and 227 deletions

View file

@ -32,6 +32,7 @@ jobs:
- name: Install
run: |
pip install -q -e packages/reme_ai_studio -e ".[dev,core]"
pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper
- name: Pre-commit starts
run: pre-commit run --all-files

View file

@ -37,11 +37,12 @@ jobs:
python -m pip install --upgrade pip setuptools wheel
pip install -e packages/reme_ai_studio -e ".[dev,core]"
pip install --no-deps -e plugins/auto-fin
pip install -e plugins/daily_paper
pip install coverage
- name: Run unit tests
run: |
coverage run -m pytest tests/unit plugins/auto-fin \
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \
-v \
--tb=long \
-s \

View file

@ -0,0 +1,148 @@
# Release checklist:
# 1. Update project.version in plugins/daily_paper/pyproject.toml and merge it into the target branch.
# 2. Publish the required reme-ai version before this plugin; the build verifies that dependency on PyPI.
# 3. Confirm PYPI_API_TOKEN is configured and the version does not already exist on PyPI.
# 4. Run "Release / Daily Paper plugin" from GitHub Actions with the exact project version (a v prefix is accepted).
#
# Recommended order: reme-ai -> reme-daily-paper -> downstream applications enabling plugins: [daily-paper].
# This workflow is intentionally manual and never publishes from a push, tag, or GitHub release event.
name: Release / Daily Paper plugin
run-name: Publish reme-daily-paper ${{ inputs.version }}
on:
workflow_dispatch:
inputs:
version:
description: Version from plugins/daily_paper/pyproject.toml (for example, 0.1.0)
required: true
type: string
permissions:
contents: read
concurrency:
group: publish-reme-daily-paper
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
env:
RELEASE_VERSION: ${{ inputs.version }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
- name: Install test and build dependencies
run: |
python -m pip install --upgrade pip
python -m pip install build packaging pytest pytest-asyncio twine
python -m pip install -e ".[core]"
python -m pip install -e plugins/daily_paper
- name: Validate package name, dependencies, and release version
id: package
run: |
python - "${RELEASE_VERSION}" <<'PY'
import os
import sys
import tomllib
from pathlib import Path
from packaging.requirements import Requirement
from packaging.version import Version
project = tomllib.loads(Path("plugins/daily_paper/pyproject.toml").read_text(encoding="utf-8"))["project"]
expected = Version(sys.argv[1].removeprefix("v"))
actual = Version(project["version"])
if project["name"] != "reme-daily-paper":
raise SystemExit(f"Expected project name 'reme-daily-paper', found {project['name']!r}")
if actual != expected:
raise SystemExit(f"Package version is {actual}, but workflow input is {expected}")
requirements = [Requirement(value) for value in project["dependencies"]]
reme_requirements = [requirement for requirement in requirements if requirement.name == "reme-ai"]
if len(reme_requirements) != 1 or set(reme_requirements[0].extras) != {"core"}:
raise SystemExit(f"Expected one reme-ai[core] dependency, found {reme_requirements!r}")
if sum(requirement.name == "pypdf" for requirement in requirements) != 1:
raise SystemExit("Expected exactly one pypdf dependency")
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
print(f"reme_requirement={reme_requirements[0]}", file=output)
print(f"Publishing {project['name']} {actual}")
PY
- name: Run Daily Paper tests
run: python -m pytest plugins/daily_paper -q
- name: Require the plugin-enabled ReMe release on PyPI
run: |
python -m pip download --no-deps \
--dest "${RUNNER_TEMP}/reme-daily-paper-core" \
"${{ steps.package.outputs.reme_requirement }}"
- name: Build and check distributions
run: |
mkdir -p dist/daily-paper
python -m build plugins/daily_paper --outdir dist/daily-paper
python -m twine check dist/daily-paper/*
- name: Verify distributions and isolated installation
run: |
DAILY_PAPER_WHEEL="$(pwd)/$(ls dist/daily-paper/reme_daily_paper-*.whl)"
DAILY_PAPER_SDIST="$(pwd)/$(ls dist/daily-paper/reme_daily_paper-*.tar.gz)"
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'reme_daily_paper/plugin.yaml'
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'reme_daily_paper/analyze.yaml'
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'dist-info/licenses/LICENSE'
python -m tarfile -l "${DAILY_PAPER_SDIST}" | grep '/LICENSE'
python -m venv "${RUNNER_TEMP}/reme-daily-paper-smoke"
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install "${DAILY_PAPER_WHEEL}"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" - <<'PY'
from importlib.metadata import distribution
from reme.plugin_manifest import load_package_manifest
package = distribution("reme-daily-paper")
plugins = {entry.name: entry for entry in package.entry_points if entry.group == "reme.plugins"}
assert plugins["daily-paper"].value == "reme_daily_paper"
manifest = load_package_manifest("reme_daily_paper", plugin_name="daily-paper")
assert set(manifest.backends) == {
"daily_paper_collect_step",
"daily_paper_rank_step",
"daily_paper_select_step",
"daily_paper_analyze_step",
"daily_paper_digest_step",
}
assert set(manifest.application_defaults["jobs"]) == {"daily_paper", "daily_paper_cron"}
PY
- name: Upload distributions
uses: actions/upload-artifact@v4
with:
name: reme-daily-paper-${{ inputs.version }}
path: dist/daily-paper/
if-no-files-found: error
publish:
needs: build
runs-on: ubuntu-latest
steps:
- name: Download distributions
uses: actions/download-artifact@v4
with:
name: reme-daily-paper-${{ inputs.version }}
path: dist/daily-paper
- name: Publish reme-daily-paper
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: dist/daily-paper

View file

@ -67,7 +67,7 @@ keeping the files under the user's control.
architecture, self-evolving workflows, hybrid search, proactive discovery, and benchmark results.
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/?doc=toolmemory-en) of agent tool-use execution built
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
- [2026.07] - Introduced optional Cookbooks: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) for paper discovery and
analysis, and [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) for researching the latest 24 hours of topic-related CLS news
with local-memory search and validated historical wikilinks.
- [2026.07] - Our
@ -217,10 +217,10 @@ These Markdown guides cover the main user workflows and the runtime contracts im
## 🔌 Plugins
Plugins are optional Python distributions that contribute Component, Step, or Job backends and configuration. They are
installed separately and enabled explicitly by configuration. Auto Fin is the complete external-plugin example; Daily
Paper remains an optional research workflow while it is migrated to the same packaging model.
installed separately and enabled explicitly by configuration. Daily Paper and Auto Fin are independently packaged
plugins; their source distributions live under [`plugins/`](plugins/README.md).
| Plugin / workflow | Capability |
| Plugin | Capability |
|-----------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| [Daily Paper](https://reme.agentscope.io/?doc=daily-paper-en) | Discover and rank papers, analyze PDFs with an agent, and generate file-native notes and a five-minute brief. |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-en) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |

View file

@ -60,7 +60,7 @@ Code 等 Agent 协作,在持续整理知识的同时,始终把文件控制
- [2026.08] - 基于 ReMe 的智能体工具使用
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
- [2026.07] - 新增可选 Cookbook 工作流[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析,
- [2026.07] - 新增可选插件[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析,
[Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
wikilink。
- [2026.07] -
@ -209,10 +209,10 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
## 🔌 插件
插件是可选的独立 Python distribution可以贡献 Component、Step、Job backend 和配置并通过配置显式启用。Auto Fin
是完整的外部插件示例;每日论文在迁移到同一打包模型前仍作为可选研究工作流提供
插件是可选的独立 Python distribution可以贡献 Component、Step、Job backend 和配置,并通过配置显式启用。每日论文与 Auto Fin
均已独立打包,源码 distribution 位于 [`plugins/`](plugins/README.md)
| 插件 / 工作流 | 能力 |
| 插件 | 能力 |
|-----------------------------------------------|--------------------------------------------------------------------------------|
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF生成文件化论文笔记和五分钟简报。 |
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |

View file

@ -77,13 +77,14 @@ reme/
base_step.py # BaseStep, Ref, dispatch_steps
common/ # version, help, health_check, status, chat
benchmark/ # LongMemEval / BEAM evaluation steps
cookbook/ # optional research workflow steps
cookbook/ # built-in cookbook support steps
file_io/ # read/write/edit/delete/move/frontmatter/daily
index/ # watch/init/update/search/traverse
evolve/ # auto_memory, auto_resource, auto_dream, proactive
transfer/ # upload/download
plugins/
auto-fin/ # independent example plugin distribution
daily_paper/ # independent paper-research plugin distribution
integrations/
claude_code/ # Claude Code adapter and marketplace
hermes_agent/ # Hermes Agent memory-provider adapter
@ -240,19 +241,20 @@ Plugin registration therefore stays local to one application;
duplicate `(component_type, backend)` providers fail during assembly instead of overwriting each other.
The legacy Python `Plugin` descriptor and `reme.configs` entry points remain accepted during migration. Configuration
files can use `extends` to inherit another built-in, legacy plugin, or file-based configuration. The
[Auto Fin plugin](../../plugins/auto-fin/README.md) is the current packaging example.
files can use `extends` to inherit another built-in, legacy plugin, or file-based configuration. See the independently
packaged [Auto Fin](../../plugins/auto-fin/README.md) and [Daily Paper](../../plugins/daily_paper/README.md) plugins.
Plugin packages are managed locally and remain separate from per-application activation:
```bash
reme plugins list
reme plugins install reme-auto-fin
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins uninstall auto-fin
reme plugins install reme-daily-paper
reme plugins show daily-paper
reme plugins validate daily-paper
reme plugins uninstall daily-paper
reme start plugins='["auto-fin"]'
reme start plugins='["auto-fin","daily-paper"]'
```
These management commands use the current Python interpreter's pip and never run through an HTTP or MCP service.

View file

@ -119,7 +119,7 @@ In other words, Auto Memory builds personal knowledge from conversations, while
### Daily Paper: An Example External-Resource Workflow
Daily Paper is an optional cookbook built on this file-based memory system. It collects papers from the weekly and monthly Hugging Face Papers rankings, removes items recommended recently, ranks the remaining papers, selects three, saves their PDFs, and generates Chinese paper notes and a briefing that takes about five minutes to read.
Daily Paper is an optional plugin built on this file-based memory system. It collects papers from the weekly and monthly Hugging Face Papers rankings, removes items recommended recently, ranks the remaining papers, selects three, saves their PDFs, and generates Chinese paper notes and a briefing that takes about five minutes to read.
Imagine that you regularly follow research on agent memory. Each morning, instead of receiving only three links, you get three detailed notes already saved locally. The briefing points to the original notes through Wikilinks, and each note links back to its PDF. A month later, when you ask, “What recent methods compress long-term memory?”, those materials are already in the same retrieval system. There is no need to search through browser history again.

View file

@ -72,13 +72,14 @@ reme/
base_step.py # BaseStep、Ref、dispatch_steps
common/ # version、help、health_check、status、chat
benchmark/ # LongMemEval / BEAM 评测步骤
cookbook/ # 可选研究工作流步骤
cookbook/ # 内置 cookbook 支持步骤
file_io/ # read/write/edit/delete/move/frontmatter/daily
index/ # watch/init/update/search/traverse
evolve/ # auto_memory、auto_resource、auto_dream、proactive
transfer/ # upload/download
plugins/
auto-fin/ # 独立发布的示例插件
daily_paper/ # 独立发布的论文研究插件
integrations/
claude_code/ # Claude Code 适配器及 marketplace
hermes_agent/ # Hermes Agent memory provider 适配器
@ -229,18 +230,20 @@ entry-point 名称就是插件标识;使用
不会互相覆盖。
迁移期间仍兼容旧的 Python `Plugin` descriptor 和 `reme.configs` entry point。配置的 `extends` 可以继承内置配置、
旧插件配置或文件配置。当前完整打包示例见 [Auto Fin 插件](../../plugins/auto-fin/README_ZH.md)。
旧插件配置或文件配置。独立打包示例见 [Auto Fin](../../plugins/auto-fin/README_ZH.md) 与
[每日论文](../../plugins/daily_paper/README_ZH.md) 插件。
插件包的本地管理与单个应用是否启用插件相互独立:
```bash
reme plugins list
reme plugins install reme-auto-fin
reme plugins show auto-fin
reme plugins validate auto-fin
reme plugins uninstall auto-fin
reme plugins install reme-daily-paper
reme plugins show daily-paper
reme plugins validate daily-paper
reme plugins uninstall daily-paper
reme start plugins='["auto-fin"]'
reme start plugins='["auto-fin","daily-paper"]'
```
这些管理命令使用当前 Python 解释器对应的 pip不通过 HTTP 或 MCP service 执行。

View file

@ -126,7 +126,7 @@ Auto Resource 提供了一条更通用的外部资料入口。资料进入 `reso
### Daily Paper外部资料工作流的一个例子
Daily Paper 是建立在这套文件化记忆之上的可选 Cookbook。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存
Daily Paper 是建立在这套文件化记忆之上的可选插件。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存
PDF并生成中文论文笔记与一份约五分钟可读完的简报。
想象一下,你持续关注 Agent Memory每天早上收到的不只是三个论文链接而是三篇已经保存到本地的详细笔记。简报通过 Wikilink

View file

@ -9,16 +9,21 @@ Manage plugin packages with the local CLI:
```bash
reme plugins list
reme plugins show auto-fin
reme plugins show daily-paper
reme plugins install reme-auto-fin
reme plugins install reme-daily-paper
reme plugins install ./plugins/auto-fin --editable
reme plugins install ./plugins/daily_paper --editable
reme plugins validate auto-fin
reme plugins validate daily-paper
reme plugins uninstall auto-fin
reme plugins uninstall daily-paper
```
Installation and activation are separate. Enable an installed plugin for one application through its config or CLI:
```bash
reme start plugins='["auto-fin"]'
reme start plugins='["auto-fin","daily-paper"]'
```
Adapters for external agent hosts belong in [`../integrations`](../integrations/README.md).

201
plugins/daily_paper/LICENSE Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2025 Alibaba Group
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -1,23 +1,24 @@
# Daily Paper Cookbook
# Daily Paper Plugin
[中文](README_ZH.md)
[中文](https://github.com/agentscope-ai/ReMe/blob/main/plugins/daily_paper/README_ZH.md)
Daily Paper selects three papers from the Hugging Face Papers weekly and monthly rankings, downloads their arXiv PDFs,
and produces detailed Chinese reading notes plus a roughly five-minute Chinese brief. The implementation lives in
[`reme/steps/cookbook/daily_paper/`](../../reme/steps/cookbook/daily_paper/) and is assembled by
[`daily_cookbook.yaml`](../../reme/config/daily_cookbook.yaml).
and produces detailed Chinese reading notes plus a roughly five-minute Chinese brief. This directory is an independent
Python distribution. Its `daily-paper` entry point exposes five Step backends and complete Job defaults through the
package's `plugin.yaml`; enable it explicitly with `plugins=["daily-paper"]` after installation.
## Quick start
The workflow requires Python 3.11 or later, the `core` dependencies, an available AgentScope LLM, and network access to
Hugging Face Papers and arXiv.
The workflow requires Python 3.11 or later, an available AgentScope LLM, and network access to Hugging Face Papers and
arXiv.
```bash
python -m pip install -e ".[core]"
python -m pip install "reme-ai[core]>=0.4.1.8"
reme plugins install reme-daily-paper
export LLM_API_KEY="your-api-key"
export LLM_MODEL_NAME="qwen3.7-plus"
export LLM_BASE_URL="https://your-provider.example/v1"
reme start config=daily_cookbook job=daily_paper
reme start plugins='["daily-paper"]' job=daily_paper
```
The built-in LLM component defaults to:
@ -26,13 +27,30 @@ The built-in LLM component defaults to:
- endpoint: no built-in `LLM_BASE_URL`; set the OpenAI-compatible endpoint required by your provider
- environment variables: `LLM_API_KEY`, `LLM_MODEL_NAME`, and `LLM_BASE_URL`
Auto Fin and Daily Paper share this single `default` LLM and the `default` AgentScope wrapper. Daily Paper Select and
Analyze call the wrapper without tools, while Daily Paper Digest and Auto Fin Merge receive the read-only
`memory_search` and `read` ReMe job tools. The interactive `dingtalk_wait` step separately overrides the wrapper per
call with AgentScope `bash` and an explicit ReMe job allowlist.
Daily Paper uses the application's `default` LLM and `default` AgentScope wrapper. Select and Analyze call the wrapper
without tools, while Digest receives only the read-only `memory_search` and `read` ReMe Job tools.
The default workspace is `reme_workspace/` beneath the process working directory. Override it with
`DAILY_PAPER_WORKSPACE_DIR`.
The default workspace is `.reme/` beneath the process working directory. Override it with `workspace_dir=...` or the
application configuration you use alongside the plugin.
## Migrating from the built-in cookbook
Daily Paper is no longer imported or configured by the core `reme-ai` distribution. Install this package and enable
`daily-paper` explicitly instead of running `config=daily_cookbook` to obtain the Daily Paper Jobs:
```bash
# Before
reme start config=daily_cookbook job=daily_paper
# Now
reme plugins install reme-daily-paper
reme start plugins='["daily-paper"]' job=daily_paper
```
The workflow schemas have moved from `reme.schema.daily_paper` to `reme_daily_paper.schema`. The arXiv and Hugging Face
clients formerly under `reme.utils` are plugin implementation details under `reme_daily_paper`; applications that used
those modules directly must install this package and update their imports. Existing workspace notes, downloaded PDFs,
and indexes are not migrated or rewritten. Point the application at the same `workspace_dir` to keep using them.
## Pipeline
@ -133,7 +151,7 @@ A failed recipient does not prevent later attempts; the step reports a combined
## Outputs
```text
reme_workspace/
.reme/
├── daily/
│ ├── YYYY-MM-DD.md
│ └── YYYY-MM-DD/
@ -182,7 +200,7 @@ when present. The two data sources reach a mirror differently: Hugging Face is g
parameter, while arXiv is driven by its environment variable alone.
```dotenv
# The built-in daily_paper_cron job enables the mirror by default; set false to use the official service
# The plugin's daily_paper_cron Job enables the mirror by default; set false to use the official service
DAILY_PAPER_USE_HF_MIRROR=false
# Read only when the manual or scheduled job enables the mirror; defaults to https://hf-mirror.com when unset
@ -202,7 +220,7 @@ trailing slash is optional. There is no fallback chain: whichever base URL a cli
> **Behavior change:** `HF_MIRROR_URL` used to redirect Hugging Face traffic on its own. It is now read only when the
> job runs with `use_hf_mirror=true`; otherwise the official service is used and the client logs a warning that the
> variable was ignored. Pass `use_hf_mirror=true` for manual requests. The built-in `daily_paper_cron` job enables the
> variable was ignored. Pass `use_hf_mirror=true` for manual requests. The plugin's `daily_paper_cron` Job enables the
> mirror by default; set `DAILY_PAPER_USE_HF_MIRROR=false` to make that scheduled job use the official service.
## Running the workflow
@ -211,7 +229,7 @@ Generate a brief for a specific date:
```bash
reme start \
config=daily_cookbook \
plugins='["daily-paper"]' \
job=daily_paper \
date=2026-08-06 \
topics="Agent memory" \
@ -221,22 +239,22 @@ reme start \
Force a rerun; valid local PDFs are still reused:
```bash
reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
reme start plugins='["daily-paper"]' job=daily_paper date=2026-08-06 force=true
```
Start the HTTP service and scheduled jobs:
```bash
reme start config=daily_cookbook
reme start plugins='["daily-paper"]'
```
The built-in service listens on `127.0.0.1:8001`. `daily_paper_cron` runs every day at 08:00 in the
`Asia/Shanghai` timezone, prioritizes the topic `大模型长期记忆`, and uses the Hugging Face mirror by default. Set
`DAILY_PAPER_USE_HF_MIRROR=false` to use the official service. Override the bind address with `DAILY_PAPER_HOST`,
`DAILY_PAPER_PORT`, or startup arguments.
With the default ReMe configuration, the HTTP service listens on `127.0.0.1:2333`. `daily_paper_cron` runs every day at
08:00 in the application timezone, prioritizes the topic `大模型长期记忆`, and uses the Hugging Face mirror by default.
Set `DAILY_PAPER_USE_HF_MIRROR=false` to use the official service. Override the service address through normal ReMe
configuration or startup arguments.
```bash
curl -s http://127.0.0.1:8001/daily_paper \
curl -s http://127.0.0.1:2333/daily_paper \
-H 'Content-Type: application/json' \
-d '{"date":"2026-08-06","force":false,"topics":"Agent memory"}'
```
@ -257,6 +275,6 @@ curl -s http://127.0.0.1:8001/daily_paper \
The focused unit tests mock Hugging Face, arXiv, AgentScope, and DingTalk boundaries and do not call real services:
```bash
python -m pip install -e ".[dev,core]"
pytest tests/unit/test_daily_paper.py -v
python -m pip install -e packages/reme_ai_studio -e ".[dev,core]" -e plugins/daily_paper
python -m pytest plugins/daily_paper -v
```

View file

@ -1,21 +1,22 @@
# 每日论文 Cookbook
# 每日论文插件
[English](README.md)
每日论文工作流从 Hugging Face Papers 的周榜和月榜中筛选三篇论文,下载 arXiv PDF生成中文论文解读和一篇约五分钟可读完的中文简报。当前实现位于
[`reme/steps/cookbook/daily_paper/`](../../reme/steps/cookbook/daily_paper/),由
[`daily_cookbook.yaml`](../../reme/config/daily_cookbook.yaml) 装配
每日论文从 Hugging Face Papers 的周榜和月榜中筛选三篇论文,下载 arXiv PDF生成中文论文解读和一篇约五分钟可读完的中文简报。本目录是一个
独立 Python distribution`daily-paper` entry point 通过 package 内的 `plugin.yaml` 暴露五个 Step backend
和完整 Job 默认配置;安装后使用 `plugins=["daily-paper"]` 显式启用
## 快速开始
要求 Python 3.11 或更高版本、`core` 依赖、可用的 AgentScope LLM以及能访问 Hugging Face Papers 和 arXiv 的网络。
要求 Python 3.11 或更高版本、可用的 AgentScope LLM以及能访问 Hugging Face Papers 和 arXiv 的网络。
```bash
python -m pip install -e ".[core]"
python -m pip install "reme-ai[core]>=0.4.1.8"
reme plugins install reme-daily-paper
export LLM_API_KEY="your-api-key"
export LLM_MODEL_NAME="qwen3.7-plus"
export LLM_BASE_URL="https://your-provider.example/v1"
reme start config=daily_cookbook job=daily_paper
reme start plugins='["daily-paper"]' job=daily_paper
```
内置 LLM 组件默认配置为:
@ -24,11 +25,28 @@ reme start config=daily_cookbook job=daily_paper
- endpoint无内置 `LLM_BASE_URL`;请设置服务商要求的 OpenAI 兼容 endpoint
- 环境变量:`LLM_API_KEY``LLM_MODEL_NAME``LLM_BASE_URL`
Auto Fin 和 Daily Paper 共用这一个 `default` LLM 和 `default` AgentScope wrapper。Daily Paper 的 Select 和 Analyze
调用不带工具Daily Paper Digest 与 Auto Fin Merge 使用只读的 ReMe Job 工具 `memory_search``read`。交互式
`dingtalk_wait` Step 则会在调用时单独覆盖 wrapper启用 AgentScope `bash` 和明确的 ReMe Job allowlist。
每日论文使用 Application 的 `default` LLM 和 `default` AgentScope wrapper。Select 和 Analyze 调用不带工具Digest
只使用只读的 ReMe Job 工具 `memory_search``read`
默认 workspace 是启动目录下的 `reme_workspace/`,可通过 `DAILY_PAPER_WORKSPACE_DIR` 覆盖。
默认 workspace 是启动目录下的 `.reme/`;可通过 `workspace_dir=...` 或与插件组合使用的 Application 配置覆盖。
## 从内置 Cookbook 迁移
Daily Paper 不再由核心 `reme-ai` distribution 导入或配置。需要安装本 package 并显式启用 `daily-paper`,而不是通过
`config=daily_cookbook` 获取 Daily Paper Job
```bash
# 旧方式
reme start config=daily_cookbook job=daily_paper
# 新方式
reme plugins install reme-daily-paper
reme start plugins='["daily-paper"]' job=daily_paper
```
工作流 schema 已从 `reme.schema.daily_paper` 移至 `reme_daily_paper.schema`。原先位于 `reme.utils` 的 arXiv 和
Hugging Face client 现在是 `reme_daily_paper` 下的插件实现细节;直接使用这些模块的应用需要安装本 package 并更新
import。已有 workspace 笔记、下载的 PDF 和索引不会被迁移或改写;将 Application 指向原 `workspace_dir` 即可继续使用。
## 工作流
@ -125,7 +143,7 @@ DINGTALK_CONVERSATION_IDS=cid-group-one,cid-group-two
## 产物
```text
reme_workspace/
.reme/
├── daily/
│ ├── YYYY-MM-DD.md
│ └── YYYY-MM-DD/
@ -172,7 +190,7 @@ reme_workspace/
Face 由 `use_hf_mirror` 任务参数控制arXiv 仅由环境变量驱动。
```dotenv
# 内置 daily_paper_cron 定时任务默认启用镜像站;设为 false 可改用官方服务
# 插件提供的 daily_paper_cron Job 默认启用镜像站;设为 false 可改用官方服务
DAILY_PAPER_USE_HF_MIRROR=false
# 仅在手动任务或定时任务启用镜像时读取;未配置时使用 https://hf-mirror.com
@ -192,7 +210,7 @@ URL就只访问该地址。
> **行为变更:** 以往只要设置 `HF_MIRROR_URL` 就会改变 Hugging Face
> 的访问地址;现在该变量仅在任务启用镜像时才会读取,否则直接访问官方站点,并输出一条“已忽略该变量”的告警日志。手动调用需传入
> `use_hf_mirror=true`内置 `daily_paper_cron` 定时任务默认启用镜像;设置 `DAILY_PAPER_USE_HF_MIRROR=false`
> `use_hf_mirror=true`插件提供的 `daily_paper_cron` Job 默认启用镜像;设置 `DAILY_PAPER_USE_HF_MIRROR=false`
> 可让该定时任务改用官方服务。
## 运行方式
@ -201,7 +219,7 @@ URL就只访问该地址。
```bash
reme start \
config=daily_cookbook \
plugins='["daily-paper"]' \
job=daily_paper \
date=2026-08-06 \
topics="Agent memory" \
@ -211,21 +229,21 @@ reme start \
强制重跑;有效的本地 PDF 仍会复用:
```bash
reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
reme start plugins='["daily-paper"]' job=daily_paper date=2026-08-06 force=true
```
启动 HTTP 服务和定时任务:
```bash
reme start config=daily_cookbook
reme start plugins='["daily-paper"]'
```
内置服务监听 `127.0.0.1:8001``daily_paper_cron``Asia/Shanghai` 时区每天 08:00 运行,默认优先关注
`大模型长期记忆`,并使用 Hugging Face 镜像站。设置 `DAILY_PAPER_USE_HF_MIRROR=false` 可改用官方服务。可通过
`DAILY_PAPER_HOST``DAILY_PAPER_PORT` 或启动参数覆盖监听地址和端口。
使用 ReMe 默认配置时HTTP 服务监听 `127.0.0.1:2333``daily_paper_cron` 按 Application 时区每天 08:00 运行,
默认优先关注 `大模型长期记忆`,并使用 Hugging Face 镜像站。设置 `DAILY_PAPER_USE_HF_MIRROR=false` 可改用官方服务
可通过常规 ReMe 配置或启动参数覆盖监听地址和端口。
```bash
curl -s http://127.0.0.1:8001/daily_paper \
curl -s http://127.0.0.1:2333/daily_paper \
-H 'Content-Type: application/json' \
-d '{"date":"2026-08-06","force":false,"topics":"Agent memory"}'
```
@ -243,6 +261,6 @@ curl -s http://127.0.0.1:8001/daily_paper \
单元测试会 mock Hugging Face、arXiv、AgentScope 和 DingTalk 边界,不访问真实服务:
```bash
python -m pip install -e ".[dev,core]"
pytest tests/unit/test_daily_paper.py -v
python -m pip install -e packages/reme_ai_studio -e ".[dev,core]" -e plugins/daily_paper
python -m pytest plugins/daily_paper -v
```

View file

@ -0,0 +1,32 @@
[project]
name = "reme-daily-paper"
version = "0.1.0"
description = "Daily Paper research and reading-note plugin for ReMe."
readme = "README.md"
license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"pypdf>=5.0.0",
"reme-ai[core]>=0.4.1.8",
]
[project.entry-points."reme.plugins"]
daily-paper = "reme_daily_paper"
[tool.setuptools]
package-dir = { "" = "src" }
packages = ["reme_daily_paper"]
include-package-data = true
[tool.setuptools.package-data]
reme_daily_paper = ["*.yaml"]
[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"
pythonpath = ["src", "../.."]
testpaths = ["tests"]
[build-system]
requires = ["setuptools>=77", "wheel"]
build-backend = "setuptools.build_meta"

View file

@ -1,9 +1,10 @@
"""Daily-paper cookbook workflow."""
"""Daily Paper plugin for ReMe."""
from .analyze import DailyPaperAnalyzeStep
from .collect import DailyPaperCollectStep
from .digest import DailyPaperDigestStep
from .rank import DailyPaperRankStep
from .schema import AnalyzedPaper, DailyPaperMarkdownOutput, PaperInfo, PaperPick, PaperPickList
from .select import DailyPaperSelectStep
__all__ = [
@ -12,4 +13,9 @@ __all__ = [
"DailyPaperDigestStep",
"DailyPaperRankStep",
"DailyPaperSelectStep",
"AnalyzedPaper",
"DailyPaperMarkdownOutput",
"PaperInfo",
"PaperPick",
"PaperPickList",
]

View file

@ -4,10 +4,8 @@ import asyncio
import json
from pathlib import Path
from ....components import R
from ....schema import AnalyzedPaper, DailyPaperMarkdownOutput, PaperInfo, PaperPick
from ....utils.arxiv import ArxivPdfClient
from ._common import (
from .arxiv import ArxivPdfClient
from .base import (
PAPER_COUNT,
DailyPaperStep,
iter_note_metadata,
@ -19,9 +17,9 @@ from ._common import (
utc_now_iso,
write_markdown,
)
from .schema import AnalyzedPaper, DailyPaperMarkdownOutput, PaperInfo, PaperPick
@R.register("daily_paper_analyze_step")
class DailyPaperAnalyzeStep(DailyPaperStep):
"""Download and analyze the three papers selected for the daily brief."""

View file

@ -1,3 +1,4 @@
# Prompt bundled with the Daily Paper plugin distribution.
analyze_user: |
你是严谨的中文 AI 论文解读作者。请详细解读下面这篇论文。内容只能依据提供的论文元信息和 PDF 提取文本。
不得臆测未出现在材料中的实验、数字、结论或引用。重要实验结论和数字尽量标注 PDF 页码,例如 [p. 7]。

View file

@ -9,7 +9,7 @@ from uuid import uuid4
import aiofiles
import httpx
from .logger_utils import get_logger
from reme.utils import get_logger
ARXIV_ID_PATTERN = re.compile(r"^\d{4}\.\d{4,5}$")
ARXIV_BASE_URL = "https://arxiv.org"

View file

@ -3,6 +3,7 @@
import datetime as dt
import os
import re
import zoneinfo
from collections.abc import Iterator
from pathlib import Path
from typing import Any, TypeVar
@ -12,8 +13,8 @@ import aiofiles
import frontmatter
from pydantic import BaseModel
from ...base_step import BaseStep
from ...file_io import get_path_lock, validate_filename_component
from reme.steps import BaseStep
from reme.steps.file_io import get_path_lock, validate_filename_component
# Number of papers selected, analyzed, and digested each run. Shared across steps.
PAPER_COUNT = 3
@ -76,6 +77,16 @@ def utc_now_iso() -> str:
return dt.datetime.now(dt.timezone.utc).isoformat()
def now(timezone: str | None = None) -> dt.datetime:
"""Return the current time in an IANA timezone, falling back to local time."""
if not timezone:
return dt.datetime.now()
try:
return dt.datetime.now(zoneinfo.ZoneInfo(timezone))
except (KeyError, ValueError, zoneinfo.ZoneInfoNotFoundError):
return dt.datetime.now()
def iter_note_metadata(day_dir: Path) -> Iterator[tuple[Path, dict[str, Any]]]:
"""Yield ``(path, frontmatter metadata)`` for each readable Markdown note in a day."""
if not day_dir.is_dir():

View file

@ -4,15 +4,12 @@ import asyncio
import datetime as dt
from pathlib import Path
from ....components import R
from ....schema import PaperInfo
from ....utils.arxiv import ARXIV_ID_PATTERN
from ....utils.huggingface_papers import HuggingFacePapersClient
from ...evolve import now
from ._common import DailyPaperStep, iter_note_metadata
from .arxiv import ARXIV_ID_PATTERN
from .base import DailyPaperStep, iter_note_metadata, now
from .huggingface_papers import HuggingFacePapersClient
from .schema import PaperInfo
@R.register("daily_paper_collect_step")
class DailyPaperCollectStep(DailyPaperStep):
"""Collect current weekly/monthly rankings and strict-yesterday exclusions."""

View file

@ -6,10 +6,9 @@ import re
from pathlib import Path
from types import SimpleNamespace
from ....components import R
from ....schema import AnalyzedPaper, DailyPaperMarkdownOutput
from ...file_io import refresh_day_index
from ._common import (
from reme.steps.file_io import refresh_day_index
from .base import (
PAPER_COUNT,
DailyPaperStep,
normalize_chinese_title,
@ -19,11 +18,11 @@ from ._common import (
utc_now_iso,
write_markdown,
)
from .schema import AnalyzedPaper, DailyPaperMarkdownOutput
_WIKILINK_RE = re.compile(r"\[\[([^\[\]\n]+)\]\]")
@R.register("daily_paper_digest_step")
class DailyPaperDigestStep(DailyPaperStep):
"""Use an agent to read the detailed notes and create the final brief."""

View file

@ -1,3 +1,4 @@
# Prompt bundled with the Daily Paper plugin distribution.
digest_user: |
你是中文 AI 研究资讯主编。请忠实综合下面三篇详细论文解读,生成一篇普通读者五分钟可以读懂的每日论文速读。
内容只能依据输入文档,不得补充文档中没有提供的事实。

View file

@ -8,9 +8,10 @@ from typing import Any
import httpx
from ..schema import PaperInfo
from reme.utils import get_logger
from .arxiv import ARXIV_ID_PATTERN
from .logger_utils import get_logger
from .schema import PaperInfo
HF_BASE_URL = "https://huggingface.co"
HF_MIRROR_BASE_URL = "https://hf-mirror.com"
@ -100,7 +101,7 @@ class HuggingFacePapersClient:
base_url=self.base_url,
timeout=self._timeout,
follow_redirects=True,
headers={"User-Agent": "ReMe daily-paper cookbook"},
headers={"User-Agent": "ReMe Daily Paper plugin"},
)
self.logger.info(f"[HuggingFacePapersClient] source={self._source}")
else:

View file

@ -0,0 +1,82 @@
backends:
daily_paper_collect_step: reme_daily_paper.collect:DailyPaperCollectStep
daily_paper_rank_step: reme_daily_paper.rank:DailyPaperRankStep
daily_paper_select_step: reme_daily_paper.select:DailyPaperSelectStep
daily_paper_analyze_step: reme_daily_paper.analyze:DailyPaperAnalyzeStep
daily_paper_digest_step: reme_daily_paper.digest:DailyPaperDigestStep
application_defaults:
jobs:
daily_paper:
backend: base
description: "Build detailed readings and a five-minute brief from Hugging Face weekly/monthly papers."
candidate_limit: &candidate_limit 20
rrf_k: &rrf_k 60
weekly_weight: &weekly_weight 0.7
history_days: &history_days 30
hf_timeout: &hf_timeout 600
hf_max_retries: &hf_max_retries 3
pdf_timeout: &pdf_timeout 600
max_pdf_bytes: &max_pdf_bytes 52428800
max_pdf_pages: &max_pdf_pages 35
max_pdf_chars: &max_pdf_chars 300000
parameters:
type: object
properties:
date:
type: string
description: "Run date in YYYY-MM-DD; empty means today in the application timezone."
default: ""
force:
type: boolean
description: "Regenerate even when that day's final brief already exists."
default: false
use_hf_mirror:
type: boolean
description: "Use the Hugging Face mirror configured by HF_MIRROR_URL, or hf-mirror.com when unset."
default: false
topics:
type: string
description: "Optional topics to prioritize when selecting papers."
default: ""
weekly_weight:
type: number
description: "Weekly contribution in reciprocal-rank fusion."
default: 0.7
history_days:
type: integer
description: "Prior recommendation window excluded by arXiv ID."
default: 30
steps: &daily_paper_steps
- backend: daily_paper_collect_step
- backend: daily_paper_rank_step
- backend: daily_paper_select_step
- backend: daily_paper_analyze_step
- backend: daily_paper_digest_step
job_tools: [memory_search, read]
- backend: dingtalk_markdown_send_step
input_mapping:
daily_paper_digest_path: markdown_path
app_key: ${DINGTALK_APP_KEY:-}
app_secret: ${DINGTALK_APP_SECRET:-}
robot_code: ${DINGTALK_ROBOT_CODE:-}
conversation_ids: ${DINGTALK_CONVERSATION_IDS:-}
title: ReMe Daily Paper
timeout: 15
daily_paper_cron:
backend: cron
cron: "0 8 * * *"
topics: "大模型长期记忆"
use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-true}
candidate_limit: *candidate_limit
rrf_k: *rrf_k
weekly_weight: *weekly_weight
history_days: *history_days
hf_timeout: *hf_timeout
hf_max_retries: *hf_max_retries
pdf_timeout: *pdf_timeout
max_pdf_bytes: *max_pdf_bytes
max_pdf_pages: *max_pdf_pages
max_pdf_chars: *max_pdf_chars
steps: *daily_paper_steps

View file

@ -1,8 +1,7 @@
"""Rank collected papers for the daily-paper workflow."""
from ....components import R
from ....schema import PaperInfo
from ._common import DailyPaperStep
from .base import DailyPaperStep
from .schema import PaperInfo
def rrf_score(
@ -28,7 +27,6 @@ def build_candidate_pool(papers: list[PaperInfo], *, limit: int = 20) -> list[Pa
return ranked[:limit]
@R.register("daily_paper_rank_step")
class DailyPaperRankStep(DailyPaperStep):
"""Apply RRF and produce the bounded selection pool."""

View file

@ -1,4 +1,4 @@
"""Typed contracts for the daily-paper cookbook workflow."""
"""Typed contracts for the Daily Paper plugin."""
from pydantic import BaseModel, Field

View file

@ -2,14 +2,12 @@
import json
from ....components import R
from ....schema import PaperInfo, PaperPick, PaperPickList
from ._common import PAPER_COUNT, DailyPaperStep, structured_output
from .base import PAPER_COUNT, DailyPaperStep, structured_output
from .schema import PaperInfo, PaperPick, PaperPickList
_MAX_SELECT_ATTEMPTS = 2
@R.register("daily_paper_select_step")
class DailyPaperSelectStep(DailyPaperStep):
"""Use an agent to select the final papers."""

View file

@ -1,3 +1,4 @@
# Prompt bundled with the Daily Paper plugin distribution.
select_user: |
从候选中选择恰好 3 篇最值得深入阅读的 AI 论文。兼顾研究价值、新颖性、影响和可读性。
{selection_preference}

View file

@ -1,4 +1,4 @@
"""Focused tests for the daily-paper cookbook workflow."""
"""Focused tests for the Daily Paper plugin."""
import datetime as dt
import importlib
@ -11,32 +11,59 @@ from unittest.mock import AsyncMock, MagicMock
import frontmatter
import httpx
import pytest
import yaml
from reme.components import ApplicationContext
from reme.components.agent_wrapper.base_agent_wrapper import BaseAgentWrapper
from reme.components.runtime_context import RuntimeContext
from reme.config.config_parser import _load_config
from reme.schema import AnalyzedPaper, DailyPaperMarkdownOutput, PaperInfo, PaperPick, PaperPickList
from reme.steps.cookbook.daily_paper import (
from reme_daily_paper import (
AnalyzedPaper,
DailyPaperMarkdownOutput,
DailyPaperAnalyzeStep,
DailyPaperCollectStep,
DailyPaperDigestStep,
DailyPaperRankStep,
DailyPaperSelectStep,
PaperInfo,
PaperPick,
PaperPickList,
)
from reme.steps.cookbook.daily_paper import analyze, collect
from reme.steps.cookbook.daily_paper._common import (
from reme_daily_paper import analyze, collect
from reme_daily_paper import arxiv as arxiv_utils
from reme_daily_paper import huggingface_papers as hf_utils
from reme_daily_paper.base import (
normalize_chinese_title,
now,
replace_surrogates,
write_atomic,
write_markdown,
)
from reme.steps.cookbook.daily_paper.rank import build_candidate_pool, rrf_score
from reme_daily_paper.huggingface_papers import paper_ids_from_html, paper_info_from_payload
from reme_daily_paper.rank import build_candidate_pool, rrf_score
from reme.components import ApplicationContext
from reme.components.agent_wrapper.base_agent_wrapper import BaseAgentWrapper
from reme.components.runtime_context import RuntimeContext
from reme.config import expand_env_vars
from reme.steps.cookbook.dingtalk import DingTalkMarkdownSendStep
from reme.steps.cookbook.dingtalk import send as dingtalk_send
from reme.utils import arxiv as arxiv_utils
from reme.utils import huggingface_papers as hf_utils
from reme.utils.huggingface_papers import paper_ids_from_html, paper_info_from_payload
PLUGIN_MANIFEST = yaml.safe_load(
(Path(__file__).parents[1] / "src" / "reme_daily_paper" / "plugin.yaml").read_text(encoding="utf-8"),
)
def _plugin_config() -> dict:
"""Load application defaults with the same environment expansion as ReMe."""
return expand_env_vars(PLUGIN_MANIFEST["application_defaults"])
def test_plugin_manifest_declares_complete_runtime_surface():
"""Keep backend registration and both public Jobs inside the distribution."""
assert set(PLUGIN_MANIFEST["backends"]) == {
"daily_paper_collect_step",
"daily_paper_rank_step",
"daily_paper_select_step",
"daily_paper_analyze_step",
"daily_paper_digest_step",
}
assert set(_plugin_config()["jobs"]) == {"daily_paper", "daily_paper_cron"}
class _QueuedAgentWrapper(BaseAgentWrapper):
@ -69,6 +96,12 @@ def test_daily_paper_replaces_surrogates_in_text_and_titles():
assert normalize_chinese_title("论文\ud800标题", "fallback") == "论文\ufffd标题"
@pytest.mark.parametrize("timezone", ["/etc/localtime", "../UTC", "invalid\x00timezone"])
def test_daily_paper_invalid_timezone_falls_back_to_local_time(timezone: str):
"""Invalid IANA timezone keys retain the workflow's local-time fallback."""
assert now(timezone).tzinfo is None
@pytest.mark.asyncio
async def test_daily_paper_atomic_write_replaces_surrogates(tmp_path: Path):
"""Markdown writes always produce valid UTF-8 even when model output is malformed."""
@ -542,7 +575,7 @@ def test_daily_paper_config_passes_dingtalk_environment(monkeypatch):
for name, value in values.items():
monkeypatch.setenv(name, value)
step = _load_config("daily_cookbook")["jobs"]["daily_paper"]["steps"][-1]
step = _plugin_config()["jobs"]["daily_paper"]["steps"][-1]
assert {key: step[key] for key in ("app_key", "app_secret", "robot_code", "conversation_ids")} == {
"app_key": "app-key",
@ -554,20 +587,13 @@ def test_daily_paper_config_passes_dingtalk_environment(monkeypatch):
def test_daily_paper_uses_agentscope_without_tools():
"""Daily Paper uses the shared tool-free agent."""
config = _load_config("daily_cookbook")
wrapper = config["components"]["agent_wrapper"]["default"]
assert wrapper == {
"backend": "agentscope",
"as_llm": "default",
"builtin_tools": False,
}
config = _plugin_config()
assert "agent_wrapper" not in config["jobs"]["daily_paper"]["steps"][2]
def test_daily_paper_topics_parameter_defaults_to_empty():
"""Topics are an optional selection preference in the public job schema."""
topics = _load_config("daily_cookbook")["jobs"]["daily_paper"]["parameters"]["properties"]["topics"]
topics = _plugin_config()["jobs"]["daily_paper"]["parameters"]["properties"]["topics"]
assert topics == {
"type": "string",
@ -578,12 +604,12 @@ def test_daily_paper_topics_parameter_defaults_to_empty():
def test_daily_paper_cron_prioritizes_long_term_llm_memory():
"""The scheduled workflow prioritizes papers about long-term LLM memory."""
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["topics"] == "大模型长期记忆"
assert _plugin_config()["jobs"]["daily_paper_cron"]["topics"] == "大模型长期记忆"
def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
"""The public job schema exposes an explicit Hugging Face mirror switch."""
use_hf_mirror = _load_config("daily_cookbook")["jobs"]["daily_paper"]["parameters"]["properties"]["use_hf_mirror"]
use_hf_mirror = _plugin_config()["jobs"]["daily_paper"]["parameters"]["properties"]["use_hf_mirror"]
assert use_hf_mirror == {
"type": "boolean",
@ -595,10 +621,10 @@ def test_daily_paper_hf_mirror_parameter_defaults_to_disabled():
def test_daily_paper_cron_hf_mirror_defaults_enabled_with_environment_override(monkeypatch):
"""The scheduled workflow uses the mirror by default and supports an explicit override."""
monkeypatch.delenv("DAILY_PAPER_USE_HF_MIRROR", raising=False)
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is True
assert _plugin_config()["jobs"]["daily_paper_cron"]["use_hf_mirror"] is True
monkeypatch.setenv("DAILY_PAPER_USE_HF_MIRROR", "false")
assert _load_config("daily_cookbook")["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
assert _plugin_config()["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
def test_paper_pick_list_uses_an_object_root_for_tool_output():
@ -612,7 +638,7 @@ def test_paper_pick_list_uses_an_object_root_for_tool_output():
def test_daily_paper_selects_three_papers_and_bounds_pdf_context():
"""The public job has no paper-count option and bounds extracted PDF text."""
job = _load_config("daily_cookbook")["jobs"]["daily_paper"]
job = _plugin_config()["jobs"]["daily_paper"]
assert "top_k" not in job
assert "top_k" not in job["parameters"]["properties"]
@ -686,7 +712,7 @@ import reme
result = subprocess.run(
[sys.executable, "-c", script],
cwd=Path(__file__).parents[2],
cwd=Path(__file__).parents[3],
capture_output=True,
text=True,
check=False,

View file

@ -38,7 +38,6 @@ dependencies = [
"uvicorn>=0.41.0",
"watchfiles>=1.1.1",
"zstandard>=0.23.0",
"pypdf>=5.0.0",
]
[project.optional-dependencies]

View file

@ -316,80 +316,6 @@ jobs:
steps:
- backend: edit_step
daily_paper:
backend: base
description: "Build detailed readings and a five-minute brief from Hugging Face weekly/monthly papers."
candidate_limit: &candidate_limit 20
rrf_k: &rrf_k 60
weekly_weight: &weekly_weight 0.7
history_days: &history_days 30
hf_timeout: &hf_timeout 600
hf_max_retries: &hf_max_retries 3
pdf_timeout: &pdf_timeout 600
max_pdf_bytes: &max_pdf_bytes 52428800
max_pdf_pages: &max_pdf_pages 35
max_pdf_chars: &max_pdf_chars 300000
parameters:
type: object
properties:
date:
type: string
description: "Run date in YYYY-MM-DD; empty means today in Asia/Shanghai."
default: ""
force:
type: boolean
description: "Regenerate even when that day's final brief already exists."
default: false
use_hf_mirror:
type: boolean
description: "Use the Hugging Face mirror configured by HF_MIRROR_URL, or hf-mirror.com when unset."
default: false
topics:
type: string
description: "Optional topics to prioritize when selecting papers."
default: ""
weekly_weight:
type: number
description: "Weekly contribution in reciprocal-rank fusion."
default: 0.7
history_days:
type: integer
description: "Prior recommendation window excluded by arXiv ID."
default: 30
steps: &daily_paper_steps
- backend: daily_paper_collect_step
- backend: daily_paper_rank_step
- backend: daily_paper_select_step
- backend: daily_paper_analyze_step
- backend: daily_paper_digest_step
job_tools: [memory_search, read]
- backend: dingtalk_markdown_send_step
input_mapping:
daily_paper_digest_path: markdown_path
app_key: ${DINGTALK_APP_KEY:-}
app_secret: ${DINGTALK_APP_SECRET:-}
robot_code: ${DINGTALK_ROBOT_CODE:-}
conversation_ids: ${DINGTALK_CONVERSATION_IDS:-}
title: ReMe Daily Paper
timeout: 15
daily_paper_cron:
backend: cron
cron: "0 8 * * *"
topics: "大模型长期记忆"
use_hf_mirror: ${DAILY_PAPER_USE_HF_MIRROR:-true}
candidate_limit: *candidate_limit
rrf_k: *rrf_k
weekly_weight: *weekly_weight
history_days: *history_days
hf_timeout: *hf_timeout
hf_max_retries: *hf_max_retries
pdf_timeout: *pdf_timeout
max_pdf_bytes: *max_pdf_bytes
max_pdf_pages: *max_pdf_pages
max_pdf_chars: *max_pdf_chars
steps: *daily_paper_steps
dingtalk_wait:
backend: background
supervisor: true

View file

@ -1,13 +1,6 @@
"""Schema"""
from .application_config import ApplicationConfig, ComponentConfig, JobConfig
from .daily_paper import (
AnalyzedPaper,
DailyPaperMarkdownOutput,
PaperInfo,
PaperPick,
PaperPickList,
)
from .dream import (
DreamExtractOutput,
DreamState,
@ -32,8 +25,6 @@ from .traverse_graph import TraverseGraph, TraverseGraphEdge, TraverseGraphNode
__all__ = [
"ApplicationConfig",
"ComponentConfig",
"AnalyzedPaper",
"DailyPaperMarkdownOutput",
"DreamExtractOutput",
"DreamState",
"DreamTopic",
@ -48,9 +39,6 @@ __all__ = [
"GraphSnapshotNode",
"IntegrateOutcome",
"JobConfig",
"PaperInfo",
"PaperPick",
"PaperPickList",
"ProactiveResult",
"Request",
"Response",

View file

@ -1,5 +1,5 @@
"""Optional, end-to-end cookbook workflows."""
from . import daily_paper, dingtalk
from . import dingtalk
__all__ = ["daily_paper", "dingtalk"]
__all__ = ["dingtalk"]

View file

@ -199,6 +199,23 @@ def test_auto_fin_requires_reme_core() -> None:
assert set(reme_requirements[0].extras) == {"core"}
def test_daily_paper_license_matches_repository() -> None:
"""Keep the independently distributed Daily Paper license complete and current."""
assert (REPOSITORY / "plugins" / "daily_paper" / "LICENSE").read_text(encoding="utf-8") == (
REPOSITORY / "LICENSE"
).read_text(encoding="utf-8")
def test_daily_paper_declares_runtime_dependencies() -> None:
"""Keep Daily Paper's ReMe feature set and PDF parser explicit in its own distribution."""
config = tomllib.loads((REPOSITORY / "plugins" / "daily_paper" / "pyproject.toml").read_text(encoding="utf-8"))
requirements = [Requirement(value) for value in config["project"]["dependencies"]]
by_name = {requirement.name: requirement for requirement in requirements}
assert set(by_name["reme-ai"].extras) == {"core"}
assert "pypdf" in by_name
def test_studio_package_preparation_preserves_static_gitignore(monkeypatch, tmp_path: Path) -> None:
"""Keep generated static assets ignored after staging the Studio build."""
package_dir = tmp_path / "reme_ai_studio"