From 513fb5b7f41e9d82c3ac90380664b1ef7d344b9e Mon Sep 17 00:00:00 2001 From: jinliyl <6469360+jinliyl@users.noreply.github.com> Date: Wed, 26 Aug 2026 17:32:46 +0800 Subject: [PATCH] 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 --- .github/workflows/ci-python-quality.yml | 1 + .github/workflows/ci-python-tests.yml | 3 +- .github/workflows/release-daily-paper.yml | 148 +++++++++++++ README.md | 8 +- README_ZH.md | 8 +- docs/en/framework.md | 16 +- docs/en/reme-blog.md | 2 +- docs/zh/framework.md | 15 +- docs/zh/reme-blog.md | 2 +- plugins/README.md | 7 +- plugins/daily_paper/LICENSE | 201 ++++++++++++++++++ plugins/daily_paper/README.md | 74 ++++--- plugins/daily_paper/README_ZH.md | 64 ++++-- plugins/daily_paper/pyproject.toml | 32 +++ .../src/reme_daily_paper}/__init__.py | 8 +- .../src/reme_daily_paper}/analyze.py | 8 +- .../src/reme_daily_paper}/analyze.yaml | 1 + .../src/reme_daily_paper}/arxiv.py | 2 +- .../daily_paper/src/reme_daily_paper/base.py | 15 +- .../src/reme_daily_paper}/collect.py | 11 +- .../src/reme_daily_paper}/digest.py | 9 +- .../src/reme_daily_paper}/digest.yaml | 1 + .../reme_daily_paper}/huggingface_papers.py | 7 +- .../src/reme_daily_paper/plugin.yaml | 82 +++++++ .../daily_paper/src/reme_daily_paper}/rank.py | 6 +- .../src/reme_daily_paper/schema.py | 2 +- .../src/reme_daily_paper}/select.py | 6 +- .../src/reme_daily_paper}/select.yaml | 1 + .../daily_paper/tests}/test_daily_paper.py | 84 +++++--- pyproject.toml | 1 - reme/config/daily_cookbook.yaml | 74 ------- reme/schema/__init__.py | 12 -- reme/steps/cookbook/__init__.py | 4 +- tests/unit/test_package_versions.py | 17 ++ 34 files changed, 705 insertions(+), 227 deletions(-) create mode 100644 .github/workflows/release-daily-paper.yml create mode 100644 plugins/daily_paper/LICENSE create mode 100644 plugins/daily_paper/pyproject.toml rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/__init__.py (60%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/analyze.py (97%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/analyze.yaml (95%) rename {reme/utils => plugins/daily_paper/src/reme_daily_paper}/arxiv.py (99%) rename reme/steps/cookbook/daily_paper/_common.py => plugins/daily_paper/src/reme_daily_paper/base.py (92%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/collect.py (96%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/digest.py (97%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/digest.yaml (96%) rename {reme/utils => plugins/daily_paper/src/reme_daily_paper}/huggingface_papers.py (98%) create mode 100644 plugins/daily_paper/src/reme_daily_paper/plugin.yaml rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/rank.py (94%) rename reme/schema/daily_paper.py => plugins/daily_paper/src/reme_daily_paper/schema.py (96%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/select.py (96%) rename {reme/steps/cookbook/daily_paper => plugins/daily_paper/src/reme_daily_paper}/select.yaml (89%) rename {tests/unit => plugins/daily_paper/tests}/test_daily_paper.py (94%) diff --git a/.github/workflows/ci-python-quality.yml b/.github/workflows/ci-python-quality.yml index 7b6a5437..1630faf2 100644 --- a/.github/workflows/ci-python-quality.yml +++ b/.github/workflows/ci-python-quality.yml @@ -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 diff --git a/.github/workflows/ci-python-tests.yml b/.github/workflows/ci-python-tests.yml index acae7f6a..67bc8ad1 100644 --- a/.github/workflows/ci-python-tests.yml +++ b/.github/workflows/ci-python-tests.yml @@ -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 \ diff --git a/.github/workflows/release-daily-paper.yml b/.github/workflows/release-daily-paper.yml new file mode 100644 index 00000000..9ea5abf3 --- /dev/null +++ b/.github/workflows/release-daily-paper.yml @@ -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 diff --git a/README.md b/README.md index ae7bcb98..543b461d 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/README_ZH.md b/README_ZH.md index 8887749b..77002eaa 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -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 报告。 | diff --git a/docs/en/framework.md b/docs/en/framework.md index c0f0ce52..547a13b8 100644 --- a/docs/en/framework.md +++ b/docs/en/framework.md @@ -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. diff --git a/docs/en/reme-blog.md b/docs/en/reme-blog.md index d52b5f8f..de77a029 100644 --- a/docs/en/reme-blog.md +++ b/docs/en/reme-blog.md @@ -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. diff --git a/docs/zh/framework.md b/docs/zh/framework.md index 619eb145..171e1558 100644 --- a/docs/zh/framework.md +++ b/docs/zh/framework.md @@ -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 执行。 diff --git a/docs/zh/reme-blog.md b/docs/zh/reme-blog.md index d8407d5b..f971bdda 100644 --- a/docs/zh/reme-blog.md +++ b/docs/zh/reme-blog.md @@ -126,7 +126,7 @@ Auto Resource 提供了一条更通用的外部资料入口。资料进入 `reso ### Daily Paper:外部资料工作流的一个例子 -Daily Paper 是建立在这套文件化记忆之上的可选 Cookbook。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存 +Daily Paper 是建立在这套文件化记忆之上的可选插件。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存 PDF,并生成中文论文笔记与一份约五分钟可读完的简报。 想象一下,你持续关注 Agent Memory:每天早上收到的不只是三个论文链接,而是三篇已经保存到本地的详细笔记。简报通过 Wikilink diff --git a/plugins/README.md b/plugins/README.md index 5f484bfa..08d3c14d 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -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). diff --git a/plugins/daily_paper/LICENSE b/plugins/daily_paper/LICENSE new file mode 100644 index 00000000..65c2c5cf --- /dev/null +++ b/plugins/daily_paper/LICENSE @@ -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. diff --git a/plugins/daily_paper/README.md b/plugins/daily_paper/README.md index 1652cc8f..315c3c60 100644 --- a/plugins/daily_paper/README.md +++ b/plugins/daily_paper/README.md @@ -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 ``` diff --git a/plugins/daily_paper/README_ZH.md b/plugins/daily_paper/README_ZH.md index c5af96e8..7744add9 100644 --- a/plugins/daily_paper/README_ZH.md +++ b/plugins/daily_paper/README_ZH.md @@ -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 ``` diff --git a/plugins/daily_paper/pyproject.toml b/plugins/daily_paper/pyproject.toml new file mode 100644 index 00000000..12bc735c --- /dev/null +++ b/plugins/daily_paper/pyproject.toml @@ -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" diff --git a/reme/steps/cookbook/daily_paper/__init__.py b/plugins/daily_paper/src/reme_daily_paper/__init__.py similarity index 60% rename from reme/steps/cookbook/daily_paper/__init__.py rename to plugins/daily_paper/src/reme_daily_paper/__init__.py index 1d1e7dde..3ee40931 100644 --- a/reme/steps/cookbook/daily_paper/__init__.py +++ b/plugins/daily_paper/src/reme_daily_paper/__init__.py @@ -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", ] diff --git a/reme/steps/cookbook/daily_paper/analyze.py b/plugins/daily_paper/src/reme_daily_paper/analyze.py similarity index 97% rename from reme/steps/cookbook/daily_paper/analyze.py rename to plugins/daily_paper/src/reme_daily_paper/analyze.py index 68e3579f..6f562e3e 100644 --- a/reme/steps/cookbook/daily_paper/analyze.py +++ b/plugins/daily_paper/src/reme_daily_paper/analyze.py @@ -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.""" diff --git a/reme/steps/cookbook/daily_paper/analyze.yaml b/plugins/daily_paper/src/reme_daily_paper/analyze.yaml similarity index 95% rename from reme/steps/cookbook/daily_paper/analyze.yaml rename to plugins/daily_paper/src/reme_daily_paper/analyze.yaml index b42c324a..65b869f4 100644 --- a/reme/steps/cookbook/daily_paper/analyze.yaml +++ b/plugins/daily_paper/src/reme_daily_paper/analyze.yaml @@ -1,3 +1,4 @@ +# Prompt bundled with the Daily Paper plugin distribution. analyze_user: | 你是严谨的中文 AI 论文解读作者。请详细解读下面这篇论文。内容只能依据提供的论文元信息和 PDF 提取文本。 不得臆测未出现在材料中的实验、数字、结论或引用。重要实验结论和数字尽量标注 PDF 页码,例如 [p. 7]。 diff --git a/reme/utils/arxiv.py b/plugins/daily_paper/src/reme_daily_paper/arxiv.py similarity index 99% rename from reme/utils/arxiv.py rename to plugins/daily_paper/src/reme_daily_paper/arxiv.py index 67c73152..68653800 100644 --- a/reme/utils/arxiv.py +++ b/plugins/daily_paper/src/reme_daily_paper/arxiv.py @@ -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" diff --git a/reme/steps/cookbook/daily_paper/_common.py b/plugins/daily_paper/src/reme_daily_paper/base.py similarity index 92% rename from reme/steps/cookbook/daily_paper/_common.py rename to plugins/daily_paper/src/reme_daily_paper/base.py index 72ec0a90..47ea1fbe 100644 --- a/reme/steps/cookbook/daily_paper/_common.py +++ b/plugins/daily_paper/src/reme_daily_paper/base.py @@ -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(): diff --git a/reme/steps/cookbook/daily_paper/collect.py b/plugins/daily_paper/src/reme_daily_paper/collect.py similarity index 96% rename from reme/steps/cookbook/daily_paper/collect.py rename to plugins/daily_paper/src/reme_daily_paper/collect.py index 716807a0..e6d5832e 100644 --- a/reme/steps/cookbook/daily_paper/collect.py +++ b/plugins/daily_paper/src/reme_daily_paper/collect.py @@ -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.""" diff --git a/reme/steps/cookbook/daily_paper/digest.py b/plugins/daily_paper/src/reme_daily_paper/digest.py similarity index 97% rename from reme/steps/cookbook/daily_paper/digest.py rename to plugins/daily_paper/src/reme_daily_paper/digest.py index aa2bbdf3..f77d4748 100644 --- a/reme/steps/cookbook/daily_paper/digest.py +++ b/plugins/daily_paper/src/reme_daily_paper/digest.py @@ -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.""" diff --git a/reme/steps/cookbook/daily_paper/digest.yaml b/plugins/daily_paper/src/reme_daily_paper/digest.yaml similarity index 96% rename from reme/steps/cookbook/daily_paper/digest.yaml rename to plugins/daily_paper/src/reme_daily_paper/digest.yaml index 54696451..3a9e8d4a 100644 --- a/reme/steps/cookbook/daily_paper/digest.yaml +++ b/plugins/daily_paper/src/reme_daily_paper/digest.yaml @@ -1,3 +1,4 @@ +# Prompt bundled with the Daily Paper plugin distribution. digest_user: | 你是中文 AI 研究资讯主编。请忠实综合下面三篇详细论文解读,生成一篇普通读者五分钟可以读懂的每日论文速读。 内容只能依据输入文档,不得补充文档中没有提供的事实。 diff --git a/reme/utils/huggingface_papers.py b/plugins/daily_paper/src/reme_daily_paper/huggingface_papers.py similarity index 98% rename from reme/utils/huggingface_papers.py rename to plugins/daily_paper/src/reme_daily_paper/huggingface_papers.py index b00ee792..c8972a68 100644 --- a/reme/utils/huggingface_papers.py +++ b/plugins/daily_paper/src/reme_daily_paper/huggingface_papers.py @@ -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: diff --git a/plugins/daily_paper/src/reme_daily_paper/plugin.yaml b/plugins/daily_paper/src/reme_daily_paper/plugin.yaml new file mode 100644 index 00000000..2b009b3b --- /dev/null +++ b/plugins/daily_paper/src/reme_daily_paper/plugin.yaml @@ -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 diff --git a/reme/steps/cookbook/daily_paper/rank.py b/plugins/daily_paper/src/reme_daily_paper/rank.py similarity index 94% rename from reme/steps/cookbook/daily_paper/rank.py rename to plugins/daily_paper/src/reme_daily_paper/rank.py index 9485b9e6..c3c474d7 100644 --- a/reme/steps/cookbook/daily_paper/rank.py +++ b/plugins/daily_paper/src/reme_daily_paper/rank.py @@ -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.""" diff --git a/reme/schema/daily_paper.py b/plugins/daily_paper/src/reme_daily_paper/schema.py similarity index 96% rename from reme/schema/daily_paper.py rename to plugins/daily_paper/src/reme_daily_paper/schema.py index 67713b59..e524364a 100644 --- a/reme/schema/daily_paper.py +++ b/plugins/daily_paper/src/reme_daily_paper/schema.py @@ -1,4 +1,4 @@ -"""Typed contracts for the daily-paper cookbook workflow.""" +"""Typed contracts for the Daily Paper plugin.""" from pydantic import BaseModel, Field diff --git a/reme/steps/cookbook/daily_paper/select.py b/plugins/daily_paper/src/reme_daily_paper/select.py similarity index 96% rename from reme/steps/cookbook/daily_paper/select.py rename to plugins/daily_paper/src/reme_daily_paper/select.py index 7c881492..4da5c9e1 100644 --- a/reme/steps/cookbook/daily_paper/select.py +++ b/plugins/daily_paper/src/reme_daily_paper/select.py @@ -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.""" diff --git a/reme/steps/cookbook/daily_paper/select.yaml b/plugins/daily_paper/src/reme_daily_paper/select.yaml similarity index 89% rename from reme/steps/cookbook/daily_paper/select.yaml rename to plugins/daily_paper/src/reme_daily_paper/select.yaml index 6152cd22..56327d26 100644 --- a/reme/steps/cookbook/daily_paper/select.yaml +++ b/plugins/daily_paper/src/reme_daily_paper/select.yaml @@ -1,3 +1,4 @@ +# Prompt bundled with the Daily Paper plugin distribution. select_user: | 从候选中选择恰好 3 篇最值得深入阅读的 AI 论文。兼顾研究价值、新颖性、影响和可读性。 {selection_preference} diff --git a/tests/unit/test_daily_paper.py b/plugins/daily_paper/tests/test_daily_paper.py similarity index 94% rename from tests/unit/test_daily_paper.py rename to plugins/daily_paper/tests/test_daily_paper.py index 851a93da..5c777818 100644 --- a/tests/unit/test_daily_paper.py +++ b/plugins/daily_paper/tests/test_daily_paper.py @@ -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, diff --git a/pyproject.toml b/pyproject.toml index 4e2c2f90..4dd483ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dependencies = [ "uvicorn>=0.41.0", "watchfiles>=1.1.1", "zstandard>=0.23.0", - "pypdf>=5.0.0", ] [project.optional-dependencies] diff --git a/reme/config/daily_cookbook.yaml b/reme/config/daily_cookbook.yaml index aa15e890..20409880 100644 --- a/reme/config/daily_cookbook.yaml +++ b/reme/config/daily_cookbook.yaml @@ -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 diff --git a/reme/schema/__init__.py b/reme/schema/__init__.py index c6e0e1f2..c52e5102 100644 --- a/reme/schema/__init__.py +++ b/reme/schema/__init__.py @@ -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", diff --git a/reme/steps/cookbook/__init__.py b/reme/steps/cookbook/__init__.py index 77dc8bd5..44d037f2 100644 --- a/reme/steps/cookbook/__init__.py +++ b/reme/steps/cookbook/__init__.py @@ -1,5 +1,5 @@ """Optional, end-to-end cookbook workflows.""" -from . import daily_paper, dingtalk +from . import dingtalk -__all__ = ["daily_paper", "dingtalk"] +__all__ = ["dingtalk"] diff --git a/tests/unit/test_package_versions.py b/tests/unit/test_package_versions.py index eef07940..833509de 100644 --- a/tests/unit/test_package_versions.py +++ b/tests/unit/test_package_versions.py @@ -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"