mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-08-28 05:25:04 +00:00
Compare commits
29 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99afc2604f | ||
|
|
2dd2255760 | ||
|
|
d8d667c6ac | ||
|
|
940a923f06 | ||
|
|
3d2ecc60d2 | ||
|
|
6f38d201b6 | ||
|
|
ef3f99f019 | ||
|
|
b78e32ef03 | ||
|
|
6a6e0b3c29 | ||
|
|
a457bf7542 | ||
|
|
513fb5b7f4 | ||
|
|
1a6b584274 | ||
|
|
15d12be6b6 | ||
|
|
626c850ccb | ||
|
|
01ef1a6efb | ||
|
|
efcc2b34d1 | ||
|
|
c8e1248769 | ||
|
|
8416fd3ac9 | ||
|
|
f44f52d919 | ||
|
|
ebcb154e37 | ||
|
|
39233f4e62 | ||
|
|
94b7dedc26 | ||
|
|
87187c1d25 | ||
|
|
f5ec230fef | ||
|
|
2f5fd46b44 | ||
|
|
618e8cec66 | ||
|
|
d3aee1adf5 | ||
|
|
6b9a75267b | ||
|
|
c792fd197c |
300 changed files with 23476 additions and 3059 deletions
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
97
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: Bug report
|
||||
description: Report reproducible incorrect or unexpected ReMe behavior
|
||||
title: "[Bug]: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for helping improve ReMe. Please remove secrets, API keys, and private memory content before submitting.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What happened, and what did you expect instead?
|
||||
placeholder: Describe the observed and expected behavior.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Provide the smallest configuration and command sequence that reproduces the problem.
|
||||
placeholder: |
|
||||
1. Configure ...
|
||||
2. Run ...
|
||||
3. Observe ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: config
|
||||
attributes:
|
||||
label: Relevant configuration
|
||||
description: Include only relevant values and redact credentials, tokens, endpoints, and private paths.
|
||||
render: yaml
|
||||
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Logs or traceback
|
||||
description: Paste relevant output after removing secrets and private workspace content.
|
||||
render: shell
|
||||
|
||||
- type: input
|
||||
id: reme-version
|
||||
attributes:
|
||||
label: ReMe version
|
||||
placeholder: e.g. 0.4.1.8 or a commit SHA
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: python-version
|
||||
attributes:
|
||||
label: Python version
|
||||
placeholder: e.g. 3.11.9
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
options:
|
||||
- Linux
|
||||
- macOS
|
||||
- Windows
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Affected area
|
||||
options:
|
||||
- CLI or configuration
|
||||
- HTTP, MCP, or local service
|
||||
- Memory or workspace files
|
||||
- Search, catalog, graph, or index
|
||||
- Model or agent integration
|
||||
- ReMe Studio
|
||||
- Plugin or external integration
|
||||
- Packaging or installation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: safety
|
||||
attributes:
|
||||
label: Data safety
|
||||
options:
|
||||
- label: I removed credentials and private memory content from this report.
|
||||
required: true
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: ReMe documentation
|
||||
url: https://reme.agentscope.io
|
||||
about: Read the installation, configuration, and usage guides.
|
||||
- name: Existing issues
|
||||
url: https://github.com/agentscope-ai/ReMe/issues
|
||||
about: Search for existing reports and discussions before opening a new issue.
|
||||
64
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
name: Feature request
|
||||
description: Propose a focused enhancement to ReMe
|
||||
title: "[Feature]: "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: textarea
|
||||
id: problem
|
||||
attributes:
|
||||
label: Problem
|
||||
description: What user problem or limitation should this change address?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposal
|
||||
attributes:
|
||||
label: Proposed behavior
|
||||
description: Describe the desired behavior and its user-visible contract.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- CLI or configuration
|
||||
- Jobs or steps
|
||||
- Memory or workspace files
|
||||
- Search, catalog, graph, or index
|
||||
- Service or client
|
||||
- Model or agent integration
|
||||
- ReMe Studio
|
||||
- Plugin or external integration
|
||||
- Documentation
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: ownership
|
||||
attributes:
|
||||
label: Local-first and compatibility considerations
|
||||
description: Explain any effect on user-owned files, rebuildable state, configuration, schemas, or service interfaces.
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Describe workarounds or alternative designs you considered.
|
||||
|
||||
- type: textarea
|
||||
id: examples
|
||||
attributes:
|
||||
label: Example usage
|
||||
description: Show the proposed CLI, configuration, API, or UI behavior when useful.
|
||||
render: shell
|
||||
|
||||
- type: checkboxes
|
||||
id: contribution
|
||||
attributes:
|
||||
label: Contribution
|
||||
options:
|
||||
- label: I am willing to help implement or test this feature.
|
||||
53
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
53
.github/ISSUE_TEMPLATE/question.yml
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
name: Usage question
|
||||
description: Ask for help using or configuring ReMe
|
||||
title: "[Question]: "
|
||||
labels: [question]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Please check the documentation and existing issues before asking a new question.
|
||||
|
||||
- type: textarea
|
||||
id: goal
|
||||
attributes:
|
||||
label: What are you trying to achieve?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: attempted
|
||||
attributes:
|
||||
label: What have you tried?
|
||||
description: Include relevant commands or configuration, with secrets and private memory content removed.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: reme-version
|
||||
attributes:
|
||||
label: ReMe version
|
||||
placeholder: e.g. 0.4.1.8 or a commit SHA
|
||||
|
||||
- type: dropdown
|
||||
id: area
|
||||
attributes:
|
||||
label: Area
|
||||
options:
|
||||
- Installation
|
||||
- Configuration
|
||||
- CLI or service usage
|
||||
- Memory and workspace management
|
||||
- Search and retrieval
|
||||
- ReMe Studio
|
||||
- Plugin or integration
|
||||
- Other
|
||||
|
||||
- type: checkboxes
|
||||
id: checked
|
||||
attributes:
|
||||
label: Before submitting
|
||||
options:
|
||||
- label: I checked the [ReMe documentation](https://reme.agentscope.io) and searched existing issues.
|
||||
required: true
|
||||
- label: I removed credentials and private memory content.
|
||||
required: true
|
||||
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
35
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
## Summary
|
||||
|
||||
<!-- Explain the problem and the smallest coherent change that addresses it. -->
|
||||
|
||||
## Related issue
|
||||
|
||||
<!-- Use "Fixes #123" when applicable. -->
|
||||
|
||||
## Contract and data impact
|
||||
|
||||
- [ ] No public configuration, schema, CLI, endpoint, streaming, or workspace-layout contract changes
|
||||
- [ ] No user-owned memory files are deleted or rewritten
|
||||
- [ ] Derived indexes, catalogs, graphs, caches, and metadata remain rebuildable
|
||||
|
||||
<!-- If any item is unchecked, describe the impact and migration or recovery path. -->
|
||||
|
||||
## Validation
|
||||
|
||||
<!-- List the exact checks run and their results. Explain relevant checks that were not run. -->
|
||||
|
||||
- [ ] Focused tests pass
|
||||
- [ ] Unit tests pass, or omitted tests are explained below
|
||||
- [ ] `pre-commit run --all-files` passes, or omitted checks are explained below
|
||||
- [ ] Frontend checks were run when `reme_studio/` changed
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I reviewed the diff for unrelated changes and sensitive data
|
||||
- [ ] Tests cover intentional behavior changes
|
||||
- [ ] Defaults, schemas, and concise documentation were updated together when required
|
||||
- [ ] Long-lived clients, tasks, services, and executors follow the application lifecycle
|
||||
|
||||
## Screenshots or additional notes
|
||||
|
||||
<!-- Include UI screenshots, compatibility notes, or follow-up work when relevant. -->
|
||||
58
.github/workflows/_build-docs.yml
vendored
Normal file
58
.github/workflows/_build-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
name: _Build documentation
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
run_tests:
|
||||
description: Run the documentation test suite before building
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
upload_pages_artifact:
|
||||
description: Upload the build for a later GitHub Pages deployment job
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: github-pages
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
cache: npm
|
||||
cache-dependency-path: github-pages/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
if: inputs.run_tests
|
||||
run: npm test
|
||||
|
||||
- name: Build documentation
|
||||
run: npm run build
|
||||
|
||||
- name: Configure Pages
|
||||
if: inputs.upload_pages_artifact
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6
|
||||
|
||||
- name: Upload Pages artifact
|
||||
if: inputs.upload_pages_artifact
|
||||
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
|
||||
with:
|
||||
path: github-pages/dist
|
||||
88
.github/workflows/_build-python-packages.yml
vendored
Normal file
88
.github/workflows/_build-python-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
name: _Build Python packages
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
expected_version:
|
||||
description: Expected release version; omit for a consistency-only check
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
upload_artifacts:
|
||||
description: Upload distributions for later publish jobs
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
distributions:
|
||||
name: Build Python distributions
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build packaging pytest twine
|
||||
|
||||
- name: Validate package versions
|
||||
if: inputs.expected_version == ''
|
||||
run: python scripts/bump_version.py --check
|
||||
|
||||
- name: Validate release version
|
||||
if: inputs.expected_version != ''
|
||||
env:
|
||||
EXPECTED_VERSION: ${{ inputs.expected_version }}
|
||||
run: python scripts/bump_version.py --check --expected-version "${EXPECTED_VERSION}"
|
||||
|
||||
- name: Run package tests
|
||||
run: PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
mkdir -p dist/reme
|
||||
python -m build --outdir dist/reme
|
||||
python -m twine check dist/reme/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme_studio/')
|
||||
python -m venv "${RUNNER_TEMP}/reme-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -m pip install "${REME_WHEEL}[as]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c "import reme"
|
||||
|
||||
- name: Verify released core dependencies
|
||||
if: inputs.expected_version != ''
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
python -m venv "${RUNNER_TEMP}/reme-core-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-core-package-smoke/bin/python" -m pip install "${REME_WHEEL}[core]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-core-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
|
||||
- name: Upload ReMe distributions
|
||||
if: inputs.upload_artifacts
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme/
|
||||
if-no-files-found: error
|
||||
48
.github/workflows/ci-docs.yml
vendored
Normal file
48
.github/workflows/ci-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
name: CI / Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-docs.yml'
|
||||
- '.github/workflows/_build-docs.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-docs.yml'
|
||||
- '.github/workflows/_build-docs.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
name: Test and build documentation
|
||||
uses: ./.github/workflows/_build-docs.yml
|
||||
with:
|
||||
run_tests: true
|
||||
40
.github/workflows/ci-packages.yml
vendored
Normal file
40
.github/workflows/ci-packages.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: CI / Python packages
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-packages.yml'
|
||||
- '.github/workflows/_build-python-packages.yml'
|
||||
- '.github/workflows/release-python.yml'
|
||||
- 'pyproject.toml'
|
||||
- 'README.md'
|
||||
- 'reme/**'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'LICENSE'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-packages.yml'
|
||||
- '.github/workflows/_build-python-packages.yml'
|
||||
- '.github/workflows/release-python.yml'
|
||||
- 'pyproject.toml'
|
||||
- 'README.md'
|
||||
- 'reme/**'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
distributions:
|
||||
name: Build and verify distributions
|
||||
uses: ./.github/workflows/_build-python-packages.yml
|
||||
40
.github/workflows/ci-python-quality.yml
vendored
Normal file
40
.github/workflows/ci-python-quality.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
name: CI / Python quality
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
name: Pre-commit
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
cache: pip
|
||||
|
||||
- name: Update setuptools
|
||||
run: |
|
||||
pip install -U setuptools wheel
|
||||
|
||||
- name: Install
|
||||
run: |
|
||||
pip install -q -e reme_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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Tests ReMe
|
||||
name: CI / Python tests
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -11,6 +11,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
unit-tests:
|
||||
name: Unit Tests - py${{ matrix.python-version }}
|
||||
|
|
@ -21,10 +24,12 @@ jobs:
|
|||
python-version: ["3.11", "3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
|
@ -32,12 +37,14 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install -e packages/reme_ai_studio -e ".[dev,core]"
|
||||
pip install -e reme_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 \
|
||||
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \
|
||||
-v \
|
||||
--tb=long \
|
||||
-s \
|
||||
90
.github/workflows/ci-reme-studio.yml
vendored
Normal file
90
.github/workflows/ci-reme-studio.yml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
name: CI / ReMe Studio
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "reme_studio/**"
|
||||
- ".github/workflows/ci-reme-studio.yml"
|
||||
- ".github/workflows/release-reme-studio.yml"
|
||||
- "scripts/package_studio.py"
|
||||
- "tests/unit/test_package_versions.py"
|
||||
- "pyproject.toml"
|
||||
- "LICENSE"
|
||||
pull_request:
|
||||
paths:
|
||||
- "reme_studio/**"
|
||||
- ".github/workflows/ci-reme-studio.yml"
|
||||
- ".github/workflows/release-reme-studio.yml"
|
||||
- "scripts/package_studio.py"
|
||||
- "tests/unit/test_package_versions.py"
|
||||
- "pyproject.toml"
|
||||
- "LICENSE"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
studio:
|
||||
name: Studio checks
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: reme_studio
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "22.22.3"
|
||||
cache: npm
|
||||
cache-dependency-path: reme_studio/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Verify npm package
|
||||
run: |
|
||||
npm pack --pack-destination "${RUNNER_TEMP}"
|
||||
tar -tzf "${RUNNER_TEMP}"/agentscope-ai-reme_studio-*.tgz | grep '^package/dist-static/index.html$'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Build and verify Python package
|
||||
working-directory: .
|
||||
run: |
|
||||
python -m pip install build packaging pytest twine
|
||||
PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
|
||||
python scripts/package_studio.py
|
||||
python -m build reme_studio --outdir dist/studio
|
||||
python -m twine check dist/studio/*
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_studio-*.whl)"
|
||||
python -m venv "${RUNNER_TEMP}/reme-studio-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" -m pip install "${STUDIO_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
51
.github/workflows/ci-typescript.yml
vendored
Normal file
51
.github/workflows/ci-typescript.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
name: CI / TypeScript integrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-typescript.yml'
|
||||
- '.github/workflows/release-typescript.yml'
|
||||
- 'typescript/**'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/ci-typescript.yml'
|
||||
- '.github/workflows/release-typescript.yml'
|
||||
- 'typescript/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
package:
|
||||
name: Type-check, test, and pack
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: typescript
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
cache: npm
|
||||
cache-dependency-path: typescript/package-lock.json
|
||||
|
||||
- run: npm ci
|
||||
- run: npm run format:check
|
||||
- run: npm run lint
|
||||
- run: npm run typecheck
|
||||
- run: npm test
|
||||
- run: npm run test:package
|
||||
- name: Validate OpenClaw package contract
|
||||
run: npx --yes clawhub@0.23.3 package validate . --json
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
name: Windows Smoke
|
||||
name: CI / Windows
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
@ -11,6 +11,9 @@ concurrency:
|
|||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
cli-smoke:
|
||||
name: CLI smoke - py${{ matrix.python-version }}
|
||||
|
|
@ -21,17 +24,12 @@ jobs:
|
|||
python-version: ["3.11"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
|
@ -39,23 +37,10 @@ jobs:
|
|||
- name: Install package
|
||||
run: |
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
pip install -e packages/reme_ai_studio -e ".[dev,core]"
|
||||
|
||||
- name: Build Studio static workspace
|
||||
working-directory: website
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:static
|
||||
|
||||
- name: Verify editable source installation serves Studio
|
||||
shell: pwsh
|
||||
run: |
|
||||
Push-Location $env:RUNNER_TEMP
|
||||
python -c "from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
|
||||
Pop-Location
|
||||
pip install -e ".[dev,as]"
|
||||
|
||||
- name: Run version job
|
||||
run: reme start service.backend=cli job=version
|
||||
run: reme start config=tests/fixtures/config/version-smoke.yaml job=version
|
||||
|
||||
- name: Run Windows path tests
|
||||
run: |
|
||||
52
.github/workflows/deploy-docs.yml
vendored
Normal file
52
.github/workflows/deploy-docs.yml
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
name: Deploy / Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "github-pages/**"
|
||||
- "docs/**"
|
||||
- "README.md"
|
||||
- "README_ZH.md"
|
||||
- "reme_studio/README*.md"
|
||||
- "reme_studio/public/og.jpg"
|
||||
- "typescript/README*.md"
|
||||
- "plugins/*/README*.md"
|
||||
- "benchmark/*/README*.md"
|
||||
- "AGENTS.md"
|
||||
- ".github/workflows/deploy-docs.yml"
|
||||
- ".github/workflows/_build-docs.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build documentation
|
||||
uses: ./.github/workflows/_build-docs.yml
|
||||
with:
|
||||
run_tests: true
|
||||
upload_pages_artifact: true
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5
|
||||
61
.github/workflows/github-pages-check.yml
vendored
61
.github/workflows/github-pages-check.yml
vendored
|
|
@ -1,61 +0,0 @@
|
|||
name: GitHub Pages Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/github-pages-check.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'website/README*.md'
|
||||
- 'website/public/og.jpg'
|
||||
- 'cookbook/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
- 'skills/reme_memory/SKILL.md'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/github-pages-check.yml'
|
||||
- 'AGENTS.md'
|
||||
- 'README.md'
|
||||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'website/README*.md'
|
||||
- 'website/public/og.jpg'
|
||||
- 'cookbook/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
- 'skills/reme_memory/SKILL.md'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test-and-build:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: github-pages
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22.13'
|
||||
cache: npm
|
||||
cache-dependency-path: github-pages/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build documentation
|
||||
run: npm run build
|
||||
41
.github/workflows/npm-format.yml
vendored
41
.github/workflows/npm-format.yml
vendored
|
|
@ -1,41 +0,0 @@
|
|||
name: NPM Format
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "website/**"
|
||||
- ".github/workflows/npm-format.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "website/**"
|
||||
- ".github/workflows/npm-format.yml"
|
||||
|
||||
jobs:
|
||||
website:
|
||||
name: Website checks
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: website
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "22"
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run format check
|
||||
run: npm run format:check
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
97
.github/workflows/package-check.yml
vendored
97
.github/workflows/package-check.yml
vendored
|
|
@ -1,97 +0,0 @@
|
|||
name: Package Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/package-check.yml'
|
||||
- '.github/workflows/python-publish.yml'
|
||||
- 'packages/reme_ai_studio/**'
|
||||
- 'pyproject.toml'
|
||||
- 'reme/__init__.py'
|
||||
- 'reme/utils/web_static.py'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'scripts/package_studio.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'website/**'
|
||||
- 'LICENSE'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
- '.github/workflows/package-check.yml'
|
||||
- '.github/workflows/python-publish.yml'
|
||||
- 'packages/reme_ai_studio/**'
|
||||
- 'pyproject.toml'
|
||||
- 'reme/__init__.py'
|
||||
- 'reme/utils/web_static.py'
|
||||
- 'scripts/bump_version.py'
|
||||
- 'scripts/package_studio.py'
|
||||
- 'tests/unit/test_package_versions.py'
|
||||
- 'website/**'
|
||||
- 'LICENSE'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
distributions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.13'
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build packaging pytest twine
|
||||
|
||||
- name: Validate release versions
|
||||
run: python scripts/bump_version.py --check
|
||||
|
||||
- name: Run package tests
|
||||
run: PYTHONPATH=. python -m pytest tests/unit/test_package_versions.py -q
|
||||
|
||||
- name: Build Studio static workspace
|
||||
working-directory: website
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:static
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
python scripts/package_studio.py
|
||||
mkdir -p dist/reme dist/studio
|
||||
python -m build --outdir dist/reme
|
||||
python -m build packages/reme_ai_studio --outdir dist/studio
|
||||
python -m twine check dist/reme/* dist/studio/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
|
||||
STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
|
||||
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
|
||||
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
|
||||
python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
|
||||
python -m venv "${RUNNER_TEMP}/reme-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -m pip install \
|
||||
--find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
|
||||
"import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
|
||||
"${RUNNER_TEMP}/reme-package-smoke/bin/python" -c \
|
||||
"from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
|
||||
68
.github/workflows/pages.yml
vendored
68
.github/workflows/pages.yml
vendored
|
|
@ -1,68 +0,0 @@
|
|||
name: Deploy ReMe documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "github-pages/**"
|
||||
- "docs/**"
|
||||
- "README.md"
|
||||
- "README_ZH.md"
|
||||
- "website/README*.md"
|
||||
- "website/public/og.jpg"
|
||||
- "cookbook/*/README*.md"
|
||||
- "benchmark/*/README*.md"
|
||||
- "skills/reme_memory/SKILL.md"
|
||||
- "AGENTS.md"
|
||||
- ".github/workflows/pages.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "22.13"
|
||||
cache: npm
|
||||
cache-dependency-path: github-pages/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: github-pages
|
||||
run: npm ci
|
||||
|
||||
- name: Build documentation
|
||||
working-directory: github-pages
|
||||
run: npm run build
|
||||
|
||||
- name: Configure Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
- name: Upload Pages artifact
|
||||
uses: actions/upload-pages-artifact@v4
|
||||
with:
|
||||
path: github-pages/dist
|
||||
|
||||
deploy:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
|
|
@ -1,16 +1,20 @@
|
|||
name: PR Title Check
|
||||
name: Policy / PR title
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
types: [opened, edited, synchronize, reopened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
check-pr-title:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check PR title format
|
||||
uses: amannn/action-semantic-pull-request@v6.1.1
|
||||
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
38
.github/workflows/pre-commit.yml
vendored
38
.github/workflows/pre-commit.yml
vendored
|
|
@ -1,38 +0,0 @@
|
|||
name: Pre-commit
|
||||
|
||||
on: [ push, pull_request ]
|
||||
|
||||
jobs:
|
||||
run:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: True
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON: '3.11'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Update setuptools
|
||||
run: |
|
||||
pip install -U setuptools wheel
|
||||
- name: Install
|
||||
run: |
|
||||
pip install -q -e packages/reme_ai_studio -e ".[dev,core]"
|
||||
- name: Install pre-commit
|
||||
run: |
|
||||
pre-commit install
|
||||
- name: Pre-commit starts
|
||||
run: |
|
||||
pre-commit run --all-files > pre-commit.log 2>&1 || true
|
||||
cat pre-commit.log
|
||||
if grep -q Failed pre-commit.log; then
|
||||
echo -e "\e[41m [**FAIL**] Please install pre-commit and format your code first. \e[0m"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "\e[46m ********************************Passed******************************** \e[0m"
|
||||
123
.github/workflows/python-publish.yml
vendored
123
.github/workflows/python-publish.yml
vendored
|
|
@ -1,123 +0,0 @@
|
|||
name: Publish Python packages to PyPI
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Release version
|
||||
required: true
|
||||
type: string
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22.13'
|
||||
cache: npm
|
||||
cache-dependency-path: website/package-lock.json
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install build packaging twine
|
||||
|
||||
- name: Validate release version
|
||||
run: python scripts/bump_version.py --check --expected-version "${RELEASE_VERSION}"
|
||||
|
||||
- name: Build Studio static workspace
|
||||
working-directory: website
|
||||
run: |
|
||||
npm ci
|
||||
npm run build:static
|
||||
|
||||
- name: Prepare and build distributions
|
||||
run: |
|
||||
python scripts/package_studio.py
|
||||
mkdir -p dist/reme dist/studio
|
||||
python -m build --outdir dist/reme
|
||||
python -m build packages/reme_ai_studio --outdir dist/studio
|
||||
python -m twine check dist/reme/* dist/studio/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio/reme_ai_studio-*.whl)"
|
||||
STUDIO_SDIST="$(pwd)/$(ls dist/studio/reme_ai_studio-*.tar.gz)"
|
||||
python -m zipfile -l "${REME_WHEEL}" | (! grep 'reme/web/')
|
||||
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'reme_ai_studio/static/index.html'
|
||||
python -m zipfile -l "${STUDIO_WHEEL}" | grep 'dist-info/licenses/LICENSE'
|
||||
python -m tarfile -l "${STUDIO_SDIST}" | grep '/LICENSE'
|
||||
python -m venv "${RUNNER_TEMP}/reme-release-smoke"
|
||||
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -m pip install \
|
||||
--find-links "$(pwd)/dist/studio" "${REME_WHEEL}[core]"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
|
||||
"import reme; from reme_ai_studio import static_dir; assert (static_dir() / 'index.html').is_file()"
|
||||
"${RUNNER_TEMP}/reme-release-smoke/bin/python" -c \
|
||||
"from reme.utils import resolve_web_static_dir; assert (resolve_web_static_dir() / 'index.html').is_file()"
|
||||
|
||||
- name: Upload ReMe Studio distributions
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: reme-studio-distributions
|
||||
path: dist/studio/
|
||||
|
||||
- name: Upload ReMe distributions
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme/
|
||||
|
||||
publish-studio:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download ReMe Studio distributions
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: reme-studio-distributions
|
||||
path: dist/studio
|
||||
|
||||
- name: Publish ReMe Studio
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages-dir: dist/studio
|
||||
skip-existing: true
|
||||
|
||||
publish-reme:
|
||||
needs: publish-studio
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download ReMe distributions
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme
|
||||
|
||||
- name: Publish ReMe
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages-dir: dist/reme
|
||||
skip-existing: true
|
||||
157
.github/workflows/release-auto-fin.yml
vendored
Normal file
157
.github/workflows/release-auto-fin.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# 发布操作手册:
|
||||
# 1. 先将 plugins/auto-fin/pyproject.toml 中的 project.version 更新为待发布版本并合入目标分支。
|
||||
# 2. 确认插件依赖的 reme-ai 版本已经发布到 PyPI;本工作流会在构建阶段验证该依赖可下载。
|
||||
# 3. 确认 PyPI Trusted Publisher 已绑定本仓库、此工作流和 pypi environment,且 PyPI 上不存在相同版本。
|
||||
# 4. 在 GitHub 仓库的 Actions 页面选择“Release / Auto Fin plugin”,点击“Run workflow”。
|
||||
# 5. 输入与 project.version 完全一致的版本号(例如 0.1.0)后运行;版本也可以带 v 前缀。
|
||||
#
|
||||
# 推荐发布顺序:reme-ai -> reme-auto-fin -> QwenPaw 更新依赖并通过 plugins: [auto-fin] 启用。
|
||||
# 当前仅支持 workflow_dispatch 手动触发,不会因 push、tag 或 release 自动发布。
|
||||
|
||||
name: Release / Auto Fin plugin
|
||||
|
||||
run-name: Publish reme-auto-fin ${{ inputs.version }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from plugins/auto-fin/pyproject.toml (for example, 0.1.0)
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-auto-fin
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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 --no-deps -e plugins/auto-fin
|
||||
|
||||
- name: Validate package name 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/auto-fin/pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
expected = Version(sys.argv[1].removeprefix("v"))
|
||||
actual = Version(project["version"])
|
||||
if project["name"] != "reme-auto-fin":
|
||||
raise SystemExit(f"Expected project name 'reme-auto-fin', found {project['name']!r}")
|
||||
if actual != expected:
|
||||
raise SystemExit(f"Package version is {actual}, but workflow input is {expected}")
|
||||
requirements = [requirement for requirement in project["dependencies"] if requirement.startswith("reme-ai")]
|
||||
if len(requirements) != 1:
|
||||
raise SystemExit(f"Expected one reme-ai dependency, found {requirements!r}")
|
||||
reme_requirement = Requirement(requirements[0])
|
||||
if reme_requirement.name != "reme-ai" or reme_requirement.extras:
|
||||
raise SystemExit(f"Expected a base reme-ai dependency, found {requirements[0]!r}")
|
||||
if Version("0.4.1.8") in reme_requirement.specifier or Version("0.4.1.9") not in reme_requirement.specifier:
|
||||
raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {requirements[0]!r}")
|
||||
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
|
||||
print(f"reme_requirement={reme_requirement}", file=output)
|
||||
print(f"Publishing {project['name']} {actual}")
|
||||
PY
|
||||
|
||||
- name: Run Auto Fin tests
|
||||
run: python -m pytest plugins/auto-fin -q
|
||||
|
||||
- name: Require the plugin-enabled ReMe release on PyPI
|
||||
env:
|
||||
REME_REQUIREMENT: ${{ steps.package.outputs.reme_requirement }}
|
||||
run: |
|
||||
python -m pip download --no-deps \
|
||||
--dest "${RUNNER_TEMP}/reme-auto-fin-base" \
|
||||
"${REME_REQUIREMENT}"
|
||||
|
||||
- name: Build and check distributions
|
||||
run: |
|
||||
mkdir -p dist/auto-fin
|
||||
python -m build plugins/auto-fin --outdir dist/auto-fin
|
||||
python -m twine check dist/auto-fin/*
|
||||
|
||||
- name: Verify distributions and isolated installation
|
||||
run: |
|
||||
AUTO_FIN_WHEEL="$(pwd)/$(ls dist/auto-fin/reme_auto_fin-*.whl)"
|
||||
AUTO_FIN_SDIST="$(pwd)/$(ls dist/auto-fin/reme_auto_fin-*.tar.gz)"
|
||||
python -m zipfile -l "${AUTO_FIN_WHEEL}" | grep 'dist-info/licenses/LICENSE'
|
||||
python -m tarfile -l "${AUTO_FIN_SDIST}" | grep '/LICENSE'
|
||||
python -m venv "${RUNNER_TEMP}/reme-auto-fin-smoke"
|
||||
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install \
|
||||
"agentscope[model-ollama]==2.0.7" "${AUTO_FIN_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" - <<'PY'
|
||||
from importlib.metadata import distribution
|
||||
|
||||
from reme.plugin_manifest import load_package_manifest
|
||||
|
||||
package = distribution("reme-auto-fin")
|
||||
plugins = {entry.name: entry for entry in package.entry_points if entry.group == "reme.plugins"}
|
||||
assert plugins["auto-fin"].value == "reme_auto_fin"
|
||||
manifest = load_package_manifest("reme_auto_fin", plugin_name="auto-fin")
|
||||
assert set(manifest.backends) == {
|
||||
"auto_fin_data_step",
|
||||
"auto_fin_topic_step",
|
||||
"auto_fin_merge_step",
|
||||
}
|
||||
assert set(manifest.application_defaults["jobs"]) == {
|
||||
"auto_fin",
|
||||
"auto_fin_cron",
|
||||
}
|
||||
PY
|
||||
|
||||
- name: Upload distributions
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-auto-fin-${{ inputs.version }}
|
||||
path: dist/auto-fin/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-auto-fin-${{ inputs.version }}
|
||||
path: dist/auto-fin
|
||||
|
||||
- name: Publish reme-auto-fin
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/auto-fin
|
||||
157
.github/workflows/release-daily-paper.yml
vendored
Normal file
157
.github/workflows/release-daily-paper.yml
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
# 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. Configure PyPI Trusted Publishing for this repository/workflow and its pypi environment.
|
||||
# 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@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # 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 reme_requirements[0].extras:
|
||||
raise SystemExit(f"Expected one base reme-ai dependency, found {reme_requirements!r}")
|
||||
if Version("0.4.1.8") in reme_requirements[0].specifier or Version("0.4.1.9") not in reme_requirements[0].specifier:
|
||||
raise SystemExit(f"Expected reme-ai>=0.4.1.9, 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
|
||||
env:
|
||||
REME_REQUIREMENT: ${{ steps.package.outputs.reme_requirement }}
|
||||
run: |
|
||||
python -m pip download --no-deps \
|
||||
--dest "${RUNNER_TEMP}/reme-daily-paper-base" \
|
||||
"${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 \
|
||||
"agentscope[model-ollama]==2.0.7" "${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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-daily-paper-${{ inputs.version }}
|
||||
path: dist/daily-paper/
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-daily-paper-${{ inputs.version }}
|
||||
path: dist/daily-paper
|
||||
|
||||
- name: Publish reme-daily-paper
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/daily-paper
|
||||
47
.github/workflows/release-python.yml
vendored
Normal file
47
.github/workflows/release-python.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Release / Python packages
|
||||
|
||||
# Configure a PyPI Trusted Publisher for this repository, workflow, and its
|
||||
# pypi environment before running the manual release.
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Release version
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-ai
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build and verify distributions
|
||||
uses: ./.github/workflows/_build-python-packages.yml
|
||||
with:
|
||||
expected_version: ${{ inputs.version }}
|
||||
upload_artifacts: true
|
||||
|
||||
publish-reme:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Download ReMe distributions
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-distributions
|
||||
path: dist/reme
|
||||
|
||||
- name: Publish ReMe
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/reme
|
||||
skip-existing: true
|
||||
158
.github/workflows/release-reme-studio.yml
vendored
Normal file
158
.github/workflows/release-reme-studio.yml
vendored
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
# Release checklist:
|
||||
# 1. Update reme_studio/pyproject.toml, package.json, and package-lock.json to the same Studio version.
|
||||
# 2. Configure npm Trusted Publishing and PyPI Trusted Publishing with the pypi environment.
|
||||
# 3. Run this workflow manually with the exact Studio version.
|
||||
|
||||
name: Release / ReMe Studio
|
||||
|
||||
run-name: Publish ReMe Studio ${{ inputs.version }} (${{ inputs.npm_tag }})
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from the Studio Python and npm manifests
|
||||
required: true
|
||||
type: string
|
||||
npm_tag:
|
||||
description: npm distribution tag
|
||||
required: true
|
||||
default: latest
|
||||
type: choice
|
||||
options:
|
||||
- next
|
||||
- latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-reme-studio
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "22.22.3"
|
||||
cache: npm
|
||||
cache-dependency-path: reme_studio/package-lock.json
|
||||
|
||||
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Validate Studio package names and version
|
||||
run: |
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
studio = Path("reme_studio")
|
||||
python_manifest = tomllib.loads((studio / "pyproject.toml").read_text(encoding="utf-8"))["project"]
|
||||
npm_manifest = json.loads((studio / "package.json").read_text(encoding="utf-8"))
|
||||
expected = os.environ["RELEASE_VERSION"].removeprefix("v")
|
||||
if python_manifest["name"] != "reme_studio":
|
||||
raise SystemExit(f"Unexpected Python package name: {python_manifest['name']}")
|
||||
if npm_manifest["name"] != "@agentscope-ai/reme_studio":
|
||||
raise SystemExit(f"Unexpected npm package name: {npm_manifest['name']}")
|
||||
if python_manifest["version"] != expected or npm_manifest["version"] != expected:
|
||||
raise SystemExit(
|
||||
f"Studio manifests are {python_manifest['version']} and {npm_manifest['version']}; "
|
||||
f"workflow input is {expected}",
|
||||
)
|
||||
prerelease = "-" in expected
|
||||
if prerelease != (os.environ["NPM_TAG"] == "next"):
|
||||
raise SystemExit("Prereleases must use next; stable releases must use latest")
|
||||
PY
|
||||
|
||||
- name: Install dependencies and run checks
|
||||
working-directory: reme_studio
|
||||
run: |
|
||||
npm ci
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm test
|
||||
|
||||
- name: Build Studio distributions
|
||||
run: |
|
||||
python -m pip install build twine
|
||||
mkdir -p dist/studio-python dist/studio-npm
|
||||
npm pack ./reme_studio --pack-destination dist/studio-npm
|
||||
python scripts/package_studio.py
|
||||
python -m build reme_studio --outdir dist/studio-python
|
||||
python -m twine check dist/studio-python/*
|
||||
|
||||
- name: Verify Studio distributions and isolated installation
|
||||
run: |
|
||||
STUDIO_WHEEL="$(pwd)/$(ls dist/studio-python/reme_studio-*.whl)"
|
||||
tar -tzf dist/studio-npm/*.tgz | grep '^package/dist-static/index.html$'
|
||||
python -m venv "${RUNNER_TEMP}/reme-studio-package-smoke"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" -m pip install "${STUDIO_WHEEL}"
|
||||
cd "${RUNNER_TEMP}"
|
||||
"${RUNNER_TEMP}/reme-studio-package-smoke/bin/python" - <<'PY'
|
||||
from reme_studio import static_dir
|
||||
|
||||
assert (static_dir() / "index.html").is_file()
|
||||
PY
|
||||
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: |
|
||||
dist/studio-python/*
|
||||
dist/studio-npm/*
|
||||
if-no-files-found: error
|
||||
|
||||
publish-python:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: dist
|
||||
|
||||
- name: Publish ReMe Studio to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
||||
with:
|
||||
packages-dir: dist/studio-python
|
||||
skip-existing: true
|
||||
|
||||
publish-npm:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: "24"
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: reme-studio-${{ inputs.version }}
|
||||
path: dist
|
||||
|
||||
- name: Publish ReMe Studio to npm
|
||||
env:
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
run: npm publish dist/studio-npm/*.tgz --access public --tag "${NPM_TAG}" --provenance
|
||||
167
.github/workflows/release-typescript.yml
vendored
Normal file
167
.github/workflows/release-typescript.yml
vendored
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# Release checklist:
|
||||
# 1. Update typescript/package.json and package-lock.json to the release version and merge them.
|
||||
# 2. Configure npm Trusted Publishing for agentscope-ai/ReMe and this workflow file.
|
||||
# 3. Run this workflow manually with the exact package version (an optional v prefix is accepted).
|
||||
# 4. Configure ClawHub Trusted Publishing or CLAWHUB_TOKEN before enabling ClawHub publication.
|
||||
# 5. Use the `next` tag for prereleases and `latest` only for stable releases.
|
||||
|
||||
name: Release / TypeScript integrations
|
||||
|
||||
run-name: Publish @agentscope-ai/reme ${{ inputs.version }} (${{ inputs.npm_tag }})
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version from typescript/package.json (for example, 0.1.0)
|
||||
required: true
|
||||
type: string
|
||||
npm_tag:
|
||||
description: npm distribution tag
|
||||
required: true
|
||||
default: latest
|
||||
type: choice
|
||||
options:
|
||||
- next
|
||||
- latest
|
||||
publish_clawhub:
|
||||
description: Also publish the verified tarball to ClawHub
|
||||
required: true
|
||||
default: false
|
||||
type: boolean
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: publish-agentscope-ai-reme
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.validate.outputs.version }}
|
||||
env:
|
||||
RELEASE_VERSION: ${{ inputs.version }}
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '22.22.3'
|
||||
|
||||
- name: Validate package name and release version
|
||||
id: validate
|
||||
working-directory: typescript
|
||||
run: |
|
||||
node --input-type=module <<'JS'
|
||||
import { appendFileSync, readFileSync } from 'node:fs';
|
||||
|
||||
const manifest = JSON.parse(readFileSync('package.json', 'utf8'));
|
||||
const expected = process.env.RELEASE_VERSION.replace(/^v/, '');
|
||||
if (manifest.name !== '@agentscope-ai/reme') {
|
||||
throw new Error(`Unexpected package name: ${manifest.name}`);
|
||||
}
|
||||
if (manifest.version !== expected) {
|
||||
throw new Error(`package.json is ${manifest.version}, workflow input is ${expected}`);
|
||||
}
|
||||
const prerelease = manifest.version.includes('-');
|
||||
const npmTag = process.env.NPM_TAG;
|
||||
if (prerelease !== (npmTag === 'next')) {
|
||||
throw new Error(prerelease
|
||||
? 'Prerelease versions must use the next npm tag'
|
||||
: 'Stable versions must use the latest npm tag');
|
||||
}
|
||||
console.log(`Preparing ${manifest.name}@${manifest.version}`);
|
||||
appendFileSync(process.env.GITHUB_OUTPUT, `version=${manifest.version}\n`);
|
||||
JS
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: typescript
|
||||
run: npm ci
|
||||
|
||||
- name: Type-check and test
|
||||
working-directory: typescript
|
||||
run: |
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm run typecheck
|
||||
npm test
|
||||
npm run test:package
|
||||
npx --yes clawhub@0.23.3 package validate . --json
|
||||
|
||||
- name: Pack npm tarball
|
||||
working-directory: typescript
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/reme-typescript-package"
|
||||
npm pack --pack-destination "${RUNNER_TEMP}/reme-typescript-package"
|
||||
|
||||
- name: Upload npm tarball
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
|
||||
with:
|
||||
name: agentscope-ai-reme-${{ inputs.version }}
|
||||
path: ${{ runner.temp }}/reme-typescript-package/*.tgz
|
||||
if-no-files-found: error
|
||||
|
||||
publish:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Set up Node for npm
|
||||
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
|
||||
with:
|
||||
node-version: '24'
|
||||
registry-url: https://registry.npmjs.org
|
||||
|
||||
- name: Download npm tarball
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
|
||||
with:
|
||||
name: agentscope-ai-reme-${{ inputs.version }}
|
||||
path: dist/typescript
|
||||
|
||||
- name: Reject an existing package version
|
||||
env:
|
||||
PACKAGE_VERSION: ${{ inputs.version }}
|
||||
run: |
|
||||
PACKAGE_VERSION="${PACKAGE_VERSION#v}"
|
||||
if npm view "@agentscope-ai/reme@${PACKAGE_VERSION}" version >/dev/null 2>&1; then
|
||||
echo "@agentscope-ai/reme@${PACKAGE_VERSION} already exists" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish to npm
|
||||
env:
|
||||
NPM_TAG: ${{ inputs.npm_tag }}
|
||||
run: npm publish dist/typescript/*.tgz --access public --tag "${NPM_TAG}" --provenance
|
||||
|
||||
publish-clawhub:
|
||||
if: ${{ inputs.publish_clawhub }}
|
||||
needs: build
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
id-token: write
|
||||
uses: openclaw/clawhub/.github/workflows/package-publish.yml@87ca030c30f3cfb78ab15c8e66b5ff1469c8f9c8 # v0.23.3
|
||||
with:
|
||||
owner: agentscope-ai
|
||||
family: code-plugin
|
||||
version: ${{ needs.build.outputs.version }}
|
||||
tags: ${{ inputs.npm_tag }}
|
||||
source_repo: ${{ github.repository }}
|
||||
source_commit: ${{ github.sha }}
|
||||
source_ref: ${{ github.ref }}
|
||||
source_path: typescript
|
||||
package_artifact_name: agentscope-ai-reme-${{ inputs.version }}
|
||||
wait_for_publication: true
|
||||
secrets:
|
||||
clawhub_token: ${{ secrets.CLAWHUB_TOKEN }}
|
||||
46
.github/workflows/security-codeql.yml
vendored
Normal file
46
.github/workflows/security-codeql.yml
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Security / CodeQL
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '0 1 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
packages: read
|
||||
security-events: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze ${{ matrix.language }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [python, javascript-typescript]
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL analysis
|
||||
uses: github/codeql-action/analyze@cdf488f595d80d6e07e03d4674febd5ab45fa938 # v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -30,11 +30,9 @@ htmlcov/
|
|||
# Packaging / build outputs
|
||||
build/
|
||||
dist/
|
||||
node_modules/
|
||||
*.egg-info/
|
||||
|
||||
# Website build integration source (not generated output)
|
||||
!website/build/
|
||||
!website/build/**
|
||||
typescript/reports/
|
||||
|
||||
# Logs / temporary files
|
||||
*.log
|
||||
|
|
|
|||
21
AGENTS.md
21
AGENTS.md
|
|
@ -41,7 +41,7 @@ and concise documentation together.
|
|||
- `reme/components/application_context.py`: application-wide wiring and in-memory shared state.
|
||||
- `reme/components/runtime_context.py`: request-scoped data, response, streaming queue, and stop event.
|
||||
- `reme/components/base_component.py`: component lifecycle, dependency binding, and workspace helpers.
|
||||
- `reme/components/component_registry.py`: the process-wide `(component type, backend)` registry.
|
||||
- `reme/components/component_registry.py`: the frozen built-in registry template and application-local registry factory.
|
||||
- `reme/components/job/`: base, stream, background, and cron job implementations.
|
||||
- `reme/components/service/`: local CLI, HTTP, and MCP service backends.
|
||||
- `reme/components/`: agent wrappers, model adapters, stores, catalogs, graphs, indexes, clients, tokenizers, and
|
||||
|
|
@ -52,11 +52,15 @@ and concise documentation together.
|
|||
accounting, and wikilink handling.
|
||||
- `tests/unit/`: primary fast, isolated validation suite.
|
||||
- `tests/integration/`: service/model tests that may need credentials or external processes.
|
||||
- `website/`: ReMe Workspace frontend source; its static build can be served by the HTTP service.
|
||||
- `plugins/claude_code/` and `plugins/hermes_agent/`: agent integrations.
|
||||
- `reme_studio/`: ReMe Studio frontend source plus the independently published `reme_studio` Python package and
|
||||
`@agentscope-ai/reme_studio` npm static distribution.
|
||||
- `typescript/`: the independently published `@agentscope-ai/reme` package, including the shared TypeScript client and
|
||||
DeepSeek Harness and OpenClaw adapters.
|
||||
- `plugins/`: installable ReMe extensions, such as Auto Fin.
|
||||
- `integrations/`: adapters that connect ReMe to external agent hosts, such as Claude Code, DSH, and Hermes Agent.
|
||||
- `skills/`: standalone skills; `reme_memory` calls ReMe, while other skills may use separate tools or direct-file
|
||||
conventions.
|
||||
- `benchmark/` and `cookbook/`: runnable evaluation and example workflows.
|
||||
- `benchmark/` and `cookbook/`: runnable evaluations and example workflows.
|
||||
- `docs/`: README-linked supporting pages and figures.
|
||||
|
||||
## Development Setup
|
||||
|
|
@ -64,7 +68,7 @@ and concise documentation together.
|
|||
ReMe requires Python 3.11 or newer. Install the editable development environment with:
|
||||
|
||||
```bash
|
||||
pip install -e packages/reme_ai_studio -e ".[dev,core]"
|
||||
pip install -e reme_studio -e ".[dev,core]"
|
||||
```
|
||||
|
||||
Before changing behavior, inspect the adjacent implementation, schema, built-in config, and focused tests. Follow
|
||||
|
|
@ -184,7 +188,7 @@ pre-commit run --all-files
|
|||
```
|
||||
|
||||
Black and Flake8 use a 120-character line limit and Python 3.11 formatting; Pylint is also run by pre-commit. If
|
||||
`website/` changes, use its Node 22.13+ scripts and run the proportionate checks from that directory, such as
|
||||
`reme_studio/` changes, use its Node 22.13+ scripts and run the proportionate checks from that directory, such as
|
||||
`npm run format:check`, `npm run lint`, or `npm test`.
|
||||
|
||||
Integration tests may contact real model providers, services, or agent subprocesses and can require credentials. Do not
|
||||
|
|
@ -196,13 +200,14 @@ environment has been supplied or authorized. Mock network, model, and subprocess
|
|||
- Preserve unrelated user changes in a dirty working tree.
|
||||
- Make the smallest coherent change and avoid unrelated cleanup or broad refactors.
|
||||
- Do not edit generated output when the source can be changed instead. The publish workflow builds
|
||||
`website/dist-static` and copies it into `reme/web`; change `website/` source for frontend work.
|
||||
`reme_studio/dist-static` and stages it under `reme_studio/src/reme_studio/static`; change `reme_studio/` source for
|
||||
frontend work.
|
||||
- Do not silently change CLI flags, configuration keys, workspace layouts, serialized schemas, endpoint shapes,
|
||||
streaming termination, or service interfaces. Preserve compatibility where practical and document intentional
|
||||
migrations.
|
||||
- Do not introduce dependencies without a concrete repository-level need.
|
||||
- Do not commit `.env` files, credentials, runtime memory, logs, indexes, caches, benchmark outputs, or generated
|
||||
website distributions.
|
||||
Studio distributions.
|
||||
- State which validations passed and which relevant checks were not run in the final handoff.
|
||||
|
||||
If a requirement is ambiguous, infer intent from nearby code, schemas, defaults, and tests. Ask the user only when the
|
||||
|
|
|
|||
278
README.md
278
README.md
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
<img src="https://raw.githubusercontent.com/agentscope-ai/ReMe/main/docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -27,45 +27,35 @@
|
|||
> [0.2.x](https://github.com/agentscope-ai/ReMe/tree/v0.2.0.6) ·
|
||||
> [MemoryScope](https://github.com/agentscope-ai/ReMe/tree/memoryscope_branch)
|
||||
|
||||
🧠 ReMe turns conversations and resources into readable, editable, searchable, and interconnected Markdown memory. It
|
||||
works alongside agents such as QwenPaw, OpenClaw, Hermes, and Claude Code, continuously organizing what they learn while
|
||||
keeping the files under the user's control.
|
||||
## ✨ Why ReMe?
|
||||
|
||||
## ✨ Core Ideas
|
||||
🧠 ReMe turns conversations and resources into readable, editable, searchable, and interconnected Markdown memory. Agents
|
||||
such as QwenPaw and DeepSeek Harness can share the same workspace to retrieve, maintain, and evolve knowledge, while
|
||||
users retain control of the durable files.
|
||||
|
||||
- **Memory as File, File as Memory**: Markdown files with frontmatter and wikilinks serve as memory nodes that both
|
||||
users and agents can inspect, edit, move, and back up directly.
|
||||
- **Self-evolving knowledge base**: Auto Memory, Auto Resource, and Auto Dream progressively transform conversations and
|
||||
resources into daily notes and long-term knowledge, while Auto Link writes relationships and sources back into the
|
||||
files.
|
||||
- **Progressive hybrid search**: ReMe combines wikilinks, BM25, and embeddings for hybrid retrieval across keyword
|
||||
matching, optional semantic recall, and relationship expansion without loading every neighboring file into context.
|
||||
- **Agent-friendly integration**: SKILL.md + CLI integration makes it easy for different agents to read, write,
|
||||
maintain, and reuse the same local workspace. HTTP, MCP, and Python integrations are also available.
|
||||
- **Memory as File, File as Memory**: ReMe stores durable memory as ordinary Markdown with frontmatter and wikilinks.
|
||||
Users and agents can inspect, edit, move, sync, and back it up with familiar tools, while indexes and generated
|
||||
metadata remain rebuildable.
|
||||
- **Self-evolving knowledge base**: ReMe progressively turns conversations and resources into daily notes and long-term
|
||||
knowledge, preserving sources while refining facts, preferences, procedures, and relationships over time.
|
||||
- **Recall is precise and context-aware.** BM25, optional embeddings, and wikilink expansion retrieve relevant
|
||||
line-level passages and their relationships without loading the entire knowledge base into the agent context.
|
||||
- **One memory workspace works across agents.** Personal assistants, coding agents, and other agent runtimes can share
|
||||
the same local workspace through native integrations, SKILL.md, CLI, HTTP, MCP, or Python APIs.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/design-philosophy.svg" alt="ReMe Design Philosophy" width="92%">
|
||||
</p>
|
||||
|
||||
## 🔭 Use Cases
|
||||
|
||||
- **Personal assistants**: Give personal assistants such as
|
||||
[QwenPaw](https://github.com/agentscope-ai/QwenPaw), [OpenClaw](https://github.com/openclaw/openclaw), and
|
||||
[Hermes](https://github.com/nousresearch/hermes-agent) a user-editable long-term memory layer.
|
||||
- **Coding agents**: Preserve coding style, project background, repository decisions, and workflow experience across
|
||||
sessions when integrating with coding agents such as [Claude Code](plugins/claude_code/reme).
|
||||
- **LLM Wiki**: Turn conversations, notes, and resources into a searchable, traceable, and linked Markdown knowledge
|
||||
base that both users and agents can maintain.
|
||||
- **Self-evolving agents**: Support agents that learn from experience by saving successful paths, failed attempts,
|
||||
reusable procedures, and periodic reflections as memory.
|
||||
|
||||
## 📰 News
|
||||
## 📰 Latest Updates
|
||||
|
||||
- [2026.08] - Published [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme), providing native
|
||||
ReMe memory integrations for DeepSeek Harness and OpenClaw plus a shared TypeScript HTTP client.
|
||||
- [2026.08] - Published the [ReMe blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog), an end-to-end introduction to its local-first memory
|
||||
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
|
||||
|
|
@ -89,8 +79,8 @@ Install from source:
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e packages/reme_ai_studio -e ".[core]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
@ -98,31 +88,6 @@ cd ..
|
|||
|
||||
The static build requires Node.js 22.13 or newer and makes Studio available from the source tree.
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Configure environment variables when you want LLM-powered memory evolution or embedding retrieval. Embeddings are
|
||||
disabled by default, so the default setup does not start an embedding model or require an embedding API key.
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# Optional: used only after embedding components are explicitly enabled in the config.
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
# Required for auto_memory, auto_resource, and auto_dream.
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
Basic file operations, BM25 search, wikilink traversal, and reading proactive topics can run without LLM credentials.
|
||||
|
||||
> [!NOTE]
|
||||
> To enable embedding-based semantic retrieval, uncomment `components.as_embedding` and
|
||||
> `components.embedding_store` in [`reme/config/default.yaml`](reme/config/default.yaml), then change
|
||||
> `components.file_store.default.embedding_store` from `""` to `default`. See the
|
||||
> [memory search guide](docs/en/memory_search.md) for details.
|
||||
|
||||
### Start the Service
|
||||
|
||||
```bash
|
||||
|
|
@ -143,12 +108,6 @@ reme help
|
|||
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
|
||||
```
|
||||
|
||||
### ReMe Studio (Optional)
|
||||
|
||||
The `core` installation above includes Studio. After starting ReMe, open <http://127.0.0.1:2333/> to browse, edit, and
|
||||
search the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the
|
||||
[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development.
|
||||
|
||||
### 5-Minute Memory Demo
|
||||
|
||||
With the service running, write a memory node, let ReMe index it, then retrieve it:
|
||||
|
|
@ -183,35 +142,81 @@ ReMe stores agent memory as readable Markdown.
|
|||
Related: [[digest/wiki/memory-as-file.md]]
|
||||
```
|
||||
|
||||
## 📚 Usage Guides
|
||||
### ReMe Studio (Optional)
|
||||
|
||||
These Markdown guides cover the main user workflows and the runtime contracts implemented by the current code.
|
||||
The `core` installation includes Studio. After starting ReMe, open <http://127.0.0.1:2333/> to browse, edit, and search
|
||||
the workspace. To add Studio to a base installation, use `pip install "reme-ai[web]"`. See the
|
||||
[ReMe Studio guide](https://reme.agentscope.io/?doc=studio-en) for source builds, configuration, and development.
|
||||
|
||||
| Guide | What you will learn |
|
||||
|-------|---------------------|
|
||||
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
|
||||
| [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. |
|
||||
| [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. |
|
||||
| [Auto Resource](docs/en/auto_resource.md) | Import supported text resources and turn them into source-linked daily cards. |
|
||||
| [Auto Dream](docs/en/auto_dream.md) and [Auto Link](docs/en/auto_link.md) | Consolidate daily notes into evolving digest nodes and readable wikilink relationships. |
|
||||
| [Memory Search](docs/en/memory_search.md) | Use BM25, optional vectors, RRF fusion, line-range recall, and progressive link expansion. |
|
||||
| [Proactive](docs/en/proactive.md) | Read interest topics safely and integrate them into a host agent's decision flow. |
|
||||
| [Agent Integration Scenarios](docs/en/reme_scene.md) | Choose among CLI/SKILL.md, HTTP, MCP, and embedded Python integration. |
|
||||
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
|
||||
| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
|
||||
### Optional Model Configuration
|
||||
|
||||
## 🧑🍳 Cookbooks
|
||||
Configure environment variables when you want LLM-powered memory evolution or embedding retrieval. Embeddings are
|
||||
disabled by default, so the default setup does not start an embedding model or require an embedding API key.
|
||||
|
||||
Cookbooks are optional, end-to-end workflows assembled from ReMe jobs and steps. They are not enabled by the default
|
||||
configuration; select the cookbook's standalone configuration when starting ReMe. Each new cookbook will be added as
|
||||
another row in this table.
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# Optional: used only after embedding components are explicitly enabled in the config.
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
| Cookbook | 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. |
|
||||
# Required for auto_memory, auto_resource, and auto_dream.
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
## 📁 Memory System
|
||||
Basic file operations, BM25 search, wikilink traversal, and reading proactive topics can run without LLM credentials.
|
||||
|
||||
> [!NOTE]
|
||||
> To enable embedding-based semantic retrieval, uncomment `components.as_embedding` and
|
||||
> `components.embedding_store` in [`reme/config/default.yaml`](reme/config/default.yaml), then change
|
||||
> `components.file_store.default.embedding_store` from `""` to `default`. See the
|
||||
> [memory search guide](docs/en/memory_search.md) for details.
|
||||
|
||||
## 🤝 Use ReMe with Your Agent
|
||||
|
||||
ReMe can run as a local memory service accessed through the CLI, HTTP API, or MCP server, or it can be embedded in the
|
||||
host process through its Python API. Host integrations can add memory guidance, recall, and capture to the agent
|
||||
lifecycle according to the capabilities of each runtime.
|
||||
|
||||
| Agent | Recommended path | Available after integration |
|
||||
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| **DeepSeek Harness** | Install [`@agentscope-ai/reme`](typescript/README.md#deepseek-harness) with `dsh plugin --profile web add @agentscope-ai/reme`. | Long-term memory guidance, the `reme_search` tool, and automatic capture of completed main-agent turns. |
|
||||
| **OpenClaw** | Install [`@agentscope-ai/reme`](typescript/README.md#openclaw) with `openclaw plugins install @agentscope-ai/reme`. | Native memory tools, recall before user-triggered runs, and automatic turn capture. |
|
||||
| **QwenPaw** | Embed ReMe in-process through its Python API. | Reuse the host lifecycle and model config while keeping memory local and file-based. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP service and install [the ReMe plugin](integrations/claude_code/reme). | MCP recall tools, the `reme-memory` skill, and a Stop hook that records sessions automatically. |
|
||||
| **Hermes** | Start the HTTP service and install [the ReMe provider](integrations/hermes_agent). | Recall before model calls and asynchronous `auto_memory` after each completed turn. |
|
||||
| **Codex and other CLI agents** | Install or copy the [ReMe Memory skill](skills/reme_memory/SKILL.md). | Search, read, and write memory through the CLI; automatic capture requires host lifecycle integration. |
|
||||
|
||||
<p align="center"><b>Integration demos</b></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🧠 How ReMe Works
|
||||
|
||||
> Memory as File, File as Memory.
|
||||
|
||||
|
|
@ -219,7 +224,7 @@ ReMe treats **memory as files**, progressively processing filtered conversation
|
|||
from `session/` and `resource/` into `daily/`, then `digest/`. The default workspace is `.reme/` under the current
|
||||
directory; `workspace_dir=...` selects a different user-owned location.
|
||||
|
||||
### Directory Structure
|
||||
### Workspace Layout
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
|
|
@ -255,13 +260,13 @@ directory; `workspace_dir=...` selects a different user-owned location.
|
|||
<img src="docs/figure/reme-overview.svg" alt="ReMe file-based memory system overview" width="92%">
|
||||
</p>
|
||||
|
||||
## 🧭 Memory Design Philosophy
|
||||
### Memory Lifecycle
|
||||
|
||||
ReMe follows a capture → index → consolidate → recall loop. Workspace files remain the durable source of truth;
|
||||
everything under `metadata/` is rebuildable.
|
||||
|
||||
| Capability | Entry point | What it does | Output |
|
||||
|---------------------------------------------|-------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
|
||||
| ------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
|
||||
| [`auto_memory`](docs/en/auto_memory.md) | Agent hook or `reme auto_memory` | Distills useful conversation facts while preserving a filtered conversation source record. | `session/dialog/*.jsonl`, `daily/<date>/<generated-name>.md` |
|
||||
| [`auto_resource`](docs/en/auto_resource.md) | Resource watcher or `reme auto_resource` | Turns files under `resource/` into source-linked, content-named daily cards. | `daily/<date>/<resource-card>.md` |
|
||||
| [`auto_index`](docs/en/memory_search.md) | Background watcher or `reme reindex` | Live-indexes Markdown in `daily/` and `digest/`; a full rebuild also scans `resource/` and JSONL. | Searchable chunks, BM25, wikilink graph, and optional vectors |
|
||||
|
|
@ -291,17 +296,18 @@ Search returns matching chunks with line ranges and bounded wikilink neighbors.
|
|||
BM25 through reciprocal rank fusion (RRF).
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> `proactive` only reads and exposes interest topics produced by Auto Dream. It does not independently browse the web,
|
||||
> send notifications, or rewrite the knowledge base; the host agent decides whether and how to act on a topic.
|
||||
|
||||
## 📊 Performance
|
||||
## 📊 Benchmarks
|
||||
|
||||
ReMe evaluates multi-session and long-context memory with agentic search-and-read workflows. The figures below are the
|
||||
published reference runs in this repository; model, prompt, dataset, and judging details are documented with each
|
||||
benchmark.
|
||||
|
||||
| Benchmark | Setting | Sample size | Agentic score | Focus |
|
||||
|--------------------------------------------------------------|--------------|-------------------------:|--------------:|--------------------------------------------------------------------|
|
||||
| Benchmark | Setting | Sample size | Agentic score | Focus |
|
||||
| --------------------------------------------------------------------------- | ------------ | -----------------------: | ------------: | ------------------------------------------------------------------ |
|
||||
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-en)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
|
||||
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
|
||||
| [BEAM](https://reme.agentscope.io/?doc=beam-en) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
|
||||
|
|
@ -311,68 +317,60 @@ ReMe also achieved a **0.580 PROC score across five user personas** in the repos
|
|||
measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task
|
||||
dependencies, and underspecified requests.
|
||||
|
||||
## 🤝 Agent-friendly Integration
|
||||
## 🧩 Extensions and Plugins
|
||||
|
||||
ReMe can run as a local memory service accessed through the CLI, HTTP API, or MCP server, or it can be embedded in the
|
||||
host process through its Python API.
|
||||
Plugins are optional Python distributions that contribute Component, Step, or Job backends and configuration. They are
|
||||
installed separately and enabled explicitly by configuration. Daily Paper and Auto Fin are independently packaged
|
||||
plugins; see the source distributions and their documentation for [Daily Paper](plugins/daily_paper/README.md) and
|
||||
[Auto Fin](plugins/auto-fin/README.md).
|
||||
|
||||
| Agents | Recommended path | Available after integration |
|
||||
|-----------------------------------------------|---------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| **QwenPaw** | Embed ReMe in-process through its Python API. | Reuse the host application's lifecycle and model config while keeping memory local and file-based. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP service and install [plugins/claude_code/reme](plugins/claude_code/reme). | MCP recall tools, a `reme-memory` skill, and a Stop hook that records sessions automatically. |
|
||||
| **Hermes** | Start the HTTP service and install [plugins/hermes_agent](plugins/hermes_agent). | Recall relevant memory before model calls and enqueue `auto_memory` after each completed turn. |
|
||||
| **Other CLI-capable agents (OpenClaw/Codex)** | Copy or install [skills/reme_memory/SKILL.md](skills/reme_memory/SKILL.md). | Search, read, and write memory via the CLI; automatic recording requires explicit host lifecycle hooks. |
|
||||
| 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. |
|
||||
|
||||
<p align="center"><b>Integration demos</b></p>
|
||||
See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory demo" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream demo" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
## 📚 Documentation
|
||||
|
||||
## 🛠️ ReMe Operations
|
||||
These guides cover the main user workflows and the runtime contracts implemented by the current code.
|
||||
|
||||
| Guide | What you will learn |
|
||||
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| [Quick Start](docs/en/quick_start.md) | Install ReMe, start the service, and run the first file and memory operations. |
|
||||
| [Memory as File](docs/en/memory_as_file.md) | Understand workspace layers, frontmatter, wikilinks, chunks, and the file-as-source-of-truth model. |
|
||||
| [Auto Memory](docs/en/auto_memory.md) | Preserve source conversations and distill reusable daily memory cards. |
|
||||
| [Auto Resource](docs/en/auto_resource.md) | Import supported text resources and turn them into source-linked daily cards. |
|
||||
| [Auto Dream](docs/en/auto_dream.md) and [Auto Link](docs/en/auto_link.md) | Consolidate daily notes into evolving digest nodes and readable wikilink relationships. |
|
||||
| [Memory Search](docs/en/memory_search.md) | Use BM25, optional vectors, RRF fusion, line-range recall, and progressive link expansion. |
|
||||
| [Proactive](docs/en/proactive.md) | Read interest topics safely and integrate them into a host agent's decision flow. |
|
||||
| [Application Scenarios](docs/en/reme_scene.md) | Follow concrete financial research, coding-memory, and personal knowledge-base examples. |
|
||||
| [Framework](docs/en/framework.md) | Understand Application, Job, Step, Component, service, configuration, and lifecycle boundaries. |
|
||||
| [TypeScript integrations](typescript/README.md) | Configure the shared client and native DeepSeek Harness and OpenClaw adapters. |
|
||||
| [ReMe Blog](https://agentscope-ai.github.io/ReMe/?doc=en-reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
Run `reme help` for the full job list. Common workspace and maintenance commands are:
|
||||
|
||||
| Command | Purpose |
|
||||
|-------------------------------------------|----------------------------------------------------------------------------------------|
|
||||
| `reme status` | Show stateful data-component memory estimates and process RSS. |
|
||||
| [`reme search`](docs/en/memory_search.md) | Retrieve memory with BM25 and wikilinks by default, plus vectors when enabled. |
|
||||
| `reme read` / `reme write` / `reme edit` | Inspect and maintain Markdown memory files. |
|
||||
| `reme traverse` / `reme graph_snapshot` | Explore wikilink neighborhoods or the category-rooted digest graph. |
|
||||
| `reme chat` | Stream a read-only, workspace-aware agent conversation. Requires LLM credentials. |
|
||||
| `reme reindex` | Rebuild search and wikilink indexes from existing files. |
|
||||
| Command | Purpose |
|
||||
| ----------------------------------------- | --------------------------------------------------------------------------------- |
|
||||
| `reme status` | Show stateful data-component memory estimates and process RSS. |
|
||||
| [`reme search`](docs/en/memory_search.md) | Retrieve memory with BM25 and wikilinks by default, plus vectors when enabled. |
|
||||
| `reme read` / `reme write` / `reme edit` | Inspect and maintain Markdown memory files. |
|
||||
| `reme traverse` / `reme graph_snapshot` | Explore wikilink neighborhoods or the category-rooted digest graph. |
|
||||
| `reme chat` | Stream a read-only, workspace-aware agent conversation. Requires LLM credentials. |
|
||||
| `reme reindex` | Rebuild search and wikilink indexes from existing files. |
|
||||
|
||||
## 🤝 Community and Support
|
||||
## 🤝 Community and Contributing
|
||||
|
||||
- **Issues, requests, and help**: Check [Open Issues](https://github.com/agentscope-ai/ReMe/issues) first. If there is no
|
||||
related discussion, open one with the background, expected behavior, and impact scope.
|
||||
- **Code contributions**: Before making changes, read
|
||||
the [contribution guide](https://docs.agentscope.io/reme/latest/en/contribution). Source, schemas, and tests are the
|
||||
authoritative architecture and extension guide.
|
||||
- **Documentation contributions**: Submit user-facing documentation changes to the
|
||||
[unified documentation repository](https://github.com/agentscope-ai/docs) under `reme/<version>/{en,zh}/`.
|
||||
- **Code contributions**: Before making changes, read the repository's
|
||||
[contribution guide](docs/en/contributing.md). Source, schemas, and tests are the authoritative architecture and
|
||||
extension guide.
|
||||
- **Documentation contributions**: Update the canonical files under `docs/en/`, `docs/zh/`, or the relevant package
|
||||
directory in this repository. The documentation site is generated from these files.
|
||||
- **Commit convention**: Conventional Commits are recommended, for example `feat(search): add link expansion option` or
|
||||
`docs(zh): update quick start`.
|
||||
- **Pre-submit checks**: Before submitting a PR, try to run `pre-commit run --all-files` and `pytest`. If tests that
|
||||
|
|
|
|||
250
README_ZH.md
250
README_ZH.md
|
|
@ -1,5 +1,5 @@
|
|||
<p align="center">
|
||||
<img src="docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
<img src="https://raw.githubusercontent.com/agentscope-ai/ReMe/main/docs/figure/reme_logo.png" alt="ReMe Logo" width="50%">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
|
|
@ -27,38 +27,34 @@
|
|||
> [0.2.x](https://github.com/agentscope-ai/ReMe/tree/v0.2.0.6) ·
|
||||
> [MemoryScope](https://github.com/agentscope-ai/ReMe/tree/memoryscope_branch)
|
||||
|
||||
🧠 ReMe 将对话和资料持续沉淀为可读、可编辑、可检索、相互链接的 Markdown 记忆。它可以与 QwenPaw、OpenClaw、Hermes 和 Claude
|
||||
Code 等 Agent 协作,在持续整理知识的同时,始终把文件控制权留给用户。
|
||||
## ✨ 为什么选择 ReMe?
|
||||
|
||||
## ✨ 核心创新
|
||||
🧠 ReMe 将对话和资料持续沉淀为可读、可编辑、可检索、相互链接的 Markdown 记忆。QwenPaw、DeepSeek Harness 等 Agent
|
||||
可以共享同一个 workspace,共同检索、维护和演化知识,而持久文件始终由用户掌控。
|
||||
|
||||
- **Memory as File, File as Memory**:以带 frontmatter 和 wikilink 的 Markdown 作为记忆节点,用户和 Agent 都能直接查看、编辑、移动和备份。
|
||||
- **自进化知识库**:Auto Memory、Auto Resource 和 Auto Dream 把对话与资料逐步加工为 daily 记忆和长期知识,Auto Link
|
||||
再将关系与来源写回文件。
|
||||
- **渐进式混合搜索**:融合 wikilink、BM25 和可选 embedding,从关键词匹配、语义召回到关系扩展,避免一次性将所有邻居全文塞入上下文。
|
||||
- **Agent 友好集成**:可通过 SKILL.md + CLI 读写和维护同一个本地 workspace,也支持 HTTP、MCP 和 Python API 接入。
|
||||
- **Memory as File, File as Memory**:ReMe 使用带 frontmatter 和 wikilink 的普通 Markdown 保存持久记忆。用户和 Agent
|
||||
都可以使用熟悉的工具查看、编辑、移动、同步和备份;索引及生成的元数据均可重建。
|
||||
- **自进化知识库**:ReMe 将对话和资料逐步加工为 daily note 与长期知识,在保留来源的同时,持续提炼事实、偏好、
|
||||
流程经验及其关系。
|
||||
- **精准召回所需上下文。** ReMe 结合 BM25、可选 embedding 和 wikilink 展开,召回带行号的相关片段及其关系,无需把整个知识库塞入
|
||||
Agent 上下文。
|
||||
- **一个 workspace,可供不同 Agent 共同使用。** 个人助理、coding agent 和其他 Agent runtime 可以通过原生集成、SKILL.md、CLI、
|
||||
HTTP、MCP 或 Python API 共享同一个本地记忆空间。
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/figure/design-philosophy.svg" alt="ReMe 设计理念" width="92%">
|
||||
</p>
|
||||
|
||||
## 🔭 适用场景
|
||||
|
||||
- **Personal assistants**:为 [QwenPaw](https://github.com/agentscope-ai/QwenPaw)、
|
||||
[OpenClaw](https://github.com/openclaw/openclaw)、[Hermes](https://github.com/nousresearch/hermes-agent)
|
||||
等个人助理提供用户可编辑的长期记忆层。
|
||||
- **Coding agents**:在接入 [Claude Code](plugins/claude_code/reme) 等 coding agent 时,跨会话保留代码风格、项目背景、仓库决策和流程经验。
|
||||
- **LLM Wiki**:把对话、笔记和资料转化为可检索、可追溯、可链接的 Markdown 知识库,由用户和 Agent 共同维护。
|
||||
- **Self-evolving agents**:帮助 Agent 从经验中学习,把成功路径、失败尝试、可复用流程和阶段性反思沉淀为记忆。
|
||||
|
||||
## 📰 新闻
|
||||
## 📰 最新动态
|
||||
|
||||
- [2026.08] - 发布 [`@agentscope-ai/reme`](https://www.npmjs.com/package/@agentscope-ai/reme),提供统一 TypeScript HTTP
|
||||
client,以及 DeepSeek Harness 和 OpenClaw 的原生 ReMe 记忆集成。
|
||||
- [2026.08] - 发布 [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
|
||||
主动发现与评测结果。
|
||||
- [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] -
|
||||
|
|
@ -82,8 +78,8 @@ pip install "reme-ai[core]"
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e packages/reme_ai_studio -e ".[core]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
@ -91,31 +87,6 @@ cd ..
|
|||
|
||||
静态构建要求 Node.js 22.13 或更高版本,并让源码安装可以直接使用 Studio。
|
||||
|
||||
### 环境变量
|
||||
|
||||
如果需要 LLM 驱动的记忆演化或 embedding 检索,可以配置环境变量。embedding 默认关闭,因此默认配置不会启动 embedding 模型,也不需要
|
||||
embedding API key。
|
||||
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# 可选:仅在配置中显式启用 embedding 组件后使用。
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
# 必须:auto_memory、auto_resource 和 auto_dream 需要 LLM。
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
基础文件读写、BM25 检索、wikilink 遍历和 proactive topics 读取可以先不配置 LLM 凭证。
|
||||
|
||||
> [!NOTE]
|
||||
> 如需启用基于 embedding 的语义检索,请取消 [`reme/config/default.yaml`](reme/config/default.yaml) 中
|
||||
> `components.as_embedding` 和 `components.embedding_store` 的注释,并将
|
||||
> `components.file_store.default.embedding_store` 从 `""` 改为 `default`。完整说明见
|
||||
> [记忆检索文档](docs/zh/memory_search.md)。
|
||||
|
||||
### 启动服务
|
||||
|
||||
```bash
|
||||
|
|
@ -136,12 +107,6 @@ reme help
|
|||
curl -s http://127.0.0.1:2333/version -H 'Content-Type: application/json' -d '{}'
|
||||
```
|
||||
|
||||
### ReMe Studio(可选)
|
||||
|
||||
上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 <http://127.0.0.1:2333/> 即可浏览、编辑和搜索 workspace。
|
||||
如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见
|
||||
[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
|
||||
|
||||
### 5 分钟记忆 Demo
|
||||
|
||||
服务运行后,可以写入一个记忆节点,让 ReMe 索引并检索它:
|
||||
|
|
@ -176,41 +141,87 @@ ReMe 会把 Agent 记忆保存为可读的 Markdown。
|
|||
相关链接:[[digest/wiki/memory-as-file.md]]
|
||||
```
|
||||
|
||||
## 📚 使用指南
|
||||
### ReMe Studio(可选)
|
||||
|
||||
下列 Markdown 文档覆盖主要使用流程,并以当前代码的运行时契约为准。
|
||||
上面的 `core` 安装已包含 Studio。启动 ReMe 后,打开 <http://127.0.0.1:2333/> 即可浏览、编辑和搜索 workspace。
|
||||
如需为基础安装单独添加 Studio,可使用 `pip install "reme-ai[web]"`。源码构建、配置和开发说明见
|
||||
[ReMe Studio 指南](https://reme.agentscope.io/?doc=studio-zh)。
|
||||
|
||||
| 文档 | 主要内容 |
|
||||
|------|----------|
|
||||
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
|
||||
| [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 |
|
||||
| [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 |
|
||||
| [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本资料,转换为可追溯来源的 daily 卡片。 |
|
||||
| [Auto Dream](docs/zh/auto_dream.md) 与 [Auto Link](docs/zh/auto_link.md) | 将 daily 记忆整理为持续演化的 digest 节点和可读 wikilink 关系。 |
|
||||
| [记忆检索](docs/zh/memory_search.md) | 使用 BM25、可选向量、RRF 融合、行号范围召回和渐进式链接扩展。 |
|
||||
| [Proactive](docs/zh/proactive.md) | 安全读取兴趣主题,并将其接入宿主 Agent 的决策流程。 |
|
||||
| [Agent 接入场景](docs/zh/reme_scene.md) | 在 CLI/SKILL.md、HTTP、MCP 和嵌入式 Python 集成之间选择。 |
|
||||
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
|
||||
| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
|
||||
### 可选模型配置
|
||||
|
||||
## 🧑🍳 Cookbooks
|
||||
如果需要 LLM 驱动的记忆演化或 embedding 检索,可以配置环境变量。embedding 默认关闭,因此默认配置不会启动 embedding 模型,也不需要
|
||||
embedding API key。
|
||||
|
||||
Cookbook 是由 ReMe jobs 和 steps 组装而成的可选端到端工作流。默认配置不会开启它们;启动 ReMe 时选择对应的独立配置即可启用。后续新增的
|
||||
cookbook 会继续在表格中按行追加。
|
||||
```bash
|
||||
cat > .env <<'EOF'
|
||||
# 可选:仅在配置中显式启用 embedding 组件后使用。
|
||||
# EMBEDDING_API_KEY=sk-xxx
|
||||
# EMBEDDING_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
|
||||
| Cookbook | 能力 |
|
||||
|-----------------------------------------------|--------------------------------------------------------------------------------|
|
||||
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
|
||||
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
|
||||
# 必须:auto_memory、auto_resource 和 auto_dream 需要 LLM。
|
||||
LLM_API_KEY=sk-xxx
|
||||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
EOF
|
||||
```
|
||||
|
||||
## 📁 记忆系统
|
||||
基础文件读写、BM25 检索、wikilink 遍历和 proactive topics 读取可以先不配置 LLM 凭证。
|
||||
|
||||
> [!NOTE]
|
||||
> 如需启用基于 embedding 的语义检索,请取消 [`reme/config/default.yaml`](reme/config/default.yaml) 中
|
||||
> `components.as_embedding` 和 `components.embedding_store` 的注释,并将
|
||||
> `components.file_store.default.embedding_store` 从 `""` 改为 `default`。完整说明见
|
||||
> [记忆检索文档](docs/zh/memory_search.md)。
|
||||
|
||||
## 🤝 将 ReMe 接入你的 Agent
|
||||
|
||||
ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP server 接入,也可以通过 Python API 嵌入宿主进程。宿主集成可根据不同
|
||||
runtime 的能力,将记忆指引、召回和捕获接入 Agent 生命周期。
|
||||
|
||||
| Agent | 推荐接入方式 | 接入后能力 |
|
||||
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| **DeepSeek Harness** | 使用 `dsh plugin --profile web add @agentscope-ai/reme` 安装 [`@agentscope-ai/reme`](typescript/README_ZH.md#deepseek-harness)。 | 长期记忆指引、`reme_search` 工具,以及自动捕获已完成的主 Agent 对话。 |
|
||||
| **OpenClaw** | 使用 `openclaw plugins install @agentscope-ai/reme` 安装 [`@agentscope-ai/reme`](typescript/README_ZH.md#openclaw)。 | 原生记忆工具、用户触发运行前召回和自动对话捕获。 |
|
||||
| **QwenPaw** | 通过 Python API 在进程内嵌入 ReMe。 | 复用宿主生命周期和模型配置,同时保持记忆本地、文件化。 |
|
||||
| **Claude Code** | 启动 streamable HTTP MCP service,并安装 [ReMe 插件](integrations/claude_code/reme)。 | MCP 召回工具、`reme-memory` skill,以及自动记录会话的 Stop hook。 |
|
||||
| **Hermes** | 启动 HTTP service,并安装 [ReMe provider](integrations/hermes_agent)。 | 模型调用前召回,每轮对话完成后异步执行 `auto_memory`。 |
|
||||
| **Codex 及其他 CLI Agent** | 安装或复制 [ReMe Memory skill](skills/reme_memory/SKILL.md)。 | 通过 CLI 搜索、读取和写入记忆;自动捕获需要显式接入宿主生命周期。 |
|
||||
|
||||
<p align="center"><b>集成演示</b></p>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## 🧠 ReMe 如何工作
|
||||
|
||||
> Memory as File, File as Memory.
|
||||
|
||||
ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资料从 `session/`、`resource/` 渐进加工到 `daily/`,再沉淀为
|
||||
`digest/`。默认 workspace 是当前目录下的 `.reme/`;可通过 `workspace_dir=...` 选择其他由用户控制的位置。
|
||||
|
||||
### 目录结构
|
||||
### Workspace 结构
|
||||
|
||||
```text
|
||||
<workspace_dir>/
|
||||
|
|
@ -246,13 +257,13 @@ ReMe 将 **记忆视为文件**,让过滤后的对话来源记录和外部资
|
|||
<img src="docs/figure/reme-overview.svg" alt="ReMe 文件化记忆系统总览" width="92%">
|
||||
</p>
|
||||
|
||||
## 🧭 记忆设计理念
|
||||
### 记忆生命周期
|
||||
|
||||
ReMe 遵循 capture → index → consolidate → recall 的循环。workspace 文件是持久化的事实来源,`metadata/` 中的内容均可重建。
|
||||
|
||||
| 能力 | 入口 | 作用 | 输出 |
|
||||
|---------------------------------------------|-------------------------------------------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------|
|
||||
| [`auto_memory`](docs/zh/auto_memory.md) | Agent hook 或 `reme auto_memory` | 提炼有长期价值的对话事实,同时保留过滤后的对话来源记录。 | `session/dialog/*.jsonl`、`daily/<date>/<generated-name>.md` |
|
||||
| ------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
|
||||
| [`auto_memory`](docs/zh/auto_memory.md) | Agent hook 或 `reme auto_memory` | 提炼有长期价值的对话事实,同时保留过滤后的对话来源记录。 | `session/dialog/*.jsonl`、`daily/<date>/<generated-name>.md` |
|
||||
| [`auto_resource`](docs/zh/auto_resource.md) | 资源监听或 `reme auto_resource` | 将 `resource/` 下的文件转为带来源链接、按内容命名的 daily 卡片。 | `daily/<date>/<resource-card>.md` |
|
||||
| [`auto_index`](docs/zh/memory_search.md) | 后台监听或 `reme reindex` | 实时索引 `daily/` 和 `digest/` 中的 Markdown;全量重建还会扫描 `resource/` 和 JSONL。 | 可检索的 chunks、BM25、wikilink 图谱和可选向量 |
|
||||
| [`auto_dream`](docs/zh/auto_dream.md) | `dream_cron` 或 `reme auto_dream` | 默认从最近两天内变化的文件中最多提取 5 个可复用 unit,再创建、印证、补充或修正 digest 节点。 | `digest/**`、`daily/<date>/interests.yaml` |
|
||||
|
|
@ -280,15 +291,16 @@ ReMe 遵循 capture → index → consolidate → recall 的循环。workspace
|
|||
搜索返回带行号范围的相关 chunks 和数量受限的 wikilink 邻居;可选向量结果通过 RRF 与 BM25 融合。
|
||||
|
||||
> [!IMPORTANT]
|
||||
>
|
||||
> `proactive` 只读取并暴露 Auto Dream 生成的兴趣主题,不会自行联网、发送通知或改写知识库;是否以及如何使用主题,由宿主 Agent
|
||||
决定。
|
||||
> 决定。
|
||||
|
||||
## 📊 性能表现
|
||||
## 📊 评测结果
|
||||
|
||||
ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上下文中的记忆能力。下表为仓库中已公开的参考实验结果;模型、prompt、数据集和评判细节见各评测文档。
|
||||
|
||||
| 基准 | 设置 | 样本量 | Agentic 得分 | 主要检验内容 |
|
||||
|-----------------------------------------------------------------|-------------|------------------:|-------------:|--------------------------------|
|
||||
| 基准 | 设置 | 样本量 | Agentic 得分 | 主要检验内容 |
|
||||
| --------------------------------------------------------------------------- | ----------- | ----------------: | -----------: | ------------------------------ |
|
||||
| **[LongMemEval cleaned-s](https://reme.agentscope.io/?doc=longmemeval-zh)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
|
||||
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
|
||||
| [BEAM](https://reme.agentscope.io/?doc=beam-zh) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
|
||||
|
|
@ -296,52 +308,43 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
|
|||
在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
|
||||
,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。
|
||||
|
||||
## 🤝 Agent-friendly Integration
|
||||
## 🧩 扩展与插件
|
||||
|
||||
ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP server 接入,也可以通过 Python API 嵌入宿主进程。不同 Agent 可以选择适合自身
|
||||
runtime 的路径。
|
||||
插件是可选的独立 Python distribution,可以贡献 Component、Step、Job backend 和配置,并通过配置显式启用。每日论文与 Auto Fin
|
||||
均已独立打包,源码 distribution 及说明分别见[每日论文](plugins/daily_paper/README_ZH.md)和
|
||||
[Auto Fin](plugins/auto-fin/README_ZH.md)。
|
||||
|
||||
| Agent | 推荐接入方式 | 接入后能力 |
|
||||
|-----------------------------------------------|-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
|
||||
| **QwenPaw** | 通过 Python API 在进程内嵌入 ReMe。 | 复用宿主应用的生命周期和模型配置,同时保持 memory 本地、文件化。 |
|
||||
| **Claude Code** | 启动 streamable HTTP MCP service,并安装 [plugins/claude_code/reme](plugins/claude_code/reme)。 | MCP recall tools、`reme-memory` skill,以及自动记录会话的 Stop hook。 |
|
||||
| **Hermes** | 启动 HTTP service,并安装 [plugins/hermes_agent](plugins/hermes_agent)。 | 在模型调用前自动召回相关记忆,并在每轮对话完成后异步调用 `auto_memory`。 |
|
||||
| **Other CLI-capable agents (OpenClaw/Codex)** | 复制或安装 [skills/reme_memory/SKILL.md](skills/reme_memory/SKILL.md)。 | 通过 CLI 搜索、读取和写入记忆;自动记录需要宿主 Agent 显式接入会话生命周期。 |
|
||||
| 插件 | 能力 |
|
||||
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
|
||||
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
|
||||
|
||||
<p align="center"><b>集成演示</b></p>
|
||||
安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center"></td>
|
||||
<td width="45%" align="center"><b>Auto Memory</b></td>
|
||||
<td width="45%" align="center"><b>Auto Dream</b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>QwenPaw</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-memory.gif" alt="QwenPaw Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/qwenpaw-auto-dream.gif" alt="QwenPaw Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center"><b>Claude Code</b></td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-memory.gif" alt="Claude Code Auto Memory 演示" width="100%">
|
||||
</td>
|
||||
<td width="45%">
|
||||
<img src="docs/figure/cc-auto-dream.gif" alt="Claude Code Auto Dream 演示" width="100%">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
## 📚 文档
|
||||
|
||||
## 🛠️ ReMe Operations
|
||||
下列文档覆盖主要使用流程,并以当前代码的运行时契约为准。
|
||||
|
||||
| 文档 | 主要内容 |
|
||||
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
|
||||
| [Memory as File](docs/zh/memory_as_file.md) | 理解 workspace 分层、frontmatter、wikilink、chunk 和文件事实来源模型。 |
|
||||
| [Auto Memory](docs/zh/auto_memory.md) | 保留过滤后的对话来源记录,并提炼可复用的 daily 记忆卡片。 |
|
||||
| [Auto Resource](docs/zh/auto_resource.md) | 导入支持的文本资料,转换为可追溯来源的 daily 卡片。 |
|
||||
| [Auto Dream](docs/zh/auto_dream.md) 与 [Auto Link](docs/zh/auto_link.md) | 将 daily 记忆整理为持续演化的 digest 节点和可读 wikilink 关系。 |
|
||||
| [记忆检索](docs/zh/memory_search.md) | 使用 BM25、可选向量、RRF 融合、行号范围召回和渐进式链接扩展。 |
|
||||
| [Proactive](docs/zh/proactive.md) | 安全读取兴趣主题,并将其接入宿主 Agent 的决策流程。 |
|
||||
| [应用场景](docs/zh/reme_scene.md) | 查看金融研究、研发记忆和个人知识库的完整使用示例。 |
|
||||
| [框架说明](docs/zh/framework.md) | 理解 Application、Job、Step、Component、service、配置和生命周期边界。 |
|
||||
| [TypeScript 集成](typescript/README_ZH.md) | 配置统一 client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。 |
|
||||
| [ReMe 博客](https://agentscope-ai.github.io/ReMe/?doc=zh-reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
|
||||
|
||||
## 🛠️ 常用命令
|
||||
|
||||
运行 `reme help` 可查看完整 job 列表。常用 workspace 与维护命令如下:
|
||||
|
||||
| 命令 | 作用 |
|
||||
|-------------------------------------------|---------------------------------------------------------------|
|
||||
| ----------------------------------------- | ------------------------------------------------------------- |
|
||||
| `reme status` | 查看有状态数据组件的内存估算及进程 RSS。 |
|
||||
| [`reme search`](docs/zh/memory_search.md) | 默认使用 BM25 和 wikilink 检索,启用后增加向量检索。 |
|
||||
| `reme read` / `reme write` / `reme edit` | 检查和维护 Markdown 记忆文件。 |
|
||||
|
|
@ -349,13 +352,12 @@ runtime 的路径。
|
|||
| `reme chat` | 与可感知 workspace 的只读 Agent 进行流式对话;需要 LLM 凭证。 |
|
||||
| `reme reindex` | 基于已有文件重建检索和 wikilink 索引。 |
|
||||
|
||||
## 🤝 社区与支持
|
||||
## 🤝 社区与贡献
|
||||
|
||||
- **问题反馈、需求与帮助**:请先查看 [Open Issues](https://github.com/agentscope-ai/ReMe/issues);如无相关讨论,可新建 Issue
|
||||
说明背景、目标行为和影响范围。
|
||||
- **代码贡献**:改动前建议阅读 [贡献指南](https://docs.agentscope.io/reme/latest/zh/contribution)。架构与扩展方式以源码、schema
|
||||
和测试为准。
|
||||
- **文档贡献**:用户可见文档请提交到[统一文档仓库](https://github.com/agentscope-ai/docs)的 `reme/<version>/{en,zh}/` 目录。
|
||||
- **代码贡献**:改动前建议阅读仓库内的[贡献指南](docs/zh/contributing.md)。架构与扩展方式以源码、schema 和测试为准。
|
||||
- **文档贡献**:请直接更新本仓库 `docs/en/`、`docs/zh/` 或对应 package 目录中的规范源文件;文档站点会从这些文件生成。
|
||||
- **提交规范**:建议使用 Conventional Commits,例如 `feat(search): add link expansion option`、
|
||||
`docs(zh): update quick start`。
|
||||
- **提交前检查**:提交 PR 前请尽量运行 `pre-commit run --all-files` 和 `pytest`;如有依赖 LLM、embedding 或外部服务的测试无法运行,请在
|
||||
|
|
|
|||
|
|
@ -1,262 +0,0 @@
|
|||
# Daily Paper Cookbook
|
||||
|
||||
[中文](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).
|
||||
|
||||
## 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.
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[core]"
|
||||
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
|
||||
```
|
||||
|
||||
The built-in LLM component defaults to:
|
||||
|
||||
- model: `qwen3.7-plus`
|
||||
- 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.
|
||||
|
||||
The default workspace is `reme_workspace/` beneath the process working directory. Override it with
|
||||
`DAILY_PAPER_WORKSPACE_DIR`.
|
||||
|
||||
## Pipeline
|
||||
|
||||
```text
|
||||
Hugging Face weekly/monthly rankings
|
||||
│
|
||||
▼
|
||||
Collect ──► Rank ──► Select 3 ──► Analyze PDFs ──► Digest ──► DingTalk (optional)
|
||||
│ │
|
||||
├─ PDFs ├─ daily brief
|
||||
└─ paper notes └─ day index
|
||||
```
|
||||
|
||||
### 1. Collect
|
||||
|
||||
`daily_paper_collect_step` concurrently fetches:
|
||||
|
||||
- the Hugging Face weekly ranking for the run date's ISO week;
|
||||
- the monthly ranking for the run date's calendar month; and
|
||||
- Hugging Face Daily Papers for exactly the previous calendar day.
|
||||
|
||||
The weekly and monthly results are merged by arXiv ID while preserving both ranks. The step then excludes papers found
|
||||
in yesterday's list or in the `arxiv_id` frontmatter of `daily/<date>/*.md` within the previous `history_days`.
|
||||
|
||||
If a Markdown file with `kind: daily-paper-brief` already exists and `force=false`, generation is skipped; the saved
|
||||
brief can still proceed to DingTalk delivery. The job fails when no eligible papers remain.
|
||||
|
||||
### 2. Rank
|
||||
|
||||
`daily_paper_rank_step` uses reciprocal-rank fusion:
|
||||
|
||||
```text
|
||||
score = 1 / (rrf_k + monthly_rank)
|
||||
+ weekly_weight / (rrf_k + weekly_rank)
|
||||
```
|
||||
|
||||
A missing rank contributes zero. Papers are ordered by fused score, upvotes, and arXiv ID. The pool is capped at
|
||||
`candidate_limit`, and Rank applies no topic preference.
|
||||
|
||||
### 3. Select
|
||||
|
||||
`daily_paper_select_step` sends candidate metadata to a tool-free AgentScope agent and requires exactly three items:
|
||||
|
||||
```json
|
||||
{"papers": [{"arxiv_id": "2601.01234", "reasoning": "A specific, verifiable reason"}]}
|
||||
```
|
||||
|
||||
All IDs must be unique and belong to the candidate pool, and every reason must be non-empty. A validation failure is
|
||||
returned to the agent for one retry. Only a non-empty `topics` value injects a personalized subject preference into the
|
||||
selection prompt; it does not change the fixed count of three papers.
|
||||
|
||||
### 4. Analyze
|
||||
|
||||
`daily_paper_analyze_step` processes the selected papers in order:
|
||||
|
||||
1. validates a modern `YYYY.NNNN` or `YYYY.NNNNN` arXiv ID;
|
||||
2. downloads the PDF to `resource/papers/<arxiv-id>.pdf`;
|
||||
3. reuses an existing target whose header is `%PDF-`;
|
||||
4. extracts paginated text with `pypdf`, bounded by `max_pdf_pages` and `max_pdf_chars`;
|
||||
5. sends metadata, selection reasoning, and PDF text to a tool-free agent; and
|
||||
6. writes a Chinese note to `daily/<date>/<Chinese-title>.md`.
|
||||
|
||||
Downloads use a temporary file and atomically replace the target only after validating the PDF header. They are also
|
||||
bounded by `max_pdf_bytes`. There is no OCR fallback, so scanned or textless PDFs fail. When extraction is truncated,
|
||||
the note records `pdf_text_truncated: true` in its frontmatter.
|
||||
|
||||
### 5. Digest
|
||||
|
||||
`daily_paper_digest_step` uses the three in-memory analyses as the factual source for the Chinese brief. It also
|
||||
searches and, when needed, reads earlier daily notes to identify related coverage; those notes may only support
|
||||
contextual wikilinks, not add facts about the current papers. The agent returns `title`, `desc`, and `body`. The code
|
||||
then:
|
||||
|
||||
- strips model-generated YAML frontmatter if present;
|
||||
- normalizes the Chinese title for use as a filename;
|
||||
- keeps model-generated wikilinks only when they point to existing `daily/` Markdown files dated before the run date;
|
||||
- deterministically appends wikilinks to all three source notes;
|
||||
- writes `daily/<date>/<Chinese-brief-title>.md`; and
|
||||
- rebuilds the `daily/<date>.md` day index.
|
||||
|
||||
Final response metadata includes the date, week/month scopes, selected arXiv IDs, selection reasons, note/PDF/brief
|
||||
paths, source counts, and exclusion counts.
|
||||
|
||||
### 6. DingTalk
|
||||
|
||||
The final `dingtalk_markdown_send_step` is optional. With no conversation IDs it is a no-op. When configured, it strips
|
||||
frontmatter and sends the brief body to each group in order:
|
||||
|
||||
```dotenv
|
||||
DINGTALK_APP_KEY=your-app-key
|
||||
DINGTALK_APP_SECRET=your-app-secret
|
||||
DINGTALK_ROBOT_CODE=your-robot-code
|
||||
DINGTALK_CONVERSATION_IDS=cid-group-one,cid-group-two
|
||||
```
|
||||
|
||||
A failed recipient does not prevent later attempts; the step reports a combined failure after trying every group.
|
||||
|
||||
## Outputs
|
||||
|
||||
```text
|
||||
reme_workspace/
|
||||
├── daily/
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <Chinese-paper-title>.md # three, kind: daily-paper-analysis
|
||||
│ └── <Chinese-brief-title>.md # one, kind: daily-paper-brief
|
||||
└── resource/
|
||||
└── papers/
|
||||
└── <arxiv-id>.pdf
|
||||
```
|
||||
|
||||
Each successful generation writes three analysis notes and one brief. A forced rerun can leave unrelated or previously
|
||||
selected analysis notes in the same day directory; ReMe does not delete them as cleanup. Filenames come from the agent's
|
||||
Chinese titles. The implementation removes unsafe path characters and resolves title collisions. Markdown and PDF
|
||||
outputs are written through same-directory temporary files and atomic replacement.
|
||||
|
||||
## Parameters and defaults
|
||||
|
||||
Public job parameters:
|
||||
|
||||
| Parameter | Default | Purpose |
|
||||
|-----------------|--------:|-----------------------------------------------------------------------------------------|
|
||||
| `date` | `""` | Run date; empty uses today in the app timezone, otherwise requires `YYYY-MM-DD` |
|
||||
| `force` | `false` | Regenerate even when the day's brief exists |
|
||||
| `use_hf_mirror` | `false` | Use the Hugging Face mirror from `HF_MIRROR_URL`, or `https://hf-mirror.com` when unset |
|
||||
| `topics` | `""` | Topics to prioritize during selection |
|
||||
| `weekly_weight` | `0.7` | Weekly contribution to RRF |
|
||||
| `history_days` | `30` | Prior recommendation exclusion window |
|
||||
|
||||
Step-level settings on the `daily_paper` job:
|
||||
|
||||
| Setting | Default | Purpose |
|
||||
|-------------------|--------------:|----------------------------------------------------|
|
||||
| `candidate_limit` | `20` | Maximum candidates sent to Select |
|
||||
| `rrf_k` | `60` | RRF constant |
|
||||
| `hf_timeout` | `600` seconds | Timeout for one Hugging Face request |
|
||||
| `hf_max_retries` | `3` | Maximum Hugging Face attempts |
|
||||
| `pdf_timeout` | `600` seconds | arXiv PDF download timeout |
|
||||
| `max_pdf_bytes` | `52428800` | PDF limit, 50 MiB |
|
||||
| `max_pdf_pages` | `35` | Maximum extracted pages |
|
||||
| `max_pdf_chars` | `300000` | Maximum extracted PDF characters sent to the agent |
|
||||
|
||||
## Mirrors
|
||||
|
||||
The data clients use httpx's default environment handling, so `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` take effect
|
||||
when present. The two data sources reach a mirror differently: Hugging Face is gated on the `use_hf_mirror` job
|
||||
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
|
||||
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
|
||||
HF_MIRROR_URL=https://hf-mirror.com
|
||||
|
||||
# Optional override; the code defaults to https://arxiv.org when unset
|
||||
ARXIV_MIRROR_URL=https://export.arxiv.org
|
||||
|
||||
# Path-prefixed relay URLs are also supported
|
||||
# HF_MIRROR_URL=http://relay-host:18080/hf
|
||||
# ARXIV_MIRROR_URL=http://relay-host:18080/arxiv
|
||||
```
|
||||
|
||||
`HF_MIRROR_URL` must implement the `/papers/...`, `/api/daily_papers`, and `/api/papers/...` routes used by the current
|
||||
client. `ARXIV_MIRROR_URL` must implement `/pdf/<arxiv-id>`. A path prefix in either base URL is preserved, and a
|
||||
trailing slash is optional. There is no fallback chain: whichever base URL a client selects is the only one it tries.
|
||||
|
||||
> **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
|
||||
> mirror by default; set `DAILY_PAPER_USE_HF_MIRROR=false` to make that scheduled job use the official service.
|
||||
|
||||
## Running the workflow
|
||||
|
||||
Generate a brief for a specific date:
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
config=daily_cookbook \
|
||||
job=daily_paper \
|
||||
date=2026-08-06 \
|
||||
topics="Agent memory" \
|
||||
history_days=30
|
||||
```
|
||||
|
||||
Force a rerun; valid local PDFs are still reused:
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
|
||||
```
|
||||
|
||||
Start the HTTP service and scheduled jobs:
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8001/daily_paper \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"date":"2026-08-06","force":false,"topics":"Agent memory"}'
|
||||
```
|
||||
|
||||
## Failures and reruns
|
||||
|
||||
- Hugging Face HTTP failures use exponential backoff up to `hf_max_retries` attempts; invalid response payloads fail
|
||||
immediately.
|
||||
- Fewer than three candidates, invalid agent selection, invalid/oversized/textless PDFs, or empty agent output stop the
|
||||
job.
|
||||
- Papers are analyzed sequentially; PDFs and notes completed before a failure remain on disk.
|
||||
- `force=true` regenerates the selected notes and the brief while reusing valid PDFs; it does not remove other notes
|
||||
already present in that day's directory.
|
||||
- The multi-file workflow is not transactional and has no global per-date execution lock.
|
||||
|
||||
## Tests
|
||||
|
||||
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
|
||||
```
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
# 每日论文 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) 装配。
|
||||
|
||||
## 快速开始
|
||||
|
||||
要求 Python 3.11 或更高版本、`core` 依赖、可用的 AgentScope LLM,以及能访问 Hugging Face Papers 和 arXiv 的网络。
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[core]"
|
||||
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
|
||||
```
|
||||
|
||||
内置 LLM 组件默认配置为:
|
||||
|
||||
- 模型:`qwen3.7-plus`
|
||||
- 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。
|
||||
|
||||
默认 workspace 是启动目录下的 `reme_workspace/`,可通过 `DAILY_PAPER_WORKSPACE_DIR` 覆盖。
|
||||
|
||||
## 工作流
|
||||
|
||||
```text
|
||||
Hugging Face 周榜/月榜
|
||||
│
|
||||
▼
|
||||
Collect ──► Rank ──► Select 3 篇 ──► Analyze PDF ──► Digest ──► DingTalk(可选)
|
||||
│ │
|
||||
├─ PDF ├─ 每日简报
|
||||
└─ 论文解读 └─ 当日索引
|
||||
```
|
||||
|
||||
### 1. Collect
|
||||
|
||||
`daily_paper_collect_step` 根据运行日期并发读取:
|
||||
|
||||
- 该日期所在 ISO week 的 Hugging Face 周榜;
|
||||
- 该日期所在自然月的 Hugging Face 月榜;
|
||||
- 严格前一个自然日的 Hugging Face Daily Papers。
|
||||
|
||||
周榜和月榜按 arXiv ID 合并,并保留各自排名。随后排除:
|
||||
|
||||
- 昨日 Daily Papers 中的论文;
|
||||
- `history_days` 窗口内,已出现在 `daily/<date>/*.md` frontmatter `arxiv_id` 中的论文。
|
||||
|
||||
如果当天已经存在 `kind: daily-paper-brief` 的 Markdown 且 `force=false`,整个生成流程会跳过;已有简报仍可进入钉钉发送步骤。没有剩余候选论文时,Job
|
||||
直接失败。
|
||||
|
||||
### 2. Rank
|
||||
|
||||
`daily_paper_rank_step` 使用 reciprocal-rank fusion:
|
||||
|
||||
```text
|
||||
score = 1 / (rrf_k + monthly_rank)
|
||||
+ weekly_weight / (rrf_k + weekly_rank)
|
||||
```
|
||||
|
||||
缺失的榜单排名贡献为零。论文按融合分、upvotes、arXiv ID 排序,候选池最多保留 `candidate_limit` 篇。Rank 阶段不应用任何主题倾向。
|
||||
|
||||
### 3. Select
|
||||
|
||||
`daily_paper_select_step` 将候选元数据交给无工具的 AgentScope Agent,并要求返回恰好三项:
|
||||
|
||||
```json
|
||||
{"papers": [{"arxiv_id": "2601.01234", "reasoning": "具体且可核验的选择理由"}]}
|
||||
```
|
||||
|
||||
三个 ID 必须唯一且都属于候选池,理由不能为空。校验失败后,错误信息会反馈给 Agent 并重试一次。只有非空 `topics`
|
||||
会向精选提示注入个性化主题,且不会改变固定的三篇数量。
|
||||
|
||||
### 4. Analyze
|
||||
|
||||
`daily_paper_analyze_step` 按精选顺序逐篇处理:
|
||||
|
||||
1. 校验新版 arXiv ID 格式 `YYYY.NNNN` 或 `YYYY.NNNNN`;
|
||||
2. 下载 PDF 到 `resource/papers/<arxiv-id>.pdf`;
|
||||
3. 如果目标文件已存在且以 `%PDF-` 开头,直接复用;
|
||||
4. 用 `pypdf` 提取分页文本,受 `max_pdf_pages` 和 `max_pdf_chars` 限制;
|
||||
5. 将论文元数据、选择理由和 PDF 文本交给无工具 Agent;
|
||||
6. 将中文解读写入 `daily/<date>/<中文标题>.md`。
|
||||
|
||||
下载采用临时文件并在校验 PDF 文件头后原子替换,同时限制 `max_pdf_bytes`。当前没有 OCR;扫描版或无文本层 PDF 会失败。提取被截断时,笔记
|
||||
frontmatter 中的 `pdf_text_truncated` 会记录为 `true`。
|
||||
|
||||
### 5. Digest
|
||||
|
||||
`daily_paper_digest_step` 以内存中的三篇解读作为本期事实来源生成中文简报,同时搜索并按需读取较早的 daily
|
||||
文章来识别相关报道;历史文章只能用于建立上下文 wikilink,不能用于补充本期论文事实。输出必须包含 `title`、`desc` 和 `body`
|
||||
。代码会:
|
||||
|
||||
- 去掉模型可能生成的 YAML frontmatter;
|
||||
- 规范化中文标题并用作文件名;
|
||||
- 只保留指向真实存在、日期早于运行日期的 `daily/` Markdown 文件的模型生成 wikilink;
|
||||
- 确定性追加三篇源笔记的 wikilink;
|
||||
- 写入 `daily/<date>/<中文简报标题>.md`;
|
||||
- 重建 `daily/<date>.md` 当日索引。
|
||||
|
||||
最终响应 metadata 包含日期、周/月范围、入选 arXiv ID、选择理由、笔记/PDF/简报路径、源榜单数量和排重数量。
|
||||
|
||||
### 6. DingTalk
|
||||
|
||||
最后的 `dingtalk_markdown_send_step` 是可选步骤。未设置群会话 ID 时无副作用跳过;配置后会去掉 frontmatter,并把简报正文依次发送给所有群:
|
||||
|
||||
```dotenv
|
||||
DINGTALK_APP_KEY=your-app-key
|
||||
DINGTALK_APP_SECRET=your-app-secret
|
||||
DINGTALK_ROBOT_CODE=your-robot-code
|
||||
DINGTALK_CONVERSATION_IDS=cid-group-one,cid-group-two
|
||||
```
|
||||
|
||||
任一群发送失败不会阻止继续尝试后续群,全部尝试结束后统一报告失败。
|
||||
|
||||
## 产物
|
||||
|
||||
```text
|
||||
reme_workspace/
|
||||
├── daily/
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <中文论文标题>.md # 三篇,kind: daily-paper-analysis
|
||||
│ └── <中文简报标题>.md # 一篇,kind: daily-paper-brief
|
||||
└── resource/
|
||||
└── papers/
|
||||
└── <arxiv-id>.pdf
|
||||
```
|
||||
|
||||
每次成功生成会写入三篇论文解读和一篇简报。强制重跑后,当日目录中可能保留其他内容或此前入选论文的解读;ReMe
|
||||
不会把它们作为清理对象删除。文件名来自 Agent 返回的中文标题。代码会清理路径不安全字符,并处理同名文件。Markdown 和 PDF
|
||||
都通过同目录临时文件写入后原子替换。
|
||||
|
||||
## 参数与默认值
|
||||
|
||||
可在调用时传入的 Job 参数:
|
||||
|
||||
| 参数 | 默认值 | 作用 |
|
||||
|-----------------|--------:|----------------------------------------------------------------------------------------------|
|
||||
| `date` | `""` | 运行日期;空值使用应用时区当天,非空值必须为 `YYYY-MM-DD` |
|
||||
| `force` | `false` | 已有当日简报时仍重新生成 |
|
||||
| `use_hf_mirror` | `false` | 是否使用 Hugging Face 镜像站;优先读取 `HF_MIRROR_URL`,未配置时使用 `https://hf-mirror.com` |
|
||||
| `topics` | `""` | 精选论文时优先考虑的主题 |
|
||||
| `weekly_weight` | `0.7` | RRF 中周榜权重 |
|
||||
| `history_days` | `30` | 历史推荐排重窗口 |
|
||||
|
||||
`daily_paper` Job 的步骤级配置:
|
||||
|
||||
| 配置 | 默认值 | 作用 |
|
||||
|-------------------|-----------:|------------------------------|
|
||||
| `candidate_limit` | `20` | 送入 Select 的最大候选数 |
|
||||
| `rrf_k` | `60` | RRF 常数 |
|
||||
| `hf_timeout` | `600` 秒 | Hugging Face 单次请求超时 |
|
||||
| `hf_max_retries` | `3` | Hugging Face 最大尝试次数 |
|
||||
| `pdf_timeout` | `600` 秒 | arXiv PDF 下载超时 |
|
||||
| `max_pdf_bytes` | `52428800` | PDF 上限,50 MiB |
|
||||
| `max_pdf_pages` | `35` | 最多提取页数 |
|
||||
| `max_pdf_chars` | `300000` | 最多送入 Agent 的 PDF 字符数 |
|
||||
|
||||
## 镜像站
|
||||
|
||||
数据客户端使用 httpx 默认的环境处理,因此存在 `HTTP_PROXY`、`HTTPS_PROXY` 或 `NO_PROXY` 时会自动生效。两个数据源启用镜像的方式不同:Hugging
|
||||
Face 由 `use_hf_mirror` 任务参数控制,arXiv 仅由环境变量驱动。
|
||||
|
||||
```dotenv
|
||||
# 内置 daily_paper_cron 定时任务默认启用镜像站;设为 false 可改用官方服务
|
||||
DAILY_PAPER_USE_HF_MIRROR=false
|
||||
|
||||
# 仅在手动任务或定时任务启用镜像时读取;未配置时使用 https://hf-mirror.com
|
||||
HF_MIRROR_URL=https://hf-mirror.com
|
||||
|
||||
# 可选覆盖;未设置时代码使用 https://arxiv.org
|
||||
ARXIV_MIRROR_URL=https://export.arxiv.org
|
||||
|
||||
# 也支持带路径前缀的中转地址
|
||||
# HF_MIRROR_URL=http://relay-host:18080/hf
|
||||
# ARXIV_MIRROR_URL=http://relay-host:18080/arxiv
|
||||
```
|
||||
|
||||
`HF_MIRROR_URL` 必须提供当前代码使用的 `/papers/...`、`/api/daily_papers` 和 `/api/papers/...` 路径。`ARXIV_MIRROR_URL`
|
||||
必须支持 `/pdf/<arxiv-id>`。两种 base URL 都会保留路径前缀,末尾 `/` 可有可无。不存在备用地址回退:客户端选定哪个 base
|
||||
URL,就只访问该地址。
|
||||
|
||||
> **行为变更:** 以往只要设置 `HF_MIRROR_URL` 就会改变 Hugging Face
|
||||
> 的访问地址;现在该变量仅在任务启用镜像时才会读取,否则直接访问官方站点,并输出一条“已忽略该变量”的告警日志。手动调用需传入
|
||||
> `use_hf_mirror=true`。内置 `daily_paper_cron` 定时任务默认启用镜像;设置 `DAILY_PAPER_USE_HF_MIRROR=false`
|
||||
> 可让该定时任务改用官方服务。
|
||||
|
||||
## 运行方式
|
||||
|
||||
生成指定日期的简报:
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
config=daily_cookbook \
|
||||
job=daily_paper \
|
||||
date=2026-08-06 \
|
||||
topics="Agent memory" \
|
||||
history_days=30
|
||||
```
|
||||
|
||||
强制重跑;有效的本地 PDF 仍会复用:
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook job=daily_paper date=2026-08-06 force=true
|
||||
```
|
||||
|
||||
启动 HTTP 服务和定时任务:
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook
|
||||
```
|
||||
|
||||
内置服务监听 `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` 或启动参数覆盖监听地址和端口。
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:8001/daily_paper \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"date":"2026-08-06","force":false,"topics":"Agent memory"}'
|
||||
```
|
||||
|
||||
## 失败与重跑
|
||||
|
||||
- Hugging Face HTTP 请求失败会指数退避重试,最多尝试 `hf_max_retries` 次;响应数据格式无效时立即失败。
|
||||
- 候选少于三篇、Agent 精选不合法、PDF 无效/过大/无文本或 Agent 输出为空都会终止 Job。
|
||||
- 三篇论文按顺序处理;中途失败时,之前已完成的 PDF 和笔记会保留。
|
||||
- `force=true` 会重新生成本次入选论文的解读和简报,并复用有效 PDF;不会删除当日目录中已有的其他笔记。
|
||||
- 多文件流程不是事务,也没有同一日期的全局运行锁。
|
||||
|
||||
## 测试
|
||||
|
||||
单元测试会 mock Hugging Face、arXiv、AgentScope 和 DingTalk 边界,不访问真实服务:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[dev,core]"
|
||||
pytest tests/unit/test_daily_paper.py -v
|
||||
```
|
||||
|
|
@ -39,8 +39,8 @@ The project requires Python 3.11 or later. A virtual environment is recommended:
|
|||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e packages/reme_ai_studio -e ".[dev,full]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[dev,full]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
|
|||
|
|
@ -55,11 +55,12 @@ Core layers:
|
|||
reme/
|
||||
reme.py # CLI entry point
|
||||
application.py # Application assembly and lifecycle
|
||||
plugin.py # installed plugin contract and entry-point loader
|
||||
config/
|
||||
default.yaml # default service / jobs / components
|
||||
config_parser.py # config=, dot notation, and env placeholder parsing
|
||||
components/
|
||||
component_registry.py # global registry R
|
||||
component_registry.py # backend registry and application-local copies
|
||||
base_component.py # ComponentMixin / BaseComponent / bind dependency declarations
|
||||
runtime_context.py # context for one Job execution
|
||||
job/ # BaseJob / StreamJob / BackgroundJob / CronJob
|
||||
|
|
@ -76,11 +77,17 @@ 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
|
||||
```
|
||||
|
||||
The default workspace directories are defined by `ApplicationConfig`:
|
||||
|
|
@ -167,8 +174,8 @@ HTTP service behavior:
|
|||
|
||||
After registering Job endpoints, the HTTP service can also mount the ReMe Studio single-page application. The default is
|
||||
`service.web_enabled=true`. Builds are resolved from `service.web_static_dir`, `REME_WEB_STATIC_DIR`, the optional
|
||||
`reme-ai-studio` package installed by the `web` and `core` extras, and source-tree locations such as
|
||||
`website/dist-static`. If no `index.html` is found, only the frontend is skipped and the Job API remains available. The
|
||||
`reme_studio` package installed by the `web` and `core` extras, and source-tree locations such as
|
||||
`reme_studio/dist-static`. If no `index.html` is found, only the frontend is skipped and the Job API remains available. The
|
||||
Studio `GET` fallback does not replace existing `POST /<job.name>` routes.
|
||||
|
||||
MCP service behavior:
|
||||
|
|
@ -216,14 +223,41 @@ The registry key is:
|
|||
The same backend name can therefore exist under different component types. For example, `http` can be both a service
|
||||
backend and a client backend.
|
||||
|
||||
### 4.2 Registration Through Module Imports
|
||||
`ComponentEnum` provides the built-in identifiers, but installed plugins may declare a new type with a namespaced
|
||||
string such as `example.reranker`. Custom identifiers use lowercase letters and numbers separated by `.`, `_`, or `-`.
|
||||
They are configured under `components` and participate in the same dependency ordering and lifecycle as built-ins.
|
||||
|
||||
Registration happens when a module is imported. `reme/components/__init__.py` imports component packages, while
|
||||
`reme/steps/__init__.py` imports `benchmark/common/cookbook/evolve/file_io/index/transfer`. Each package's `__init__.py`
|
||||
then imports its concrete modules, causing `@R.register(...)` to execute.
|
||||
### 4.2 Built-in and Plugin Registration
|
||||
|
||||
After adding a Step file, make sure the package's `__init__.py` imports it. Otherwise, the backend will not appear in
|
||||
the registry.
|
||||
Built-in implementations populate the built-in registry through package imports. ReMe freezes that template after
|
||||
bootstrap, and each `Application` receives a mutable copy. Runtime code resolves backends through the application's
|
||||
registry rather than changing the process-wide template. ReMe then loads only the installed plugins explicitly named by
|
||||
`plugins` in the resolved configuration. A plugin exposes its package through the `reme.plugins` Python entry-point
|
||||
group. The package's `plugin.yaml` has two optional mappings: `backends` maps registration names to
|
||||
`module:Class` targets, and `application_defaults` contributes a low-priority `ApplicationConfig` fragment. The
|
||||
entry-point name is the plugin's identity.
|
||||
Plugins are enabled explicitly through the application config's `plugins` list or a `plugins=[...]` CLI override.
|
||||
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. 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 install reme-daily-paper
|
||||
reme plugins show daily-paper
|
||||
reme plugins validate daily-paper
|
||||
reme plugins uninstall daily-paper
|
||||
|
||||
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.
|
||||
|
||||
### 4.3 Component.bind
|
||||
|
||||
|
|
|
|||
|
|
@ -388,3 +388,8 @@ This lets the agent see not only an isolated paragraph but also its structural p
|
|||
|
||||
Non-Markdown files use `DefaultFileChunker` by default. It splits by byte size and preserves a small overlap. For
|
||||
Markdown, the chunker also avoids cutting `[[wikilinks]]` in the middle.
|
||||
|
||||
`DefaultFileChunker` and `MarkdownFileChunker` decode files with their configured `encoding` and normalize platform
|
||||
newlines to LF before indexing. Their default `invalid_encoding_policy: replace` keeps decodable content searchable
|
||||
when a source contains invalid bytes, without modifying the source file. Set `invalid_encoding_policy: strict` on a
|
||||
chunker component to reject such files instead.
|
||||
|
|
|
|||
|
|
@ -117,6 +117,14 @@ Out of the box, search therefore uses primarily BM25 plus link expansion. After
|
|||
`SearchStep` runs vector and keyword recall together. Additionally, switching the `file_store` `backend` from `local` to
|
||||
`faiss` upgrades vector retrieval from a linear scan to a FAISS HNSW index, offering faster recall at scale.
|
||||
|
||||
The embedding store accepts `health_check_timeout` for its startup probe. A temporary failure skips the current vector
|
||||
backfill while keeping BM25 available; a later successful provider request resumes the missing-vector backfill
|
||||
automatically.
|
||||
|
||||
Embedded integrations that have already verified a provider can call `resume_embedding(verified=True)`. When changing
|
||||
the embedding vector space, pass `rebuild=True`; persisted vectors are invalidated before a serial background rebuild,
|
||||
and vector search remains unavailable until the rebuilt vectors are safely persisted.
|
||||
|
||||
## How to Search
|
||||
|
||||
The `search` Job is also configured in `default.yaml`:
|
||||
|
|
|
|||
225
docs/en/plugin_management.md
Normal file
225
docs/en/plugin_management.md
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
# Plugin Management
|
||||
|
||||
ReMe plugins are ordinary Python distributions discovered through the `reme.plugins` entry-point group. Installing a
|
||||
plugin makes it available to the current Python environment; it does not enable the plugin in every ReMe application.
|
||||
|
||||
Keep these two operations separate:
|
||||
|
||||
```text
|
||||
reme plugins install ... install a package into the current Python environment
|
||||
plugins: [auto-fin] enable an installed plugin for one Application
|
||||
```
|
||||
|
||||
Plugin package management is local-only. It does not run through a ReMe HTTP or MCP service and never edits application
|
||||
configuration files automatically.
|
||||
|
||||
A typical plugin workflow has three stages:
|
||||
|
||||
1. Install ReMe and the plugin distribution.
|
||||
2. Configure the plugin's runtime environment as described in the
|
||||
[ReMe model-configuration guide](../../README.md#optional-model-configuration).
|
||||
3. Start an Application with the plugin explicitly enabled, for example
|
||||
`reme start plugins='["auto-fin"]'`.
|
||||
|
||||
## List installed plugins
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
```
|
||||
|
||||
The table shows the plugin entry-point name, Python distribution, version, and plugin contract:
|
||||
|
||||
```text
|
||||
PLUGIN DISTRIBUTION VERSION FORMAT
|
||||
-------- ------------- ------- --------
|
||||
auto-fin reme-auto-fin X.Y.Z manifest
|
||||
```
|
||||
|
||||
`manifest` plugins use the current package-level `plugin.yaml` contract. `legacy` plugins use the compatible Python
|
||||
descriptor contract.
|
||||
|
||||
A manifest separates backend registration from application configuration:
|
||||
|
||||
```yaml
|
||||
backends:
|
||||
example_step: example_plugin.steps:ExampleStep
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
example:
|
||||
backend: base
|
||||
steps:
|
||||
- backend: example_step
|
||||
```
|
||||
|
||||
`application_defaults` is a partial `ApplicationConfig`. It is kept below the manifest's `backends` namespace because
|
||||
backend import declarations are part of plugin discovery and are not application configuration.
|
||||
|
||||
Use JSON when another local tool needs structured output:
|
||||
|
||||
```bash
|
||||
reme plugins list --json
|
||||
```
|
||||
|
||||
To compare installed plugins with one application config:
|
||||
|
||||
```bash
|
||||
reme plugins list --config default
|
||||
```
|
||||
|
||||
The optional `ENABLED` column reflects only the `plugins` list resolved from that config. A command-line override used
|
||||
by another running process is not a global enable state.
|
||||
|
||||
## Install a plugin package
|
||||
|
||||
Install a published distribution:
|
||||
|
||||
```bash
|
||||
reme plugins install reme-auto-fin
|
||||
```
|
||||
|
||||
Install or upgrade a pinned version:
|
||||
|
||||
```bash
|
||||
reme plugins install 'reme-auto-fin==X.Y.Z'
|
||||
reme plugins install reme-auto-fin --upgrade
|
||||
```
|
||||
|
||||
Install a local plugin project:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin
|
||||
```
|
||||
|
||||
Use editable mode while developing it:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin --editable
|
||||
```
|
||||
|
||||
ReMe invokes pip through the same Python interpreter that runs the `reme` command. Pip remains responsible for package
|
||||
resolution, downloads, dependency changes, and build execution. Install only packages and local projects you trust.
|
||||
|
||||
After installation, confirm the discovered plugin name:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
## Inspect a plugin
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin
|
||||
```
|
||||
|
||||
For a manifest plugin, the result includes its registered backend names and default Job names. JSON output is also
|
||||
available:
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin --json
|
||||
```
|
||||
|
||||
`show` identifies the package contract without constructing a ReMe Application.
|
||||
|
||||
## Validate a plugin
|
||||
|
||||
Validate an installed plugin:
|
||||
|
||||
```bash
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
Validate a local project before installation:
|
||||
|
||||
```bash
|
||||
reme plugins validate ./plugins/auto-fin
|
||||
```
|
||||
|
||||
Validation checks the entry point, `plugin.yaml`, backend imports and component types, registry collisions, merged
|
||||
`application_defaults`, and the resulting `ApplicationConfig`. Validation imports plugin backend modules, so run it
|
||||
only for trusted code.
|
||||
|
||||
## Enable a plugin in a service
|
||||
|
||||
Installation alone does not load plugin code into an Application. Enable plugins explicitly in configuration:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- auto-fin
|
||||
```
|
||||
|
||||
Or add them for one service launch:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
When `config` is omitted, ReMe loads `default.yaml`. The plugin's `application_defaults` are merged below that config,
|
||||
so explicit config values and CLI overrides win. This mapping is an `ApplicationConfig` fragment, not a separate
|
||||
configuration schema. The plugin backends are registered only in that Application's local registry.
|
||||
|
||||
After the default HTTP service starts, access plugin Jobs through ReMe's CLI client or HTTP:
|
||||
|
||||
```bash
|
||||
reme auto_fin topics="黄金,AI,存储芯片"
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/auto_fin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"黄金,AI,存储芯片"}'
|
||||
```
|
||||
|
||||
When the application uses an MCP service, service-enabled plugin Jobs appear as MCP tools instead.
|
||||
|
||||
Custom application configs must provide the plugin's runtime dependencies, including an `agent_wrapper.default` and
|
||||
the `search` and `read` Jobs used by Auto Fin.
|
||||
|
||||
## Uninstall a plugin
|
||||
|
||||
Use the plugin entry-point name, not necessarily the distribution name:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin
|
||||
```
|
||||
|
||||
Skip pip's confirmation prompt when needed:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin --yes
|
||||
```
|
||||
|
||||
ReMe resolves `auto-fin` to the distribution that provides it, such as `reme-auto-fin`. If one distribution provides
|
||||
multiple plugin entry points, the command lists the other plugins that will also be removed.
|
||||
|
||||
Uninstallation does not rewrite user configuration. Remove the plugin from relevant `plugins` lists yourself;
|
||||
otherwise the next Application startup fails explicitly because the configured plugin is no longer installed. Restart
|
||||
already-running ReMe processes after installing, upgrading, or uninstalling packages.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Plugin is installed but unavailable
|
||||
|
||||
Check that the `reme` command and pip package share one Python interpreter:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
python -c 'import sys; print(sys.executable)'
|
||||
```
|
||||
|
||||
Using `reme plugins install` avoids the most common interpreter mismatch because it runs `python -m pip` with ReMe's
|
||||
own interpreter.
|
||||
|
||||
### Plugin is installed but not loaded
|
||||
|
||||
Add its entry-point name to the Application's `plugins` list. ReMe intentionally has no global enable/disable state.
|
||||
|
||||
### Startup reports that the plugin is not installed
|
||||
|
||||
The active config still enables a missing plugin. Reinstall it or remove the corresponding name from `plugins`.
|
||||
|
||||
### Changes are not visible in a running service
|
||||
|
||||
Plugin discovery and backend registration happen during Application construction. Restart the service after changing
|
||||
installed packages.
|
||||
|
|
@ -15,8 +15,8 @@ Install from source:
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e packages/reme_ai_studio -e ".[core]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
@ -315,10 +315,12 @@ that best fits their runtime environment and share the same local memory workspa
|
|||
|
||||
| Agent | Recommended integration | Capabilities after integration |
|
||||
|-------|-------------------------|--------------------------------|
|
||||
| **DeepSeek Harness** | Install [`@agentscope-ai/reme`](../../typescript/README.md#deepseek-harness) as a DSH profile bundle. | Long-term memory guidance, `reme_search`, automatic capture of completed main-agent turns, and scheduled Auto Dream. |
|
||||
| **OpenClaw** | Install [`@agentscope-ai/reme`](../../typescript/README.md#openclaw) as the native memory plugin. | Recall before conversational root-agent runs, explicit search, automatic turn capture, and scheduled Auto Dream. |
|
||||
| **QwenPaw** | Embed ReMe in-process through the Python API. | Reuse the host application's lifecycle and model configuration while keeping memories local and file-based. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP Service and install [`plugins/claude_code/reme`](../../plugins/claude_code/reme). | MCP memory-recall tools, the `reme-memory` skill, and a Stop hook that automatically records sessions. |
|
||||
| **Hermes** | Start the HTTP Service and install [`plugins/hermes_agent`](../../plugins/hermes_agent). | Automatically recall relevant memories before model calls and invoke `auto_memory` asynchronously after each conversation turn. |
|
||||
| **OpenClaw, Codex, and other CLI-capable agents** | Copy or install [`skills/reme_memory/SKILL.md`](../../skills/reme_memory/SKILL.md). | Search, read, and write memories through the CLI; automatic recording requires the host agent to integrate explicitly with the conversation lifecycle. |
|
||||
| **Claude Code** | Start the streamable HTTP MCP Service and install [`integrations/claude_code/reme`](../../integrations/claude_code/reme). | MCP memory-recall tools, the `reme-memory` skill, and a Stop hook that automatically records sessions. |
|
||||
| **Hermes** | Start the HTTP Service and install [`integrations/hermes_agent`](../../integrations/hermes_agent). | Automatically recall relevant memories before model calls and invoke `auto_memory` asynchronously after each conversation turn. |
|
||||
| **Codex and other CLI-capable agents** | Copy or install [`skills/reme_memory/SKILL.md`](../../skills/reme_memory/SKILL.md). | Search, read, and write memories through the CLI; automatic recording requires the host agent to integrate explicitly with the conversation lifecycle. |
|
||||
|
||||
For installation, configuration, and integration demos, see the [README](../../README.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ ReMe 的核心代码位于:
|
|||
```bash
|
||||
python -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -e packages/reme_ai_studio -e ".[dev,full]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[dev,full]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
|
|||
|
|
@ -50,11 +50,12 @@ flowchart LR
|
|||
reme/
|
||||
reme.py # CLI 入口
|
||||
application.py # Application 装配与生命周期
|
||||
plugin.py # 已安装插件契约与 entry-point loader
|
||||
config/
|
||||
default.yaml # 默认 service / jobs / components
|
||||
config_parser.py # config=、dot notation、env 占位符解析
|
||||
components/
|
||||
component_registry.py # 全局注册表 R
|
||||
component_registry.py # backend 注册表及 Application 局部副本
|
||||
base_component.py # ComponentMixin / BaseComponent / bind 依赖声明
|
||||
runtime_context.py # 单次 Job 执行上下文
|
||||
job/ # BaseJob / StreamJob / BackgroundJob / CronJob
|
||||
|
|
@ -71,11 +72,17 @@ 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 适配器
|
||||
```
|
||||
|
||||
默认 workspace 目录由 `ApplicationConfig` 定义:
|
||||
|
|
@ -161,8 +168,8 @@ HTTP service 行为:
|
|||
| `enable_serve: false` | 不注册 endpoint |
|
||||
|
||||
HTTP service 还可以在所有 Job endpoint 注册完成后挂载 ReMe Studio 单页应用。默认 `service.web_enabled=true`;构建产物按
|
||||
`service.web_static_dir`、`REME_WEB_STATIC_DIR`、由 `web` 或 `core` extra 安装的可选 `reme-ai-studio` 包,以及源码树
|
||||
`website/dist-static` 等候选位置解析。找不到 `index.html` 时只跳过前端,Job API 仍然可用。Studio 的 `GET` fallback 不会覆盖
|
||||
`service.web_static_dir`、`REME_WEB_STATIC_DIR`、由 `web` 或 `core` extra 安装的可选 `reme_studio` 包,以及源码树
|
||||
`reme_studio/dist-static` 等候选位置解析。找不到 `index.html` 时只跳过前端,Job API 仍然可用。Studio 的 `GET` fallback 不会覆盖
|
||||
已有的 `POST /<job.name>`。
|
||||
|
||||
MCP service 行为:
|
||||
|
|
@ -208,13 +215,38 @@ class VersionStep(BaseStep):
|
|||
|
||||
所以同名 backend 在不同 component type 下可以共存。例如 `http` 同时可以是 service backend 和 client backend。
|
||||
|
||||
### 4.2 模块导入触发注册
|
||||
`ComponentEnum` 提供内置类型标识;已安装插件也可以用 `example.reranker` 这样的命名空间字符串声明新类型。自定义标识仅使用
|
||||
小写字母和数字,并以 `.`、`_` 或 `-` 分隔。它们配置在 `components` 下,与内置组件参与相同的依赖排序和生命周期。
|
||||
|
||||
注册发生在模块 import 时。`reme/components/__init__.py` 会 import 各组件包,`reme/steps/__init__.py` 会 import
|
||||
`benchmark/common/cookbook/evolve/file_io/index/transfer`。这些包的 `__init__.py` 再 import 具体模块,从而执行
|
||||
`@R.register(...)`。
|
||||
### 4.2 内置注册与插件注册
|
||||
|
||||
新增 Step 文件后,必须保证它所在包的 `__init__.py` 会 import 该模块,否则注册表里找不到这个 backend。
|
||||
内置实现通过 package import 填充内置注册表;bootstrap 完成后 ReMe 会冻结这个模板,并为每个 `Application` 创建可写副本。
|
||||
运行期代码通过当前 Application 的注册表解析 backend,不能修改进程级模板。随后只加载最终配置中 `plugins` 明确启用的已安装插件。
|
||||
插件通过 Python `reme.plugins` entry-point group 暴露其 package。package 内的 `plugin.yaml` 只有两个可选 mapping:
|
||||
`backends` 将注册名映射到 `module:Class`,`application_defaults` 提供低优先级的 `ApplicationConfig` 配置片段。
|
||||
entry-point 名称就是插件标识;使用
|
||||
应用配置的 `plugins` 列表或 CLI 的 `plugins=[...]` override 显式启用插件。插件注册因此只影响当前 Application;两个插件提供相同
|
||||
`(component_type, backend)` 时会在装配阶段失败,
|
||||
不会互相覆盖。
|
||||
|
||||
迁移期间仍兼容旧的 Python `Plugin` descriptor 和 `reme.configs` entry point。配置的 `extends` 可以继承内置配置、
|
||||
旧插件配置或文件配置。独立打包示例见 [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 install reme-daily-paper
|
||||
reme plugins show daily-paper
|
||||
reme plugins validate daily-paper
|
||||
reme plugins uninstall daily-paper
|
||||
|
||||
reme start plugins='["auto-fin","daily-paper"]'
|
||||
```
|
||||
|
||||
这些管理命令使用当前 Python 解释器对应的 pip,不通过 HTTP 或 MCP service 执行。
|
||||
|
||||
### 4.3 Component.bind
|
||||
|
||||
|
|
|
|||
|
|
@ -363,3 +363,7 @@ FileChunk[]
|
|||
这样检索命中时,Agent 不只看到孤立段落,还能看到它在原文件中的结构位置。
|
||||
|
||||
非 Markdown 默认走 `DefaultFileChunker`:按字节大小切分,并保留少量 overlap;对 Markdown 则会避免把 `[[wikilink]]` 从中间切开。
|
||||
|
||||
`DefaultFileChunker` 和 `MarkdownFileChunker` 使用各自配置的 `encoding` 解码文件,并在索引前将平台换行符统一为
|
||||
LF。默认的 `invalid_encoding_policy: replace` 会在源文件含无效字节时保留其中可解码的内容用于检索,但不会修改源
|
||||
文件;如需拒绝此类文件,可在 chunker 组件上设置 `invalid_encoding_policy: strict`。
|
||||
|
|
|
|||
|
|
@ -106,6 +106,12 @@ file_store:
|
|||
所以开箱搜索主要是 BM25 + 链接展开。把 `embedding_store: default` 打开后,`SearchStep` 会同时跑向量召回和关键词召回。此时若将
|
||||
`file_store` 的 `backend` 从 `local` 改为 `faiss`,向量检索会从线性扫描升级为 FAISS HNSW 索引,在大规模 chunk 场景下召回效率更高。
|
||||
|
||||
Embedding store 可通过 `health_check_timeout` 配置启动探测。临时失败只会跳过本次向量回填,BM25 仍可使用;
|
||||
后续真实请求成功后会自动恢复缺失向量的回填。
|
||||
|
||||
已经完成真实服务验证的嵌入式集成可以调用 `resume_embedding(verified=True)`。切换 Embedding 向量空间时应同时传入
|
||||
`rebuild=True`;ReMe 会先使旧向量失效,再串行后台重建,并在新向量安全持久化前暂停向量搜索。
|
||||
|
||||
## 怎么搜索
|
||||
|
||||
`search` Job 也是在 `default.yaml` 中配置:
|
||||
|
|
|
|||
215
docs/zh/plugin_management.md
Normal file
215
docs/zh/plugin_management.md
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# 插件管理
|
||||
|
||||
ReMe 插件是通过 `reme.plugins` entry-point group 发现的普通 Python distribution。安装插件只表示它在当前 Python
|
||||
环境中可用,并不会让所有 ReMe Application 自动启用该插件。
|
||||
|
||||
需要区分两个操作:
|
||||
|
||||
```text
|
||||
reme plugins install ... 将插件包安装到当前 Python 环境
|
||||
plugins: [auto-fin] 为一个 Application 启用已安装插件
|
||||
```
|
||||
|
||||
插件包管理仅在本地 CLI 执行,不经过 ReMe HTTP 或 MCP service,也不会自动修改应用配置文件。
|
||||
|
||||
典型的插件使用流程分为三个阶段:
|
||||
|
||||
1. 安装 ReMe 和插件 distribution。
|
||||
2. 按照 [ReMe 可选模型配置说明](../../README_ZH.md#可选模型配置)配置插件运行所需的环境变量。
|
||||
3. 启动 Application 时显式启用插件,例如 `reme start plugins='["auto-fin"]'`。
|
||||
|
||||
## 查看已安装插件
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
```
|
||||
|
||||
输出包含插件 entry-point 名称、Python distribution、版本和插件契约:
|
||||
|
||||
```text
|
||||
PLUGIN DISTRIBUTION VERSION FORMAT
|
||||
-------- ------------- ------- --------
|
||||
auto-fin reme-auto-fin X.Y.Z manifest
|
||||
```
|
||||
|
||||
`manifest` 表示插件使用当前的 package-level `plugin.yaml` 契约;`legacy` 表示插件使用仍然兼容的 Python descriptor
|
||||
契约。
|
||||
|
||||
manifest 将 backend 注册与应用配置分开:
|
||||
|
||||
```yaml
|
||||
backends:
|
||||
example_step: example_plugin.steps:ExampleStep
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
example:
|
||||
backend: base
|
||||
steps:
|
||||
- backend: example_step
|
||||
```
|
||||
|
||||
`application_defaults` 是一段不完整的 `ApplicationConfig`。它与 manifest 的 `backends` 命名空间分开,因为 backend
|
||||
导入声明属于插件发现协议,并不是应用配置。
|
||||
|
||||
本地工具需要结构化结果时可以使用 JSON:
|
||||
|
||||
```bash
|
||||
reme plugins list --json
|
||||
```
|
||||
|
||||
对照某个应用配置查看启用状态:
|
||||
|
||||
```bash
|
||||
reme plugins list --config default
|
||||
```
|
||||
|
||||
可选的 `ENABLED` 列只反映该配置解析出的 `plugins` 列表。其他运行中进程使用的 CLI override 不是全局启用状态。
|
||||
|
||||
## 安装插件包
|
||||
|
||||
安装已发布的 distribution:
|
||||
|
||||
```bash
|
||||
reme plugins install reme-auto-fin
|
||||
```
|
||||
|
||||
安装指定版本或升级:
|
||||
|
||||
```bash
|
||||
reme plugins install 'reme-auto-fin==X.Y.Z'
|
||||
reme plugins install reme-auto-fin --upgrade
|
||||
```
|
||||
|
||||
安装本地插件项目:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin
|
||||
```
|
||||
|
||||
开发插件时使用 editable 模式:
|
||||
|
||||
```bash
|
||||
reme plugins install ./plugins/auto-fin --editable
|
||||
```
|
||||
|
||||
ReMe 会通过运行 `reme` 命令的同一个 Python 解释器调用 pip。包解析、下载、依赖变更和构建执行仍由 pip 负责。请只安装
|
||||
可信的包和本地项目。
|
||||
|
||||
安装后确认 ReMe 实际发现的插件名:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
## 查看插件详情
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin
|
||||
```
|
||||
|
||||
对于 manifest 插件,结果包含注册的 backend 名称和默认 Job 名称。也可以输出 JSON:
|
||||
|
||||
```bash
|
||||
reme plugins show auto-fin --json
|
||||
```
|
||||
|
||||
`show` 只检查包契约,不构造 ReMe Application。
|
||||
|
||||
## 校验插件
|
||||
|
||||
校验已安装插件:
|
||||
|
||||
```bash
|
||||
reme plugins validate auto-fin
|
||||
```
|
||||
|
||||
安装前校验本地插件项目:
|
||||
|
||||
```bash
|
||||
reme plugins validate ./plugins/auto-fin
|
||||
```
|
||||
|
||||
校验范围包括 entry point、`plugin.yaml`、backend 导入和组件类型、registry 冲突、`application_defaults` 合并以及最终的
|
||||
`ApplicationConfig`。校验过程会导入插件 backend 模块,因此只能对可信代码执行。
|
||||
|
||||
## 在服务中启用插件
|
||||
|
||||
只安装插件不会将插件代码加载到 Application。需要在配置中显式启用:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
- auto-fin
|
||||
```
|
||||
|
||||
也可以只为本次服务启动追加插件:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
未传入 `config` 时,ReMe 加载 `default.yaml`。插件的 `application_defaults` 合并在该配置之下,因此显式配置和 CLI
|
||||
override 优先。这个 mapping 是 `ApplicationConfig` 配置片段,并不是另一套配置 schema。插件 backend 只注册到该
|
||||
Application 的局部 registry。
|
||||
|
||||
默认 HTTP service 启动后,可以通过 ReMe CLI client 或 HTTP 访问插件 Job:
|
||||
|
||||
```bash
|
||||
reme auto_fin topics="黄金,AI,存储芯片"
|
||||
```
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/auto_fin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"黄金,AI,存储芯片"}'
|
||||
```
|
||||
|
||||
当应用使用 MCP service 时,允许对外服务的插件 Job 会显示为 MCP tool。
|
||||
|
||||
自定义应用配置需要提供插件的运行依赖,包括 `agent_wrapper.default`,以及 Auto Fin 使用的 `search` 和 `read` Jobs。
|
||||
|
||||
## 卸载插件
|
||||
|
||||
这里使用插件 entry-point 名称,它不一定等于 distribution 名称:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin
|
||||
```
|
||||
|
||||
需要跳过 pip 确认时:
|
||||
|
||||
```bash
|
||||
reme plugins uninstall auto-fin --yes
|
||||
```
|
||||
|
||||
ReMe 会将 `auto-fin` 解析为提供它的 distribution,例如 `reme-auto-fin`。如果一个 distribution 提供多个插件 entry
|
||||
point,命令会列出同时被移除的其他插件。
|
||||
|
||||
卸载不会重写用户配置。请自行从相关 `plugins` 列表中删除插件,否则下一次启动 Application 时会因为配置的插件未安装而明确
|
||||
失败。安装、升级或卸载包后,需要重启已经运行的 ReMe 进程。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 插件已经安装,但 ReMe 找不到
|
||||
|
||||
检查 `reme` 命令与安装插件使用的 pip 是否属于同一个 Python 解释器:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
python -c 'import sys; print(sys.executable)'
|
||||
```
|
||||
|
||||
使用 `reme plugins install` 可以避免最常见的解释器不一致问题,因为它通过 ReMe 自己的解释器运行 `python -m pip`。
|
||||
|
||||
### 插件已经安装,但没有加载
|
||||
|
||||
将插件 entry-point 名称加入 Application 的 `plugins` 列表。ReMe 刻意不提供全局 enable/disable 状态。
|
||||
|
||||
### 启动时报插件未安装
|
||||
|
||||
当前配置仍然启用了缺失插件。请重新安装插件,或者从 `plugins` 中删除对应名称。
|
||||
|
||||
### 运行中的服务看不到插件变化
|
||||
|
||||
插件发现和 backend 注册发生在 Application 构造阶段。修改已安装包后需要重启服务。
|
||||
|
|
@ -15,8 +15,8 @@ pip install "reme-ai[core]"
|
|||
```bash
|
||||
git clone https://github.com/agentscope-ai/ReMe.git
|
||||
cd ReMe
|
||||
pip install -e packages/reme_ai_studio -e ".[core]"
|
||||
cd website
|
||||
pip install -e reme_studio -e ".[core]"
|
||||
cd reme_studio
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ Auto Resource 提供了一条更通用的外部资料入口。资料进入 `reso
|
|||
|
||||
### Daily Paper:外部资料工作流的一个例子
|
||||
|
||||
Daily Paper 是建立在这套文件化记忆之上的可选 Cookbook。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存
|
||||
Daily Paper 是建立在这套文件化记忆之上的可选插件。它会从 Hugging Face Papers 的周榜和月榜收集论文,去除近期已经推荐过的内容,排序后精选三篇,保存
|
||||
PDF,并生成中文论文笔记与一份约五分钟可读完的简报。
|
||||
|
||||
想象一下,你持续关注 Agent Memory:每天早上收到的不只是三个论文链接,而是三篇已经保存到本地的详细笔记。简报通过 Wikilink
|
||||
|
|
@ -334,10 +334,12 @@ ReMe 既可以作为本地记忆服务,通过 CLI、HTTP API 或 MCP Server
|
|||
|
||||
| Agent | 推荐接入方式 | 接入后能力 |
|
||||
|----------------------------------------|--------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
|
||||
| **DeepSeek Harness** | 将 [`@agentscope-ai/reme`](../../typescript/README_ZH.md#deepseek-harness) 安装为 DSH profile bundle。 | 长期记忆指引、`reme_search`、自动捕获主 Agent 已完成的对话,以及定时 Auto Dream。 |
|
||||
| **OpenClaw** | 将 [`@agentscope-ai/reme`](../../typescript/README_ZH.md#openclaw) 安装为原生 memory plugin。 | 根 Agent 对话运行前召回、显式搜索、自动捕获对话,以及定时 Auto Dream。 |
|
||||
| **QwenPaw** | 通过 Python API 在进程内嵌入 ReMe。 | 复用宿主应用的生命周期和模型配置,同时保持记忆本地、文件化。 |
|
||||
| **Claude Code** | 启动 streamable HTTP MCP Service,并安装 [`plugins/claude_code/reme`](../../plugins/claude_code/reme)。 | MCP 记忆召回工具、`reme-memory` skill,以及自动记录会话的 Stop hook。 |
|
||||
| **Hermes** | 启动 HTTP Service,并安装 [`plugins/hermes_agent`](../../plugins/hermes_agent)。 | 在模型调用前自动召回相关记忆,并在每轮对话完成后异步调用 `auto_memory`。 |
|
||||
| **OpenClaw、Codex 等支持 CLI 的 Agent** | 复制或安装 [`skills/reme_memory/SKILL.md`](../../skills/reme_memory/SKILL.md)。 | 通过 CLI 搜索、读取和写入记忆;自动记录需要宿主 Agent 显式接入会话生命周期。 |
|
||||
| **Claude Code** | 启动 streamable HTTP MCP Service,并安装 [`integrations/claude_code/reme`](../../integrations/claude_code/reme)。 | MCP 记忆召回工具、`reme-memory` skill,以及自动记录会话的 Stop hook。 |
|
||||
| **Hermes** | 启动 HTTP Service,并安装 [`integrations/hermes_agent`](../../integrations/hermes_agent)。 | 在模型调用前自动召回相关记忆,并在每轮对话完成后异步调用 `auto_memory`。 |
|
||||
| **Codex 等支持 CLI 的 Agent** | 复制或安装 [`skills/reme_memory/SKILL.md`](../../skills/reme_memory/SKILL.md)。 | 通过 CLI 搜索、读取和写入记忆;自动记录需要宿主 Agent 显式接入会话生命周期。 |
|
||||
|
||||
安装、配置与集成演示可查看 [README 中文版](../../README_ZH.md)。
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This directory contains the standalone Vite documentation site published at <https://reme.agentscope.io>. The
|
||||
GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not depend on the ReMe Studio application in
|
||||
`website/`.
|
||||
`reme_studio/`.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -50,10 +50,10 @@ The build script reads the canonical repository files directly. Do not edit gene
|
|||
- `README.md` and `README_ZH.md`: project introductions
|
||||
- `docs/en/` and `docs/zh/`: English and Chinese guides
|
||||
- `docs/figure/`: documentation images
|
||||
- `website/README.md` and `website/README_ZH.md`: ReMe Studio guide
|
||||
- `cookbook/*/README*.md`: research workflow guides
|
||||
- `reme_studio/README.md` and `reme_studio/README_ZH.md`: ReMe Studio guide
|
||||
- `typescript/README.md` and `typescript/README_ZH.md`: TypeScript client, DeepSeek Harness, and OpenClaw integration guide
|
||||
- `plugins/*/README*.md`: plugin guides
|
||||
- `benchmark/{beam,longmemeval,pibench,toolmemory}/README*.md`: benchmark guides and results
|
||||
- `skills/reme_memory/SKILL.md`: ReMe Memory skill guide
|
||||
- `AGENTS.md`: repository development guide
|
||||
|
||||
To add or reorganize a document in the site navigation, update
|
||||
|
|
@ -75,7 +75,7 @@ github-pages/
|
|||
|
||||
## Deployment
|
||||
|
||||
The repository workflow `.github/workflows/pages.yml` builds this directory and publishes `dist/` to GitHub Pages.
|
||||
The repository workflow `.github/workflows/deploy-docs.yml` builds this directory and publishes `dist/` to GitHub Pages.
|
||||
It runs after relevant documentation or site files change on `main`, and it can also be started manually from the
|
||||
GitHub Actions page.
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"dev": "node scripts/generate-content.mjs && vite",
|
||||
"build": "node scripts/generate-content.mjs && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "node --test tests/*.test.mjs"
|
||||
"test": "node scripts/generate-content.mjs && node --test tests/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.6",
|
||||
|
|
|
|||
|
|
@ -6,8 +6,22 @@ const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|||
const repoDir = path.resolve(siteDir, "..");
|
||||
const outputDir = path.join(siteDir, ".generated", "content");
|
||||
|
||||
const navigationGroupOrder = [
|
||||
"overview",
|
||||
"start",
|
||||
"integration",
|
||||
"fundamentals",
|
||||
"automation",
|
||||
"concepts",
|
||||
"workspace",
|
||||
"plugins",
|
||||
"benchmarks",
|
||||
"development",
|
||||
];
|
||||
|
||||
const topicOrder = [
|
||||
"quick_start",
|
||||
"plugin_management",
|
||||
"memory_as_file",
|
||||
"memory_search",
|
||||
"auto_memory",
|
||||
|
|
@ -23,6 +37,7 @@ const topicOrder = [
|
|||
|
||||
const groups = {
|
||||
quick_start: "start",
|
||||
plugin_management: "start",
|
||||
memory_as_file: "fundamentals",
|
||||
memory_search: "fundamentals",
|
||||
auto_memory: "automation",
|
||||
|
|
@ -38,6 +53,7 @@ const groups = {
|
|||
|
||||
const localizedTitles = {
|
||||
quick_start: { zh: "快速开始", en: "Quick Start" },
|
||||
plugin_management: { zh: "插件管理", en: "Plugin Management" },
|
||||
memory_as_file: { zh: "文件即记忆", en: "Memory as File" },
|
||||
memory_search: { zh: "记忆检索", en: "Memory Search" },
|
||||
auto_memory: { zh: "自动记忆", en: "Auto Memory" },
|
||||
|
|
@ -52,9 +68,19 @@ const localizedTitles = {
|
|||
};
|
||||
|
||||
const productDocuments = [
|
||||
{
|
||||
slug: "typescript",
|
||||
source: "typescript",
|
||||
titles: { zh: "TypeScript Agent 集成", en: "TypeScript Agent Integrations" },
|
||||
descriptions: {
|
||||
zh: "配置统一 HTTP client,以及 DeepSeek Harness 和 OpenClaw 原生适配器。",
|
||||
en: "Configure the shared HTTP client and native DeepSeek Harness and OpenClaw adapters.",
|
||||
},
|
||||
group: "integration",
|
||||
},
|
||||
{
|
||||
slug: "studio",
|
||||
source: "website",
|
||||
source: "reme_studio",
|
||||
titles: { zh: "ReMe 工作台", en: "ReMe Studio" },
|
||||
descriptions: {
|
||||
zh: "浏览、编辑和搜索本地记忆,并探索记忆图谱。",
|
||||
|
|
@ -64,23 +90,23 @@ const productDocuments = [
|
|||
},
|
||||
{
|
||||
slug: "daily-paper",
|
||||
source: "cookbook/daily_paper",
|
||||
titles: { zh: "每日论文", en: "Daily Paper" },
|
||||
source: "plugins/daily_paper",
|
||||
titles: { zh: "每日论文插件", en: "Daily Paper Plugin" },
|
||||
descriptions: {
|
||||
zh: "发现论文、解析 PDF,并生成阅读笔记与每日简报。",
|
||||
en: "Discover papers, analyze PDFs, and produce reading notes and a daily brief.",
|
||||
},
|
||||
group: "cookbooks",
|
||||
group: "plugins",
|
||||
},
|
||||
{
|
||||
slug: "auto-fin",
|
||||
source: "cookbook/auto-fin",
|
||||
titles: { zh: "财经研究", en: "Auto Fin" },
|
||||
source: "plugins/auto-fin",
|
||||
titles: { zh: "Auto Fin 插件", en: "Auto Fin Plugin" },
|
||||
descriptions: {
|
||||
zh: "结合最新财联社新闻与本地历史记忆生成研究报告。",
|
||||
en: "Research recent CLS news with historical context from local memory.",
|
||||
},
|
||||
group: "cookbooks",
|
||||
group: "plugins",
|
||||
},
|
||||
{
|
||||
slug: "beam",
|
||||
|
|
@ -125,18 +151,6 @@ const productDocuments = [
|
|||
];
|
||||
|
||||
const sharedDocuments = [
|
||||
{
|
||||
id: "reme-memory-skill",
|
||||
path: "skills/reme_memory/SKILL.md",
|
||||
sourcePath: "skills/reme_memory/SKILL.md",
|
||||
titles: {
|
||||
zh: "ReMe 记忆技能",
|
||||
en: "ReMe Memory Skill",
|
||||
},
|
||||
description: "Bootstrap, retrieve, write, and consolidate memory from an agent.",
|
||||
group: "integration",
|
||||
language: "shared",
|
||||
},
|
||||
{
|
||||
id: "agents-guide",
|
||||
path: "AGENTS.md",
|
||||
|
|
@ -206,7 +220,9 @@ async function buildManifest() {
|
|||
}
|
||||
}
|
||||
|
||||
return [...documents, ...sharedDocuments];
|
||||
return [...documents, ...sharedDocuments].sort(
|
||||
(left, right) => navigationGroupOrder.indexOf(left.group) - navigationGroupOrder.indexOf(right.group),
|
||||
);
|
||||
}
|
||||
|
||||
await rm(path.join(siteDir, ".generated"), { recursive: true, force: true });
|
||||
|
|
@ -227,14 +243,6 @@ for (const product of productDocuments) {
|
|||
await cp(path.join(repoDir, product.source, filename), path.join(outputDir, product.source, filename));
|
||||
}
|
||||
}
|
||||
await mkdir(path.join(outputDir, "website", "public"), { recursive: true });
|
||||
await cp(path.join(repoDir, "website", "public", "og.jpg"), path.join(outputDir, "website", "public", "og.jpg"));
|
||||
await mkdir(path.join(outputDir, "skills", "reme_memory"), { recursive: true });
|
||||
await cp(
|
||||
path.join(repoDir, "skills", "reme_memory", "SKILL.md"),
|
||||
path.join(outputDir, "skills", "reme_memory", "SKILL.md"),
|
||||
);
|
||||
|
||||
await writeFile(
|
||||
path.join(outputDir, "manifest.json"),
|
||||
`${JSON.stringify({ documents: await buildManifest() }, null, 2)}\n`,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const copy = {
|
|||
concepts: "架构与场景",
|
||||
integration: "Agent 集成",
|
||||
workspace: "工作区",
|
||||
cookbooks: "研究工作流",
|
||||
plugins: "插件",
|
||||
benchmarks: "评测",
|
||||
development: "开发者规范",
|
||||
},
|
||||
|
|
@ -46,7 +46,7 @@ const copy = {
|
|||
concepts: "Architecture & scenarios",
|
||||
integration: "Agent integration",
|
||||
workspace: "Workspace",
|
||||
cookbooks: "Research workflows",
|
||||
plugins: "Plugins",
|
||||
benchmarks: "Benchmarks",
|
||||
development: "Development",
|
||||
},
|
||||
|
|
@ -71,8 +71,8 @@ const homeCopy = {
|
|||
exploreDescription: "选择你现在想完成的事情。每个入口都直接连接到对应的完整文档。",
|
||||
cards: [
|
||||
{ id: "studio-zh", icon: "◫", label: "管理记忆", title: "ReMe 工作台", description: "在本地 Web 工作区中浏览、编辑、搜索记忆,并探索 wikilink 图谱。", tone: "mint" },
|
||||
{ id: "daily-paper-zh", icon: "◌", label: "发现与分析", title: "每日论文", description: "从论文榜单筛选值得阅读的工作,解析 PDF,并生成笔记与五分钟简报。", tone: "blue" },
|
||||
{ id: "auto-fin-zh", icon: "↗", label: "主题研究", title: "财经研究", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" },
|
||||
{ id: "daily-paper-zh", icon: "◌", label: "发现与分析", title: "每日论文插件", description: "从论文榜单筛选值得阅读的工作,解析 PDF,并生成笔记与五分钟简报。", tone: "blue" },
|
||||
{ id: "auto-fin-zh", icon: "↗", label: "主题研究", title: "Auto Fin 插件", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" },
|
||||
],
|
||||
benchmark: "验证记忆能力",
|
||||
benchmarkDescription: "从检索规模、跨会话问答、个人智能体到工具经验,查看 ReMe 的四套评测与复现实验。",
|
||||
|
|
@ -88,8 +88,8 @@ const homeCopy = {
|
|||
exploreDescription: "Start with what you want to accomplish. Every entry opens the complete guide.",
|
||||
cards: [
|
||||
{ id: "studio-en", icon: "◫", label: "Manage memory", title: "ReMe Studio", description: "Browse, edit, and search memory in a local web workspace, then explore its wikilink graph.", tone: "mint" },
|
||||
{ id: "daily-paper-en", icon: "◌", label: "Discover & analyze", title: "Daily Paper", description: "Select useful papers from rankings, analyze PDFs, and create notes plus a five-minute brief.", tone: "blue" },
|
||||
{ id: "auto-fin-en", icon: "↗", label: "Research topics", title: "Auto Fin", description: "Connect recent CLS news with historical local memory to produce traceable research reports.", tone: "amber" },
|
||||
{ id: "daily-paper-en", icon: "◌", label: "Discover & analyze", title: "Daily Paper Plugin", description: "Select useful papers from rankings, analyze PDFs, and create notes plus a five-minute brief.", tone: "blue" },
|
||||
{ id: "auto-fin-en", icon: "↗", label: "Research topics", title: "Auto Fin Plugin", description: "Connect recent CLS news with historical local memory to produce traceable research reports.", tone: "amber" },
|
||||
],
|
||||
benchmark: "Validate memory systems",
|
||||
benchmarkDescription: "Explore four reproducible evaluations covering retrieval scale, cross-session QA, personal agents, and tool-use experience.",
|
||||
|
|
@ -284,7 +284,7 @@ function renderHome(pushHistory = true) {
|
|||
</div>
|
||||
</section>
|
||||
<section class="home-explore">
|
||||
<p class="section-kicker">01 / PRODUCT & WORKFLOWS</p>
|
||||
<p class="section-kicker">01 / PRODUCT & PLUGINS</p>
|
||||
<h2>${labels.explore}</h2>
|
||||
<p class="section-lead">${labels.exploreDescription}</p>
|
||||
<div class="feature-grid">
|
||||
|
|
|
|||
23
github-pages/tests/content-manifest.test.mjs
Normal file
23
github-pages/tests/content-manifest.test.mjs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
const manifestUrl = new URL("../.generated/content/manifest.json", import.meta.url);
|
||||
|
||||
test("omits retired Agent documents and places Agent integration after getting started", async () => {
|
||||
const manifest = JSON.parse(await readFile(manifestUrl, "utf8"));
|
||||
const documents = manifest.documents;
|
||||
const groups = [...new Set(documents.map((document) => document.group))];
|
||||
|
||||
assert.equal(documents.some((document) => document.id === "reme-memory-skill"), false);
|
||||
assert.equal(documents.some((document) => document.sourcePath.endsWith("agent_integration_plan.md")), false);
|
||||
assert.deepEqual(
|
||||
documents
|
||||
.filter((document) => document.group === "plugins")
|
||||
.map((document) => document.title || document.titles?.en),
|
||||
["每日论文插件", "Auto Fin 插件", "Daily Paper Plugin", "Auto Fin Plugin"],
|
||||
);
|
||||
assert.equal(documents.some((document) => document.group === "cookbooks"), false);
|
||||
assert.ok(groups.indexOf("integration") > groups.indexOf("start"));
|
||||
assert.ok(groups.indexOf("integration") < groups.indexOf("fundamentals"));
|
||||
});
|
||||
11
integrations/README.md
Normal file
11
integrations/README.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Agent Integrations
|
||||
|
||||
This directory contains host-specific adapters that connect external agents to ReMe. An integration may use the host's
|
||||
plugin API, hooks, MCP configuration, or client interface, but it does not extend ReMe's runtime through the
|
||||
`reme.plugins` entry-point group.
|
||||
|
||||
The shared TypeScript client and the DeepSeek Harness and OpenClaw adapters live in
|
||||
[`../typescript`](../typescript/README.md).
|
||||
|
||||
Installable extensions of ReMe itself include [Auto Fin](../plugins/auto-fin/README.md) and
|
||||
[Daily Paper](../plugins/daily_paper/README.md).
|
||||
|
|
@ -40,19 +40,19 @@ server means one set of background watchers / dream cron across all your Claude
|
|||
LLM_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
```
|
||||
|
||||
3. Start the ReMe MCP server (one time, leave it running):
|
||||
3. Start the ReMe HTTP server (one time, leave it running):
|
||||
|
||||
```bash
|
||||
reme start service.backend=mcp service.transport=streamable-http
|
||||
reme start service.backend=http
|
||||
```
|
||||
|
||||
It serves `http://127.0.0.1:2333/mcp`. To use a different port, start with
|
||||
The same process serves the JSON Job API and MCP at `http://127.0.0.1:2333/mcp`. To use a different port, start with
|
||||
`service.port=<port>` and update the `url` in `.mcp.json` to match.
|
||||
|
||||
## Install the plugin
|
||||
|
||||
```
|
||||
/plugin marketplace add ./plugins/claude_code
|
||||
/plugin marketplace add ./integrations/claude_code
|
||||
/plugin install reme@reme-marketplace
|
||||
```
|
||||
|
||||
|
|
@ -62,10 +62,10 @@ recall memory and report server health.
|
|||
|
||||
## Notes
|
||||
|
||||
- The plugin's MCP server URL lives in `plugins/claude_code/reme/.mcp.json`. Keep it in sync with how you start
|
||||
- The plugin's MCP server URL lives in `integrations/claude_code/reme/.mcp.json`. Keep it in sync with how you start
|
||||
ReMe (host/port). The Stop hook reads this same file to find the server (override with `REME_HOST`
|
||||
/ `REME_PORT` env vars).
|
||||
- The Stop hook needs `python3` on `PATH` and resolves transcripts under `~/.claude/projects`
|
||||
(override the base with `CLAUDE_CONFIG_DIR`). It logs to `plugins/claude_code/reme/logs/auto_memory_hook.log`.
|
||||
(override the base with `CLAUDE_CONFIG_DIR`). It logs to `integrations/claude_code/reme/logs/auto_memory_hook.log`.
|
||||
- The MCP tool-name prefix (`mcp__reme__…`) may include the server segment depending on your Claude
|
||||
Code version; the skill uses the `mcp__reme__*` wildcard so it works either way.
|
||||
|
|
@ -14,7 +14,7 @@ The recall tools come from the `reme` MCP server (surfaced as `mcp__reme__…`):
|
|||
running:
|
||||
|
||||
```
|
||||
reme start service.backend=mcp service.transport=streamable-http
|
||||
reme start service.backend=http
|
||||
```
|
||||
|
||||
If the tools are missing, that server is not running — tell the user the command above instead of
|
||||
|
|
@ -44,7 +44,7 @@ If nothing useful comes back, say so plainly rather than guessing.
|
|||
To check ReMe is up: call `version` and `health_check`, then summarize the version and the health
|
||||
snapshot (components, workspace). If the `mcp__reme__…` tools are not available at all, the server
|
||||
is not running — tell the user to start it with the command above. The plugin connects at
|
||||
`http://127.0.0.1:2333/mcp`; a different host/port must match the `url` in `plugins/reme/.mcp.json`.
|
||||
`http://127.0.0.1:2333/mcp`; a different host/port must match the `url` in the bundled `.mcp.json`.
|
||||
|
||||
## Workspace model
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ retrieval is enabled. Keep the service running while Hermes is active.
|
|||
Hermes supports installing a plugin from a repository subdirectory:
|
||||
|
||||
```bash
|
||||
hermes plugins install agentscope-ai/ReMe/plugins/hermes_agent
|
||||
hermes plugins install agentscope-ai/ReMe/integrations/hermes_agent
|
||||
hermes memory setup
|
||||
```
|
||||
|
||||
1
packages/reme_ai_studio/.gitignore
vendored
1
packages/reme_ai_studio/.gitignore
vendored
|
|
@ -1 +0,0 @@
|
|||
/LICENSE
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
# ReMe Studio
|
||||
|
||||
English | [简体中文](#简体中文)
|
||||
|
||||
ReMe Studio is the local web workspace for ReMe. It lets you browse and edit user-owned workspace files, explore memory
|
||||
links, and chat with the ReMe Agent without moving durable memory into a separate application database. Search indexes,
|
||||
graphs, and other derived metadata remain rebuildable from the source files.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
Install Studio together with ReMe's optional integrations:
|
||||
|
||||
```bash
|
||||
pip install "reme-ai[core]"
|
||||
```
|
||||
|
||||
For Studio without the other optional integrations, use `pip install "reme-ai[web]"`. The base `reme-ai` package is
|
||||
headless and does not include the frontend assets.
|
||||
|
||||
## Features
|
||||
|
||||
- **Workspace browsing**: browse the full workspace or focus on journal and knowledge files through dedicated views. The
|
||||
navigator refreshes as files change on disk.
|
||||
- **Markdown editing and preview**: open multiple files in tabs, render Markdown front matter and GitHub Flavored
|
||||
Markdown, edit with Monaco, save with optimistic modification-time checks, and download files locally.
|
||||
- **Memory graph**: inspect indexed wikilinks under the `wiki`, `personal`, and
|
||||
`procedure` knowledge roots, follow inbound and outbound links, and open the corresponding Markdown source.
|
||||
- **Agent chat**: stream conversations with the read-only workspace Agent, see tool calls and token usage, and drag
|
||||
workspace files into the conversation as references.
|
||||
- **Service management**: inspect service and component memory usage, review the effective redacted configuration and
|
||||
version, and rebuild derived indexes without modifying source memory files.
|
||||
- **Personalization**: switch between English and Chinese, and use light, dark, or system appearance.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.11 or newer with ReMe installed.
|
||||
- A running ReMe HTTP service. Agent chat additionally requires a working Agent and model configuration.
|
||||
- Node.js 22.13 or newer is required only when developing or building Studio from source.
|
||||
|
||||
See the [repository README](https://github.com/agentscope-ai/ReMe#readme) for ReMe installation and backend configuration.
|
||||
|
||||
## Development
|
||||
|
||||
Start ReMe from the repository root, then run the frontend in another terminal:
|
||||
|
||||
```bash
|
||||
# Terminal 1, from the repository root
|
||||
reme start
|
||||
|
||||
# Terminal 2
|
||||
cd website
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open <http://localhost:3000>. The frontend connects to
|
||||
`http://127.0.0.1:2333` by default. Override it when needed:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_REME_API_URL=http://127.0.0.1:8000 npm run dev
|
||||
```
|
||||
|
||||
## ReMe-hosted static build
|
||||
|
||||
ReMe can serve Studio from the same FastAPI process as its HTTP API. Build the static variant and restart ReMe:
|
||||
|
||||
```bash
|
||||
cd website
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
reme start
|
||||
```
|
||||
|
||||
Open <http://127.0.0.1:2333>. The static build uses same-origin requests by default. For standalone static development,
|
||||
run `npm run dev:static` and set
|
||||
`VITE_REME_API_URL` to the running ReMe service URL when necessary.
|
||||
|
||||
The regular `npm run build` command remains the vinext/Sites deployment build;
|
||||
`npm run build:static` creates `dist-static/` exclusively for FastAPI and Python package distribution.
|
||||
|
||||
## Configuration
|
||||
|
||||
The workspace hides dotfiles and dot-directories. It displays only Markdown and text files by default. Configure the
|
||||
allowed extensions as a comma-separated list in `.env.local`:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_REME_WORKSPACE_EXTENSIONS=md,txt,mdx
|
||||
```
|
||||
|
||||
The memory graph requires an index built by ReMe. Rebuilding the index from the Studio settings regenerates derived data
|
||||
from workspace files and does not modify the source memory.
|
||||
|
||||
## Checks
|
||||
|
||||
```bash
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm run build
|
||||
npm run build:static
|
||||
npm test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 简体中文
|
||||
|
||||
[English](#reme-studio) | 简体中文
|
||||
|
||||
ReMe Studio 是 ReMe 的本地 Web 工作区。你可以在这里浏览和编辑自己拥有的工作区文件、探索记忆之间的联系,并与 ReMe Agent
|
||||
对话,而无需将持久记忆迁移到独立的应用数据库中。搜索索引、图谱和其他派生元数据均可根据源文件重建。
|
||||
|
||||

|
||||
|
||||
## 安装
|
||||
|
||||
安装 Studio 和 ReMe 的可选集成功能:
|
||||
|
||||
```bash
|
||||
pip install "reme-ai[core]"
|
||||
```
|
||||
|
||||
如果只需要 Studio,不需要其他可选集成,可以使用 `pip install "reme-ai[web]"`。基础 `reme-ai` 包以无界面模式分发,
|
||||
不包含前端资源。
|
||||
|
||||
## 功能
|
||||
|
||||
- **浏览工作区**:浏览完整工作区,或通过独立视图聚焦日记和知识文件;磁盘中的文件发生变化后,导航器会自动刷新。
|
||||
- **Markdown 编辑与预览**:在多个标签页中打开文件,渲染 Markdown front matter 和 GitHub Flavored Markdown,使用 Monaco
|
||||
编辑器编辑,通过修改时间检查安全保存,并可将文件下载到本地。
|
||||
- **记忆图谱**:查看知识库 `wiki`、`personal` 和 `procedure` 目录中已索引的 wikilink,检查入链和出链,并从图谱打开对应的
|
||||
Markdown 源文件。
|
||||
- **Agent 对话**:与只读工作区 Agent 进行流式对话,查看工具调用和 token 用量,还可将工作区文件拖入对话作为引用。
|
||||
- **服务管理**:查看服务及组件的内存使用情况、当前生效的脱敏配置和版本,并在不修改记忆源文件的情况下重建派生索引。
|
||||
- **个性化设置**:切换中英文界面,并使用浅色、深色或跟随系统的外观。
|
||||
|
||||
## 环境要求
|
||||
|
||||
- Python 3.11 或更高版本,并已安装 ReMe。
|
||||
- 正在运行的 ReMe HTTP 服务。Agent 对话还需要可用的 Agent 和模型配置。
|
||||
- 只有从源码开发或构建 Studio 时才需要 Node.js 22.13 或更高版本。
|
||||
|
||||
ReMe 的安装和后端配置请参阅[仓库中文 README](https://github.com/agentscope-ai/ReMe/blob/main/README_ZH.md)。
|
||||
|
||||
## 本地开发
|
||||
|
||||
先在仓库根目录启动 ReMe,然后在另一个终端运行前端:
|
||||
|
||||
```bash
|
||||
# 终端 1:仓库根目录
|
||||
reme start
|
||||
|
||||
# 终端 2
|
||||
cd website
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
打开 <http://localhost:3000>。前端默认连接 `http://127.0.0.1:2333`,需要时可覆盖该地址:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_REME_API_URL=http://127.0.0.1:8000 npm run dev
|
||||
```
|
||||
|
||||
## 由 ReMe 托管的静态构建
|
||||
|
||||
ReMe 可以通过提供 HTTP API 的同一个 FastAPI 进程托管 Studio。构建静态版本并重启 ReMe:
|
||||
|
||||
```bash
|
||||
cd website
|
||||
npm ci
|
||||
npm run build:static
|
||||
cd ..
|
||||
reme start
|
||||
```
|
||||
|
||||
打开 <http://127.0.0.1:2333>。静态构建默认使用同源请求。进行独立的静态开发时,运行
|
||||
`npm run dev:static`;如有需要,将 `VITE_REME_API_URL` 设置为正在运行的 ReMe 服务地址。
|
||||
|
||||
常规的 `npm run build` 命令仍用于 vinext/Sites 部署构建;`npm run build:static` 仅为 FastAPI 和 Python 包分发生成
|
||||
`dist-static/`。
|
||||
|
||||
## 配置
|
||||
|
||||
工作区会隐藏点文件和点目录,并且默认只显示 Markdown 和文本文件。可以在 `.env.local` 中通过逗号分隔的列表配置允许显示的扩展名:
|
||||
|
||||
```bash
|
||||
NEXT_PUBLIC_REME_WORKSPACE_EXTENSIONS=md,txt,mdx
|
||||
```
|
||||
|
||||
记忆图谱依赖 ReMe 构建的索引。在 Studio 设置中重建索引时,只会根据工作区文件重新生成派生数据,不会修改记忆源文件。
|
||||
|
||||
## 检查
|
||||
|
||||
```bash
|
||||
npm run format:check
|
||||
npm run lint
|
||||
npm run build
|
||||
npm run build:static
|
||||
npm test
|
||||
```
|
||||
201
plugins/auto-fin/LICENSE
Normal file
201
plugins/auto-fin/LICENSE
Normal 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.
|
||||
|
|
@ -1,36 +1,65 @@
|
|||
# Auto Fin Cookbook
|
||||
# Auto Fin Plugin
|
||||
|
||||
[中文](README_ZH.md)
|
||||
|
||||
Auto Fin fetches a rolling window of CLS telegraph news (24 hours by default), selects items related to configured
|
||||
topics, searches ReMe for useful historical context, and writes one Chinese Markdown report with validated wikilinks.
|
||||
Current news and topic selection stay in runtime memory; only the final report becomes durable memory. The
|
||||
implementation lives in [`reme/steps/cookbook/auto_fin/`](../../reme/steps/cookbook/auto_fin/) and is assembled by
|
||||
[`daily_cookbook.yaml`](../../reme/config/daily_cookbook.yaml).
|
||||
Current news and topic selection stay in runtime memory; only the final report becomes durable memory. This directory
|
||||
is an independent Python distribution. Its single `reme.plugins` entry point exposes a `plugin.yaml` containing the
|
||||
three Step backends and their Job configuration under `application_defaults`. Enable the installed plugin explicitly
|
||||
through `plugins=["auto-fin"]`.
|
||||
|
||||
> Auto Fin has no reliable market-price feed. It does not calculate returns, targets, or entry points and is not
|
||||
> investment advice.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[core]"
|
||||
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=auto_fin
|
||||
```
|
||||
|
||||
`LLM_MODEL_NAME` defaults to `qwen3.7-plus`. There is no built-in `LLM_BASE_URL`, so set the OpenAI-compatible endpoint
|
||||
required by the selected provider.
|
||||
|
||||
The default topics are `黄金,机器人,半导体`. Override them per run:
|
||||
### 1. Install ReMe and Auto Fin
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook job=auto_fin topics="黄金,AI,存储芯片"
|
||||
python -m pip install "reme-ai[core]>=0.4.1.9"
|
||||
reme plugins install reme-auto-fin
|
||||
```
|
||||
|
||||
An empty value also uses the defaults.
|
||||
### 2. Configure the model environment
|
||||
|
||||
Configure the LLM environment variables as described in the
|
||||
[ReMe model-configuration guide](../../README.md#optional-model-configuration). Other compatible models and providers
|
||||
can also be used.
|
||||
|
||||
### 3. Start ReMe with the plugin
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
With no explicit `config`, ReMe loads `default.yaml` and adds the plugin to that service.
|
||||
|
||||
From another terminal, call the running HTTP service through ReMe's CLI client:
|
||||
|
||||
```bash
|
||||
reme auto_fin topics="黄金,AI,存储芯片"
|
||||
```
|
||||
|
||||
Or call its HTTP endpoint directly:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/auto_fin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"黄金,AI,存储芯片"}'
|
||||
```
|
||||
|
||||
The HTTP service also exposes the same Job as the `auto_fin` MCP tool at `/mcp`. The default topics are
|
||||
`黄金,机器人,半导体`; an empty value also uses these defaults.
|
||||
|
||||
To host the application with both JSON and MCP access:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]' \
|
||||
service.backend=http
|
||||
```
|
||||
|
||||
Custom application configs must provide `agent_wrapper.default` and the `search` and `read` Jobs used by Auto Fin.
|
||||
|
||||
## Pipeline
|
||||
|
||||
|
|
@ -41,7 +70,7 @@ normalize and deduplicate in RuntimeContext
|
|||
↓
|
||||
topic Agent selects real news IDs in bounded batches
|
||||
↓
|
||||
research Agent uses memory_search + read on historical memory
|
||||
research Agent uses search + read on historical memory
|
||||
↓
|
||||
validate historical wikilinks in code
|
||||
↓
|
||||
|
|
@ -56,8 +85,8 @@ records outside the window are discarded.
|
|||
IDs and deduplicates repeated IDs, then preserves the source-news order. If nothing is relevant, the job succeeds as a
|
||||
skip without writing or sending a report.
|
||||
|
||||
`auto_fin_merge_step` receives only selected current news. It exposes `memory_search` and `read`, instructs the Agent to
|
||||
search no later than yesterday, and keeps current CLS IDs, times, and titles as plain evidence. The prompt limits
|
||||
`auto_fin_merge_step` receives only selected current news. It exposes `search` and `read`, and keeps current CLS IDs,
|
||||
times, and titles as plain evidence. The prompt limits
|
||||
wikilinks to historical Markdown actually used by the Agent; the code-level boundary independently keeps only existing,
|
||||
workspace-relative Markdown targets. Missing, absolute, escaping, backslash, and self-referential targets are degraded
|
||||
to their readable aliases.
|
||||
|
|
@ -76,23 +105,22 @@ refreshes the daily index. No JSONL, intermediate Markdown, or structured Agent
|
|||
| `request_interval` | `10` | Minimum delay in seconds after every CLS request attempt; may be zero |
|
||||
| `max_retries` | `3` | Maximum attempts for each CLS page request; must be at least one |
|
||||
|
||||
The built-in schedules run daily at 09:30, 11:30, and 18:00 in `Asia/Shanghai`.
|
||||
The plugin cron Job starts with the application and runs daily at 18:00 in the application timezone.
|
||||
|
||||
## Output
|
||||
|
||||
```text
|
||||
reme_workspace/daily/YYYY-MM-DD/auto_fin.md
|
||||
.reme/daily/YYYY-MM-DD/auto_fin.md
|
||||
```
|
||||
|
||||
The report includes a title, description, current CLS evidence, historical analysis, contextual wikilinks, and a fixed
|
||||
non-investment disclaimer. Network errors and invalid Agent output fail explicitly; no relevant current news is a
|
||||
successful skip. If `DINGTALK_CONVERSATION_IDS` is empty, delivery is a no-op. If it is set, the DingTalk credentials
|
||||
described in the [Daily Paper cookbook](../daily_paper/README.md#6-dingtalk) are required.
|
||||
successful skip.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
pytest tests/unit/test_auto_fin.py -v
|
||||
python -m pytest plugins/auto-fin -v
|
||||
```
|
||||
|
||||
Unit tests mock the CLS and Agent boundaries and do not contact external services.
|
||||
|
|
@ -1,33 +1,60 @@
|
|||
# Auto Fin Cookbook
|
||||
# Auto Fin 插件
|
||||
|
||||
[English](README.md)
|
||||
|
||||
Auto Fin 自动拉取一个滚动时间窗口内的财联社电报(默认 24 小时),按配置 topics 筛选相关新闻,搜索 ReMe 中有回顾价值的历史材料,最后写入一份带校验
|
||||
wikilink 的中文 Markdown 报告。当前新闻和筛选结果只存在于本次运行内存中,只有最终报告成为持久记忆。实现位于
|
||||
[`reme/steps/cookbook/auto_fin/`](../../reme/steps/cookbook/auto_fin/),并由
|
||||
[`daily_cookbook.yaml`](../../reme/config/daily_cookbook.yaml) 装配。
|
||||
wikilink 的中文 Markdown 报告。当前新闻和筛选结果只存在于本次运行内存中,只有最终报告成为持久记忆。本目录是一个独立 Python
|
||||
distribution:单个 `reme.plugins` entry point 暴露 `plugin.yaml`,其中声明三个 Step backend,并在
|
||||
`application_defaults` 下提供 Job 配置;通过 `plugins=["auto-fin"]` 显式启用这个已安装插件。
|
||||
|
||||
> Auto Fin 没有可靠行情数据,不计算收益、目标价或买卖点,也不提供投资建议。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[core]"
|
||||
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=auto_fin
|
||||
```
|
||||
|
||||
`LLM_MODEL_NAME` 默认是 `qwen3.7-plus`。代码没有内置 `LLM_BASE_URL`,请设置所选服务商提供的 OpenAI 兼容 endpoint。
|
||||
|
||||
默认 topics 是 `黄金,机器人,半导体`。可在运行时覆盖:
|
||||
### 1. 安装 ReMe 和 Auto Fin
|
||||
|
||||
```bash
|
||||
reme start config=daily_cookbook job=auto_fin topics="黄金,AI,存储芯片"
|
||||
python -m pip install "reme-ai[core]>=0.4.1.9"
|
||||
reme plugins install reme-auto-fin
|
||||
```
|
||||
|
||||
传入空值也会使用默认 topics。
|
||||
### 2. 配置模型环境变量
|
||||
|
||||
按照 ReMe README 的[可选模型配置说明](../../README_ZH.md#可选模型配置)配置 LLM 环境变量,也可以使用其他兼容的模型和服务商。
|
||||
|
||||
### 3. 带插件启动 ReMe
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
未显式传入 `config` 时,ReMe 会加载 `default.yaml`,并将插件叠加到该服务上。
|
||||
|
||||
在另一个终端中,通过 ReMe CLI client 调用正在运行的 HTTP 服务:
|
||||
|
||||
```bash
|
||||
reme auto_fin topics="黄金,AI,存储芯片"
|
||||
```
|
||||
|
||||
也可以直接调用 HTTP endpoint:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/auto_fin \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"黄金,AI,存储芯片"}'
|
||||
```
|
||||
|
||||
HTTP service 也会在 `/mcp` 中将同一个 Job 暴露为 `auto_fin` MCP tool。默认 topics 是 `黄金,机器人,半导体`,
|
||||
传入空值也会使用默认值。
|
||||
|
||||
如果需要同时通过 JSON 和 MCP 访问同一个应用:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]' \
|
||||
service.backend=http
|
||||
```
|
||||
|
||||
自定义应用配置需要提供 `agent_wrapper.default`,以及 Auto Fin 使用的 `search` 和 `read` Jobs。
|
||||
|
||||
## 流程
|
||||
|
||||
|
|
@ -38,7 +65,7 @@ reme start config=daily_cookbook job=auto_fin topics="黄金,AI,存储芯片"
|
|||
↓
|
||||
Topic Agent 分批选择真实 news_id
|
||||
↓
|
||||
Research Agent 使用 memory_search + read 检索历史记忆
|
||||
Research Agent 使用 search + read 检索历史记忆
|
||||
↓
|
||||
代码校验历史 wikilink
|
||||
↓
|
||||
|
|
@ -51,8 +78,8 @@ daily/YYYY-MM-DD/auto_fin.md
|
|||
`auto_fin_topic_step` 分批接收当前新闻,只返回相关的 `news_id`。代码会忽略未知 ID、去除重复 ID,并保持源新闻顺序。如果没有相关新闻,Job
|
||||
会成功跳过,不写报告也不发送通知。
|
||||
|
||||
`auto_fin_merge_step` 只接收筛选后的当前新闻,并向 Agent 开放 `memory_search` 和 `read`。历史检索截止到昨天;当前新闻以
|
||||
CLS ID、时间和标题作为普通证据。Prompt 要求 Agent 只链接实际使用过的历史 Markdown;代码边界则独立保证只保留真实存在、相对
|
||||
`auto_fin_merge_step` 只接收筛选后的当前新闻,并向 Agent 开放 `search` 和 `read`。当前新闻以 CLS ID、时间和标题作为普通证据。
|
||||
Prompt 要求 Agent 只链接实际使用过的历史 Markdown;代码边界则独立保证只保留真实存在、相对
|
||||
workspace 的 Markdown 目标。不存在、绝对路径、越界、带反斜杠和自引用的目标都会降级为可读 alias。
|
||||
|
||||
同日重跑会参考当天已有报告并覆盖为修订结果。最终写入使用原子替换并刷新当天索引;流程不会写入 JSONL、中间 Markdown 或 Agent
|
||||
|
|
@ -69,22 +96,21 @@ workspace 的 Markdown 目标。不存在、绝对路径、越界、带反斜杠
|
|||
| `request_interval` | `10` | 每次财联社请求尝试后的最小等待秒数,可设为 0 |
|
||||
| `max_retries` | `3` | 每页财联社请求的最大尝试次数,至少为 1 |
|
||||
|
||||
内置定时任务每天按 `Asia/Shanghai` 在 09:30、11:30 和 18:00 运行。
|
||||
插件的 cron Job 随应用启动,并按应用配置的时区在每天 18:00 运行。
|
||||
|
||||
## 产物
|
||||
|
||||
```text
|
||||
reme_workspace/daily/YYYY-MM-DD/auto_fin.md
|
||||
.reme/daily/YYYY-MM-DD/auto_fin.md
|
||||
```
|
||||
|
||||
报告包含标题、说明、当前 CLS 证据、历史分析、上下文 wikilink 和固定非投资建议声明。网络错误与无效 Agent 输出
|
||||
会明确失败;没有相关当前新闻则成功跳过。`DINGTALK_CONVERSATION_IDS` 为空时发送步骤无副作用;设置该变量后,
|
||||
还必须提供[每日论文 Cookbook](../daily_paper/README_ZH.md#6-dingtalk)中列出的钉钉凭据。
|
||||
会明确失败;没有相关当前新闻则成功跳过。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
pytest tests/unit/test_auto_fin.py -v
|
||||
python -m pytest plugins/auto-fin -v
|
||||
```
|
||||
|
||||
单元测试 mock CLS 与 Agent 边界,不访问外部服务。
|
||||
31
plugins/auto-fin/pyproject.toml
Normal file
31
plugins/auto-fin/pyproject.toml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
[project]
|
||||
name = "reme-auto-fin"
|
||||
version = "0.1.2"
|
||||
description = "Auto Fin example plugin for ReMe."
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE"]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"reme-ai>=0.4.1.9",
|
||||
]
|
||||
|
||||
[project.entry-points."reme.plugins"]
|
||||
auto-fin = "reme_auto_fin"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = { "" = "src" }
|
||||
packages = ["reme_auto_fin"]
|
||||
include-package-data = true
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
reme_auto_fin = ["*.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"
|
||||
|
|
@ -2,10 +2,13 @@
|
|||
|
||||
from .data import AutoFinDataStep
|
||||
from .merge import AutoFinMergeStep
|
||||
from .schema import AutoFinReportOutput, AutoFinTopicOutput
|
||||
from .topic import AutoFinTopicStep
|
||||
|
||||
__all__ = [
|
||||
"AutoFinReportOutput",
|
||||
"AutoFinDataStep",
|
||||
"AutoFinMergeStep",
|
||||
"AutoFinTopicOutput",
|
||||
"AutoFinTopicStep",
|
||||
]
|
||||
|
|
@ -12,7 +12,7 @@ from uuid import uuid4
|
|||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ...base_step import BaseStep
|
||||
from reme.steps import BaseStep
|
||||
|
||||
AGENT_INPUT_LOG_LIMIT = 2000
|
||||
AGENT_OUTPUT_LOG_LIMIT = 4000
|
||||
|
|
@ -76,6 +76,7 @@ class AutoFinStep(BaseStep):
|
|||
prompt_name: str,
|
||||
model: type[BaseModel],
|
||||
job_tools: list[str] | None = None,
|
||||
injected_job_kwargs: dict[str, Any] | None = None,
|
||||
**values: str,
|
||||
) -> BaseModel:
|
||||
if self.agent_wrapper is None:
|
||||
|
|
@ -89,6 +90,8 @@ class AutoFinStep(BaseStep):
|
|||
kwargs: dict[str, Any] = {"output_schema": model}
|
||||
if job_tools:
|
||||
kwargs["job_tools"] = job_tools
|
||||
if injected_job_kwargs:
|
||||
kwargs["injected_job_kwargs"] = injected_job_kwargs
|
||||
result = await self.agent_wrapper.reply(prompt, **kwargs)
|
||||
if not isinstance(result, dict) or result.get("structured_output") is None:
|
||||
raise ValueError(f"Auto Fin Agent returned no structured output: {self._preview(result)}")
|
||||
|
|
@ -9,8 +9,7 @@ from typing import Any
|
|||
|
||||
import httpx
|
||||
|
||||
from ....components import R
|
||||
from ._base import AutoFinStep, _plain_text
|
||||
from .base import AutoFinStep, _plain_text
|
||||
|
||||
API_URL = "https://www.cls.cn/v1/roll/get_roll_list"
|
||||
HEADERS = {
|
||||
|
|
@ -23,7 +22,6 @@ DEFAULT_TOPICS = ("黄金", "机器人", "半导体")
|
|||
WINDOW = timedelta(hours=24)
|
||||
|
||||
|
||||
@R.register("auto_fin_data_step")
|
||||
class AutoFinDataStep(AutoFinStep):
|
||||
"""Fetch and normalize one rolling day of CLS news without writing files."""
|
||||
|
||||
|
|
@ -8,10 +8,10 @@ from datetime import date, timedelta
|
|||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
|
||||
from ....components import R
|
||||
from ....schema import AutoFinReportOutput
|
||||
from ...file_io import refresh_day_index
|
||||
from ._base import AutoFinStep, _write
|
||||
from reme.steps.file_io import refresh_day_index
|
||||
|
||||
from .base import AutoFinStep, _write
|
||||
from .schema import AutoFinReportOutput
|
||||
|
||||
_WIKILINK_RE = re.compile(r"\[\[([^\[\]\n]+)\]\]")
|
||||
_HYBRID_WIKILINK_RE = re.compile(
|
||||
|
|
@ -19,7 +19,6 @@ _HYBRID_WIKILINK_RE = re.compile(
|
|||
)
|
||||
|
||||
|
||||
@R.register("auto_fin_merge_step")
|
||||
class AutoFinMergeStep(AutoFinStep):
|
||||
"""Give one Agent read-only ReMe tools, then validate links in its Markdown."""
|
||||
|
||||
|
|
@ -103,17 +102,24 @@ class AutoFinMergeStep(AutoFinStep):
|
|||
)
|
||||
|
||||
async def execute(self):
|
||||
"""Research the selected news and persist the validated report."""
|
||||
assert self.context is not None
|
||||
if self.context.get("auto_fin_skipped"):
|
||||
return self.context.response
|
||||
run_date = date.fromisoformat(str(self._required("auto_fin_date")))
|
||||
historical_search = {
|
||||
"limit": 5,
|
||||
"min_score": 0.0,
|
||||
"start_date": None,
|
||||
"end_date": (run_date - timedelta(days=1)).isoformat(),
|
||||
}
|
||||
output = await self._reply(
|
||||
"merge_user",
|
||||
AutoFinReportOutput,
|
||||
job_tools=list(self.kwargs.get("job_tools") or []),
|
||||
injected_job_kwargs=historical_search,
|
||||
decision_at=str(self._required("auto_fin_decision_at")),
|
||||
window_start=str(self._required("auto_fin_window_start")),
|
||||
historical_end=(run_date - timedelta(days=1)).isoformat(),
|
||||
topics=json.dumps(self._required("auto_fin_topics"), ensure_ascii=False),
|
||||
news=json.dumps(self._required("auto_fin_selected_news"), ensure_ascii=False),
|
||||
current_report=self._current_report(run_date),
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
merge_user: |
|
||||
你是主题新闻研究 Agent。当前新闻已经按 topics 做过语义筛选。你可以使用 `memory_search` 搜索历史记忆,
|
||||
你是主题新闻研究 Agent。当前新闻已经按 topics 做过语义筛选。你可以使用 `search` 搜索历史记忆,
|
||||
并使用 `read` 阅读可能相关的完整 Markdown。不得使用外部搜索,不得虚构行情、收益、价格或未提供的数据。
|
||||
|
||||
研究窗口:{window_start} 至 {decision_at}
|
||||
|
|
@ -8,7 +8,7 @@ merge_user: |
|
|||
今天早些时段的报告(如有,请保留仍成立的判断,只修订变化部分):
|
||||
{current_report}
|
||||
|
||||
先围绕 topics 和当前重要事件多次调用 `memory_search`,并将 end_date 设为 {historical_end},避免召回今天的旧报告。
|
||||
先围绕 topics 和当前重要事件多次调用 `search` 检索历史记忆。
|
||||
只对明显相关的结果调用 `read`。说明历史事件与当前事件的相同点、关键差异,以及旧判断是否仍适用。
|
||||
给出值得回顾的新闻、应继续观察的信息,以及哪些条件会强化或推翻判断,但不要给出投资建议。
|
||||
|
||||
50
plugins/auto-fin/src/reme_auto_fin/plugin.yaml
Normal file
50
plugins/auto-fin/src/reme_auto_fin/plugin.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
backends:
|
||||
auto_fin_data_step: reme_auto_fin.data:AutoFinDataStep
|
||||
auto_fin_topic_step: reme_auto_fin.topic:AutoFinTopicStep
|
||||
auto_fin_merge_step: reme_auto_fin.merge:AutoFinMergeStep
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
auto_fin:
|
||||
backend: base
|
||||
description: "Fetch and research recent topic-related CLS news."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
description: "Current date in YYYY-MM-DD; empty means today in Asia/Shanghai."
|
||||
default: ""
|
||||
now:
|
||||
type: string
|
||||
description: "Optional simulated current time in ISO 8601 format; empty means the real current time."
|
||||
default: ""
|
||||
topics:
|
||||
type: string
|
||||
description: "Comma-separated topics used to filter current CLS news."
|
||||
default: "黄金,机器人,半导体"
|
||||
window_hours:
|
||||
type: number
|
||||
exclusiveMinimum: 0
|
||||
description: "Rolling number of hours of CLS news to fetch."
|
||||
default: 24
|
||||
request_interval:
|
||||
type: number
|
||||
minimum: 0
|
||||
description: "Minimum delay in seconds after each CLS request attempt."
|
||||
default: 10
|
||||
max_retries:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: "Maximum attempts for each CLS page request."
|
||||
default: 3
|
||||
steps: &auto_fin_steps
|
||||
- backend: auto_fin_data_step
|
||||
- backend: auto_fin_topic_step
|
||||
- backend: auto_fin_merge_step
|
||||
job_tools: [search, read]
|
||||
|
||||
auto_fin_cron:
|
||||
backend: cron
|
||||
cron: "0 18 * * *"
|
||||
steps: *auto_fin_steps
|
||||
|
|
@ -4,12 +4,10 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
|
||||
from ....components import R
|
||||
from ....schema import AutoFinTopicOutput
|
||||
from ._base import AutoFinStep
|
||||
from .base import AutoFinStep
|
||||
from .schema import AutoFinTopicOutput
|
||||
|
||||
|
||||
@R.register("auto_fin_topic_step")
|
||||
class AutoFinTopicStep(AutoFinStep):
|
||||
"""Filter current news in bounded Agent batches without writing files."""
|
||||
|
||||
|
|
@ -7,17 +7,21 @@ from pathlib import Path
|
|||
from zoneinfo import ZoneInfo
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from reme_auto_fin.base import _plain_text, _write
|
||||
from reme_auto_fin.data import AutoFinDataStep
|
||||
from reme_auto_fin.merge import AutoFinMergeStep
|
||||
from reme_auto_fin.schema import AutoFinReportOutput, AutoFinTopicOutput
|
||||
from reme_auto_fin.topic import AutoFinTopicStep
|
||||
from reme.components import ApplicationContext
|
||||
from reme.components.agent_wrapper.base_agent_wrapper import BaseAgentWrapper
|
||||
from reme.components.runtime_context import RuntimeContext
|
||||
from reme.schema import AutoFinReportOutput, AutoFinTopicOutput
|
||||
from reme.steps.cookbook.auto_fin._base import _plain_text, _write
|
||||
from reme.steps.cookbook.auto_fin.data import AutoFinDataStep
|
||||
from reme.steps.cookbook.auto_fin.merge import AutoFinMergeStep
|
||||
from reme.steps.cookbook.auto_fin.topic import AutoFinTopicStep
|
||||
|
||||
SHANGHAI = ZoneInfo("Asia/Shanghai")
|
||||
PLUGIN_MANIFEST = yaml.safe_load(
|
||||
(Path(__file__).parents[1] / "src" / "reme_auto_fin" / "plugin.yaml").read_text(encoding="utf-8"),
|
||||
)
|
||||
|
||||
|
||||
def _row(news_id: int, value: datetime, title: str = "新闻", content: str = "正文") -> dict:
|
||||
|
|
@ -28,7 +32,7 @@ def test_atomic_write_preserves_existing_file_on_failure(tmp_path: Path, monkeyp
|
|||
path = tmp_path / "result.md"
|
||||
path.write_text("existing", encoding="utf-8")
|
||||
monkeypatch.setattr(
|
||||
"reme.steps.cookbook.auto_fin._base.os.replace",
|
||||
"reme_auto_fin.base.os.replace",
|
||||
lambda *_args: (_ for _ in ()).throw(OSError()),
|
||||
)
|
||||
|
||||
|
|
@ -191,14 +195,23 @@ async def test_merge_writes_only_final_report_and_validates_historical_links(tmp
|
|||
response = await AutoFinMergeStep(
|
||||
app_context=app_context,
|
||||
agent_wrapper=agent,
|
||||
job_tools=["memory_search", "read"],
|
||||
job_tools=["search", "read"],
|
||||
)(context)
|
||||
|
||||
prompt, kwargs = agent.calls[0]
|
||||
assert "end_date 设为 2026-08-09" in prompt
|
||||
assert "调用 `memory_search`" in prompt
|
||||
assert "end_date" not in prompt
|
||||
assert "调用 `search`" in prompt
|
||||
assert "调用 `read`" in prompt
|
||||
assert kwargs == {"output_schema": AutoFinReportOutput, "job_tools": ["memory_search", "read"]}
|
||||
assert kwargs == {
|
||||
"output_schema": AutoFinReportOutput,
|
||||
"job_tools": ["search", "read"],
|
||||
"injected_job_kwargs": {
|
||||
"limit": 5,
|
||||
"min_score": 0.0,
|
||||
"start_date": None,
|
||||
"end_date": "2026-08-09",
|
||||
},
|
||||
}
|
||||
report = (tmp_path / "daily" / "2026-08-10" / "auto_fin.md").read_text(encoding="utf-8")
|
||||
assert "[[daily/2026-08-01/auto_fin.md|历史黄金观察]]" in report
|
||||
assert "](daily/2026-08-01/auto_fin.md)" not in report
|
||||
|
|
@ -210,7 +223,7 @@ async def test_merge_writes_only_final_report_and_validates_historical_links(tmp
|
|||
|
||||
|
||||
def test_hybrid_wikilink_normalization_is_conservative_and_failure_safe(tmp_path: Path, monkeypatch):
|
||||
import reme.steps.cookbook.auto_fin.merge as merge_module
|
||||
import reme_auto_fin.merge as merge_module
|
||||
|
||||
step = AutoFinMergeStep(
|
||||
app_context=ApplicationContext(workspace_dir=str(tmp_path), timezone="Asia/Shanghai"),
|
||||
|
|
@ -237,11 +250,9 @@ def test_hybrid_wikilink_normalization_is_conservative_and_failure_safe(tmp_path
|
|||
assert step._normalize_hybrid_wikilinks(body) == body
|
||||
|
||||
|
||||
def test_config_has_default_topics_and_no_intermediate_index_step():
|
||||
from reme.config.config_parser import _load_config
|
||||
|
||||
config = _load_config("daily_cookbook")
|
||||
job = config["jobs"]["auto_fin"]
|
||||
def test_plugin_config_has_default_topics_and_no_intermediate_index_step():
|
||||
jobs = PLUGIN_MANIFEST["application_defaults"]["jobs"]
|
||||
job = jobs["auto_fin"]
|
||||
assert job["parameters"]["properties"]["topics"]["default"] == "黄金,机器人,半导体"
|
||||
assert job["parameters"]["properties"]["window_hours"]["default"] == 24
|
||||
assert job["parameters"]["properties"]["request_interval"]["default"] == 10
|
||||
|
|
@ -251,16 +262,18 @@ def test_config_has_default_topics_and_no_intermediate_index_step():
|
|||
"auto_fin_data_step",
|
||||
"auto_fin_topic_step",
|
||||
"auto_fin_merge_step",
|
||||
"dingtalk_markdown_send_step",
|
||||
]
|
||||
assert job["steps"][2]["job_tools"] == ["memory_search", "read"]
|
||||
for name, schedule in {
|
||||
"auto_fin_0930_cron": "30 9 * * *",
|
||||
"auto_fin_1130_cron": "30 11 * * *",
|
||||
"auto_fin_1800_cron": "0 18 * * *",
|
||||
}.items():
|
||||
assert config["jobs"][name]["cron"] == schedule
|
||||
assert config["jobs"][name]["steps"] == job["steps"]
|
||||
assert job["steps"][2]["job_tools"] == ["search", "read"]
|
||||
assert jobs["auto_fin_cron"]["cron"] == "0 18 * * *"
|
||||
assert jobs["auto_fin_cron"]["steps"] == job["steps"]
|
||||
assert (
|
||||
not {
|
||||
"auto_fin_0930_cron",
|
||||
"auto_fin_1130_cron",
|
||||
"auto_fin_1800_cron",
|
||||
}
|
||||
& jobs.keys()
|
||||
)
|
||||
|
||||
|
||||
def test_agent_schemas_are_small_and_required():
|
||||
201
plugins/daily_paper/LICENSE
Normal file
201
plugins/daily_paper/LICENSE
Normal 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.
|
||||
137
plugins/daily_paper/README.md
Normal file
137
plugins/daily_paper/README.md
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
# Daily Paper Plugin
|
||||
|
||||
[中文](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. This directory is an independent
|
||||
Python distribution. Its single `reme.plugins` entry point exposes a `plugin.yaml` containing five Step backends and
|
||||
their Job configuration under `application_defaults`. Enable the installed plugin explicitly through
|
||||
`plugins=["daily-paper"]`.
|
||||
|
||||
## Quick start
|
||||
|
||||
### 1. Install ReMe and Daily Paper
|
||||
|
||||
```bash
|
||||
python -m pip install "reme-ai[core]>=0.4.1.9"
|
||||
reme plugins install reme-daily-paper
|
||||
```
|
||||
|
||||
### 2. Configure the model environment
|
||||
|
||||
Configure the LLM environment variables as described in the
|
||||
[ReMe model-configuration guide](../../README.md#optional-model-configuration). Other compatible models and providers
|
||||
can also be used. The workflow also requires network access to Hugging Face Papers and arXiv.
|
||||
|
||||
### 3. Start ReMe with the plugin
|
||||
|
||||
```bash
|
||||
reme start plugins='["daily-paper"]'
|
||||
```
|
||||
|
||||
With no explicit `config`, ReMe loads `default.yaml` and adds the plugin to that service. The plugin starts
|
||||
`daily_paper_cron`, which runs daily at 08:00. From another terminal, generate a brief manually through ReMe's CLI
|
||||
client:
|
||||
|
||||
```bash
|
||||
reme daily_paper topics="Agent memory"
|
||||
```
|
||||
|
||||
Or call its HTTP endpoint directly:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/daily_paper \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"Agent memory"}'
|
||||
```
|
||||
|
||||
To run the Job once without starting a long-lived service:
|
||||
|
||||
```bash
|
||||
reme start plugins='["daily-paper"]' job=daily_paper topics="Agent memory"
|
||||
```
|
||||
|
||||
## Pipeline
|
||||
|
||||
```text
|
||||
Hugging Face weekly/monthly rankings
|
||||
↓
|
||||
merge ranks and exclude yesterday's and recently recommended papers
|
||||
↓
|
||||
rank with RRF and let an Agent select three papers
|
||||
↓
|
||||
download and parse arXiv PDFs, then write three Chinese analyses
|
||||
↓
|
||||
use search + read to connect prior memory and generate a brief
|
||||
↓
|
||||
refresh the daily index and optionally send the brief to DingTalk
|
||||
```
|
||||
|
||||
`daily_paper_collect_step` concurrently reads the weekly and monthly rankings for the run date plus the strictly
|
||||
preceding day's Daily Papers. It merges candidates by arXiv ID and excludes both yesterday's list and papers recommended
|
||||
within `history_days`.
|
||||
|
||||
`daily_paper_rank_step` combines weekly and monthly positions with reciprocal-rank fusion and retains at most
|
||||
`candidate_limit` papers. `daily_paper_select_step` then asks a tool-free Agent to select three unique candidate IDs.
|
||||
Non-empty `topics` affect selection preference but not the fixed count.
|
||||
|
||||
`daily_paper_analyze_step` downloads PDFs into `resource/papers/`, reuses existing valid files, and extracts text within
|
||||
the configured page, character, and file-size limits. It writes the three Chinese analyses in selection order. Scanned
|
||||
PDFs and files without a text layer fail explicitly.
|
||||
|
||||
`daily_paper_digest_step` treats those three analyses as the factual source and receives only the read-only
|
||||
`search` and `read` tools for linking earlier memory. Code validates historical wikilinks, appends links to all
|
||||
three source notes, and rebuilds the daily index. The optional `dingtalk_markdown_send_step` sends the final brief when
|
||||
conversation IDs are configured and otherwise skips without side effects.
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Default | Purpose |
|
||||
|-----------------|--------:|-----------------------------------------------------------------------------------|
|
||||
| `date` | `""` | Empty uses today in the application timezone; otherwise use `YYYY-MM-DD` |
|
||||
| `force` | `false` | Regenerate when that day's final brief already exists |
|
||||
| `use_hf_mirror` | `false` | Use `HF_MIRROR_URL`, or `https://hf-mirror.com` when it is unset |
|
||||
| `topics` | `""` | Optional topics to prioritize during selection |
|
||||
| `weekly_weight` | `0.7` | Weekly contribution in reciprocal-rank fusion |
|
||||
| `history_days` | `30` | Prior recommendation window excluded by arXiv ID |
|
||||
|
||||
Step-level defaults are `candidate_limit=20`, `rrf_k=60`, `hf_timeout=600`, `hf_max_retries=3`, `pdf_timeout=600`,
|
||||
`max_pdf_bytes=52428800`, `max_pdf_pages=35`, and `max_pdf_chars=300000`.
|
||||
|
||||
The data clients automatically honor `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY`. Manual runs enable the Hugging Face
|
||||
mirror with `use_hf_mirror=true`; the cron Job enables it by default and can use the official service with
|
||||
`DAILY_PAPER_USE_HF_MIRROR=false`. These environment variables override data sources and DingTalk settings:
|
||||
|
||||
```dotenv
|
||||
HF_MIRROR_URL=https://hf-mirror.com
|
||||
ARXIV_MIRROR_URL=https://export.arxiv.org
|
||||
DINGTALK_APP_KEY=your-app-key
|
||||
DINGTALK_APP_SECRET=your-app-secret
|
||||
DINGTALK_ROBOT_CODE=your-robot-code
|
||||
DINGTALK_CONVERSATION_IDS=cid-group-one,cid-group-two
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
```text
|
||||
.reme/
|
||||
├── daily/
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <Chinese-paper-title>.md # three, kind: daily-paper-analysis
|
||||
│ └── <Chinese-brief-title>.md # one, kind: daily-paper-brief
|
||||
└── resource/papers/
|
||||
└── <arxiv-id>.pdf
|
||||
```
|
||||
|
||||
Markdown and PDF files are written atomically through temporary files in the same directory. `force=true` regenerates
|
||||
the selected analyses and brief while reusing valid PDFs; it does not delete other notes already present for that day.
|
||||
Network errors, too few candidates, invalid Agent output, and unparseable PDFs fail explicitly.
|
||||
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
python -m pytest plugins/daily_paper -v
|
||||
```
|
||||
|
||||
Unit tests mock the Hugging Face, arXiv, AgentScope, and DingTalk boundaries and do not contact external services.
|
||||
127
plugins/daily_paper/README_ZH.md
Normal file
127
plugins/daily_paper/README_ZH.md
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
# 每日论文插件
|
||||
|
||||
[English](README.md)
|
||||
|
||||
每日论文从 Hugging Face Papers 的周榜和月榜中筛选三篇论文,下载 arXiv PDF,生成中文论文解读和一篇约五分钟可读完的
|
||||
中文简报。本目录是一个独立 Python distribution:单个 `reme.plugins` entry point 暴露 `plugin.yaml`,其中声明五个
|
||||
Step backend,并在 `application_defaults` 下提供 Job 配置;通过 `plugins=["daily-paper"]` 显式启用这个已安装插件。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装 ReMe 和每日论文插件
|
||||
|
||||
```bash
|
||||
python -m pip install "reme-ai[core]>=0.4.1.9"
|
||||
reme plugins install reme-daily-paper
|
||||
```
|
||||
|
||||
### 2. 配置模型环境变量
|
||||
|
||||
按照 ReMe README 的[可选模型配置说明](../../README_ZH.md#可选模型配置)配置 LLM 环境变量,也可以使用其他兼容的模型和
|
||||
服务商。工作流还需要能够访问 Hugging Face Papers 和 arXiv。
|
||||
|
||||
### 3. 带插件启动 ReMe
|
||||
|
||||
```bash
|
||||
reme start plugins='["daily-paper"]'
|
||||
```
|
||||
|
||||
未显式传入 `config` 时,ReMe 会加载 `default.yaml`,并将插件叠加到该服务上。插件随应用启动每天 08:00 运行的
|
||||
`daily_paper_cron`;在另一个终端中,也可以通过 ReMe CLI client 手动生成简报:
|
||||
|
||||
```bash
|
||||
reme daily_paper topics="Agent memory"
|
||||
```
|
||||
|
||||
也可以直接调用 HTTP endpoint:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/daily_paper \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"topics":"Agent memory"}'
|
||||
```
|
||||
|
||||
如果只需运行一次 Job,无需启动长期服务:
|
||||
|
||||
```bash
|
||||
reme start plugins='["daily-paper"]' job=daily_paper topics="Agent memory"
|
||||
```
|
||||
|
||||
## 流程
|
||||
|
||||
```text
|
||||
Hugging Face 周榜/月榜
|
||||
↓
|
||||
合并排名并排除昨日及近期已推荐论文
|
||||
↓
|
||||
RRF 排序后由 Agent 精选三篇
|
||||
↓
|
||||
下载并解析 arXiv PDF,生成三篇中文解读
|
||||
↓
|
||||
使用 search + read 关联历史记忆并生成简报
|
||||
↓
|
||||
写入当日索引,并按需发送到钉钉
|
||||
```
|
||||
|
||||
`daily_paper_collect_step` 并发读取运行日期所在周和所在月的榜单,以及严格前一日的 Daily Papers。候选按 arXiv ID
|
||||
合并,并排除昨日榜单和 `history_days` 窗口内已经推荐的论文。
|
||||
|
||||
`daily_paper_rank_step` 使用 reciprocal-rank fusion 合并周榜和月榜排名,最多保留 `candidate_limit` 篇;
|
||||
`daily_paper_select_step` 再让无工具 Agent 精选三个唯一的候选 ID。非空 `topics` 只影响精选偏好,不改变固定数量。
|
||||
|
||||
`daily_paper_analyze_step` 下载 PDF 到 `resource/papers/`,复用已有的有效文件,并在页数、字符数和文件大小限制内提取
|
||||
文本。三篇中文解读按精选顺序写入当天目录;扫描版或没有文本层的 PDF 会明确失败。
|
||||
|
||||
`daily_paper_digest_step` 以本次生成的三篇解读为事实来源,只开放只读的 `search` 和 `read` 来关联较早记忆。
|
||||
代码会校验历史 wikilink、追加三篇源笔记链接,并重建当日索引。可选的 `dingtalk_markdown_send_step` 在配置群会话后
|
||||
发送最终简报;未配置时无副作用跳过。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 默认值 | 作用 |
|
||||
|-----------------|--------:|----------------------------------------------------------------------------------------------|
|
||||
| `date` | `""` | 运行日期;空值使用应用时区当天,非空值必须为 `YYYY-MM-DD` |
|
||||
| `force` | `false` | 已有当日简报时仍重新生成 |
|
||||
| `use_hf_mirror` | `false` | 使用 `HF_MIRROR_URL`;未配置时使用 `https://hf-mirror.com` |
|
||||
| `topics` | `""` | 精选论文时优先考虑的主题 |
|
||||
| `weekly_weight` | `0.7` | RRF 中周榜权重 |
|
||||
| `history_days` | `30` | 历史推荐排重窗口 |
|
||||
|
||||
步骤级默认值包括:`candidate_limit=20`、`rrf_k=60`、`hf_timeout=600`、`hf_max_retries=3`、
|
||||
`pdf_timeout=600`、`max_pdf_bytes=52428800`、`max_pdf_pages=35` 和 `max_pdf_chars=300000`。
|
||||
|
||||
数据客户端自动使用 `HTTP_PROXY`、`HTTPS_PROXY` 和 `NO_PROXY`。手动任务通过 `use_hf_mirror=true` 启用 Hugging Face
|
||||
镜像;定时任务默认启用,可设置 `DAILY_PAPER_USE_HF_MIRROR=false` 改用官方服务。以下环境变量可覆盖数据源和钉钉配置:
|
||||
|
||||
```dotenv
|
||||
HF_MIRROR_URL=https://hf-mirror.com
|
||||
ARXIV_MIRROR_URL=https://export.arxiv.org
|
||||
DINGTALK_APP_KEY=your-app-key
|
||||
DINGTALK_APP_SECRET=your-app-secret
|
||||
DINGTALK_ROBOT_CODE=your-robot-code
|
||||
DINGTALK_CONVERSATION_IDS=cid-group-one,cid-group-two
|
||||
```
|
||||
|
||||
## 产物
|
||||
|
||||
```text
|
||||
.reme/
|
||||
├── daily/
|
||||
│ ├── YYYY-MM-DD.md
|
||||
│ └── YYYY-MM-DD/
|
||||
│ ├── <中文论文标题>.md # 三篇,kind: daily-paper-analysis
|
||||
│ └── <中文简报标题>.md # 一篇,kind: daily-paper-brief
|
||||
└── resource/papers/
|
||||
└── <arxiv-id>.pdf
|
||||
```
|
||||
|
||||
Markdown 和 PDF 都通过同目录临时文件原子写入。`force=true` 会重新生成本次入选论文的解读和简报,并复用有效 PDF;
|
||||
不会删除当天已有的其他笔记。网络错误、候选不足、无效 Agent 输出和无法解析的 PDF 都会明确失败。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
python -m pytest plugins/daily_paper -v
|
||||
```
|
||||
|
||||
单元测试 mock Hugging Face、arXiv、AgentScope 和钉钉边界,不访问外部服务。
|
||||
32
plugins/daily_paper/pyproject.toml
Normal file
32
plugins/daily_paper/pyproject.toml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
[project]
|
||||
name = "reme-daily-paper"
|
||||
version = "0.1.2"
|
||||
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>=0.4.1.9",
|
||||
]
|
||||
|
||||
[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"
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -4,23 +4,22 @@ 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,
|
||||
normalize_chinese_title,
|
||||
replace_surrogates,
|
||||
resolve_unique_note_path,
|
||||
strip_frontmatter,
|
||||
structured_output,
|
||||
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."""
|
||||
|
||||
|
|
@ -43,7 +42,8 @@ class DailyPaperAnalyzeStep(DailyPaperStep):
|
|||
page_count = min(len(reader.pages), max_pages)
|
||||
truncated = len(reader.pages) > max_pages
|
||||
for page_number, page in enumerate(reader.pages[:page_count], start=1):
|
||||
block = f"\n\n--- PAGE {page_number} ---\n\n{(page.extract_text() or '').strip()}"
|
||||
page_text = replace_surrogates((page.extract_text() or "").strip())
|
||||
block = f"\n\n--- PAGE {page_number} ---\n\n{page_text}"
|
||||
if size + len(block) > max_chars:
|
||||
if (remaining := max_chars - size) > 0:
|
||||
chunks.append(block[:remaining])
|
||||
|
|
@ -126,8 +126,9 @@ class DailyPaperAnalyzeStep(DailyPaperStep):
|
|||
)
|
||||
used_titles.add(title)
|
||||
note_rel = note_path.relative_to(self.workspace_path).as_posix()
|
||||
body = strip_frontmatter(output.body)
|
||||
if not output.desc.strip() or not body:
|
||||
desc = replace_surrogates(output.desc.strip())
|
||||
body = replace_surrogates(strip_frontmatter(output.body))
|
||||
if not desc or not body:
|
||||
raise ValueError(f"Agent returned an empty paper note for {paper.arxiv_id}")
|
||||
await write_markdown(
|
||||
note_path,
|
||||
|
|
@ -135,7 +136,7 @@ class DailyPaperAnalyzeStep(DailyPaperStep):
|
|||
{
|
||||
"name": title,
|
||||
"title": title,
|
||||
"description": output.desc.strip(),
|
||||
"description": desc,
|
||||
"kind": "daily-paper-analysis",
|
||||
"arxiv_id": paper.arxiv_id,
|
||||
"source_title": paper.title,
|
||||
|
|
@ -163,7 +164,7 @@ class DailyPaperAnalyzeStep(DailyPaperStep):
|
|||
arxiv_id=paper.arxiv_id,
|
||||
reasoning=selected.reasoning,
|
||||
title=title,
|
||||
desc=output.desc.strip(),
|
||||
desc=desc,
|
||||
body=body,
|
||||
note_path=note_rel,
|
||||
pdf_path=pdf_rel,
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# Prompt bundled with the Daily Paper plugin distribution.
|
||||
analyze_user: |
|
||||
你是严谨的中文 AI 论文解读作者。请详细解读下面这篇论文。内容只能依据提供的论文元信息和 PDF 提取文本。
|
||||
不得臆测未出现在材料中的实验、数字、结论或引用。重要实验结论和数字尽量标注 PDF 页码,例如 [p. 7]。
|
||||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
@ -22,6 +23,7 @@ _FRONTMATTER_PATTERN = re.compile(r"^---\s*\n.*?\n---\s*\n", re.DOTALL)
|
|||
_MARKDOWN_HEADING_PATTERN = re.compile(r"^#+\s*")
|
||||
_UNSAFE_FILENAME_CHARS = re.compile(r'[<>:"/\\|?*\x00-\x1f]')
|
||||
_CHINESE_PATTERN = re.compile(r"[\u3400-\u4dbf\u4e00-\u9fff]")
|
||||
_SURROGATE_PATTERN = re.compile(r"[\ud800-\udfff]")
|
||||
_OutputT = TypeVar("_OutputT", bound=BaseModel)
|
||||
|
||||
|
||||
|
|
@ -36,9 +38,28 @@ def strip_frontmatter(body: str) -> str:
|
|||
return _FRONTMATTER_PATTERN.sub("", body.strip(), count=1).strip()
|
||||
|
||||
|
||||
def replace_surrogates(value: str) -> str:
|
||||
"""Replace invalid Unicode surrogate code points with replacement characters."""
|
||||
return _SURROGATE_PATTERN.sub("\ufffd", value)
|
||||
|
||||
|
||||
def _replace_surrogates_recursive(value: Any) -> Any:
|
||||
"""Replace surrogates in strings nested in frontmatter metadata."""
|
||||
if isinstance(value, str):
|
||||
return replace_surrogates(value)
|
||||
if isinstance(value, dict):
|
||||
return {_replace_surrogates_recursive(key): _replace_surrogates_recursive(item) for key, item in value.items()}
|
||||
if isinstance(value, list):
|
||||
return [_replace_surrogates_recursive(item) for item in value]
|
||||
if isinstance(value, tuple):
|
||||
return tuple(_replace_surrogates_recursive(item) for item in value)
|
||||
return value
|
||||
|
||||
|
||||
def normalize_chinese_title(raw: str, fallback: str) -> str:
|
||||
"""Return one safe Chinese title that can also be used as the filename stem."""
|
||||
title = _MARKDOWN_HEADING_PATTERN.sub("", str(raw or "").strip())
|
||||
title = replace_surrogates(str(raw or "").strip())
|
||||
title = _MARKDOWN_HEADING_PATTERN.sub("", title)
|
||||
if title.lower().endswith(".md"):
|
||||
title = title[:-3]
|
||||
title = _UNSAFE_FILENAME_CHARS.sub("-", title)
|
||||
|
|
@ -56,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():
|
||||
|
|
@ -92,7 +123,7 @@ async def write_atomic(path: Path, content: str | bytes) -> None:
|
|||
lock = await get_path_lock(path)
|
||||
async with lock:
|
||||
temp_path = path.with_name(f".{path.name}.{uuid4().hex}.tmp")
|
||||
payload = content.encode("utf-8") if isinstance(content, str) else content
|
||||
payload = replace_surrogates(content).encode("utf-8") if isinstance(content, str) else content
|
||||
try:
|
||||
async with aiofiles.open(temp_path, "wb") as stream:
|
||||
await stream.write(payload)
|
||||
|
|
@ -104,7 +135,9 @@ async def write_atomic(path: Path, content: str | bytes) -> None:
|
|||
|
||||
async def write_markdown(path: Path, body: str, metadata: dict[str, Any]) -> None:
|
||||
"""Serialize a frontmatter Markdown document atomically."""
|
||||
rendered = frontmatter.dumps(frontmatter.Post(body.strip(), **metadata))
|
||||
safe_body = replace_surrogates(body).strip()
|
||||
safe_metadata = _replace_surrogates_recursive(metadata)
|
||||
rendered = frontmatter.dumps(frontmatter.Post(safe_body, **safe_metadata))
|
||||
await write_atomic(path, rendered if rendered.endswith("\n") else f"{rendered}\n")
|
||||
|
||||
|
||||
|
|
@ -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."""
|
||||
|
||||
|
|
@ -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."""
|
||||
|
||||
|
|
@ -65,6 +64,7 @@ class DailyPaperDigestStep(DailyPaperStep):
|
|||
return _WIKILINK_RE.sub(replace, body)
|
||||
|
||||
async def execute(self):
|
||||
"""Generate and persist the final brief from analyzed papers."""
|
||||
assert self.context is not None
|
||||
if self._skip():
|
||||
self.logger.info(f"[{self.name}] skip existing digest")
|
||||
|
|
@ -80,16 +80,23 @@ class DailyPaperDigestStep(DailyPaperStep):
|
|||
|
||||
documents = [{"title": item.title, "desc": item.desc, "body": item.body} for item in analyses]
|
||||
wikilinks = [f"[[{item.note_path}]]" for item in analyses]
|
||||
previous_day = (dt.date.fromisoformat(self._run_day()) - dt.timedelta(days=1)).isoformat()
|
||||
run_day = dt.date.fromisoformat(self._run_day())
|
||||
daily_dir = str(self.config_value("daily_dir")).strip("/")
|
||||
self.logger.info(f"[{self.name}] agent start notes={len(analyses)}")
|
||||
result = await self.agent_wrapper.reply(
|
||||
self.prompt_format(
|
||||
"digest_user",
|
||||
documents=json.dumps(documents, ensure_ascii=False, indent=2),
|
||||
previous_day=previous_day,
|
||||
daily_dir=daily_dir,
|
||||
),
|
||||
output_schema=DailyPaperMarkdownOutput,
|
||||
job_tools=list(self.kwargs.get("job_tools") or []),
|
||||
injected_job_kwargs={
|
||||
"limit": 20,
|
||||
"min_score": 0.0,
|
||||
"start_date": None,
|
||||
"end_date": (run_day - dt.timedelta(days=1)).isoformat(),
|
||||
},
|
||||
)
|
||||
self.logger.info(f"[{self.name}] agent done notes={len(analyses)}")
|
||||
output = structured_output(result, DailyPaperMarkdownOutput)
|
||||
|
|
@ -97,8 +104,7 @@ class DailyPaperDigestStep(DailyPaperStep):
|
|||
if not output.desc.strip() or not body:
|
||||
raise ValueError("Agent returned an empty daily paper brief")
|
||||
|
||||
day = self._run_day()
|
||||
daily_dir = str(self.config_value("daily_dir")).strip("/")
|
||||
day = run_day.isoformat()
|
||||
title = normalize_chinese_title(output.title, f"每日论文简报-{day}")
|
||||
existing_rel = str(self._state("existing_digest_path") or "").strip()
|
||||
existing_path = self.workspace_path / existing_rel if existing_rel else None
|
||||
|
|
@ -111,7 +117,7 @@ class DailyPaperDigestStep(DailyPaperStep):
|
|||
existing=existing_path,
|
||||
)
|
||||
digest_rel = digest_path.relative_to(self.workspace_path).as_posix()
|
||||
body = self._validate_historical_wikilinks(body, dt.date.fromisoformat(day), digest_path)
|
||||
body = self._validate_historical_wikilinks(body, run_day, digest_path)
|
||||
body += "\n\n## 详细论文\n\n" + "\n".join(f"- {link}" for link in wikilinks)
|
||||
selected_ids = [item.arxiv_id for item in analyses]
|
||||
await write_markdown(
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
# Prompt bundled with the Daily Paper plugin distribution.
|
||||
digest_user: |
|
||||
你是中文 AI 研究资讯主编。请忠实综合下面三篇详细论文解读,生成一篇普通读者五分钟可以读懂的每日论文速读。
|
||||
内容只能依据输入文档,不得补充文档中没有提供的事实。
|
||||
保留技术准确性,同时解释三篇论文为什么值得关注,以及它们之间有什么联系。
|
||||
|
||||
在写作前,先调用 `memory_search` 检索以前的文章:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询,
|
||||
使用 end_date={previous_day}、limit=20。主题跨度较大时可以多次检索。只把 `daily/` 下日期早于今天、
|
||||
且与本期内容确实相似或互补的 Markdown 文章作为候选;必要时调用 `read` 核验全文,不要仅凭标题判断。
|
||||
在写作前,先调用 `search` 检索已有记忆:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询。
|
||||
主题跨度较大时可以多次检索,搜索结果不必局限于 `{daily_dir}/`。只有 `{daily_dir}/` 下日期早于今天、
|
||||
且与本期内容确实相似或互补的 Markdown 文章才可作为正文中的历史链接候选;必要时调用 `read` 核验全文,
|
||||
不要仅凭标题判断。
|
||||
将确认相关的旧文章以 Wikilink 自然织入正文,并用句子说明关联(延续、对比、补充或方法相似);
|
||||
链接必须采用带 `.md` 的完整 workspace-relative 路径,例如
|
||||
`[[daily/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。
|
||||
`[[{daily_dir}/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。
|
||||
旧文章只用于判断关联和建立链接,不得用来补充本期事实。如果没有真正相关的旧文章,不要强行添加;
|
||||
当日三篇详细解读的链接会由系统统一附在文末。
|
||||
|
||||
|
|
@ -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:
|
||||
82
plugins/daily_paper/src/reme_daily_paper/plugin.yaml
Normal file
82
plugins/daily_paper/src/reme_daily_paper/plugin.yaml
Normal 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: [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
|
||||
|
|
@ -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."""
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
"""Typed contracts for the daily-paper cookbook workflow."""
|
||||
"""Typed contracts for the Daily Paper plugin."""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
|
@ -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."""
|
||||
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# Prompt bundled with the Daily Paper plugin distribution.
|
||||
select_user: |
|
||||
从候选中选择恰好 3 篇最值得深入阅读的 AI 论文。兼顾研究价值、新颖性、影响和可读性。
|
||||
{selection_preference}
|
||||
|
|
@ -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,26 +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.rank import build_candidate_pool, rrf_score
|
||||
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_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):
|
||||
|
|
@ -57,6 +90,79 @@ def _paper(arxiv_id: str, *, title: str = "Paper", upvotes: int = 10) -> PaperIn
|
|||
)
|
||||
|
||||
|
||||
def test_daily_paper_replaces_surrogates_in_text_and_titles():
|
||||
"""Invalid surrogate code points become visible replacement characters."""
|
||||
assert replace_surrogates("before\ud800middle\udfffafter") == "before\ufffdmiddle\ufffdafter"
|
||||
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."""
|
||||
target = tmp_path / "note.md"
|
||||
|
||||
await write_atomic(target, "before\ud800after")
|
||||
|
||||
assert target.read_text(encoding="utf-8") == "before\ufffdafter"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_daily_paper_markdown_write_replaces_surrogates_in_frontmatter(tmp_path: Path):
|
||||
"""Frontmatter serialization sanitizes nested metadata before YAML encoding."""
|
||||
target = tmp_path / "note.md"
|
||||
|
||||
await write_markdown(
|
||||
target,
|
||||
"body\ud800text",
|
||||
{"description": "meta\udffftext", "authors": ["safe", "author\ud800name"]},
|
||||
)
|
||||
|
||||
post = frontmatter.load(target)
|
||||
assert post.content == "body\ufffdtext"
|
||||
assert post.metadata == {
|
||||
"description": "meta\ufffdtext",
|
||||
"authors": ["safe", "author\ufffdname"],
|
||||
}
|
||||
|
||||
|
||||
def test_daily_paper_pdf_extraction_replaces_surrogates(monkeypatch, tmp_path: Path):
|
||||
"""Malformed PDF text is sanitized before it enters an agent prompt."""
|
||||
|
||||
class FakePage:
|
||||
"""Return text containing one unpaired surrogate."""
|
||||
|
||||
@staticmethod
|
||||
def extract_text():
|
||||
"""Return the malformed page text."""
|
||||
return "before\ud800after"
|
||||
|
||||
class FakeReader:
|
||||
"""Expose one fake PDF page."""
|
||||
|
||||
def __init__(self, _path: str):
|
||||
self.pages = [FakePage()]
|
||||
|
||||
import pypdf
|
||||
|
||||
monkeypatch.setattr(pypdf, "PdfReader", FakeReader)
|
||||
|
||||
content, page_count, truncated = DailyPaperAnalyzeStep._extract_pdf_text_sync( # pylint: disable=protected-access
|
||||
tmp_path / "paper.pdf",
|
||||
20,
|
||||
300_000,
|
||||
)
|
||||
|
||||
assert content == "--- PAGE 1 ---\n\nbefore\ufffdafter"
|
||||
assert page_count == 1
|
||||
assert truncated is False
|
||||
|
||||
|
||||
def test_hf_payload_and_html_normalization():
|
||||
"""HF list/detail shapes normalize and HTML rank order de-duplicates."""
|
||||
payload = {
|
||||
|
|
@ -469,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",
|
||||
|
|
@ -481,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",
|
||||
|
|
@ -505,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",
|
||||
|
|
@ -522,10 +621,31 @@ 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_digest_prompt_uses_configured_daily_directory(tmp_path: Path):
|
||||
"""Use the host application's daily directory in historical-link guidance."""
|
||||
step = DailyPaperDigestStep(
|
||||
app_context=ApplicationContext(
|
||||
workspace_dir=str(tmp_path),
|
||||
daily_dir="memory",
|
||||
),
|
||||
)
|
||||
|
||||
prompt = step.prompt_format(
|
||||
"digest_user",
|
||||
documents="[]",
|
||||
daily_dir=str(step.config_value("daily_dir")).strip("/"),
|
||||
)
|
||||
|
||||
assert "`memory/`" in prompt
|
||||
assert "搜索结果不必局限于 `memory/`" in prompt
|
||||
assert "[[memory/2026-07-01/旧文章.md" in prompt
|
||||
assert "[[daily/2026-07-01/" not in prompt
|
||||
|
||||
|
||||
def test_paper_pick_list_uses_an_object_root_for_tool_output():
|
||||
|
|
@ -539,7 +659,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"]
|
||||
|
|
@ -613,7 +733,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,
|
||||
|
|
@ -705,8 +825,8 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
|
|||
},
|
||||
{
|
||||
"title": "记忆代理研究",
|
||||
"desc": "Detailed note one",
|
||||
"body": "Evidence one [p. 1].",
|
||||
"desc": "Detailed note\ud800 one",
|
||||
"body": "Evidence\udfff one [p. 1].",
|
||||
},
|
||||
{
|
||||
"title": "上下文压缩研究",
|
||||
|
|
@ -747,7 +867,7 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
|
|||
await DailyPaperDigestStep(
|
||||
app_context=app_context,
|
||||
agent_wrapper=cc_wrapper,
|
||||
job_tools=["memory_search", "read"],
|
||||
job_tools=["search", "read"],
|
||||
)(context)
|
||||
|
||||
assert _FakeHfClient.requested_daily == ["2026-07-20"]
|
||||
|
|
@ -787,7 +907,13 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
|
|||
assert all(call["kwargs"] == {"output_schema": DailyPaperMarkdownOutput} for call in cc_wrapper.calls[1:-1])
|
||||
assert cc_wrapper.calls[-1]["kwargs"] == {
|
||||
"output_schema": DailyPaperMarkdownOutput,
|
||||
"job_tools": ["memory_search", "read"],
|
||||
"job_tools": ["search", "read"],
|
||||
"injected_job_kwargs": {
|
||||
"limit": 20,
|
||||
"min_score": 0.0,
|
||||
"start_date": None,
|
||||
"end_date": "2026-07-20",
|
||||
},
|
||||
}
|
||||
assert [call["kwargs"]["output_schema"] for call in cc_wrapper.calls] == [
|
||||
PaperPickList,
|
||||
|
|
@ -801,12 +927,16 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
|
|||
assert "ReMe" not in analysis_prompt
|
||||
assert "# PDF 分页文本" in analysis_prompt
|
||||
digest_prompt = cc_wrapper.calls[-1]["inputs"]
|
||||
assert "Evidence one [p. 1]." in digest_prompt
|
||||
assert "Evidence\ufffd one [p. 1]." in digest_prompt
|
||||
assert "Detailed note\ufffd one" in digest_prompt
|
||||
assert not any("\ud800" <= character <= "\udfff" for character in digest_prompt)
|
||||
assert "调用 Read" not in digest_prompt
|
||||
assert "daily/2026-07-21" not in digest_prompt
|
||||
assert "长期记忆" not in digest_prompt
|
||||
assert "先调用 `memory_search` 检索以前的文章" in digest_prompt
|
||||
assert "end_date=2026-07-20" in digest_prompt
|
||||
assert "先调用 `search` 检索已有记忆" in digest_prompt
|
||||
assert "搜索结果不必局限于 `daily/`" in digest_prompt
|
||||
assert "end_date" not in digest_prompt
|
||||
assert "limit=" not in digest_prompt
|
||||
assert "Wikilink" in digest_prompt
|
||||
|
||||
rerun = RuntimeContext(date="2026-07-21")
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue