mirror of
https://github.com/agentscope-ai/ReMe.git
synced 2026-10-10 03:30:56 +00:00
Merge remote-tracking branch 'upstream/main' into feat/resource-image-caption
This commit is contained in:
commit
552940211f
129 changed files with 6978 additions and 2757 deletions
1
.github/workflows/_build-docs.yml
vendored
1
.github/workflows/_build-docs.yml
vendored
|
|
@ -28,6 +28,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
|
|
|
|||
12
.github/workflows/ci-docs.yml
vendored
12
.github/workflows/ci-docs.yml
vendored
|
|
@ -11,11 +11,17 @@ on:
|
|||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme/config/default.yaml'
|
||||
- 'integrations/claude_code/README.md'
|
||||
- 'integrations/hermes_agent/README.md'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'typescript/docs/**'
|
||||
- 'typescript/figures/**'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
- 'benchmark/toolmemory/gitcha.png'
|
||||
pull_request:
|
||||
branches: [main, master, dev, develop]
|
||||
paths:
|
||||
|
|
@ -26,11 +32,17 @@ on:
|
|||
- 'README_ZH.md'
|
||||
- 'docs/**'
|
||||
- 'github-pages/**'
|
||||
- 'reme/config/default.yaml'
|
||||
- 'integrations/claude_code/README.md'
|
||||
- 'integrations/hermes_agent/README.md'
|
||||
- 'reme_studio/README*.md'
|
||||
- 'reme_studio/public/og.jpg'
|
||||
- 'typescript/README*.md'
|
||||
- 'typescript/docs/**'
|
||||
- 'typescript/figures/**'
|
||||
- 'plugins/*/README*.md'
|
||||
- 'benchmark/*/README*.md'
|
||||
- 'benchmark/toolmemory/gitcha.png'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
|
|
|||
2
.github/workflows/ci-python-quality.yml
vendored
2
.github/workflows/ci-python-quality.yml
vendored
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
- 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
|
||||
pip install -q --no-deps -e plugins/auto-fin -e plugins/daily_paper -e plugins/lme -e plugins/beam
|
||||
|
||||
- name: Pre-commit starts
|
||||
run: pre-commit run --all-files
|
||||
|
|
|
|||
3
.github/workflows/ci-python-tests.yml
vendored
3
.github/workflows/ci-python-tests.yml
vendored
|
|
@ -40,11 +40,12 @@ jobs:
|
|||
pip install -e reme_studio -e ".[dev,core]"
|
||||
pip install --no-deps -e plugins/auto-fin
|
||||
pip install -e plugins/daily_paper
|
||||
pip install -e plugins/lme -e plugins/beam
|
||||
pip install coverage
|
||||
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper \
|
||||
coverage run -m pytest tests/unit plugins/auto-fin plugins/daily_paper plugins/lme plugins/beam \
|
||||
-v \
|
||||
--tb=long \
|
||||
-s \
|
||||
|
|
|
|||
6
.github/workflows/deploy-docs.yml
vendored
6
.github/workflows/deploy-docs.yml
vendored
|
|
@ -6,13 +6,19 @@ on:
|
|||
paths:
|
||||
- "github-pages/**"
|
||||
- "docs/**"
|
||||
- "reme/config/default.yaml"
|
||||
- "integrations/claude_code/README.md"
|
||||
- "integrations/hermes_agent/README.md"
|
||||
- "README.md"
|
||||
- "README_ZH.md"
|
||||
- "reme_studio/README*.md"
|
||||
- "reme_studio/public/og.jpg"
|
||||
- "typescript/README*.md"
|
||||
- "typescript/docs/**"
|
||||
- "typescript/figures/**"
|
||||
- "plugins/*/README*.md"
|
||||
- "benchmark/*/README*.md"
|
||||
- "benchmark/toolmemory/gitcha.png"
|
||||
- "AGENTS.md"
|
||||
- ".github/workflows/deploy-docs.yml"
|
||||
- ".github/workflows/_build-docs.yml"
|
||||
|
|
|
|||
9
.github/workflows/release-typescript.yml
vendored
9
.github/workflows/release-typescript.yml
vendored
|
|
@ -2,8 +2,9 @@
|
|||
# 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 for agentscope-ai/ReMe and this workflow file.
|
||||
# 5. Use the `next` tag for prereleases and `latest` only for stable releases.
|
||||
# 4. Keep the ClawHub publication docs in typescript/docs/openclaw.md and openclaw.zh-CN.md.
|
||||
# 5. Configure ClawHub Trusted Publishing for agentscope-ai/ReMe and this workflow file.
|
||||
# 6. Use the `next` tag for prereleases and `latest` only for stable releases.
|
||||
|
||||
name: Release / TypeScript integrations
|
||||
|
||||
|
|
@ -112,6 +113,8 @@ jobs:
|
|||
- name: Pack ClawHub tarball
|
||||
working-directory: typescript
|
||||
run: |
|
||||
test -f docs/openclaw.md
|
||||
test -f docs/openclaw.zh-CN.md
|
||||
mkdir -p "${RUNNER_TEMP}/reme-clawhub-package"
|
||||
npm run pack:clawhub -- "${RUNNER_TEMP}/reme-clawhub-package"
|
||||
|
||||
|
|
@ -171,7 +174,7 @@ jobs:
|
|||
tags: ${{ inputs.npm_tag }}
|
||||
source_repo: ${{ github.repository }}
|
||||
source_commit: ${{ github.sha }}
|
||||
source_ref: ${{ github.ref }}
|
||||
source_ref: ${{ github.sha }}
|
||||
source_path: typescript
|
||||
package_artifact_name: agentscope-ai-reme-clawhub-${{ inputs.version }}
|
||||
dry_run: false
|
||||
|
|
|
|||
11
AGENTS.md
11
AGENTS.md
|
|
@ -46,8 +46,8 @@ and concise documentation together.
|
|||
- `reme/components/service/`: local CLI, HTTP, and MCP service backends.
|
||||
- `reme/components/`: agent wrappers, model adapters, stores, catalogs, graphs, indexes, clients, tokenizers, and
|
||||
outbound proxies.
|
||||
- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, benchmark, and transfer
|
||||
concerns.
|
||||
- `reme/steps/`: registered job steps grouped by common, file I/O, index, evolve, cookbook, and transfer
|
||||
concerns, plus shared benchmark base classes under `benchmark/`.
|
||||
- `reme/utils/`: shared utilities, including service discovery, logging, web-static resolution, session I/O, token
|
||||
accounting, and wikilink handling.
|
||||
- `tests/unit/`: primary fast, isolated validation suite.
|
||||
|
|
@ -56,12 +56,14 @@ and concise documentation together.
|
|||
`@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.
|
||||
- `plugins/`: installable ReMe extensions, including Auto Fin and LME/BEAM benchmark Steps and application presets.
|
||||
- `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 evaluations and example workflows.
|
||||
- `docs/`: README-linked supporting pages and figures.
|
||||
- `github-pages/`: VitePress build shell, generated-content assembly, documentation checks, and GitHub Pages output. The
|
||||
canonical theme and guides remain under `docs/`; `.generated/` and `dist/` are disposable.
|
||||
|
||||
## Development Setup
|
||||
|
||||
|
|
@ -195,6 +197,9 @@ Integration tests may contact real model providers, services, or agent subproces
|
|||
run credentialed or externally mutating tests automatically; run them only when the task requires them and the necessary
|
||||
environment has been supplied or authorized. Mock network, model, and subprocess boundaries in unit tests.
|
||||
|
||||
If documentation or the documentation theme changes, run `npm test` and `npm run build` from `github-pages/`. The Job
|
||||
reference is generated from `reme/config/default.yaml`; do not edit generated pages directly.
|
||||
|
||||
## Change Guardrails
|
||||
|
||||
- Preserve unrelated user changes in a dirty working tree.
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -51,12 +51,12 @@ users retain control of the durable files.
|
|||
|
||||
- [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
|
||||
- [2026.08] - Published the [ReMe blog](https://reme.agentscope.io/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
|
||||
- [2026.08] - [Experience-driven enhancement method](https://reme.agentscope.io/en/benchmarks/toolmemory) of agent tool-use execution built
|
||||
on ReMe is available on [arXiv:2608.03403](https://arxiv.org/abs/2608.03403).
|
||||
- [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
|
||||
- [2026.07] - Introduced optional plugins: [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) for paper discovery and
|
||||
analysis, and [Auto Fin](https://reme.agentscope.io/en/plugins/auto-fin) for researching the latest 24 hours of topic-related CLS news
|
||||
with local-memory search and validated historical wikilinks.
|
||||
- [2026.07] - Our
|
||||
paper [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
|
||||
|
|
@ -171,7 +171,7 @@ Related: [[digest/wiki/memory-as-file.md]]
|
|||
|
||||
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.
|
||||
[ReMe Studio guide](https://reme.agentscope.io/en/workspace/studio) for source builds, configuration, and development.
|
||||
|
||||
## 🤝 Use ReMe with Your Agent
|
||||
|
||||
|
|
@ -308,12 +308,12 @@ benchmark.
|
|||
|
||||
| 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 |
|
||||
| **[LongMemEval cleaned-s](https://reme.agentscope.io/en/benchmarks/longmemeval)** | **Overall** | **500 questions** | **89.4%** | Cross-session retrieval, knowledge updates, and temporal reasoning |
|
||||
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 100K context | 20 cases / 400 questions | 66.1% | Ten types of long-context memory tasks |
|
||||
| [BEAM](https://reme.agentscope.io/en/benchmarks/beam) | 1M context | 35 cases / 700 questions | 65.0% | Ultra-long conversation settings |
|
||||
|
||||
ReMe also achieved a **0.580 PROC score across five user personas** in the repository's
|
||||
[π-Bench evaluation](https://reme.agentscope.io/?doc=pibench-en), 2.4% above NanoBot under the same test-model configuration. PROC
|
||||
[π-Bench evaluation](https://reme.agentscope.io/en/benchmarks/pibench), 2.4% above NanoBot under the same test-model configuration. PROC
|
||||
measures proactive handling of hidden intent, clarification, cross-session preferences and conventions, task
|
||||
dependencies, and underspecified requests.
|
||||
|
||||
|
|
@ -326,8 +326,8 @@ plugins; see the source distributions and their documentation for [Daily Paper](
|
|||
|
||||
| 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. |
|
||||
| [Daily Paper](https://reme.agentscope.io/en/plugins/daily-paper) | 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/en/plugins/auto-fin) | Fetch topic-related CLS news, search ReMe history, and generate wikilink-backed Markdown reports. |
|
||||
|
||||
See [Plugin Management](docs/en/plugin_management.md) to install, inspect, validate, enable, and uninstall ReMe plugins.
|
||||
|
||||
|
|
@ -338,6 +338,8 @@ These guides cover the main user workflows and the runtime contracts implemented
|
|||
| 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. |
|
||||
| [Configuration](docs/en/configuration.md) | Configure the workspace, models, Service, Jobs, Components, plugins, and CLI overrides. |
|
||||
| [Services and Deployment](docs/en/services.md) | Use HTTP, SSE, MCP, and Studio while respecting the default security boundary. |
|
||||
| [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 and image resources as source-linked daily cards. |
|
||||
|
|
@ -347,7 +349,9 @@ These guides cover the main user workflows and the runtime contracts implemented
|
|||
| [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. |
|
||||
| [CLI and Job API](docs/en/reference/cli.md) | Learn command syntax and use the generated default Job parameter reference. |
|
||||
| [Operations and Recovery](docs/en/operations.md) | Diagnose services, maintain indexes, and back up, migrate, or recover a workspace. |
|
||||
| [ReMe Blog](https://reme.agentscope.io/en/reme-blog) | Read the product story, design rationale, examples, and benchmark summary. |
|
||||
|
||||
## 🛠️ Common Commands
|
||||
|
||||
|
|
|
|||
28
README_ZH.md
28
README_ZH.md
|
|
@ -49,13 +49,13 @@
|
|||
|
||||
- [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/zh/reme-blog),系统介绍本地优先的记忆架构、自进化工作流、混合检索、
|
||||
主动发现与评测结果。
|
||||
- [2026.08] - 基于 ReMe 的智能体工具使用
|
||||
[经验驱动增强方法](https://reme.agentscope.io/?doc=toolmemory-zh)已发布,见
|
||||
[经验驱动增强方法](https://reme.agentscope.io/zh/benchmarks/toolmemory)已发布,见
|
||||
[arXiv:2608.03403](https://arxiv.org/abs/2608.03403)。
|
||||
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/?doc=daily-paper-zh)用于论文发现与解析,
|
||||
[Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
|
||||
- [2026.07] - 新增可选插件:[每日论文](https://reme.agentscope.io/zh/plugins/daily-paper)用于论文发现与解析,
|
||||
[Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin)用于研究最近 24 小时的主题相关财联社新闻,通过本地记忆搜索回顾历史材料并构建
|
||||
wikilink。
|
||||
- [2026.07] -
|
||||
我们的论文 [Remember Me, Refine Me: A Dynamic Procedural Memory Framework for Experience-Driven Agent Evolution](https://aclanthology.org/2026.findings-acl.829/)
|
||||
|
|
@ -170,7 +170,7 @@ ReMe 会把 Agent 记忆保存为可读的 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)。
|
||||
[ReMe Studio 指南](https://reme.agentscope.io/zh/workspace/studio)。
|
||||
|
||||
## 🤝 将 ReMe 接入你的 Agent
|
||||
|
||||
|
|
@ -301,11 +301,11 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
|
|||
|
||||
| 基准 | 设置 | 样本量 | 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% | 超长对话设置 |
|
||||
| **[LongMemEval cleaned-s](https://reme.agentscope.io/zh/benchmarks/longmemeval)** | **整体** | **500 题** | **89.4%** | 跨会话检索、知识更新与时间推理 |
|
||||
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 100K 上下文 | 20 cases / 400 题 | 66.1% | 十类长上下文记忆任务 |
|
||||
| [BEAM](https://reme.agentscope.io/zh/benchmarks/beam) | 1M 上下文 | 35 cases / 700 题 | 65.0% | 超长对话设置 |
|
||||
|
||||
在仓库的 [π-Bench 评测](https://reme.agentscope.io/?doc=pibench-zh)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
|
||||
在仓库的 [π-Bench 评测](https://reme.agentscope.io/zh/benchmarks/pibench)中,ReMe Agent 在 5 种用户角色上的平均 **PROC 得分为 0.580**
|
||||
,比相同测试模型配置的 NanoBot 高 2.4%。PROC 用于评估隐藏意图完成、针对性澄清、跨会话偏好和规范复用、跨任务依赖推断以及欠规格请求推进等主动性能力。
|
||||
|
||||
## 🧩 扩展与插件
|
||||
|
|
@ -316,8 +316,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
|
|||
|
||||
| 插件 | 能力 |
|
||||
| ---------------------------------------------------------- | ------------------------------------------------------------------------------ |
|
||||
| [每日论文](https://reme.agentscope.io/?doc=daily-paper-zh) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
|
||||
| [Auto Fin](https://reme.agentscope.io/?doc=auto-fin-zh) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
|
||||
| [每日论文](https://reme.agentscope.io/zh/plugins/daily-paper) | 发现并排序论文,使用 Agent 解读 PDF,生成文件化论文笔记和五分钟简报。 |
|
||||
| [Auto Fin](https://reme.agentscope.io/zh/plugins/auto-fin) | 拉取主题相关财联社新闻,搜索 ReMe 历史材料并生成带 wikilink 的 Markdown 报告。 |
|
||||
|
||||
安装、查看、校验、启用和卸载 ReMe 插件的方法见[插件管理](docs/zh/plugin_management.md)。
|
||||
|
||||
|
|
@ -328,6 +328,8 @@ ReMe 通过 Agent 多轮搜索与读取的方式,评测多会话和超长上
|
|||
| 文档 | 主要内容 |
|
||||
| ------------------------------------------------------------------------ | ---------------------------------------------------------------------- |
|
||||
| [快速开始](docs/zh/quick_start.md) | 安装 ReMe、启动服务,并执行首次文件和记忆操作。 |
|
||||
| [基础配置](docs/zh/configuration.md) | 配置 workspace、模型、Service、Job、Component、插件和命令行覆盖。 |
|
||||
| [服务与部署](docs/zh/services.md) | 使用 HTTP、SSE、MCP 和 Studio,并理解默认安全边界。 |
|
||||
| [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 卡片。 |
|
||||
|
|
@ -337,7 +339,9 @@ ReMe 通过 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) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
|
||||
| [CLI 与 Job API](docs/zh/reference/cli.md) | 查询命令语法,以及由默认配置自动生成的 Job 参数参考。 |
|
||||
| [运维与恢复](docs/zh/operations.md) | 诊断服务、维护索引,并备份、迁移和恢复 workspace。 |
|
||||
| [ReMe 博客](https://reme.agentscope.io/zh/reme-blog) | 了解完整产品故事、设计动机、使用示例和评测摘要。 |
|
||||
|
||||
## 🛠️ 常用命令
|
||||
|
||||
|
|
|
|||
|
|
@ -15,8 +15,20 @@ include abstention, contradiction resolution, event ordering, information
|
|||
extraction, instruction following, knowledge update, multi-session reasoning,
|
||||
preference following, summarization, and temporal reasoning.
|
||||
|
||||
> For the shared setup (dependencies, credentials, log conventions) see the
|
||||
> [top-level benchmark README](../README.md).
|
||||
Install ReMe and the BEAM plugin in editable mode from the repository root:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/beam --editable
|
||||
reme plugins validate beam
|
||||
```
|
||||
|
||||
The runner explicitly enables the installed `beam` plugin and combines its defaults with
|
||||
ReMe's built-in `benchmark` preset. Editable installation keeps changes under
|
||||
[`plugins/beam`](../../plugins/beam/README.md) visible without reinstalling the plugin.
|
||||
Custom application config paths still work through `reme.config` and can use `extends: benchmark`.
|
||||
This directory continues to own the runner, evaluation settings, dataset and outputs.
|
||||
Model credentials use the environment variables declared by the shared benchmark configuration.
|
||||
|
||||
## 1. Get the Dataset
|
||||
|
||||
|
|
@ -59,7 +71,7 @@ python benchmark/beam/run.py --eval_only # reuse existing workspac
|
|||
| `dataset.start_index` / `num_items` | Case pagination (`num_items` `0` = all). |
|
||||
| `dataset.workspace_root` | Per-case workspace root (`benchmark/beam/workspaces/beam`). |
|
||||
| `evaluation.num_workers` | `0` = auto, `1` = sequential, `>1` = parallel. |
|
||||
| `reme.config` | ReMe config used (`beam.yaml`). |
|
||||
| `reme.config` | ReMe config used (`benchmark`). |
|
||||
| `output.dir` | Results directory (`benchmark/beam/results`). |
|
||||
|
||||
## 5. Outputs
|
||||
|
|
|
|||
|
|
@ -13,7 +13,19 @@ ordering(事件排序)、information extraction(信息抽取)、instruct
|
|||
knowledge update(知识更新)、multi-session reasoning(多会话推理)、preference following
|
||||
(偏好遵循)、summarization(摘要)与 temporal reasoning(时间推理)。
|
||||
|
||||
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
|
||||
在仓库根目录以 editable 模式安装 ReMe 和 BEAM 插件:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/beam --editable
|
||||
reme plugins validate beam
|
||||
```
|
||||
|
||||
runner 显式启用已安装的 `beam` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。
|
||||
editable 安装会让 [`plugins/beam`](../../plugins/beam/README_ZH.md) 下的源码修改直接生效,无需重复安装。
|
||||
本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定,
|
||||
并可使用 `extends: benchmark`。
|
||||
模型凭据通过公共 benchmark 配置中声明的环境变量设置。
|
||||
|
||||
## 1. 获取数据集
|
||||
|
||||
|
|
@ -55,7 +67,7 @@ python benchmark/beam/run.py --eval_only # 复用已有工作区
|
|||
| `dataset.start_index` / `num_items` | case 分页(`num_items` 为 `0` 表示全部)。 |
|
||||
| `dataset.workspace_root` | case 工作区根目录(`benchmark/beam/workspaces/beam`)。 |
|
||||
| `evaluation.num_workers` | `0` = 自动,`1` = 串行,`>1` = 并行。 |
|
||||
| `reme.config` | 使用的 ReMe 配置(`beam.yaml`)。 |
|
||||
| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 |
|
||||
| `output.dir` | 结果目录(`benchmark/beam/results`)。 |
|
||||
|
||||
## 5. 输出
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ evaluation:
|
|||
compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression
|
||||
|
||||
reme:
|
||||
config: "beam.yaml" # reme config (in reme/config/)
|
||||
config: "benchmark" # shared ReMe benchmark preset; runner enables the installed beam plugin
|
||||
|
||||
output:
|
||||
dir: "benchmark/beam/results"
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import yaml
|
|||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env from project root
|
||||
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
load_dotenv(_PROJECT_ROOT / ".env")
|
||||
|
||||
# Workspace root — read from config.yaml (dataset.workspace_root)
|
||||
|
|
@ -151,6 +151,22 @@ def load_eval_config(config_path: str | None = None) -> dict:
|
|||
return yaml.safe_load(raw)
|
||||
|
||||
|
||||
def create_reme_app(config: str = "benchmark", **overrides):
|
||||
"""Create an app with the installed BEAM plugin explicitly enabled.
|
||||
|
||||
Plugin discovery remains environment-based; editable installation keeps local
|
||||
plugin source changes visible to every multiprocessing worker.
|
||||
"""
|
||||
from reme import Application
|
||||
from reme.config import resolve_app_config
|
||||
|
||||
enabled_plugins = list(overrides.pop("plugins", ()) or ())
|
||||
if "beam" not in enabled_plugins:
|
||||
enabled_plugins.append("beam")
|
||||
app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides)
|
||||
return Application(**app_config)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# BEAM data loading
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -319,8 +335,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
|
|||
Returns:
|
||||
A results dict with all questions, answers, and judgments.
|
||||
"""
|
||||
from reme import Application
|
||||
from reme.config import resolve_app_config
|
||||
|
||||
dataset_cfg = eval_config["dataset"]
|
||||
chat_size = dataset_cfg["chat_size"]
|
||||
|
|
@ -375,7 +389,7 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
|
|||
force_init=True,
|
||||
)
|
||||
|
||||
cfg = resolve_app_config(
|
||||
app = create_reme_app(
|
||||
config=eval_config["reme"]["config"],
|
||||
workspace_dir=workspace_dir,
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
|
|
@ -383,7 +397,6 @@ async def evaluate_case(eval_config: dict, case_id: str, eval_only: bool = False
|
|||
enable_logo=False,
|
||||
)
|
||||
|
||||
app = Application(**cfg)
|
||||
await app.start()
|
||||
|
||||
from reme.utils.evaluation_interface import check_agent_token_usage # noqa: E402
|
||||
|
|
|
|||
|
|
@ -12,8 +12,20 @@ agentic (ReAct) mode, and scores the answer with an LLM-as-judge.
|
|||
Question types include single-session (user / assistant / preference),
|
||||
multi-session reasoning, knowledge update, and temporal reasoning.
|
||||
|
||||
> For the shared setup (dependencies, credentials, log conventions) see the
|
||||
> [top-level benchmark README](../README.md).
|
||||
Install ReMe and the LongMemEval plugin in editable mode from the repository root:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/lme --editable
|
||||
reme plugins validate lme
|
||||
```
|
||||
|
||||
The runner explicitly enables the installed `lme` plugin and combines its defaults with
|
||||
ReMe's built-in `benchmark` preset. Editable installation keeps changes under
|
||||
[`plugins/lme`](../../plugins/lme/README.md) visible without reinstalling the plugin.
|
||||
Custom application config paths still work through `reme.config` and can use `extends: benchmark`.
|
||||
This directory continues to own the runner, evaluation settings, dataset and outputs.
|
||||
Model credentials use the environment variables declared by the shared benchmark configuration.
|
||||
|
||||
## 1. Get the Dataset
|
||||
|
||||
|
|
@ -46,7 +58,8 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w
|
|||
|
||||
1. Load the dataset (ground truth is embedded in the data file).
|
||||
2. For each item, create an isolated workspace and ingest sessions in chronological order.
|
||||
3. Trigger `auto_dream` when consecutive sessions cross the configured hour (default 23:00).
|
||||
3. If a custom application configuration enables `auto_dream`, trigger it when sessions cross the configured hour
|
||||
(default 23:00). The packaged preset leaves it disabled.
|
||||
4. Answer each question via agentic (ReAct) mode.
|
||||
5. Judge the answer (binary yes/no) with the `answer_judge` job and print per-type accuracy.
|
||||
|
||||
|
|
@ -60,7 +73,7 @@ python benchmark/longmemeval/run.py --eval_only # reuse existing w
|
|||
| `dataset.workspace_root` | Per-item workspace root (`benchmark/longmemeval/workspaces/longmemeval-s`). |
|
||||
| `evaluation.num_workers` | `0` = auto (cpu-2), `1` = sequential, `>1` = parallel. |
|
||||
| `evaluation.filter_future_sessions` | Only ingest sessions with timestamp ≤ `question_date`. |
|
||||
| `reme.config` | ReMe config used (`lme.yaml`). |
|
||||
| `reme.config` | ReMe config used (`benchmark`). |
|
||||
| `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | Dream triggering behavior. |
|
||||
| `output.dir` | Results directory (`benchmark/longmemeval/results`). |
|
||||
|
||||
|
|
@ -93,4 +106,4 @@ agentscope==2.0.4.post1, conda reme env, 32 workers, eval-only (reusing prebuilt
|
|||
| single-session-preference | 0.633 | 36,802 | 818 | 37,620 | 3.60 |
|
||||
| single-session-user | 0.986 | 27,433 | 359 | 27,792 | 2.60 |
|
||||
| temporal-reasoning | 0.902 | 62,674 | 985 | 63,659 | 4.97 |
|
||||
| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** |
|
||||
| **OVERALL** | **0.894** | **43,448** | **876** | **44,324** | **3.69** |
|
||||
|
|
|
|||
|
|
@ -8,7 +8,19 @@ LongMemEval 是一个面向**多轮多会话历史的长期记忆能力**的评
|
|||
|
||||
题型包括单会话(user / assistant / preference)、多会话推理、知识更新与时间推理等。
|
||||
|
||||
> 公共设置(依赖、凭据、日志约定)见[总评测说明](../README_ZH.md)。
|
||||
在仓库根目录以 editable 模式安装 ReMe 和 LongMemEval 插件:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/lme --editable
|
||||
reme plugins validate lme
|
||||
```
|
||||
|
||||
runner 显式启用已安装的 `lme` 插件,并将插件默认配置与 ReMe 内置的 `benchmark` 配置组合。
|
||||
editable 安装会让 [`plugins/lme`](../../plugins/lme/README_ZH.md) 下的源码修改直接生效,无需重复安装。
|
||||
本目录继续保留评测参数、数据集及输出。自定义完整应用配置路径仍可通过 `reme.config` 指定,
|
||||
并可使用 `extends: benchmark`。
|
||||
模型凭据通过公共 benchmark 配置中声明的环境变量设置。
|
||||
|
||||
## 1. 获取数据集
|
||||
|
||||
|
|
@ -41,7 +53,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工
|
|||
|
||||
1. 加载数据集(ground truth 已内嵌在数据文件中)。
|
||||
2. 为每个条目创建独立工作区,按时间顺序摄入会话。
|
||||
3. 当相邻会话跨越配置的时刻(默认 23:00)时触发 `auto_dream`。
|
||||
3. 若自定义应用配置启用了 `auto_dream`,在相邻会话跨越配置时刻(默认 23:00)时触发;插件预设保持关闭。
|
||||
4. 以 agentic(ReAct)模式回答每个问题。
|
||||
5. 通过 `answer_judge` 任务对答案做二元(yes/no)评判,并输出各类型准确率。
|
||||
|
||||
|
|
@ -55,7 +67,7 @@ python benchmark/longmemeval/run.py --eval_only # 复用已有工
|
|||
| `dataset.workspace_root` | 条目工作区根目录(`benchmark/longmemeval/workspaces/longmemeval-s`)。 |
|
||||
| `evaluation.num_workers` | `0` = 自动(cpu-2),`1` = 串行,`>1` = 并行。 |
|
||||
| `evaluation.filter_future_sessions` | 仅摄入时间戳 ≤ `question_date` 的会话。 |
|
||||
| `reme.config` | 使用的 ReMe 配置(`lme.yaml`)。 |
|
||||
| `reme.config` | 使用的 ReMe 配置(`benchmark`)。 |
|
||||
| `reme.dream_trigger_hour` / `dream_scan_days` / `dream_max_units` | dream 触发行为。 |
|
||||
| `output.dir` | 结果目录(`benchmark/longmemeval/results`)。 |
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ dataset:
|
|||
workspace_root: "benchmark/longmemeval/workspaces/longmemeval-s" # workspace root for item workspaces
|
||||
|
||||
evaluation:
|
||||
# LLM-as-judge uses the 'judge' as_llm component defined in lme.yaml
|
||||
# LLM-as-judge uses the 'judge' as_llm component defined in benchmark.yaml
|
||||
# Model and credentials are configured there (reading from .env)
|
||||
# Judgment is always binary (yes/no) — defined in lme/llm_judge.yaml
|
||||
num_workers: 32 # 0 = auto (cpu_count - 2, min 1); 1 = sequential; >1 = parallel
|
||||
|
|
@ -18,7 +18,7 @@ evaluation:
|
|||
compress_session: false # true = compress session chunks in search_v2 (query-aware); false = no compression
|
||||
|
||||
reme:
|
||||
config: "lme.yaml" # reme config to use (in reme/config/)
|
||||
config: "benchmark" # shared ReMe benchmark preset; runner enables the installed lme plugin
|
||||
# Dream trigger: when gap between consecutive sessions crosses this hour (23:00)
|
||||
dream_trigger_hour: 23
|
||||
# Dream scan_days for each trigger
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import yaml
|
|||
from dotenv import load_dotenv
|
||||
|
||||
# Load .env from project root
|
||||
_PROJECT_ROOT = Path(__file__).parent.parent.parent
|
||||
_PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
|
||||
load_dotenv(_PROJECT_ROOT / ".env")
|
||||
|
||||
# Workspace root for evaluation items — read from config.yaml (dataset.workspace_root)
|
||||
|
|
@ -150,6 +150,22 @@ def load_eval_config(config_path: str | None = None) -> dict:
|
|||
return yaml.safe_load(raw)
|
||||
|
||||
|
||||
def create_reme_app(config: str = "benchmark", **overrides):
|
||||
"""Create an app with the installed LongMemEval plugin explicitly enabled.
|
||||
|
||||
Plugin discovery remains environment-based; editable installation keeps local
|
||||
plugin source changes visible to every multiprocessing worker.
|
||||
"""
|
||||
from reme import Application
|
||||
from reme.config import resolve_app_config
|
||||
|
||||
enabled_plugins = list(overrides.pop("plugins", ()) or ())
|
||||
if "lme" not in enabled_plugins:
|
||||
enabled_plugins.append("lme")
|
||||
app_config = resolve_app_config(config=config, plugins=enabled_plugins, **overrides)
|
||||
return Application(**app_config)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Date utilities
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -257,8 +273,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
|
|||
using the existing workspace. Useful for re-evaluating different query
|
||||
configurations without re-ingesting sessions.
|
||||
"""
|
||||
from reme import Application
|
||||
from reme.config import resolve_app_config
|
||||
from reme.utils.evaluation_interface import track_agent_token_usage, track_job_counts
|
||||
|
||||
reme_cfg = eval_config["reme"]
|
||||
|
|
@ -325,7 +339,7 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
|
|||
force_init=True,
|
||||
)
|
||||
|
||||
cfg = resolve_app_config(
|
||||
app = create_reme_app(
|
||||
config=reme_cfg["config"],
|
||||
workspace_dir=workspace_dir,
|
||||
log_to_console=output_cfg.get("log_to_console", True),
|
||||
|
|
@ -333,7 +347,6 @@ async def evaluate_item(item: dict, eval_config: dict, item_index: int, eval_onl
|
|||
enable_logo=False,
|
||||
)
|
||||
|
||||
app = Application(**cfg)
|
||||
await app.start()
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ removed"). They need the executed tool calls in the trace. The pipeline:
|
|||
| user_agent / judger models | `config/models/reme.yaml` |
|
||||
| Agent system prompt | `bridge_reme.py` `build_system_prompt()` |
|
||||
| Memory retrieval limit/threshold | `--search-limit/--search-min-score` on the bridge command in `run_persona.sh` |
|
||||
| ReMe internal parameters | **Do not modify ReMe source**; write a dedicated config modeled on `reme/config/beam.yaml` and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) |
|
||||
| ReMe internal parameters | **Do not modify ReMe source**; extend the built-in `benchmark` config and override via `resolve_app_config(config=...)` (see bridge `_init_reme_app`) |
|
||||
| Turn timeout / tool iteration cap | `config/models/reme.yaml` `run.turn_timeout`, `model.max_tool_iterations` |
|
||||
|
||||
## 11. Troubleshooting
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ grep -h "overall_average_score\|overall_proactiveness" \
|
|||
| user_agent / judger 模型 | `config/models/reme.yaml` |
|
||||
| agent system prompt | `bridge_reme.py` `build_system_prompt()` |
|
||||
| 记忆检索条数/阈值 | `run_persona.sh` bridge 启动命令的 `--search-limit/--search-min-score` |
|
||||
| ReMe 内部参数 | **不要改 ReMe 源码**;仿照 `reme/config/beam.yaml` 写专有配置,经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) |
|
||||
| ReMe 内部参数 | **不要改 ReMe 源码**;继承内置 `benchmark` 配置,并经 `resolve_app_config(config=...)` 覆盖(见 bridge `_init_reme_app`) |
|
||||
| 轮超时/工具迭代上限 | `config/models/reme.yaml` `run.turn_timeout`、`model.max_tool_iterations` |
|
||||
|
||||
## 11. 故障排查
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
> Code: [https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
|
||||
|
||||
<p align="center">
|
||||
<img src="gitcha.png" alt="ExpG challenges and overview" width="85%">
|
||||
<img src="./gitcha.png" alt="ExpG challenges and overview" width="85%">
|
||||
</p>
|
||||
|
||||
### Overview
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
> 代码:[https://github.com/WangCan1178/ExpG](https://github.com/WangCan1178/ExpG)
|
||||
|
||||
<p align="center">
|
||||
<img src="gitcha.png" alt="ExpG 挑战与概览" width="85%">
|
||||
<img src="./gitcha.png" alt="ExpG 挑战与概览" width="85%">
|
||||
</p>
|
||||
|
||||
### 简介
|
||||
|
|
|
|||
344
docs/.vitepress/config.mts
Normal file
344
docs/.vitepress/config.mts
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig, type DefaultTheme } from "vitepress";
|
||||
import { legacyRoutes } from "./legacy-routes.mjs";
|
||||
|
||||
const sourceRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repositoryRoot = path.resolve(sourceRoot, "../../..");
|
||||
const repository = "https://github.com/agentscope-ai/ReMe";
|
||||
const base = process.env.DOCS_BASE || "/";
|
||||
|
||||
function readSourceMap(): Record<string, string> {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(path.join(sourceRoot, ".source-map.json"), "utf8"));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const sourceMap = readSourceMap();
|
||||
|
||||
function collectMarkdown(directory: string, root = directory): string[] {
|
||||
const files: string[] = [];
|
||||
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
||||
if (entry.name.startsWith(".") || entry.name === "public" || entry.name === "figure") continue;
|
||||
const absolute = path.join(directory, entry.name);
|
||||
if (entry.isDirectory()) files.push(...collectMarkdown(absolute, root));
|
||||
else if (entry.name.endsWith(".md")) files.push(path.relative(root, absolute).replaceAll(path.sep, "/"));
|
||||
}
|
||||
return files.sort();
|
||||
}
|
||||
|
||||
function buildLlmsFiles(outDir: string) {
|
||||
const pages = collectMarkdown(sourceRoot);
|
||||
const index = [
|
||||
"# ReMe Documentation",
|
||||
"",
|
||||
"> Local-first, file-native memory for agents.",
|
||||
"",
|
||||
...pages.map((relativePath) => {
|
||||
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
|
||||
const title = source.match(/^#\s+(.+)$/m)?.[1]
|
||||
|| source.match(/^title:\s*(.+)$/m)?.[1]
|
||||
|| path.basename(relativePath, ".md");
|
||||
const route = relativePath.replace(/(?:^|\/)index\.md$/, "").replace(/\.md$/, "");
|
||||
return `- [${title}](https://reme.agentscope.io/${route})`;
|
||||
}),
|
||||
"",
|
||||
];
|
||||
fs.writeFileSync(path.join(outDir, "llms.txt"), index.join("\n"), "utf8");
|
||||
|
||||
const full = ["# ReMe Documentation", ""];
|
||||
for (const relativePath of pages) {
|
||||
const source = fs.readFileSync(path.join(sourceRoot, relativePath), "utf8");
|
||||
full.push(`<!-- source: ${sourcePathFor(relativePath)} -->`, "", source, "", "---", "");
|
||||
const pageDir = path.join(outDir, relativePath.replace(/\.md$/, ""));
|
||||
fs.mkdirSync(pageDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(pageDir, "llms.txt"), source, "utf8");
|
||||
}
|
||||
fs.writeFileSync(path.join(outDir, "llms-full.txt"), full.join("\n"), "utf8");
|
||||
}
|
||||
|
||||
function sourcePathFor(relativePath: string) {
|
||||
return sourceMap[relativePath] || `docs/${relativePath}`;
|
||||
}
|
||||
|
||||
function sourceLastUpdated(relativePath: string): number | undefined {
|
||||
const sourcePath = sourcePathFor(relativePath);
|
||||
try {
|
||||
const timestamp = execFileSync("git", ["log", "-1", "--format=%ct", "--", sourcePath], {
|
||||
cwd: repositoryRoot,
|
||||
encoding: "utf8",
|
||||
}).trim();
|
||||
if (timestamp) return Number(timestamp) * 1000;
|
||||
} catch {
|
||||
// Fall back to the canonical file timestamp outside a Git checkout.
|
||||
}
|
||||
try {
|
||||
return fs.statSync(path.join(repositoryRoot, sourcePath)).mtimeMs;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const legacyRedirectScript = `(() => {
|
||||
const routes = ${JSON.stringify(legacyRoutes)};
|
||||
const id = new URLSearchParams(window.location.search).get("doc");
|
||||
const target = id && routes[id];
|
||||
const base = ${JSON.stringify(base)};
|
||||
if (target) {
|
||||
const destination = /^https?:/.test(target)
|
||||
? target
|
||||
: base.replace(/\\/$/, "") + target;
|
||||
window.location.replace(destination + window.location.hash);
|
||||
return;
|
||||
}
|
||||
const root = base.endsWith("/") ? base : base + "/";
|
||||
if (window.location.pathname === root) {
|
||||
window.location.replace(root + "zh/" + window.location.hash);
|
||||
}
|
||||
})();`;
|
||||
|
||||
function nav(language: "zh" | "en"): DefaultTheme.NavItem[] {
|
||||
const zh = language === "zh";
|
||||
return [
|
||||
{ text: zh ? "开始使用" : "Get Started", link: `/${language}/quick_start` },
|
||||
{ text: zh ? "核心概念" : "Concepts", link: `/${language}/memory_as_file` },
|
||||
{ text: zh ? "指南" : "Guides", link: `/${language}/auto_memory` },
|
||||
{ text: zh ? "集成" : "Integrations", link: `/${language}/integrations` },
|
||||
{ text: zh ? "API 参考" : "API Reference", link: `/${language}/reference/cli` },
|
||||
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
|
||||
];
|
||||
}
|
||||
|
||||
function sidebar(language: "zh" | "en"): DefaultTheme.SidebarItem[] {
|
||||
const zh = language === "zh";
|
||||
return [
|
||||
{
|
||||
text: zh ? "开始使用" : "Get Started",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: zh ? "项目介绍" : "Introduction", link: `/${language}/` },
|
||||
{ text: zh ? "快速开始" : "Quick Start", link: `/${language}/quick_start` },
|
||||
{ text: zh ? "基础配置" : "Configuration", link: `/${language}/configuration` },
|
||||
{ text: zh ? "服务与部署" : "Services and Deployment", link: `/${language}/services` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "核心概念" : "Core Concepts",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: zh ? "文件即记忆" : "Memory as File", link: `/${language}/memory_as_file` },
|
||||
{ text: zh ? "记忆检索" : "Memory Search", link: `/${language}/memory_search` },
|
||||
{ text: zh ? "自动关联" : "Auto Link", link: `/${language}/auto_link` },
|
||||
{ text: zh ? "应用场景" : "Application Scenarios", link: `/${language}/reme_scene` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "记忆工作流" : "Memory Workflows",
|
||||
collapsed: false,
|
||||
items: [
|
||||
{ text: "Auto Memory", link: `/${language}/auto_memory` },
|
||||
{ text: "Auto Resource", link: `/${language}/auto_resource` },
|
||||
{ text: "Auto Dream", link: `/${language}/auto_dream` },
|
||||
{ text: "Proactive", link: `/${language}/proactive` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "Agent 集成" : "Agent Integrations",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: zh ? "集成总览" : "Overview", link: `/${language}/integrations` },
|
||||
{ text: "Claude Code", link: `/${language}/integrations/claude-code` },
|
||||
{ text: "Hermes Agent", link: `/${language}/integrations/hermes` },
|
||||
{ text: zh ? "TypeScript 客户端" : "TypeScript Client", link: `/${language}/integrations/typescript` },
|
||||
{ text: "DeepSeek Harness", link: `/${language}/integrations/dsh` },
|
||||
{ text: "OpenClaw", link: `/${language}/integrations/openclaw` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "工作区与插件" : "Workspace and Plugins",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "ReMe Studio", link: `/${language}/workspace/studio` },
|
||||
{ text: zh ? "插件管理" : "Plugin Management", link: `/${language}/plugin_management` },
|
||||
{ text: zh ? "插件开发" : "Plugin Development", link: `/${language}/plugin_development` },
|
||||
{ text: zh ? "每日论文" : "Daily Paper", link: `/${language}/plugins/daily-paper` },
|
||||
{ text: "Auto Fin", link: `/${language}/plugins/auto-fin` },
|
||||
{ text: "LME", link: `/${language}/plugins/lme` },
|
||||
{ text: "BEAM", link: `/${language}/plugins/beam` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "API 参考" : "API Reference",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "CLI", link: `/${language}/reference/cli` },
|
||||
{ text: zh ? "Job API" : "Job API", link: `/${language}/reference/jobs` },
|
||||
{ text: "HTTP / MCP", link: `/${language}/services#http-api` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "运维" : "Operations",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: zh ? "诊断、备份与恢复" : "Diagnostics, Backup, and Recovery", link: `/${language}/operations` },
|
||||
{ text: zh ? "常见问题" : "FAQ", link: `/${language}/faq` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "开发者" : "Development",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: zh ? "代码框架" : "Framework", link: `/${language}/framework` },
|
||||
{ text: zh ? "开源与贡献" : "Contributing", link: `/${language}/contributing` },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: zh ? "评测" : "Benchmarks",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "BEAM", link: `/${language}/benchmarks/beam` },
|
||||
{ text: "LongMemEval", link: `/${language}/benchmarks/longmemeval` },
|
||||
{ text: "π-Bench", link: `/${language}/benchmarks/pibench` },
|
||||
{ text: "Tool Memory / ExpG", link: `/${language}/benchmarks/toolmemory` },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function configureRepositoryLinks(md: any) {
|
||||
for (const ruleName of ["link_open", "image"] as const) {
|
||||
const original = md.renderer.rules[ruleName];
|
||||
md.renderer.rules[ruleName] = (tokens: any[], index: number, options: any, env: any, self: any) => {
|
||||
const attribute = ruleName === "image" ? "src" : "href";
|
||||
const token = tokens[index];
|
||||
const attributeIndex = token.attrIndex(attribute);
|
||||
const target = attributeIndex >= 0 ? token.attrs[attributeIndex][1] : "";
|
||||
if (target && !/^(?:[a-z]+:|#|\/)/i.test(target)) {
|
||||
const cleanTarget = target.split("#")[0].split("?")[0];
|
||||
const generatedTarget = path.resolve(sourceRoot, path.dirname(env.relativePath), cleanTarget);
|
||||
if (!fs.existsSync(generatedTarget)) {
|
||||
const originalPage = sourcePathFor(env.relativePath);
|
||||
const originalTarget = path.posix.normalize(path.posix.join(path.posix.dirname(originalPage), cleanTarget));
|
||||
const suffix = target.slice(cleanTarget.length);
|
||||
const url = ruleName === "image"
|
||||
? `https://raw.githubusercontent.com/agentscope-ai/ReMe/main/${originalTarget}${suffix}`
|
||||
: `${repository}/blob/main/${originalTarget}${suffix}`;
|
||||
token.attrs[attributeIndex][1] = url;
|
||||
}
|
||||
}
|
||||
return original ? original(tokens, index, options, env, self) : self.renderToken(tokens, index, options);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
lang: "zh-CN",
|
||||
title: "ReMe",
|
||||
description: "Local-first, file-native memory for agents",
|
||||
base,
|
||||
cleanUrls: true,
|
||||
lastUpdated: true,
|
||||
ignoreDeadLinks: [/^http:\/\/localhost(?::\d+)?(?:\/|$)/],
|
||||
sitemap: {
|
||||
hostname: "https://reme.agentscope.io",
|
||||
transformItems(items) {
|
||||
const isRoot = (url: string) => url.replace(/^\/+|\/+$/g, "") === "";
|
||||
return items.filter((item) => !isRoot(item.url)).map((item) => {
|
||||
const route = item.url.replace(/^\/+/, "");
|
||||
const relativePath = !route || route.endsWith("/") ? `${route}index.md` : `${route}.md`;
|
||||
const links = item.links?.filter((link) => !isRoot(link.url));
|
||||
return { ...item, links, lastmod: sourceLastUpdated(relativePath) };
|
||||
});
|
||||
},
|
||||
},
|
||||
head: [
|
||||
["link", { rel: "icon", type: "image/svg+xml", href: `${base}reme-icon.svg` }],
|
||||
["meta", { name: "theme-color", content: "#087f6a" }],
|
||||
["script", {}, legacyRedirectScript],
|
||||
],
|
||||
markdown: {
|
||||
config: configureRepositoryLinks,
|
||||
},
|
||||
transformPageData(pageData, { siteConfig }) {
|
||||
const sourcePath = path.join(siteConfig.srcDir, pageData.relativePath);
|
||||
pageData.frontmatter._sourcePath = sourcePathFor(pageData.relativePath);
|
||||
pageData.lastUpdated = sourceLastUpdated(pageData.relativePath);
|
||||
try {
|
||||
pageData.frontmatter._rawMarkdown = fs.readFileSync(sourcePath, "utf8");
|
||||
} catch {
|
||||
pageData.frontmatter._rawMarkdown = "";
|
||||
}
|
||||
},
|
||||
buildEnd(siteConfig) {
|
||||
buildLlmsFiles(siteConfig.outDir);
|
||||
},
|
||||
themeConfig: {
|
||||
logo: "/reme-icon.svg",
|
||||
siteTitle: "ReMe",
|
||||
nav: [
|
||||
...nav("zh"),
|
||||
{
|
||||
text: "语言",
|
||||
items: [
|
||||
{ text: "简体中文", link: "/zh/" },
|
||||
{ text: "English", link: "/en/" },
|
||||
],
|
||||
},
|
||||
],
|
||||
outline: { label: "页面导航", level: [2, 3] },
|
||||
search: {
|
||||
provider: "local",
|
||||
options: {
|
||||
locales: {
|
||||
zh: {
|
||||
translations: {
|
||||
button: { buttonText: "搜索文档", buttonAriaLabel: "搜索文档" },
|
||||
modal: {
|
||||
noResultsText: "没有找到相关内容",
|
||||
resetButtonTitle: "清除查询",
|
||||
footer: { selectText: "选择", navigateText: "切换", closeText: "关闭" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
socialLinks: [{ icon: "github", link: repository }],
|
||||
footer: {
|
||||
message: "Released under the Apache-2.0 License.",
|
||||
copyright: "Copyright ReMe contributors",
|
||||
},
|
||||
},
|
||||
locales: {
|
||||
zh: {
|
||||
label: "简体中文",
|
||||
lang: "zh-CN",
|
||||
link: "/zh/",
|
||||
themeConfig: {
|
||||
nav: nav("zh"),
|
||||
sidebar: { "/zh/": sidebar("zh") },
|
||||
outline: { label: "页面导航", level: [2, 3] },
|
||||
docFooter: { prev: "上一页", next: "下一页" },
|
||||
darkModeSwitchLabel: "外观",
|
||||
sidebarMenuLabel: "菜单",
|
||||
returnToTopLabel: "返回顶部",
|
||||
langMenuLabel: "切换语言",
|
||||
},
|
||||
},
|
||||
en: {
|
||||
label: "English",
|
||||
lang: "en-US",
|
||||
link: "/en/",
|
||||
themeConfig: {
|
||||
nav: nav("en"),
|
||||
sidebar: { "/en/": sidebar("en") },
|
||||
outline: { label: "On this page", level: [2, 3] },
|
||||
docFooter: { prev: "Previous page", next: "Next page" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
47
docs/.vitepress/legacy-routes.mjs
Normal file
47
docs/.vitepress/legacy-routes.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export const legacyRoutes = {
|
||||
"readme-zh": "/zh/",
|
||||
"readme-en": "/en/",
|
||||
"zh-quick_start": "/zh/quick_start",
|
||||
"en-quick_start": "/en/quick_start",
|
||||
"zh-plugin_management": "/zh/plugin_management",
|
||||
"en-plugin_management": "/en/plugin_management",
|
||||
"zh-memory_as_file": "/zh/memory_as_file",
|
||||
"en-memory_as_file": "/en/memory_as_file",
|
||||
"zh-memory_search": "/zh/memory_search",
|
||||
"en-memory_search": "/en/memory_search",
|
||||
"zh-auto_memory": "/zh/auto_memory",
|
||||
"en-auto_memory": "/en/auto_memory",
|
||||
"zh-auto_resource": "/zh/auto_resource",
|
||||
"en-auto_resource": "/en/auto_resource",
|
||||
"zh-auto_link": "/zh/auto_link",
|
||||
"en-auto_link": "/en/auto_link",
|
||||
"zh-auto_dream": "/zh/auto_dream",
|
||||
"en-auto_dream": "/en/auto_dream",
|
||||
"zh-proactive": "/zh/proactive",
|
||||
"en-proactive": "/en/proactive",
|
||||
"zh-reme_scene": "/zh/reme_scene",
|
||||
"en-reme_scene": "/en/reme_scene",
|
||||
"zh-framework": "/zh/framework",
|
||||
"en-framework": "/en/framework",
|
||||
"zh-reme-blog": "/zh/reme-blog",
|
||||
"en-reme-blog": "/en/reme-blog",
|
||||
"zh-contributing": "/zh/contributing",
|
||||
"en-contributing": "/en/contributing",
|
||||
"typescript-zh": "/zh/integrations/typescript",
|
||||
"typescript-en": "/en/integrations/typescript",
|
||||
"studio-zh": "/zh/workspace/studio",
|
||||
"studio-en": "/en/workspace/studio",
|
||||
"daily-paper-zh": "/zh/plugins/daily-paper",
|
||||
"daily-paper-en": "/en/plugins/daily-paper",
|
||||
"auto-fin-zh": "/zh/plugins/auto-fin",
|
||||
"auto-fin-en": "/en/plugins/auto-fin",
|
||||
"beam-zh": "/zh/benchmarks/beam",
|
||||
"beam-en": "/en/benchmarks/beam",
|
||||
"longmemeval-zh": "/zh/benchmarks/longmemeval",
|
||||
"longmemeval-en": "/en/benchmarks/longmemeval",
|
||||
"pibench-zh": "/zh/benchmarks/pibench",
|
||||
"pibench-en": "/en/benchmarks/pibench",
|
||||
"toolmemory-zh": "/zh/benchmarks/toolmemory",
|
||||
"toolmemory-en": "/en/benchmarks/toolmemory",
|
||||
"agents-guide": "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md",
|
||||
};
|
||||
32
docs/.vitepress/theme/CopyMarkdownButton.vue
Normal file
32
docs/.vitepress/theme/CopyMarkdownButton.vue
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { useData } from "vitepress";
|
||||
|
||||
const { frontmatter, lang } = useData();
|
||||
const copied = ref(false);
|
||||
const label = computed(() => {
|
||||
if (copied.value) return lang.value.startsWith("zh") ? "已复制" : "Copied";
|
||||
return lang.value.startsWith("zh") ? "复制 Markdown" : "Copy Markdown";
|
||||
});
|
||||
|
||||
async function copyMarkdown() {
|
||||
const markdown = String(frontmatter.value._rawMarkdown || "");
|
||||
if (!markdown) return;
|
||||
await navigator.clipboard.writeText(markdown);
|
||||
copied.value = true;
|
||||
window.setTimeout(() => { copied.value = false; }, 1800);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="copy-markdown-wrap">
|
||||
<button class="copy-markdown" type="button" :class="{ copied }" @click="copyMarkdown">
|
||||
<svg v-if="!copied" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
<svg v-else viewBox="0 0 24 24" aria-hidden="true"><path d="m5 12 4 4L19 6" /></svg>
|
||||
{{ label }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
15
docs/.vitepress/theme/SourceLink.vue
Normal file
15
docs/.vitepress/theme/SourceLink.vue
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useData } from "vitepress";
|
||||
|
||||
const { frontmatter, lang } = useData();
|
||||
const sourcePath = computed(() => String(frontmatter.value._sourcePath || ""));
|
||||
const label = computed(() => lang.value.startsWith("zh") ? "在 GitHub 查看源文件" : "View source on GitHub");
|
||||
const href = computed(() => `https://github.com/agentscope-ai/ReMe/blob/main/${sourcePath.value}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="sourcePath" class="source-link-wrap">
|
||||
<a :href="href" target="_blank" rel="noreferrer">{{ label }} ↗</a>
|
||||
</div>
|
||||
</template>
|
||||
336
docs/.vitepress/theme/custom.css
Normal file
336
docs/.vitepress/theme/custom.css
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
:root {
|
||||
--vp-layout-max-width: 1560px;
|
||||
--vp-c-brand-1: #087f6a;
|
||||
--vp-c-brand-2: #086554;
|
||||
--vp-c-brand-3: #19a98f;
|
||||
--vp-c-brand-soft: rgba(8, 127, 106, 0.14);
|
||||
--vp-c-bg: #ffffff;
|
||||
--vp-c-bg-alt: #f4f7f5;
|
||||
--vp-c-bg-elv: #ffffff;
|
||||
--vp-c-bg-soft: #f1f6f3;
|
||||
--vp-c-text-1: #17221d;
|
||||
--vp-c-text-2: #526159;
|
||||
--vp-c-text-3: #718078;
|
||||
--vp-c-divider: #dce5e0;
|
||||
--vp-font-family-base: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
--vp-font-family-mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
|
||||
--reme-blue: #3156d9;
|
||||
--reme-green: #087f6a;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--vp-c-brand-1: #57dfc3;
|
||||
--vp-c-brand-2: #35c6a9;
|
||||
--vp-c-brand-3: #087f6a;
|
||||
--vp-c-brand-soft: rgba(87, 223, 195, 0.14);
|
||||
--vp-c-bg: #0d1512;
|
||||
--vp-c-bg-alt: #09100d;
|
||||
--vp-c-bg-elv: #14201b;
|
||||
--vp-c-bg-soft: #17251f;
|
||||
--vp-c-text-1: #edf7f3;
|
||||
--vp-c-text-2: #bacbc4;
|
||||
--vp-c-text-3: #91a49c;
|
||||
--vp-c-divider: #283a33;
|
||||
}
|
||||
|
||||
body {
|
||||
background:
|
||||
radial-gradient(circle at 8% 8%, rgba(25, 201, 176, 0.055), transparent 28rem),
|
||||
var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.VPNav {
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--vp-c-divider) 84%, transparent);
|
||||
background: color-mix(in srgb, var(--vp-c-bg) 88%, transparent);
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
}
|
||||
|
||||
.VPNavBarTitle .logo {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.VPNavBarTitle .title {
|
||||
font-weight: 780;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.VPNavBarSearch .DocSearch-Button,
|
||||
.VPNavBarSearch button {
|
||||
min-width: 190px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
background: var(--vp-c-bg-alt);
|
||||
}
|
||||
|
||||
.VPSidebar {
|
||||
border-right: 1px solid var(--vp-c-divider);
|
||||
background: color-mix(in srgb, var(--vp-c-bg-alt) 82%, transparent);
|
||||
}
|
||||
|
||||
.VPSidebarItem .text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.VPSidebarItem.level-0 > .item > .text {
|
||||
color: var(--vp-c-text-1);
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.VPSidebarItem.is-active > .item .link > .text {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPDocAsideOutline {
|
||||
border-left-color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.VPDoc .container > .content {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.VPDoc .content-container {
|
||||
max-width: 900px !important;
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 16px;
|
||||
line-height: 1.78;
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
margin-bottom: 28px;
|
||||
font-size: clamp(36px, 5vw, 52px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.045em;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
margin-top: 52px;
|
||||
border-top-color: var(--vp-c-divider);
|
||||
font-size: 27px;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.vp-doc h3 {
|
||||
margin-top: 34px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.vp-doc :not(pre) > code {
|
||||
border-radius: 5px;
|
||||
color: color-mix(in srgb, var(--vp-c-brand-1) 82%, var(--vp-c-text-1));
|
||||
}
|
||||
|
||||
.vp-doc div[class*="language-"] {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
box-shadow: inset 3px 0 0 color-mix(in srgb, var(--vp-c-brand-1) 62%, transparent);
|
||||
}
|
||||
|
||||
.copy-markdown-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.copy-markdown {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
min-height: 34px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 9px;
|
||||
color: var(--vp-c-text-2);
|
||||
background: var(--vp-c-bg-soft);
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.copy-markdown:hover,
|
||||
.copy-markdown.copied {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.copy-markdown svg {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.source-link-wrap {
|
||||
margin-top: 48px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.source-link-wrap a {
|
||||
color: var(--vp-c-text-3);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.source-link-wrap a:hover {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPHome {
|
||||
background:
|
||||
radial-gradient(circle at 14% 14%, rgba(25, 201, 176, 0.14), transparent 30rem),
|
||||
radial-gradient(circle at 86% 10%, rgba(49, 86, 217, 0.11), transparent 28rem);
|
||||
}
|
||||
|
||||
.VPHero .name {
|
||||
background: linear-gradient(120deg, var(--reme-green), var(--reme-blue));
|
||||
background-clip: text;
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.VPHero .name,
|
||||
.VPHero .text {
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.VPHero .image-bg {
|
||||
width: min(88%, 420px);
|
||||
height: 220px;
|
||||
border-radius: 42%;
|
||||
background: linear-gradient(125deg, rgba(25, 201, 176, 0.34), rgba(49, 86, 217, 0.25));
|
||||
filter: blur(52px);
|
||||
}
|
||||
|
||||
.VPHero .image-container {
|
||||
isolation: isolate;
|
||||
perspective: 900px;
|
||||
}
|
||||
|
||||
.VPHero .image-container::before,
|
||||
.VPHero .image-container::after {
|
||||
position: absolute;
|
||||
content: "";
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.VPHero .image-container::before {
|
||||
z-index: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: min(88%, 430px);
|
||||
height: 210px;
|
||||
border: 1px solid color-mix(in srgb, var(--vp-c-bg-elv) 64%, var(--reme-blue));
|
||||
border-radius: 30px;
|
||||
background:
|
||||
linear-gradient(135deg, color-mix(in srgb, var(--vp-c-bg-elv) 92%, transparent), color-mix(in srgb, var(--vp-c-bg-soft) 76%, transparent)),
|
||||
radial-gradient(circle at 15% 15%, rgba(25, 201, 176, 0.14), transparent 42%);
|
||||
box-shadow:
|
||||
0 30px 70px rgba(19, 70, 91, 0.16),
|
||||
inset 0 1px 0 color-mix(in srgb, white 72%, transparent);
|
||||
backdrop-filter: blur(22px) saturate(135%);
|
||||
transform: translate(-50%, -50%) rotate(-1.5deg);
|
||||
}
|
||||
|
||||
.VPHero .image-container::after {
|
||||
z-index: -1;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: min(78%, 380px);
|
||||
height: 210px;
|
||||
border: 1px solid rgba(49, 86, 217, 0.17);
|
||||
border-radius: 30px;
|
||||
background: linear-gradient(135deg, rgba(25, 201, 176, 0.1), rgba(49, 86, 217, 0.11));
|
||||
transform: translate(-46%, -48%) rotate(7deg);
|
||||
}
|
||||
|
||||
.VPHero .image-src {
|
||||
z-index: 1;
|
||||
width: min(76%, 380px);
|
||||
max-width: 380px !important;
|
||||
max-height: 150px !important;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 12px 20px rgba(18, 78, 105, 0.16));
|
||||
}
|
||||
|
||||
.VPHomeFeatures .item:nth-child(1) { --feature-accent: #18b99e; }
|
||||
.VPHomeFeatures .item:nth-child(2) { --feature-accent: #25a8dc; }
|
||||
.VPHomeFeatures .item:nth-child(3) { --feature-accent: #6575e8; }
|
||||
.VPHomeFeatures .item:nth-child(4) { --feature-accent: #e69a42; }
|
||||
|
||||
.VPHomeFeatures .VPFeature {
|
||||
border-color: color-mix(in srgb, var(--feature-accent) 28%, var(--vp-c-divider));
|
||||
border-radius: 16px;
|
||||
background:
|
||||
radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 16%, transparent), transparent 52%),
|
||||
linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 7%, var(--vp-c-bg-soft)));
|
||||
box-shadow:
|
||||
inset 0 1px 0 color-mix(in srgb, white 76%, transparent),
|
||||
0 8px 24px color-mix(in srgb, var(--feature-accent) 7%, transparent);
|
||||
transition: transform 160ms ease, border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature .box {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
grid-template-rows: auto 1fr;
|
||||
column-gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature .icon {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature .title {
|
||||
grid-column: 2;
|
||||
grid-row: 1;
|
||||
color: color-mix(in srgb, var(--feature-accent) 22%, var(--vp-c-text-1));
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature .details {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: 2;
|
||||
align-self: start;
|
||||
padding-top: 18px;
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature .link-text {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.VPHomeFeatures .VPFeature:hover {
|
||||
transform: translateY(-3px);
|
||||
border-color: color-mix(in srgb, var(--feature-accent) 48%, var(--vp-c-divider));
|
||||
box-shadow: 0 18px 38px color-mix(in srgb, var(--feature-accent) 15%, transparent);
|
||||
}
|
||||
|
||||
.dark .VPHomeFeatures .VPFeature {
|
||||
background:
|
||||
radial-gradient(circle at 10% 4%, color-mix(in srgb, var(--feature-accent) 18%, transparent), transparent 54%),
|
||||
linear-gradient(150deg, var(--vp-c-bg-elv), color-mix(in srgb, var(--feature-accent) 8%, var(--vp-c-bg-soft)));
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.vp-doc h1 { font-size: 34px; }
|
||||
.vp-doc h2 { margin-top: 44px; font-size: 24px; }
|
||||
.copy-markdown-wrap { margin-top: -8px; }
|
||||
.VPHero .image-container::before,
|
||||
.VPHero .image-container::after { height: 176px; border-radius: 24px; }
|
||||
.VPHero .image-src { width: 72%; max-height: 120px !important; }
|
||||
}
|
||||
15
docs/.vitepress/theme/index.ts
Normal file
15
docs/.vitepress/theme/index.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { h } from "vue";
|
||||
import DefaultTheme from "vitepress/theme";
|
||||
import CopyMarkdownButton from "./CopyMarkdownButton.vue";
|
||||
import SourceLink from "./SourceLink.vue";
|
||||
import "./custom.css";
|
||||
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout() {
|
||||
return h(DefaultTheme.Layout, null, {
|
||||
"doc-before": () => h(CopyMarkdownButton),
|
||||
"doc-after": () => h(SourceLink),
|
||||
});
|
||||
},
|
||||
};
|
||||
157
docs/en/configuration.md
Normal file
157
docs/en/configuration.md
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
---
|
||||
title: Configuration
|
||||
description: ReMe configuration files, environment expansion, command-line overrides, and core components.
|
||||
---
|
||||
|
||||
# Configuration
|
||||
|
||||
ReMe uses YAML or JSON to describe its Service, Jobs, and Components. The built-in default is `reme/config/default.yaml`. Select another configuration at startup and apply command-line overrides when needed.
|
||||
|
||||
## Precedence
|
||||
|
||||
Configuration is merged in this order, with later values winning:
|
||||
|
||||
1. `application_defaults` from enabled plugins.
|
||||
2. The selected file; `default` is used when none is specified.
|
||||
3. CLI dot-notation overrides.
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start config=demo
|
||||
reme start config=/absolute/path/to/app.yaml
|
||||
reme start service.port=8181 workspace_dir=/data/reme
|
||||
```
|
||||
|
||||
`config` accepts a built-in name or a `.yaml`, `.yml`, or `.json` file. Overrides are deep-merged, so changing `service.port` preserves sibling service settings.
|
||||
|
||||
## CLI values
|
||||
|
||||
Arguments use `key=value`; leading `-` or `--` is accepted:
|
||||
|
||||
```bash
|
||||
reme start --service.port=8181 --service.web_enabled=false
|
||||
```
|
||||
|
||||
Values support null, booleans, numbers, JSON arrays and objects, quoted JSON strings, and plain strings. Numeric-looking values with leading zeroes, such as `007`, remain strings. Quote values such as `"true"` in JSON when they must remain strings.
|
||||
|
||||
## Environment variables
|
||||
|
||||
Configuration recursively expands:
|
||||
|
||||
```yaml
|
||||
api_key: ${LLM_API_KEY}
|
||||
base_url: ${LLM_BASE_URL:-https://example.com/v1}
|
||||
```
|
||||
|
||||
`${VAR}` fails when undefined; `${VAR:-default}` uses its fallback. ReMe also searches for `.env` from the command's working directory through at most five parents.
|
||||
|
||||
Keep secrets in `.env` or the process environment, never in committed configuration.
|
||||
|
||||
## Application fields
|
||||
|
||||
| Field | Default | Purpose |
|
||||
|---|---|---|
|
||||
| `app_name` | `ReMe` | Display name |
|
||||
| `workspace_dir` | `.reme` | User-owned workspace root, normalized to an absolute path |
|
||||
| `metadata_dir` | `metadata` | Rebuildable indexes, graphs, and catalogs |
|
||||
| `session_dir` | `session` | Agent sessions; standard transcripts use `session/dialog` |
|
||||
| `mem_session_dir` | `mem_session` | Agent-wrapper sessions and configuration |
|
||||
| `resource_dir` | `resource` | External resources |
|
||||
| `daily_dir` | `daily` | Daily memory |
|
||||
| `digest_dir` | `digest` | Consolidated long-term memory |
|
||||
| `timezone` | `Asia/Shanghai` | IANA timezone used for dates and cron jobs |
|
||||
| `language` | empty | Default language for LLM interactions |
|
||||
| `plugins` | `[]` | Installed plugins enabled for this Application |
|
||||
| `service` | HTTP | Service configuration |
|
||||
| `jobs` | default Jobs | Job configurations by name |
|
||||
| `components` | defaults | Components grouped by type and name |
|
||||
|
||||
`session_dir` must remain workspace-relative.
|
||||
|
||||
## LLM
|
||||
|
||||
The default LLM uses an OpenAI-compatible interface:
|
||||
|
||||
```yaml
|
||||
components:
|
||||
as_llm:
|
||||
default:
|
||||
backend: openai
|
||||
model: qwen3.7-plus
|
||||
context_size: 200000
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
```
|
||||
|
||||
Built-in registrations include `openai`, `anthropic`, `dashscope`, `deepseek`, `gemini`, `moonshot`, `ollama`, and `xai`. Their detailed model fields follow the corresponding AgentScope wrappers.
|
||||
|
||||
File operations, BM25 search, and wikilink traversal do not require an LLM. Evolution workflows such as `auto_memory`, `auto_resource`, and `auto_dream` do.
|
||||
|
||||
## Embeddings
|
||||
|
||||
Vector retrieval is disabled by default. Credentials alone do not enable it: configure `as_embedding`, `embedding_store`, and connect the store to `file_store`.
|
||||
|
||||
```yaml
|
||||
components:
|
||||
as_embedding:
|
||||
default:
|
||||
backend: openai
|
||||
model: text-embedding-v4
|
||||
dimensions: 1024
|
||||
credential:
|
||||
api_key: ${EMBEDDING_API_KEY}
|
||||
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
|
||||
embedding_store:
|
||||
default:
|
||||
backend: local
|
||||
as_embedding: default
|
||||
file_store:
|
||||
default:
|
||||
backend: local
|
||||
embedding_store: default
|
||||
keyword_index: default
|
||||
file_graph: default
|
||||
```
|
||||
|
||||
Rebuild the embedding index after changing the model or dimensions.
|
||||
|
||||
## Service and Jobs
|
||||
|
||||
Minimal HTTP configuration:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
host: 127.0.0.1
|
||||
port: 2333
|
||||
web_enabled: true
|
||||
mcp_enabled: true
|
||||
mcp_path: /mcp
|
||||
```
|
||||
|
||||
A Job declares a backend, parameter schema, and ordered Steps:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
example:
|
||||
backend: base
|
||||
description: Example job
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text: { type: string }
|
||||
required: [text]
|
||||
steps:
|
||||
- backend: example_step
|
||||
```
|
||||
|
||||
Set `enable_serve: false` to keep a Job internal. Background and cron Jobs are never service-exposed.
|
||||
|
||||
## Inspect the effective configuration
|
||||
|
||||
```bash
|
||||
reme app_config
|
||||
```
|
||||
|
||||
The result is the merged, validated configuration with secrets redacted. Use it when diagnosing plugin or override precedence. The authoritative contracts remain `reme/schema/application_config.py` and `reme/config/default.yaml`.
|
||||
|
|
@ -209,6 +209,13 @@ Documentation lives under:
|
|||
docs/
|
||||
```
|
||||
|
||||
User guides should have matching `docs/zh/` and `docs/en/` versions and appear in the corresponding navigation in
|
||||
`docs/.vitepress/config.mts`. The ReMe Studio, TypeScript, plugin, and benchmark READMEs remain canonical in their own
|
||||
directories; `github-pages/scripts/generate-content.mjs` mirrors them during builds. Never edit `.generated/` or `dist/`.
|
||||
|
||||
The Job API reference is generated from `reme/config/default.yaml`. Update that YAML and its tests when a default Job
|
||||
contract changes rather than editing generated pages.
|
||||
|
||||
Documentation should:
|
||||
|
||||
- Use clear titles that directly identify a capability or flow.
|
||||
|
|
@ -216,6 +223,15 @@ Documentation should:
|
|||
- Use real repository paths such as `reme/config/default.yaml`, `reme/steps/`, and `tests/unit/`.
|
||||
- Describe default behavior according to the current code, `pyproject.toml`, and default configuration.
|
||||
|
||||
Validate the documentation site with:
|
||||
|
||||
```bash
|
||||
cd github-pages
|
||||
npm ci
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Getting Help
|
||||
|
|
|
|||
71
docs/en/faq.md
Normal file
71
docs/en/faq.md
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: Frequently Asked Questions
|
||||
description: Quick answers for ReMe installation, services, models, retrieval, files, and plugins.
|
||||
---
|
||||
|
||||
# Frequently Asked Questions
|
||||
|
||||
## Do basic file operations require a model API key?
|
||||
|
||||
No. `write`, `read`, `list`, `stat`, BM25 search, and wikilink traversal work without model credentials. `auto_memory`, `auto_resource`, and `auto_dream` require an LLM.
|
||||
|
||||
## Why is search still BM25-only after setting an embedding key?
|
||||
|
||||
Embeddings are disabled by default. Configure `as_embedding` and `embedding_store`, then connect `file_store.default.embedding_store` to that component. See [Configuration](./configuration.md#embeddings).
|
||||
|
||||
## Why did `reme reindex` not discover a new file?
|
||||
|
||||
`reindex` rebuilds indexes from current `file_chunks`; it does not scan the workspace. Check `index_update_loop`, the watched directory and extension, and `health_check`.
|
||||
|
||||
## How do I use another workspace?
|
||||
|
||||
```bash
|
||||
reme start workspace_dir=/absolute/path/to/memory
|
||||
```
|
||||
|
||||
Ordinary CLI calls discover the running service, so they do not need the workspace argument again.
|
||||
|
||||
## What if port 2333 is occupied?
|
||||
|
||||
Do not stop an unknown listener. Select another port:
|
||||
|
||||
```bash
|
||||
reme start service.port=8181
|
||||
```
|
||||
|
||||
Then confirm it with `reme find_reme`.
|
||||
|
||||
## Why is an installed plugin missing its Jobs?
|
||||
|
||||
Installation only makes the distribution discoverable in the active Python environment. Enable it for the Application:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
Restart a running service after changing package or enablement state.
|
||||
|
||||
## May I edit workspace Markdown directly?
|
||||
|
||||
Yes. Files are the source of truth and watchers ingest changes. Keep frontmatter valid, use complete workspace-relative wikilinks, and avoid unconditional concurrent saves.
|
||||
|
||||
## May I expose ReMe publicly?
|
||||
|
||||
Not with the default configuration alone. Jobs can write and delete, HTTP CORS is permissive, and there is no general authentication layer. Use a controlled network or authenticated TLS reverse proxy and restrict `service.jobs`.
|
||||
|
||||
## How should I back up and migrate memory?
|
||||
|
||||
Stop writes and back up the complete workspace. `session/`, `resource/`, `daily/`, and `digest/` are the key sources; `metadata/` can be backed up or rebuilt. See [Diagnostics, Backup, and Recovery](./operations.md).
|
||||
|
||||
## Why is Studio unavailable?
|
||||
|
||||
The base `reme-ai` package has no frontend assets. Install `reme-ai[web]` or `reme-ai[core]`, or set `service.web_static_dir`. Missing Studio assets do not disable the Job API.
|
||||
|
||||
## Which capabilities does the running service expose?
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme app_config
|
||||
```
|
||||
|
||||
Static documentation describes defaults; plugins and custom configuration may change the active service.
|
||||
36
docs/en/index.md
Normal file
36
docs/en/index.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: home
|
||||
title: ReMe Documentation
|
||||
titleTemplate: false
|
||||
hero:
|
||||
name: ReMe
|
||||
text: Memory that works for agents
|
||||
tagline: Files remain yours. ReMe turns conversations and resources into readable, editable, searchable, interconnected local files.
|
||||
image:
|
||||
src: /reme-logo.svg
|
||||
alt: ReMe logo
|
||||
actions:
|
||||
- theme: brand
|
||||
text: Quick Start
|
||||
link: /en/quick_start
|
||||
- theme: alt
|
||||
text: Configuration
|
||||
link: /en/configuration
|
||||
features:
|
||||
- icon: 📁
|
||||
title: Memory as files
|
||||
details: Workspace files are the durable source of truth; indexes, graphs, and caches remain rebuildable.
|
||||
link: /en/memory_as_file
|
||||
- icon: 🧠
|
||||
title: Memory workflows
|
||||
details: Turn conversations and resources into daily notes, then consolidate them into connected long-term digests.
|
||||
link: /en/auto_memory
|
||||
- icon: 🔎
|
||||
title: Search and graph
|
||||
details: Combine keyword and optional vector retrieval with wikilink graph expansion.
|
||||
link: /en/memory_search
|
||||
- icon: 🔌
|
||||
title: Agent integrations
|
||||
details: Connect existing agents through the CLI, HTTP, MCP, and host adapters.
|
||||
link: /en/integrations
|
||||
---
|
||||
63
docs/en/integrations.md
Normal file
63
docs/en/integrations.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: Agent Integrations
|
||||
description: Connect ReMe to agents through the CLI, HTTP, MCP, Skills, and host adapters.
|
||||
---
|
||||
|
||||
# Agent Integrations
|
||||
|
||||
ReMe keeps memory in an independent service and a user-owned workspace. Multiple agents can call the same memory system without binding storage to one model or host.
|
||||
|
||||
## Choose an interface
|
||||
|
||||
| Scenario | Recommended interface |
|
||||
|---|---|
|
||||
| Local script or hook | ReMe CLI |
|
||||
| Application backend | HTTP Client |
|
||||
| Tool-protocol host | MCP |
|
||||
| TypeScript agent | `@agentscope-ai/reme` |
|
||||
| Claude Code | MCP + Skill + Stop Hook |
|
||||
| Hermes Agent | Memory provider adapter |
|
||||
| Codex or another coding agent | `reme_memory` Skill or MCP |
|
||||
|
||||
## General memory loop
|
||||
|
||||
1. Before answering, call `search` for relevant memory.
|
||||
2. Use `read` on high-value results and `traverse` when relationships matter.
|
||||
3. Retain workspace-relative source paths in the answer.
|
||||
4. At session end, pass source messages to `auto_memory`.
|
||||
5. Let background or scheduled workflows consolidate daily notes into digest memory.
|
||||
|
||||
An empty search result must remain empty; do not present model inference as recalled history.
|
||||
|
||||
## MCP
|
||||
|
||||
The default HTTP service exposes streamable HTTP MCP at `http://127.0.0.1:2333/mcp`. Common tools include `search`, `read`, `traverse`, `list`, `auto_memory`, and `proactive`.
|
||||
|
||||
Use `service.jobs` to expose a read-only subset or keep write tools in a separate configuration.
|
||||
|
||||
## CLI and Skill
|
||||
|
||||
`skills/reme_memory/SKILL.md` defines a general workflow for agents that can run local commands: installation checks, service discovery, retrieval, reading, and persistence boundaries.
|
||||
|
||||
It deliberately avoids silently modifying Python environments, stopping unknown processes on port conflicts, writing recalled tool output back as conversation source, or persisting credentials.
|
||||
|
||||
## TypeScript, OpenClaw, and DeepSeek Harness
|
||||
|
||||
The [`@agentscope-ai/reme` TypeScript package](./integrations/typescript.md) provides the shared HTTP client and host adapters. See the dedicated guides for [DeepSeek Harness](./integrations/dsh.md) and [OpenClaw](./integrations/openclaw.md).
|
||||
|
||||
## Claude Code
|
||||
|
||||
`integrations/claude_code/` provides streamable HTTP MCP configuration, a `reme-memory` Skill, and a Stop hook that calls `auto_memory_cc`. Follow that directory's README for installation.
|
||||
|
||||
## Hermes Agent
|
||||
|
||||
`integrations/hermes_agent/` provides a memory provider that recalls context before model calls and asynchronously invokes `auto_memory` after each turn.
|
||||
|
||||
## Production guidance
|
||||
|
||||
- choose a stable absolute `workspace_dir`;
|
||||
- reuse a service discovered by `reme find_reme`;
|
||||
- treat `reme help` as the active Job contract;
|
||||
- apply timeouts and failure logging to writes;
|
||||
- do not block the host's core response path when memory is temporarily unavailable;
|
||||
- use authentication, TLS, and a minimal Job allowlist for remote access.
|
||||
102
docs/en/operations.md
Normal file
102
docs/en/operations.md
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
---
|
||||
title: Diagnostics, Backup, and Recovery
|
||||
description: ReMe health checks, logs, index maintenance, workspace backup, migration, and recovery.
|
||||
---
|
||||
|
||||
# Diagnostics, Backup, and Recovery
|
||||
|
||||
ReMe recovery protects user-owned workspace files and rebuilds catalogs, indexes, and graphs from those sources. Never delete or rewrite user memory merely to repair derived state.
|
||||
|
||||
## Quick diagnosis
|
||||
|
||||
Run these in order:
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
reme version
|
||||
reme health_check
|
||||
reme status
|
||||
reme app_config
|
||||
```
|
||||
|
||||
- `find_reme` confirms the actual host, port, and PID;
|
||||
- `version` verifies that the CLI reaches the service;
|
||||
- `health_check` reports component health;
|
||||
- `status` estimates stateful component memory and process RSS;
|
||||
- `app_config` returns the effective configuration with secrets redacted.
|
||||
|
||||
## Logs and common symptoms
|
||||
|
||||
`log_to_console` and `log_to_file` control logging. For startup failures, inspect the first exception rather than later client connection errors.
|
||||
|
||||
| Symptom | Check first |
|
||||
|---|---|
|
||||
| CLI cannot find ReMe | `reme find_reme`, process state, startup directory, and port |
|
||||
| Automatic memory fails | LLM backend, model, API key, and base URL |
|
||||
| Search is BM25-only | Whether an embedding store is connected to `file_store` |
|
||||
| New files are absent | Directory, extension, watcher, and `health_check` |
|
||||
| Studio fails but API works | Installed web extra, static path, and browser console |
|
||||
| Installed plugin is unavailable | Python interpreter, `plugins` configuration, and service restart |
|
||||
|
||||
## Index maintenance
|
||||
|
||||
```bash
|
||||
reme reindex scope=all
|
||||
reme reindex scope=bm25
|
||||
reme reindex scope=embedding
|
||||
```
|
||||
|
||||
`reindex` rebuilds BM25 and/or embedding indexes from the current `file_chunks`. It does not scan the workspace, rechunk files, or rebuild the wikilink graph. Diagnose the watcher first when ingestion is the problem.
|
||||
|
||||
Rebuild a daily index page separately:
|
||||
|
||||
```bash
|
||||
reme daily_reindex date=2026-09-04
|
||||
```
|
||||
|
||||
## Backup
|
||||
|
||||
Stop writes or stop the service, then back up the complete workspace. The most important sources are:
|
||||
|
||||
- `session/` for conversation sources;
|
||||
- `resource/` for external resources;
|
||||
- `daily/` for daily memory;
|
||||
- `digest/` for consolidated memory.
|
||||
|
||||
`metadata/` contains indexes, graphs, and catalogs. Backing it up accelerates restoration, but it is not the sole source of truth.
|
||||
|
||||
Use an explicit, stable absolute `workspace_dir` for durable deployments rather than relying on an incidental `.reme/` under the current directory.
|
||||
|
||||
## Migrate a workspace
|
||||
|
||||
1. Stop the old service to prevent writes during the copy.
|
||||
2. Copy the complete workspace while preserving timestamps.
|
||||
3. Start with the new absolute path:
|
||||
|
||||
```bash
|
||||
reme start workspace_dir=/new/location/reme-memory
|
||||
```
|
||||
|
||||
4. Run `health_check`, `status`, and a representative `search`.
|
||||
5. Rebuild embeddings if their model or dimensions changed.
|
||||
|
||||
Do not push a workspace containing private conversations to a public repository.
|
||||
|
||||
## Recover derived state
|
||||
|
||||
Do not remove anything until a backup exists. Then:
|
||||
|
||||
1. preserve `session/`, `resource/`, `daily/`, and `digest/`;
|
||||
2. record the effective configuration and component backends;
|
||||
3. verify that the failure is limited to `metadata/`;
|
||||
4. move suspect derived state to an isolated backup location;
|
||||
5. restart with the same configuration and let watchers rebuild;
|
||||
6. validate search, graph traversal, and daily indexes.
|
||||
|
||||
The internal layout of metadata files is not a public automation contract.
|
||||
|
||||
## Concurrent editing
|
||||
|
||||
When Studio or an editor saves a complete file, pass the mtime from `stat` as `save.expected_mtime`. A save then fails if another actor changed the file after it was opened, avoiding silent overwrites.
|
||||
|
||||
File Jobs enforce workspace containment and per-path locking. Do not bypass them to write arbitrary absolute paths.
|
||||
101
docs/en/plugin_development.md
Normal file
101
docs/en/plugin_development.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: Plugin Development
|
||||
description: Create, register, configure, test, and publish a ReMe plugin.
|
||||
---
|
||||
|
||||
# Plugin Development
|
||||
|
||||
A ReMe plugin is a regular Python distribution exposed through the `reme.plugins` entry-point group. Its package-level `plugin.yaml` can register Step and Component backends and provide default Application configuration.
|
||||
|
||||
## Minimal structure
|
||||
|
||||
```text
|
||||
my-plugin/
|
||||
├── pyproject.toml
|
||||
└── src/my_plugin/
|
||||
├── __init__.py
|
||||
├── plugin.yaml
|
||||
└── steps.py
|
||||
```
|
||||
|
||||
`pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project.entry-points."reme.plugins"]
|
||||
my-plugin = "my_plugin"
|
||||
```
|
||||
|
||||
`plugin.yaml`:
|
||||
|
||||
```yaml
|
||||
name: my-plugin
|
||||
backends:
|
||||
my_step: my_plugin.steps:MyStep
|
||||
application_defaults:
|
||||
jobs:
|
||||
my_action:
|
||||
backend: base
|
||||
description: Run my plugin action
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text: { type: string }
|
||||
required: [text]
|
||||
steps:
|
||||
- backend: my_step
|
||||
```
|
||||
|
||||
## Implement a Step
|
||||
|
||||
```python
|
||||
from reme.components.component_registry import R
|
||||
from reme.steps.base_step import BaseStep
|
||||
|
||||
|
||||
@R.register("my_step")
|
||||
class MyStep(BaseStep):
|
||||
async def execute(self):
|
||||
self.context.response.answer = self.context.data["text"]
|
||||
```
|
||||
|
||||
Step instances belong to one Job invocation. Put shared in-memory state under a narrow `app_context.metadata` key. Promote state that needs lifecycle, locking, or persistence to a Component or workspace file.
|
||||
|
||||
## Configuration merge
|
||||
|
||||
`application_defaults` is a partial `ApplicationConfig`:
|
||||
|
||||
```text
|
||||
plugin defaults < selected/default config < CLI overrides
|
||||
```
|
||||
|
||||
Plugins must not rewrite user configuration. Their backends enter an Application-local registry only when the plugin appears in that Application's `plugins` list.
|
||||
|
||||
## Local validation
|
||||
|
||||
```bash
|
||||
reme plugins validate ./path/to/my-plugin
|
||||
reme plugins install ./path/to/my-plugin --editable
|
||||
reme plugins list
|
||||
reme plugins show my-plugin
|
||||
reme start plugins='["my-plugin"]'
|
||||
reme my_action text=hello
|
||||
```
|
||||
|
||||
Validation imports plugin code, so run it only for trusted sources.
|
||||
|
||||
## Test boundaries
|
||||
|
||||
- create workspaces with `tmp_path`;
|
||||
- mock network, model, and subprocess boundaries;
|
||||
- verify disabled plugins do not mutate the built-in registry;
|
||||
- verify plugin defaults and explicit configuration precedence;
|
||||
- keep tasks, clients, and executors under Component lifecycle;
|
||||
- never delete or rewrite user source files to repair derived state.
|
||||
|
||||
The repository's Daily Paper, Auto Fin, LME, and BEAM plugins are complete examples.
|
||||
|
||||
## Compatibility
|
||||
|
||||
Legacy Python Plugin descriptors and the `reme.configs` entry point remain supported during migration, but new plugins should use `plugin.yaml`. Enablement always belongs to an Application rather than a process-global switch.
|
||||
|
||||
See [Plugin Management](./plugin_management.md) for installation, upgrades, and removal.
|
||||
|
|
@ -176,6 +176,16 @@ When the application uses an MCP service, service-enabled plugin Jobs appear as
|
|||
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.
|
||||
|
||||
## Benchmark application presets
|
||||
|
||||
The [LME](../../plugins/lme/README.md) and [BEAM](../../plugins/beam/README.md) plugins
|
||||
register their backends and plugin-owned Jobs in `plugin.yaml`. ReMe's built-in `benchmark`
|
||||
preset provides the shared core Jobs and components without inheriting `default`, so default
|
||||
background and cron jobs are not included. Install the selected benchmark plugin, then use
|
||||
`config=benchmark` together with `plugins=["lme"]` or `plugins=["beam"]`. The repository's
|
||||
benchmark runners enable the corresponding installed plugin automatically; editable installation
|
||||
keeps local plugin changes visible. Dataset runners remain under `benchmark/`.
|
||||
|
||||
## Uninstall a plugin
|
||||
|
||||
Use the plugin entry-point name, not necessarily the distribution name:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
---
|
||||
title: Quick Start
|
||||
description: Install and start ReMe, then complete a first file, retrieval, and automatic-memory workflow.
|
||||
---
|
||||
|
||||
# Quick Start
|
||||
|
||||
This page gets one working loop running. See [Configuration](./configuration.md) for the full configuration contract and [Services and Deployment](./services.md) for HTTP or MCP integration.
|
||||
|
||||
## Installation
|
||||
|
||||
ReMe requires Python 3.11+.
|
||||
|
|
@ -237,3 +244,5 @@ You can also specify a YAML or JSON configuration file:
|
|||
```bash
|
||||
reme start config=/path/to/custom.yaml
|
||||
```
|
||||
|
||||
Continue with the [CLI Reference](./reference/cli.md), [Job API Reference](./reference/jobs.md), or [Diagnostics, Backup, and Recovery](./operations.md).
|
||||
|
|
|
|||
87
docs/en/reference/cli.md
Normal file
87
docs/en/reference/cli.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: CLI Reference
|
||||
description: ReMe command syntax, service invocation, configuration overrides, and plugin commands.
|
||||
---
|
||||
|
||||
# CLI Reference
|
||||
|
||||
The basic syntax is:
|
||||
|
||||
```text
|
||||
reme ACTION key=value ...
|
||||
```
|
||||
|
||||
## Start an Application
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start config=demo
|
||||
reme start workspace_dir=/data/reme service.port=8181
|
||||
reme start job=search query="keywords" limit=5
|
||||
```
|
||||
|
||||
`start job=<name>` runs one Job through a one-shot service; plain `start` runs the configured Service.
|
||||
|
||||
## Call Jobs
|
||||
|
||||
Once a service is running, each action name is a Job name:
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme health_check
|
||||
reme search query="project decision" limit=10
|
||||
reme read path=digest/wiki/project.md start_line=1 end_line=80
|
||||
```
|
||||
|
||||
Use JSON for structured values:
|
||||
|
||||
```bash
|
||||
reme auto_memory \
|
||||
session_id=example \
|
||||
messages='[{"role":"user","content":"Remember this preference"}]'
|
||||
```
|
||||
|
||||
Client-selection arguments—`backend`, `transport`, `host`, `port`, `timeout`, `command`, `args`, and `show_metadata`—configure the client and never leak into the Job payload.
|
||||
|
||||
## Configuration overrides
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
config=/path/to/custom.yaml \
|
||||
service.port=8181 \
|
||||
service.web_enabled=false \
|
||||
plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
Leading `-` or `--` is optional. Use dots for nested keys and JSON for arrays and objects.
|
||||
|
||||
## Service discovery
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
```
|
||||
|
||||
This reports a discovered service but never starts or replaces a process.
|
||||
|
||||
## Plugin commands
|
||||
|
||||
Package management runs locally rather than through HTTP or MCP:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins show auto-fin
|
||||
reme plugins validate auto-fin
|
||||
reme plugins install reme-auto-fin
|
||||
reme plugins uninstall auto-fin
|
||||
```
|
||||
|
||||
See [Plugin Management](../plugin_management.md) for the complete workflow.
|
||||
|
||||
## Discover active capabilities
|
||||
|
||||
The [Job API Reference](./jobs.md) describes the default configuration. Plugins and custom YAML may change the running service, so automation should prefer:
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme app_config
|
||||
```
|
||||
115
docs/en/services.md
Normal file
115
docs/en/services.md
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: Services and Deployment
|
||||
description: Run ReMe through HTTP, SSE, MCP, the CLI, and ReMe Studio while respecting its local security boundary.
|
||||
---
|
||||
|
||||
# Services and Deployment
|
||||
|
||||
ReMe can run as a local HTTP service, a standalone MCP server, or a one-shot CLI Job. The default starts HTTP on `127.0.0.1:2333` and serves JSON, SSE, streamable HTTP MCP, and optional ReMe Studio from one process.
|
||||
|
||||
## HTTP API
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start service.host=127.0.0.1 service.port=8181
|
||||
```
|
||||
|
||||
Regular Jobs become `POST /<job-name>`:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"query":"user preferences","limit":5}'
|
||||
```
|
||||
|
||||
Job arguments live at the request body's top level. A regular response follows the `Response` schema:
|
||||
|
||||
```json
|
||||
{"success": true, "answer": "...", "metadata": {}}
|
||||
```
|
||||
|
||||
Unhandled Step failures become unsuccessful responses.
|
||||
|
||||
## Streaming and SSE
|
||||
|
||||
Jobs with `backend: stream` also use `POST /<job-name>`, returning `text/event-stream`. Error paths emit an error chunk and always terminate the stream. The default `chat` Job is streaming:
|
||||
|
||||
```bash
|
||||
curl -N http://127.0.0.1:2333/chat \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"query":"Summarize my long-term preferences"}'
|
||||
```
|
||||
|
||||
MCP does not expose Stream Jobs.
|
||||
|
||||
## MCP
|
||||
|
||||
The default HTTP service mounts streamable HTTP MCP at `/mcp`:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
mcp_enabled: true
|
||||
mcp_path: /mcp
|
||||
mcp_stateless_http: false
|
||||
```
|
||||
|
||||
For a standalone MCP service:
|
||||
|
||||
```bash
|
||||
reme start service.backend=mcp service.transport=stdio
|
||||
reme start service.backend=mcp service.transport=sse service.port=2333
|
||||
reme start service.backend=mcp service.transport=streamable-http service.port=2333
|
||||
```
|
||||
|
||||
MCP tools come from non-stream Jobs with `enable_serve: true`. Use `service.jobs` as an allowlist. `injected_job_kwargs` adds server-managed values that callers cannot override.
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
jobs: [search, read, traverse, auto_memory]
|
||||
injected_job_kwargs:
|
||||
tenant_id: local-user
|
||||
tool_error_on_failure: true
|
||||
```
|
||||
|
||||
## ReMe Studio
|
||||
|
||||
After installing `reme-ai[web]` or `reme-ai[core]`, the default HTTP origin also serves Studio:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:2333/
|
||||
```
|
||||
|
||||
Disable it with `service.web_enabled=false` or select a build with `service.web_static_dir`. Missing static assets do not prevent the Job API from starting.
|
||||
|
||||
## One-shot Jobs
|
||||
|
||||
```bash
|
||||
reme start job=search query="user preferences" limit=5
|
||||
```
|
||||
|
||||
This selects the one-shot CLI Service while retaining the normal Application, Component, and Job lifecycle.
|
||||
|
||||
## Service discovery
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
```
|
||||
|
||||
Ordinary `reme <action>` commands prefer the running service's actual backend, host, port, and transport. They fall back to local configuration only when no service is discovered.
|
||||
|
||||
## Security boundary
|
||||
|
||||
ReMe is local-first:
|
||||
|
||||
- the default binds to `127.0.0.1`;
|
||||
- HTTP CORS allows any origin;
|
||||
- Jobs may write, move, or delete files;
|
||||
- the service layer has no general-purpose user authentication.
|
||||
|
||||
Do not expose the default service directly to the public internet. For remote access, place it on a controlled network or behind an authenticated TLS reverse proxy, apply access controls and request-size limits, and expose only necessary Jobs.
|
||||
|
||||
## OpenAPI
|
||||
|
||||
FastAPI exposes the active endpoints through `/docs`, `/redoc`, and `/openapi.json`. The Studio SPA fallback preserves these reserved paths.
|
||||
27
docs/figure/reme-icon.svg
Normal file
27
docs/figure/reme-icon.svg
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="ReMe">
|
||||
<defs>
|
||||
<linearGradient id="brand" x1="14" y1="12" x2="51" y2="51" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#21d1bb"/>
|
||||
<stop offset="0.52" stop-color="#159fc9"/>
|
||||
<stop offset="1" stop-color="#3156d9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="glass" x1="10" y1="7" x2="56" y2="59" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffffff" stop-opacity="0.94"/>
|
||||
<stop offset="0.48" stop-color="#e4fffa" stop-opacity="0.82"/>
|
||||
<stop offset="1" stop-color="#dbe7ff" stop-opacity="0.74"/>
|
||||
</linearGradient>
|
||||
<filter id="shadow" x="-30%" y="-30%" width="160%" height="170%">
|
||||
<feDropShadow dx="0" dy="3" stdDeviation="3" flood-color="#287ca6" flood-opacity="0.22"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<g filter="url(#shadow)">
|
||||
<rect x="3" y="3" width="58" height="58" rx="18" fill="url(#glass)"/>
|
||||
<rect x="3.75" y="3.75" width="56.5" height="56.5" rx="17.25" fill="none" stroke="url(#brand)" stroke-opacity="0.46" stroke-width="1.5"/>
|
||||
<path d="M10 21C18 8 43 7 55 17" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round" opacity="0.9"/>
|
||||
</g>
|
||||
|
||||
<path d="M18 49V15h14.5C41 15 46 19.5 46 27s-5 12-13.5 12H27" fill="none" stroke="url(#brand)" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M27 38.5 46 49" fill="none" stroke="url(#brand)" stroke-width="7.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="27" cy="38.5" r="2.2" fill="white" stroke="#43d7c5" stroke-width="0.8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
46
docs/figure/reme-logo-fashion.svg
Normal file
46
docs/figure/reme-logo-fashion.svg
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 780 220" role="img" aria-labelledby="title description">
|
||||
<title id="title">ReMe</title>
|
||||
<desc id="description">ReMe gradient wordmark and open memory ribbon</desc>
|
||||
<defs>
|
||||
<linearGradient id="brand" x1="24" y1="28" x2="746" y2="188" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#20d2b7"/>
|
||||
<stop offset="0.4" stop-color="#12a9cc"/>
|
||||
<stop offset="0.74" stop-color="#2877e6"/>
|
||||
<stop offset="1" stop-color="#5146e5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="icon-glass" x1="22" y1="18" x2="194" y2="204" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#ffffff" stop-opacity="0.96"/>
|
||||
<stop offset="0.5" stop-color="#e8fffb" stop-opacity="0.84"/>
|
||||
<stop offset="1" stop-color="#dce8ff" stop-opacity="0.76"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wordmark" x1="252" y1="72" x2="742" y2="164" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#12b6bd"/>
|
||||
<stop offset="0.5" stop-color="#168fd0"/>
|
||||
<stop offset="1" stop-color="#4961dc"/>
|
||||
</linearGradient>
|
||||
<filter id="soft-shadow" x="-25%" y="-30%" width="150%" height="170%">
|
||||
<feDropShadow dx="0" dy="8" stdDeviation="8" flood-color="#237caa" flood-opacity="0.16"/>
|
||||
</filter>
|
||||
<filter id="icon-shadow" x="-25%" y="-25%" width="150%" height="160%">
|
||||
<feDropShadow dx="0" dy="7" stdDeviation="8" flood-color="#287ca6" flood-opacity="0.2"/>
|
||||
</filter>
|
||||
<filter id="wordmark-shadow" x="-10%" y="-20%" width="120%" height="150%">
|
||||
<feDropShadow dx="0" dy="4" stdDeviation="4" flood-color="#2876b8" flood-opacity="0.18"/>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- The memory ribbon lives in its own glass app tile, separated from the wordmark. -->
|
||||
<g filter="url(#icon-shadow)">
|
||||
<rect x="15" y="15" width="190" height="190" rx="54" fill="url(#icon-glass)"/>
|
||||
<rect x="16.5" y="16.5" width="187" height="187" rx="52.5" fill="none" stroke="url(#brand)" stroke-opacity="0.48" stroke-width="3"/>
|
||||
<path d="M37 69C63 27 144 24 182 57" fill="none" stroke="white" stroke-width="7" stroke-linecap="round" opacity="0.92"/>
|
||||
</g>
|
||||
|
||||
<g fill="none" stroke="url(#brand)" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M64 166V54h48c28 0 45 15 45 39.5S140 133 112 133H94" stroke-width="25"/>
|
||||
<path d="M94 131 157 166" stroke-width="25"/>
|
||||
</g>
|
||||
<circle cx="94" cy="131" r="7" fill="white" stroke="#3bd7c2" stroke-width="3"/>
|
||||
|
||||
<text x="252" y="159" fill="url(#wordmark)" font-family="Optima, Candara, 'Segoe UI', sans-serif" font-size="132" font-weight="600" letter-spacing="-3" filter="url(#wordmark-shadow)">ReMe</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
43
docs/index.md
Normal file
43
docs/index.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
layout: home
|
||||
title: ReMe Documentation
|
||||
titleTemplate: false
|
||||
head:
|
||||
- - meta
|
||||
- http-equiv: refresh
|
||||
content: "0; url=/zh/"
|
||||
- - link
|
||||
- rel: canonical
|
||||
href: https://reme.agentscope.io/zh/
|
||||
hero:
|
||||
name: ReMe
|
||||
text: 让 Agent 真正记住
|
||||
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
|
||||
image:
|
||||
src: /reme-logo.svg
|
||||
alt: ReMe Logo
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /zh/quick_start
|
||||
- theme: alt
|
||||
text: 核心概念
|
||||
link: /zh/memory_as_file
|
||||
features:
|
||||
- icon: 📁
|
||||
title: Local-first
|
||||
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
|
||||
link: /zh/memory_as_file
|
||||
- icon: 🧠
|
||||
title: Memory workflows
|
||||
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
|
||||
link: /zh/auto_memory
|
||||
- icon: 🔎
|
||||
title: Search and graph
|
||||
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
|
||||
link: /zh/memory_search
|
||||
- icon: 🔌
|
||||
title: Agent integrations
|
||||
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
|
||||
link: /zh/integrations
|
||||
---
|
||||
168
docs/zh/configuration.md
Normal file
168
docs/zh/configuration.md
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
title: 基础配置
|
||||
description: ReMe 配置文件、环境变量、命令行覆盖和核心组件配置。
|
||||
---
|
||||
|
||||
# 基础配置
|
||||
|
||||
ReMe 使用 YAML 或 JSON 描述 Service、Job 和 Component。默认配置位于 `reme/config/default.yaml`;启动时可以选择其他配置,再用命令行覆盖其中的字段。
|
||||
|
||||
## 配置优先级
|
||||
|
||||
配置按下面的顺序合并,靠后的值优先:
|
||||
|
||||
1. 已启用插件提供的 `application_defaults`。
|
||||
2. 选中的配置文件;未指定时使用内置 `default`。
|
||||
3. 命令行 dot notation 覆盖。
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start config=demo
|
||||
reme start config=/absolute/path/to/app.yaml
|
||||
reme start service.port=8181 workspace_dir=/data/reme
|
||||
```
|
||||
|
||||
`config` 支持内置配置名以及 `.yaml`、`.yml`、`.json` 文件。覆盖采用深度合并,不会因为修改 `service.port` 而丢失 `service` 下的其他字段。
|
||||
|
||||
## 值的解析
|
||||
|
||||
CLI 参数使用 `key=value`,前导 `-` 或 `--` 也会被接受:
|
||||
|
||||
```bash
|
||||
reme start --service.port=8181 --service.web_enabled=false
|
||||
```
|
||||
|
||||
值支持:
|
||||
|
||||
- `null`、布尔值、整数和浮点数;
|
||||
- JSON 数组和对象;
|
||||
- JSON 引号字符串;
|
||||
- 普通字符串。
|
||||
|
||||
类似 `007` 的前导零字符串不会被转换成数字。需要保留 `true`、`false` 等字面字符串时,使用 JSON 引号:`value='"true"'`。
|
||||
|
||||
## 环境变量
|
||||
|
||||
配置文件会递归展开两种表达式:
|
||||
|
||||
```yaml
|
||||
api_key: ${LLM_API_KEY}
|
||||
base_url: ${LLM_BASE_URL:-https://example.com/v1}
|
||||
```
|
||||
|
||||
`${VAR}` 在变量未定义时会报错;`${VAR:-default}` 使用默认值。ReMe 还会从命令启动目录向上查找 `.env`,最多检查五级父目录。
|
||||
|
||||
不要把密钥提交到配置文件或 Git。推荐把密钥放在 `.env` 或进程环境中。
|
||||
|
||||
## Application 字段
|
||||
|
||||
| 字段 | 默认值 | 作用 |
|
||||
|---|---|---|
|
||||
| `app_name` | `ReMe` | 应用显示名称 |
|
||||
| `workspace_dir` | `.reme` | 用户拥有的 workspace 根目录,会规范化为绝对路径 |
|
||||
| `metadata_dir` | `metadata` | 索引、图谱和 catalog 等派生状态 |
|
||||
| `session_dir` | `session` | Agent 对话记录;标准 transcript 位于 `session/dialog` |
|
||||
| `mem_session_dir` | `mem_session` | Agent wrapper 的会话和配置 |
|
||||
| `resource_dir` | `resource` | 外部资料 |
|
||||
| `daily_dir` | `daily` | Daily memory |
|
||||
| `digest_dir` | `digest` | 长期整理后的记忆 |
|
||||
| `timezone` | `Asia/Shanghai` | Cron、日期和梦境流程使用的 IANA 时区 |
|
||||
| `language` | 空 | LLM 交互默认语言 |
|
||||
| `plugins` | `[]` | 为当前 Application 启用的已安装插件 |
|
||||
| `service` | HTTP | 服务端配置 |
|
||||
| `jobs` | 默认 Job | Job 名到 Job 配置的映射 |
|
||||
| `components` | 默认组件 | 按类型和名称组织的组件配置 |
|
||||
|
||||
`session_dir` 必须保持 workspace-relative。其他 workspace 子目录也应使用清晰、稳定的相对名称。
|
||||
|
||||
## LLM 配置
|
||||
|
||||
默认 LLM 使用 OpenAI-compatible 接口:
|
||||
|
||||
```yaml
|
||||
components:
|
||||
as_llm:
|
||||
default:
|
||||
backend: openai
|
||||
model: qwen3.7-plus
|
||||
context_size: 200000
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
```
|
||||
|
||||
可注册的内置 backend 包括 `openai`、`anthropic`、`dashscope`、`deepseek`、`gemini`、`moonshot`、`ollama` 和 `xai`。实际字段由对应 AgentScope model wrapper 决定。
|
||||
|
||||
基础文件操作、BM25 检索、wikilink 遍历不需要 LLM。`auto_memory`、`auto_resource`、`auto_dream` 等演化流程需要可用 LLM。
|
||||
|
||||
## Embedding 配置
|
||||
|
||||
向量检索默认关闭。只设置 `EMBEDDING_API_KEY` 不会自动启用它;还需要同时启用 `as_embedding`、`embedding_store`,并把它连接到 `file_store`:
|
||||
|
||||
```yaml
|
||||
components:
|
||||
as_embedding:
|
||||
default:
|
||||
backend: openai
|
||||
model: text-embedding-v4
|
||||
dimensions: 1024
|
||||
credential:
|
||||
api_key: ${EMBEDDING_API_KEY}
|
||||
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
|
||||
embedding_store:
|
||||
default:
|
||||
backend: local
|
||||
as_embedding: default
|
||||
file_store:
|
||||
default:
|
||||
backend: local
|
||||
embedding_store: default
|
||||
keyword_index: default
|
||||
file_graph: default
|
||||
```
|
||||
|
||||
修改 embedding 模型或维度后,应重新构建 embedding 索引。
|
||||
|
||||
## Service 和 Job
|
||||
|
||||
最小 HTTP 配置:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
host: 127.0.0.1
|
||||
port: 2333
|
||||
web_enabled: true
|
||||
mcp_enabled: true
|
||||
mcp_path: /mcp
|
||||
```
|
||||
|
||||
Job 由 backend、参数 schema 和顺序执行的 Step 组成:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
example:
|
||||
backend: base
|
||||
description: Example job
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text: { type: string }
|
||||
required: [text]
|
||||
steps:
|
||||
- backend: example_step
|
||||
```
|
||||
|
||||
设置 `enable_serve: false` 可以保留内部 Job、禁止 Service 暴露。后台和 Cron Job始终不会作为请求端点暴露。
|
||||
|
||||
## 查看生效配置
|
||||
|
||||
服务启动后运行:
|
||||
|
||||
```bash
|
||||
reme app_config
|
||||
```
|
||||
|
||||
返回的是已合并、已校验并隐藏密钥后的配置。排查覆盖顺序或插件配置时,应以它为准,而不是只查看某一个 YAML 文件。
|
||||
|
||||
完整字段定义以 `reme/schema/application_config.py` 和 `reme/config/default.yaml` 为准。
|
||||
|
|
@ -190,6 +190,12 @@ pytest tests/unit/test_reme_cli.py
|
|||
docs/
|
||||
```
|
||||
|
||||
用户指南应同时提供 `docs/zh/` 与 `docs/en/` 版本,并在 `docs/.vitepress/config.mts` 的对应导航中注册。ReMe Studio、
|
||||
TypeScript、插件和评测的 README 是各自目录中的规范源文件;`github-pages/scripts/generate-content.mjs` 会在构建时镜像它们,
|
||||
不要编辑 `.generated/` 或 `dist/`。
|
||||
|
||||
`Job API 参考`由 `reme/config/default.yaml` 自动生成。修改默认 Job 参数时更新 YAML 和测试,不要手工维护生成页。
|
||||
|
||||
建议文档保持:
|
||||
|
||||
- 标题明确,直接说明能力或流程。
|
||||
|
|
@ -197,6 +203,15 @@ docs/
|
|||
- 涉及路径时使用仓库内真实路径,例如 `reme/config/default.yaml`、`reme/steps/`、`tests/unit/`。
|
||||
- 涉及默认行为时,以当前代码和 `pyproject.toml`、默认配置为准。
|
||||
|
||||
文档站检查:
|
||||
|
||||
```bash
|
||||
cd github-pages
|
||||
npm ci
|
||||
npm test
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 获取帮助
|
||||
|
|
|
|||
75
docs/zh/faq.md
Normal file
75
docs/zh/faq.md
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
title: 常见问题
|
||||
description: ReMe 安装、服务、模型、检索、文件和插件问题的快速答案。
|
||||
---
|
||||
|
||||
# 常见问题
|
||||
|
||||
## 基础文件操作需要模型 API Key 吗?
|
||||
|
||||
不需要。`write`、`read`、`list`、`stat`、BM25 搜索和 wikilink 遍历可以在没有模型凭据时运行。`auto_memory`、`auto_resource` 和 `auto_dream` 需要 LLM。
|
||||
|
||||
## 为什么配置了 Embedding Key 仍然只有 BM25?
|
||||
|
||||
Embedding 默认未启用。除了凭据,还必须配置 `as_embedding`、`embedding_store`,并让 `file_store.default.embedding_store` 指向该组件。参见[基础配置](./configuration.md#embedding-配置)。
|
||||
|
||||
## 为什么 `reme reindex` 没有发现新文件?
|
||||
|
||||
`reindex` 只从当前 `file_chunks` 重建 BM25 或 embedding 索引,不扫描 workspace。检查 `index_update_loop` watcher、文件目录、后缀和 `health_check`。
|
||||
|
||||
## 怎样使用另一个 workspace?
|
||||
|
||||
启动时传入稳定的绝对路径:
|
||||
|
||||
```bash
|
||||
reme start workspace_dir=/absolute/path/to/memory
|
||||
```
|
||||
|
||||
普通 CLI 调用会发现运行服务,不必重复 workspace 参数。
|
||||
|
||||
## 端口 2333 被占用怎么办?
|
||||
|
||||
不要停止未知监听者。选择另一个端口:
|
||||
|
||||
```bash
|
||||
reme start service.port=8181
|
||||
```
|
||||
|
||||
然后用 `reme find_reme` 确认发现结果。
|
||||
|
||||
## 为什么插件安装后仍然没有对应 Job?
|
||||
|
||||
安装只让 distribution 在当前 Python 环境中可见。还需要在应用配置中启用:
|
||||
|
||||
```bash
|
||||
reme start plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
修改安装或启用状态后需要重启已运行的服务。
|
||||
|
||||
## 可以直接编辑 workspace 中的 Markdown 吗?
|
||||
|
||||
可以。文件是事实源,watcher 会摄取修改。应保留有效 frontmatter、使用完整 workspace-relative wikilink,并避免同时由多个编辑器无条件覆盖同一文件。
|
||||
|
||||
## 可以把服务暴露到公网吗?
|
||||
|
||||
默认配置不适合直接公网暴露。服务包含写入和删除 Job,HTTP CORS 宽松,且没有通用认证层。请使用受控网络或带 TLS、认证、访问控制的反向代理,并限制 `service.jobs`。
|
||||
|
||||
## 怎样备份和迁移?
|
||||
|
||||
停止写入后备份整个 workspace。`session/`、`resource/`、`daily/` 和 `digest/` 是最重要的用户数据;`metadata/` 可以随同备份,也可以从源文件重建。详见[诊断、备份与恢复](./operations.md)。
|
||||
|
||||
## Studio 找不到怎么办?
|
||||
|
||||
基础 `reme-ai` 包不包含前端资源。安装 `reme-ai[web]` 或 `reme-ai[core]`,或通过 `service.web_static_dir` 指向构建产物。Studio 缺失不会影响 Job API。
|
||||
|
||||
## 当前服务到底开放了哪些能力?
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme app_config
|
||||
```
|
||||
|
||||
静态文档描述默认配置;运行服务可能由自定义配置和插件改变。
|
||||
36
docs/zh/index.md
Normal file
36
docs/zh/index.md
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
layout: home
|
||||
title: ReMe 文档
|
||||
titleTemplate: false
|
||||
hero:
|
||||
name: ReMe
|
||||
text: 让 Agent 真正记住
|
||||
tagline: 文件属于你,记忆服务于 Agent。ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的本地文件。
|
||||
image:
|
||||
src: /reme-logo.svg
|
||||
alt: ReMe Logo
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 快速开始
|
||||
link: /zh/quick_start
|
||||
- theme: alt
|
||||
text: 查看配置
|
||||
link: /zh/configuration
|
||||
features:
|
||||
- icon: 📁
|
||||
title: 文件即记忆
|
||||
details: Workspace 文件是持久事实源;索引、图谱和缓存都可以重新构建。
|
||||
link: /zh/memory_as_file
|
||||
- icon: 🧠
|
||||
title: 自动记忆
|
||||
details: 将对话和资料写入 daily,自动整理为互相关联的长期 digest。
|
||||
link: /zh/auto_memory
|
||||
- icon: 🔎
|
||||
title: 检索与图谱
|
||||
details: 结合关键词、可选向量检索与 wikilink 图谱,渐进式展开上下文。
|
||||
link: /zh/memory_search
|
||||
- icon: 🔌
|
||||
title: Agent 集成
|
||||
details: 通过 CLI、HTTP、MCP 和宿主适配器接入已有 Agent。
|
||||
link: /zh/integrations
|
||||
---
|
||||
88
docs/zh/integrations.md
Normal file
88
docs/zh/integrations.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
title: Agent 集成
|
||||
description: 通过 CLI、HTTP、MCP、Skill 和宿主适配器把 ReMe 接入 Agent。
|
||||
---
|
||||
|
||||
# Agent 集成
|
||||
|
||||
ReMe 把记忆能力放在独立服务和用户拥有的 workspace 中。Agent 可以通过标准接口调用同一套记忆,而不必把存储逻辑绑定到某一个模型或宿主。
|
||||
|
||||
## 选择接入方式
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|---|---|
|
||||
| 本机脚本或 Hook | ReMe CLI |
|
||||
| 应用后端 | HTTP Client |
|
||||
| 支持工具协议的 Agent | MCP |
|
||||
| TypeScript Agent | `@agentscope-ai/reme` |
|
||||
| Claude Code | MCP + Skill + Stop Hook |
|
||||
| Hermes Agent | Memory provider adapter |
|
||||
| Codex 或其他 coding agent | `reme_memory` Skill 或 MCP |
|
||||
|
||||
## 通用接入循环
|
||||
|
||||
一个完整但可控的 Agent 记忆循环通常包含:
|
||||
|
||||
1. 会话开始或回答前,用 `search` 找到相关记忆;
|
||||
2. 对高价值结果使用 `read`,必要时用 `traverse` 展开关系;
|
||||
3. 在回答中保留 workspace-relative 来源路径;
|
||||
4. 会话结束后,把原始消息交给 `auto_memory`;
|
||||
5. 由后台或定时任务把 daily 内容整理到 digest。
|
||||
|
||||
搜索不到内容时应明确返回空结果,不应把模型推测当成历史记忆。
|
||||
|
||||
## MCP
|
||||
|
||||
默认 HTTP 服务在 `http://127.0.0.1:2333/mcp` 提供 streamable HTTP MCP。常用工具包括:
|
||||
|
||||
- `search`
|
||||
- `read`
|
||||
- `traverse`
|
||||
- `list`
|
||||
- `auto_memory`
|
||||
- `proactive`
|
||||
|
||||
根据宿主风险模型,可以用 `service.jobs` 只暴露只读工具,或将写入工具放在单独配置中。
|
||||
|
||||
## CLI 和 Skill
|
||||
|
||||
仓库中的 `skills/reme_memory/SKILL.md` 描述了一个通用 Agent 工作流,包括安装检查、服务发现、检索、读取和写入边界。它适合能够执行本地命令的 Agent。
|
||||
|
||||
Skill 不应:
|
||||
|
||||
- 未经允许安装或升级 Python 环境;
|
||||
- 发现端口冲突后停止未知进程;
|
||||
- 把召回的工具结果再次写入对话来源;
|
||||
- 将密钥或敏感信息写入记忆。
|
||||
|
||||
## TypeScript、OpenClaw 与 DeepSeek Harness
|
||||
|
||||
统一 HTTP 客户端和包能力见 [TypeScript Agent 集成](./integrations/typescript.md)。宿主的完整安装、配置和运行说明见 [DeepSeek Harness](./integrations/dsh.md) 与 [OpenClaw](./integrations/openclaw.md) 指南。它们包含:
|
||||
|
||||
- HTTP Client;
|
||||
- DeepSeek Harness adapter;
|
||||
- OpenClaw adapter;
|
||||
- 构建与发布检查。
|
||||
|
||||
## Claude Code
|
||||
|
||||
仓库的 `integrations/claude_code/` 提供:
|
||||
|
||||
- streamable HTTP MCP 配置;
|
||||
- `reme-memory` Skill;
|
||||
- 会话停止时调用 `auto_memory_cc` 的 Hook。
|
||||
|
||||
完整安装步骤以仓库中的 `integrations/claude_code/README.md` 为准。
|
||||
|
||||
## Hermes Agent
|
||||
|
||||
`integrations/hermes_agent/` 提供 memory provider:模型调用前检索相关记忆,每轮结束后异步调用 `auto_memory`。完整配置见该目录 README。
|
||||
|
||||
## 生产接入建议
|
||||
|
||||
- 明确选择稳定、绝对的 `workspace_dir`;
|
||||
- 启动前复用 `reme find_reme` 发现的服务;
|
||||
- 以 `reme help` 为当前 Job 契约;
|
||||
- 为写入动作设置超时和失败日志;
|
||||
- 不因记忆服务暂时不可用而阻塞宿主的核心回答流程;
|
||||
- 对远程访问使用认证、TLS 和最小 Job allowlist。
|
||||
67
docs/zh/integrations/claude-code.md
Normal file
67
docs/zh/integrations/claude-code.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: Claude Code 集成
|
||||
description: 通过 MCP、reme-memory Skill 和 Stop Hook 将 Claude Code 连接到 ReMe。
|
||||
---
|
||||
|
||||
# Claude Code 集成
|
||||
|
||||
ReMe 的 Claude Code 插件提供长期记忆召回,并在每次会话结束后异步记录对话。Daily 到 digest 的整理仍由共享的 ReMe 服务负责。
|
||||
|
||||
## 能力
|
||||
|
||||
- 通过 MCP 使用 `search`、`traverse`、`daily_list`、`frontmatter_read`、`read`、`auto_memory_cc` 等工具;
|
||||
- `reme-memory` Skill 在回答前召回长期记忆并保留来源路径;
|
||||
- Stop Hook 只把 Claude Code `session_id` 传给服务端,服务端从本地 transcript 解析会话;
|
||||
- 记录在脱离 Claude Code 的后台进程中进行,不延迟退出;服务不可用时记录日志并结束。
|
||||
|
||||
## 部署模型
|
||||
|
||||
插件连接到用户预先启动的共享 HTTP MCP 服务,不为每个 Claude Code 窗口创建 ReMe。这样所有窗口共享一个 workspace、一组 watcher 和一次 dream cron。
|
||||
|
||||
## 准备 ReMe
|
||||
|
||||
```bash
|
||||
pip install "reme-ai[core]"
|
||||
```
|
||||
|
||||
在稳定目录配置 LLM 环境,然后启动:
|
||||
|
||||
```bash
|
||||
reme start service.backend=http
|
||||
```
|
||||
|
||||
默认 JSON Job API 和 MCP 地址分别位于同一个 `127.0.0.1:2333` 服务,MCP 路径是 `/mcp`。使用其他端口时,必须同步修改插件的 `.mcp.json`。
|
||||
|
||||
默认搜索使用 BM25;只有启用向量检索时才需要 Embedding 配置。
|
||||
|
||||
## 安装插件
|
||||
|
||||
在 Claude Code 中运行:
|
||||
|
||||
```text
|
||||
/plugin marketplace add ./integrations/claude_code
|
||||
/plugin install reme@reme-marketplace
|
||||
```
|
||||
|
||||
重启 Claude Code,再运行 `/mcp`,确认 `reme` server 和工具已经连接。
|
||||
|
||||
## Hook 与路径
|
||||
|
||||
- MCP 配置:`integrations/claude_code/reme/.mcp.json`;
|
||||
- 自动记忆 Hook:`integrations/claude_code/reme/hooks/auto_memory.py`;
|
||||
- Hook 日志:`integrations/claude_code/reme/logs/auto_memory_hook.log`;
|
||||
- 默认 transcript 根目录:`~/.claude/projects`;
|
||||
- 可通过 `CLAUDE_CONFIG_DIR` 修改 transcript 根目录;
|
||||
- 可通过 `REME_HOST`、`REME_PORT` 覆盖 Hook 使用的服务地址。
|
||||
|
||||
Hook 需要 `python3` 位于 `PATH`。MCP 工具名前缀可能随 Claude Code 版本包含 server segment;Skill 使用 `mcp__reme__*` 匹配这一差异。
|
||||
|
||||
## 验证
|
||||
|
||||
1. `reme health_check` 返回健康;
|
||||
2. Claude Code `/mcp` 显示 ReMe;
|
||||
3. `reme-memory` 能召回一条已存在记忆;
|
||||
4. 完成测试会话后,Hook 日志没有错误;
|
||||
5. 对应内容出现在当天 daily note 中。
|
||||
|
||||
英文原始部署说明位于 `integrations/claude_code/README.md`。
|
||||
59
docs/zh/integrations/hermes.md
Normal file
59
docs/zh/integrations/hermes.md
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
---
|
||||
title: Hermes Agent 集成
|
||||
description: 使用 ReMe memory provider 在 Hermes 调用模型前召回、每轮结束后异步记录。
|
||||
---
|
||||
|
||||
# Hermes Agent 集成
|
||||
|
||||
Hermes memory provider 连接到一个已经运行的 ReMe HTTP 服务,在每次模型调用前召回相关记忆,并在用户/助手回合完成后异步调用 `auto_memory`。
|
||||
|
||||
## Workspace 隔离
|
||||
|
||||
ReMe 的搜索范围是一个完整 workspace。多个 Hermes profile 指向同一个 workspace 时会共享召回结果;需要隔离时,为每个 profile 使用独立 workspace 和端点。
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
workspace_dir="/absolute/path/to/reme-hermes-default" \
|
||||
service.backend=http \
|
||||
service.host=127.0.0.1 \
|
||||
service.port=2333
|
||||
```
|
||||
|
||||
自动记忆需要 LLM;默认 BM25 搜索不需要 Embedding。
|
||||
|
||||
## 安装与配置
|
||||
|
||||
```bash
|
||||
hermes plugins install agentscope-ai/ReMe/integrations/hermes_agent
|
||||
hermes memory setup
|
||||
```
|
||||
|
||||
选择 `reme`,接受默认的 `http://127.0.0.1:2333`,或输入上一步使用的端点。Setup 会先调用 `health_check`,只有新端点健康时才替换现有 provider 配置。
|
||||
|
||||
配置存放在 `$HERMES_HOME/reme.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"endpoint": "http://127.0.0.1:2333",
|
||||
"request_timeout": 600.0,
|
||||
"recall_timeout": 5.0,
|
||||
"health_timeout": 2.0,
|
||||
"health_retry_seconds": 30.0,
|
||||
"shutdown_timeout": 30.0,
|
||||
"recall_limit": 5
|
||||
}
|
||||
```
|
||||
|
||||
运行 `hermes memory status` 检查安装和配置。新的 Hermes 会话还会重新检查端点健康状态。
|
||||
|
||||
## 生命周期和失败行为
|
||||
|
||||
- `prefetch` 调用 ReMe `search`,Hermes 将结果放入受保护的 memory context;
|
||||
- `sync_turn` 把完成的回合加入串行后台写队列,再调用 `auto_memory`;
|
||||
- cron、flush 和 subagent context 不写入对话记忆;
|
||||
- 健康检查失败后,在 cooldown 结束前暂停召回和记录;
|
||||
- 召回与写入有独立 cooldown,单项失败不会关闭另一项;
|
||||
- 召回使用较短超时,避免慢搜索长期阻塞模型调用;
|
||||
- shutdown 会在有限时间内排空写队列,ReMe 服务仍由用户独立管理。
|
||||
|
||||
英文权威安装说明位于 `integrations/hermes_agent/README.md`。
|
||||
106
docs/zh/operations.md
Normal file
106
docs/zh/operations.md
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
title: 诊断、备份与恢复
|
||||
description: ReMe 服务健康检查、日志、索引维护、备份迁移和常见恢复流程。
|
||||
---
|
||||
|
||||
# 诊断、备份与恢复
|
||||
|
||||
ReMe 的恢复原则是:保护 workspace 中的用户文件,通过源文件重建索引、图谱和 catalog。不要为了修复索引而删除或改写用户记忆。
|
||||
|
||||
## 快速诊断
|
||||
|
||||
依次执行:
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
reme version
|
||||
reme health_check
|
||||
reme status
|
||||
reme app_config
|
||||
```
|
||||
|
||||
- `find_reme`:服务是否存在,以及实际 host、port、PID;
|
||||
- `version`:CLI 能否成功访问服务;
|
||||
- `health_check`:组件健康状态;
|
||||
- `status`:状态组件的内存估算和进程 RSS;
|
||||
- `app_config`:隐藏密钥后的实际生效配置。
|
||||
|
||||
## 日志
|
||||
|
||||
`log_to_console` 和 `log_to_file` 控制日志目标。排查启动失败时先查看第一条异常,而不是后续客户端连接错误。
|
||||
|
||||
常见类别:
|
||||
|
||||
| 现象 | 优先检查 |
|
||||
|---|---|
|
||||
| CLI 找不到服务 | `reme find_reme`、启动目录、端口和进程状态 |
|
||||
| 自动记忆失败 | LLM backend、model、API key、base URL |
|
||||
| 只有 BM25 结果 | embedding 组件是否真正接入 `file_store` |
|
||||
| 新文件没有进入搜索 | 文件所在目录、后缀、watcher 和 `health_check` |
|
||||
| Studio 空白但 API 正常 | web extra、静态构建路径和浏览器控制台 |
|
||||
| 插件安装后不可用 | 当前 Python 解释器、`plugins` 配置、服务重启 |
|
||||
|
||||
## 索引维护
|
||||
|
||||
```bash
|
||||
reme reindex scope=all
|
||||
reme reindex scope=bm25
|
||||
reme reindex scope=embedding
|
||||
```
|
||||
|
||||
`reindex` 从当前 `file_chunks` 重建 BM25 和/或 embedding 派生索引。它不会扫描 workspace、重新分块或重建 wikilink 图谱。
|
||||
|
||||
如果问题发生在文件摄取阶段,应先确认后台 watcher 正常运行;不能把 `reindex` 当成通用“重新扫描”命令。
|
||||
|
||||
Daily 索引页可单独重建:
|
||||
|
||||
```bash
|
||||
reme daily_reindex date=2026-09-04
|
||||
```
|
||||
|
||||
## 备份
|
||||
|
||||
停止写入或停止服务后,优先备份整个 workspace。最重要的目录是:
|
||||
|
||||
- `session/`:原始对话来源;
|
||||
- `resource/`:外部资料;
|
||||
- `daily/`:每日记忆;
|
||||
- `digest/`:长期记忆。
|
||||
|
||||
`metadata/` 包含索引、图谱和 file catalog。一起备份可以加速恢复,但它不是唯一事实源。
|
||||
|
||||
不要只备份进程目录下偶然生成的 `.reme/`;生产使用应明确设置稳定的绝对 `workspace_dir`。
|
||||
|
||||
## 迁移 workspace
|
||||
|
||||
1. 停止旧服务,避免迁移期间继续写入。
|
||||
2. 复制完整 workspace,并保留文件时间信息。
|
||||
3. 使用新的绝对路径启动:
|
||||
|
||||
```bash
|
||||
reme start workspace_dir=/new/location/reme-memory
|
||||
```
|
||||
|
||||
4. 运行 `health_check`、`status` 和一次代表性 `search`。
|
||||
5. 如果 embedding 模型或维度发生变化,再重建 embedding 索引。
|
||||
|
||||
普通 Markdown 和资源文件可以使用版本控制或同步工具;包含敏感对话的 workspace 不应推送到公开仓库。
|
||||
|
||||
## 从派生状态故障恢复
|
||||
|
||||
在确认备份可用前,不要删除任何内容。恢复顺序应为:
|
||||
|
||||
1. 保留 `session/`、`resource/`、`daily/` 和 `digest/`;
|
||||
2. 记录当前配置和组件 backend;
|
||||
3. 确认故障只发生在 `metadata/`;
|
||||
4. 将有问题的派生状态移到隔离备份位置;
|
||||
5. 用同一配置启动 ReMe,让 watcher 从源文件重建;
|
||||
6. 验证搜索、图谱和 Daily 索引。
|
||||
|
||||
具体 metadata 文件属于实现细节,不应在自动化脚本里依赖其内部格式。
|
||||
|
||||
## 并发编辑
|
||||
|
||||
Studio 或其他编辑器保存完整文件时,应使用 `stat` 返回的 mtime 作为 `save.expected_mtime`。如果文件在打开后被外部修改,保存会失败,从而避免静默覆盖。
|
||||
|
||||
文件 Job 会校验 workspace containment,并对同一路径加锁。不要绕过这些 Job 直接向不受控制的绝对路径写入。
|
||||
101
docs/zh/plugin_development.md
Normal file
101
docs/zh/plugin_development.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
title: 插件开发
|
||||
description: 创建、注册、配置、测试和发布 ReMe 插件。
|
||||
---
|
||||
|
||||
# 插件开发
|
||||
|
||||
ReMe 插件是一个普通 Python distribution,通过 `reme.plugins` entry-point group 暴露 package-level `plugin.yaml`。插件可以注册新的 Step、Component backend,并提供默认 Application 配置。
|
||||
|
||||
## 最小结构
|
||||
|
||||
```text
|
||||
my-plugin/
|
||||
├── pyproject.toml
|
||||
└── src/my_plugin/
|
||||
├── __init__.py
|
||||
├── plugin.yaml
|
||||
└── steps.py
|
||||
```
|
||||
|
||||
`pyproject.toml`:
|
||||
|
||||
```toml
|
||||
[project.entry-points."reme.plugins"]
|
||||
my-plugin = "my_plugin"
|
||||
```
|
||||
|
||||
`plugin.yaml`:
|
||||
|
||||
```yaml
|
||||
name: my-plugin
|
||||
backends:
|
||||
my_step: my_plugin.steps:MyStep
|
||||
application_defaults:
|
||||
jobs:
|
||||
my_action:
|
||||
backend: base
|
||||
description: Run my plugin action
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text: { type: string }
|
||||
required: [text]
|
||||
steps:
|
||||
- backend: my_step
|
||||
```
|
||||
|
||||
## 实现 Step
|
||||
|
||||
```python
|
||||
from reme.components.component_registry import R
|
||||
from reme.steps.base_step import BaseStep
|
||||
|
||||
|
||||
@R.register("my_step")
|
||||
class MyStep(BaseStep):
|
||||
async def execute(self):
|
||||
self.context.response.answer = self.context.data["text"]
|
||||
```
|
||||
|
||||
Step 实例属于单次 Job 调用。跨调用的内存状态应放在带命名空间的 `app_context.metadata`;需要生命周期、锁或持久化时,应升级为 Component 或 workspace 文件。
|
||||
|
||||
## 配置合并
|
||||
|
||||
`application_defaults` 是不完整的 `ApplicationConfig`。合并顺序为:
|
||||
|
||||
```text
|
||||
插件默认值 < 选中/default 配置 < CLI 覆盖
|
||||
```
|
||||
|
||||
插件不应自动修改用户的配置文件。只有在 Application 的 `plugins` 列表中显式启用后,插件 backend 才会加入该 Application 的局部 registry。
|
||||
|
||||
## 本地验证
|
||||
|
||||
```bash
|
||||
reme plugins validate ./path/to/my-plugin
|
||||
reme plugins install ./path/to/my-plugin --editable
|
||||
reme plugins list
|
||||
reme plugins show my-plugin
|
||||
reme start plugins='["my-plugin"]'
|
||||
reme my_action text=hello
|
||||
```
|
||||
|
||||
校验会导入插件代码,因此只应对可信源码执行。
|
||||
|
||||
## 测试边界
|
||||
|
||||
- 使用 `tmp_path` 创建 workspace;
|
||||
- Mock 网络、模型和子进程;
|
||||
- 验证插件未启用时不会污染 built-in registry;
|
||||
- 验证默认配置与显式配置的合并优先级;
|
||||
- 验证后台任务、客户端和 executor 都由 Component 生命周期关闭;
|
||||
- 验证失败不会删除或重写用户源文件。
|
||||
|
||||
仓库中的 `plugins/daily_paper`、`plugins/auto-fin`、`plugins/lme` 和 `plugins/beam` 是完整参考实现。
|
||||
|
||||
## 兼容性
|
||||
|
||||
迁移期间仍兼容旧的 Python Plugin descriptor 和 `reme.configs` entry point,但新插件应使用 `plugin.yaml`。不要依赖进程级全局 enable/disable 状态;插件启用始终属于具体 Application 配置。
|
||||
|
||||
包管理命令、升级与卸载行为见[插件管理](./plugin_management.md)。
|
||||
|
|
@ -169,6 +169,15 @@ curl -s http://127.0.0.1:2333/auto_fin \
|
|||
|
||||
自定义应用配置需要提供插件的运行依赖,包括 `agent_wrapper.default`,以及 Auto Fin 使用的 `search` 和 `read` Jobs。
|
||||
|
||||
## Benchmark 应用配置
|
||||
|
||||
[LME](../../plugins/lme/README_ZH.md) 和 [BEAM](../../plugins/beam/README_ZH.md) 插件通过
|
||||
`plugin.yaml` 注册 backend 和插件拥有的 Job。ReMe 内置的 `benchmark` 配置提供公共核心 Job 和
|
||||
Component,并且不继承 `default`,因此不包含默认后台和定时任务。先安装所需的 benchmark 插件,
|
||||
再使用 `config=benchmark`,同时指定 `plugins=["lme"]` 或 `plugins=["beam"]`。仓库内的 benchmark
|
||||
runner 会自动启用对应的已安装插件;editable 安装可让本地源码修改直接生效。数据集 runner 仍位于
|
||||
`benchmark/`。
|
||||
|
||||
## 卸载插件
|
||||
|
||||
这里使用插件 entry-point 名称,它不一定等于 distribution 名称:
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
---
|
||||
title: 快速开始
|
||||
description: 安装并启动 ReMe,完成文件写入、检索和自动记忆的第一个闭环。
|
||||
---
|
||||
|
||||
# 快速开始
|
||||
|
||||
本页用于完成第一次可运行闭环。需要完整配置字段时查看[基础配置](./configuration.md);接入 HTTP 或 MCP 时查看[服务与部署](./services.md)。
|
||||
|
||||
## 安装
|
||||
|
||||
ReMe 要求 Python 3.11+。
|
||||
|
|
@ -229,3 +236,5 @@ reme start \
|
|||
```bash
|
||||
reme start config=/path/to/custom.yaml
|
||||
```
|
||||
|
||||
下一步可以查看 [CLI 参考](./reference/cli.md)、[Job API 参考](./reference/jobs.md)和[诊断、备份与恢复](./operations.md)。
|
||||
|
|
|
|||
87
docs/zh/reference/cli.md
Normal file
87
docs/zh/reference/cli.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
title: CLI 参考
|
||||
description: ReMe 命令行语法、服务调用、配置覆盖与插件命令。
|
||||
---
|
||||
|
||||
# CLI 参考
|
||||
|
||||
ReMe 的基本语法是:
|
||||
|
||||
```text
|
||||
reme ACTION key=value ...
|
||||
```
|
||||
|
||||
## 启动应用
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start config=demo
|
||||
reme start workspace_dir=/data/reme service.port=8181
|
||||
reme start job=search query="关键词" limit=5
|
||||
```
|
||||
|
||||
`start job=<name>` 运行一次性 Job;普通 `start` 启动配置中的 Service。
|
||||
|
||||
## 调用 Job
|
||||
|
||||
服务运行后,Action 名就是 Job 名:
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme health_check
|
||||
reme search query="项目决策" limit=10
|
||||
reme read path=digest/wiki/project.md start_line=1 end_line=80
|
||||
```
|
||||
|
||||
复杂值使用 JSON:
|
||||
|
||||
```bash
|
||||
reme auto_memory \
|
||||
session_id=example \
|
||||
messages='[{"role":"user","content":"记住这条偏好"}]'
|
||||
```
|
||||
|
||||
服务选择参数 `backend`、`transport`、`host`、`port`、`timeout`、`command`、`args` 和 `show_metadata` 只用于构造客户端,不会泄漏到 Job 参数。
|
||||
|
||||
## 配置覆盖
|
||||
|
||||
```bash
|
||||
reme start \
|
||||
config=/path/to/custom.yaml \
|
||||
service.port=8181 \
|
||||
service.web_enabled=false \
|
||||
plugins='["auto-fin"]'
|
||||
```
|
||||
|
||||
参数前的 `-` 或 `--` 可以省略。嵌套键使用点号,数组和对象使用 JSON。
|
||||
|
||||
## 服务发现
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
```
|
||||
|
||||
成功时输出可复用的服务信息;找不到服务时不会自动启动新进程。
|
||||
|
||||
## 插件命令
|
||||
|
||||
插件包管理是本地命令,不经过 HTTP 或 MCP:
|
||||
|
||||
```bash
|
||||
reme plugins list
|
||||
reme plugins show auto-fin
|
||||
reme plugins validate auto-fin
|
||||
reme plugins install reme-auto-fin
|
||||
reme plugins uninstall auto-fin
|
||||
```
|
||||
|
||||
完整说明见[插件管理](../plugin_management.md)。
|
||||
|
||||
## 发现当前能力
|
||||
|
||||
默认 Job 参数见 [Job API 参考](./jobs.md)。运行配置可能由插件和自定义 YAML 改变,因此自动化程序应优先调用:
|
||||
|
||||
```bash
|
||||
reme help
|
||||
reme app_config
|
||||
```
|
||||
131
docs/zh/services.md
Normal file
131
docs/zh/services.md
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
---
|
||||
title: 服务与部署
|
||||
description: 使用 ReMe 的 HTTP、SSE、MCP 和 Studio 服务,并理解默认安全边界。
|
||||
---
|
||||
|
||||
# 服务与部署
|
||||
|
||||
ReMe 可以作为本地 HTTP 服务、独立 MCP Server 或一次性 CLI Job 运行。默认模式是在 `127.0.0.1:2333` 启动 HTTP 服务,并在同一进程中提供 JSON API、SSE、MCP 与可选的 ReMe Studio。
|
||||
|
||||
## HTTP 服务
|
||||
|
||||
```bash
|
||||
reme start
|
||||
reme start service.host=127.0.0.1 service.port=8181
|
||||
```
|
||||
|
||||
普通 Job 暴露为 `POST /<job-name>`:
|
||||
|
||||
```bash
|
||||
curl -s http://127.0.0.1:2333/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"query":"用户偏好","limit":5}'
|
||||
```
|
||||
|
||||
请求体允许 Job 参数位于顶层。响应遵循 `Response`:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"answer": "...",
|
||||
"metadata": {}
|
||||
}
|
||||
```
|
||||
|
||||
未被 Step 捕获的错误会转换成 `success: false` 的响应。
|
||||
|
||||
## Streaming 与 SSE
|
||||
|
||||
`backend: stream` 的 Job 仍使用 `POST /<job-name>`,但响应类型是 `text/event-stream`。每个 chunk 使用统一的 streaming schema;失败时会发出错误 chunk,并以终止事件结束。
|
||||
|
||||
默认 `chat` 是 Stream Job:
|
||||
|
||||
```bash
|
||||
curl -N http://127.0.0.1:2333/chat \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"query":"总结我的长期偏好"}'
|
||||
```
|
||||
|
||||
MCP 不暴露 Stream Job。
|
||||
|
||||
## MCP
|
||||
|
||||
默认 HTTP 服务会在 `/mcp` 挂载 streamable HTTP MCP:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
mcp_enabled: true
|
||||
mcp_path: /mcp
|
||||
mcp_stateless_http: false
|
||||
```
|
||||
|
||||
也可以改用独立 MCP Service:
|
||||
|
||||
```bash
|
||||
reme start service.backend=mcp service.transport=stdio
|
||||
reme start service.backend=mcp service.transport=sse service.port=2333
|
||||
reme start service.backend=mcp service.transport=streamable-http service.port=2333
|
||||
```
|
||||
|
||||
MCP tool 来自 `enable_serve: true` 的非流式 Job。`service.jobs` 可以设置允许列表;`injected_job_kwargs` 可以注入服务端管理、调用方不能覆盖的参数。
|
||||
|
||||
```yaml
|
||||
service:
|
||||
backend: http
|
||||
jobs: [search, read, traverse, auto_memory]
|
||||
injected_job_kwargs:
|
||||
tenant_id: local-user
|
||||
tool_error_on_failure: true
|
||||
```
|
||||
|
||||
## ReMe Studio
|
||||
|
||||
安装 `reme-ai[web]` 或 `reme-ai[core]` 后,默认 HTTP 地址同时提供 ReMe Studio:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:2333/
|
||||
```
|
||||
|
||||
可通过配置关闭或指定自定义构建:
|
||||
|
||||
```yaml
|
||||
service:
|
||||
web_enabled: false
|
||||
# web_static_dir: /absolute/path/to/static
|
||||
```
|
||||
|
||||
静态资源缺失不会阻止 Job API 启动。
|
||||
|
||||
## 一次性 Job
|
||||
|
||||
需要脚本式执行而不常驻服务时:
|
||||
|
||||
```bash
|
||||
reme start job=search query="用户偏好" limit=5
|
||||
```
|
||||
|
||||
它会切换到一次性 CLI Service,但仍经过正常的 Application、Component 和 Job 生命周期。
|
||||
|
||||
## 服务发现
|
||||
|
||||
```bash
|
||||
reme find_reme
|
||||
```
|
||||
|
||||
ReMe 会记录本机运行服务的启动参数。普通 `reme <action>` 优先使用实际运行服务的 backend、host、port 和 transport;找不到运行服务时才回退到本地配置。
|
||||
|
||||
## 安全边界
|
||||
|
||||
ReMe 默认定位为本地服务:
|
||||
|
||||
- 默认绑定 `127.0.0.1`;
|
||||
- HTTP CORS 配置允许任意 origin;
|
||||
- Job 可进行文件写入、移动和删除;
|
||||
- 当前服务层不提供通用用户认证。
|
||||
|
||||
不要直接把默认服务暴露到公网。需要远程访问时,在受控网络或带身份认证、TLS、访问控制和请求大小限制的反向代理后部署,并通过 `service.jobs` 只开放必要 Job。
|
||||
|
||||
## OpenAPI
|
||||
|
||||
HTTP 服务使用 FastAPI。启用 HTTP 服务时,可以通过 `/docs`、`/redoc` 和 `/openapi.json` 查看当前配置实际注册的端点;Studio 的 SPA fallback 不会覆盖这些保留路径。
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
# ReMe GitHub Pages
|
||||
# ReMe documentation site
|
||||
|
||||
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
|
||||
`reme_studio/`.
|
||||
This package builds the VitePress site published at <https://reme.agentscope.io>. The canonical documentation lives in
|
||||
`docs/`; selected product, integration, plugin, and benchmark READMEs are mirrored into a disposable source tree during
|
||||
the build. Do not edit `.generated/` or `dist/`.
|
||||
|
||||
## Requirements
|
||||
|
||||
|
|
@ -11,79 +11,41 @@ GitHub Pages fallback is <https://agentscope-ai.github.io/ReMe/>. It does not de
|
|||
|
||||
## Local development
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
cd github-pages
|
||||
npm install
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Open the URL printed by Vite, normally <http://localhost:5173/>. The development server watches the frontend source.
|
||||
When a repository Markdown file changes, restart the development command to regenerate the documentation content.
|
||||
The development server prints its local URL. Restart it after changing a mirrored README or `reme/config/default.yaml`
|
||||
so the generated source tree and Job reference are refreshed. Changes under `docs/` are also refreshed on restart.
|
||||
|
||||
For subsequent installs or CI-compatible dependency installation, use:
|
||||
|
||||
```bash
|
||||
npm ci
|
||||
```
|
||||
|
||||
## Preview the production build
|
||||
|
||||
Build and start the preview server:
|
||||
## Validation
|
||||
|
||||
```bash
|
||||
npm test
|
||||
npm run build
|
||||
npm run preview
|
||||
```
|
||||
|
||||
Open the URL printed by Vite, normally <http://localhost:4173/>. Production assets use relative paths so the same build
|
||||
works on both the custom domain and the GitHub Pages project path.
|
||||
The test suite verifies bilingual core pages, canonical-source mappings, generated Job coverage, and disposable output.
|
||||
The production build is written to `github-pages/dist/` for the existing GitHub Pages workflow.
|
||||
|
||||
The generated `dist/` and `.generated/` directories are disposable build output and are excluded from Git.
|
||||
## Sources
|
||||
|
||||
## Documentation sources
|
||||
|
||||
The build script reads the canonical repository files directly. Do not edit generated copies under `.generated/` or
|
||||
`dist/`.
|
||||
|
||||
- `README.md` and `README_ZH.md`: project introductions
|
||||
- `docs/en/` and `docs/zh/`: English and Chinese guides
|
||||
- `docs/figure/`: documentation images
|
||||
- `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
|
||||
- `docs/`: canonical guides, VitePress configuration, theme, and brand assets
|
||||
- `reme/config/default.yaml`: generated callable Job reference
|
||||
- `reme_studio/README*.md`: ReMe Studio
|
||||
- `typescript/README*.md`: TypeScript client and adapters
|
||||
- `plugins/*/README*.md`: plugin guides
|
||||
- `benchmark/{beam,longmemeval,pibench,toolmemory}/README*.md`: benchmark guides and results
|
||||
- `AGENTS.md`: repository development guide
|
||||
- `benchmark/*/README*.md`: benchmark guides
|
||||
- `scripts/generate-content.mjs`: source mirroring and reference generation
|
||||
|
||||
To add or reorganize a document in the site navigation, update
|
||||
[`scripts/generate-content.mjs`](./scripts/generate-content.mjs). Presentation and interaction code lives in `src/`.
|
||||
|
||||
## Project structure
|
||||
|
||||
```text
|
||||
github-pages/
|
||||
├── index.html
|
||||
├── package.json
|
||||
├── scripts/
|
||||
│ └── generate-content.mjs
|
||||
├── src/
|
||||
│ ├── main.js
|
||||
│ └── styles.css
|
||||
└── vite.config.js
|
||||
```
|
||||
When adding a canonical guide, add both `docs/zh/<name>.md` and `docs/en/<name>.md`, then include it in the appropriate
|
||||
sidebar in `docs/.vitepress/config.mts`. Add repository-owned READMEs to `externalDocuments` in the generator rather than
|
||||
duplicating their full content under `docs/`.
|
||||
|
||||
## Deployment
|
||||
|
||||
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.
|
||||
|
||||
The repository's **Settings → Pages → Build and deployment → Source** must be set to **GitHub Actions**. Its custom
|
||||
domain must be set to `reme.agentscope.io`; `public/CNAME` preserves that domain in the published artifact.
|
||||
|
||||
Useful links:
|
||||
|
||||
- ReMe documentation: <https://reme.agentscope.io>
|
||||
- GitHub Pages fallback: <https://agentscope-ai.github.io/ReMe/>
|
||||
- ReMe repository: <https://github.com/agentscope-ai/ReMe>
|
||||
`.github/workflows/deploy-docs.yml` uses the reusable documentation build workflow and publishes `dist/` to GitHub
|
||||
Pages. `public/CNAME` preserves the `reme.agentscope.io` custom domain.
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="ReMe documentation — a local-first, file-native memory system for agents."
|
||||
/>
|
||||
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon.svg" />
|
||||
<title>ReMe Documentation</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1906
github-pages/package-lock.json
generated
1906
github-pages/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -7,16 +7,14 @@
|
|||
"node": ">=22.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node scripts/generate-content.mjs && vite",
|
||||
"build": "node scripts/generate-content.mjs && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "node scripts/generate-content.mjs && node --test tests/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"dompurify": "^3.2.6",
|
||||
"marked": "^16.2.1"
|
||||
"content": "node scripts/generate-content.mjs",
|
||||
"dev": "npm run content && vitepress dev .generated/site",
|
||||
"build": "npm run content && vitepress build .generated/site --outDir dist && node scripts/verify-build.mjs",
|
||||
"preview": "vitepress preview .generated/site --outDir dist",
|
||||
"test": "npm run content && node --test tests/*.test.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^7.1.1"
|
||||
"vitepress": "1.6.4",
|
||||
"yaml": "^2.8.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,249 +1,236 @@
|
|||
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoDir = path.resolve(siteDir, "..");
|
||||
const outputDir = path.join(siteDir, ".generated", "content");
|
||||
const outputDir = path.join(siteDir, ".generated", "site");
|
||||
|
||||
const navigationGroupOrder = [
|
||||
"overview",
|
||||
"start",
|
||||
"integration",
|
||||
"fundamentals",
|
||||
"automation",
|
||||
"concepts",
|
||||
"workspace",
|
||||
"plugins",
|
||||
"benchmarks",
|
||||
"development",
|
||||
const externalDocuments = [
|
||||
["en/integrations/claude-code.md", "integrations/claude_code/README.md"],
|
||||
["en/integrations/hermes.md", "integrations/hermes_agent/README.md"],
|
||||
["zh/integrations/typescript.md", "typescript/README_ZH.md"],
|
||||
["en/integrations/typescript.md", "typescript/README.md"],
|
||||
["zh/integrations/dsh.md", "typescript/docs/dsh.zh-CN.md"],
|
||||
["en/integrations/dsh.md", "typescript/docs/dsh.md"],
|
||||
["zh/integrations/openclaw.md", "typescript/docs/openclaw.zh-CN.md"],
|
||||
["en/integrations/openclaw.md", "typescript/docs/openclaw.md"],
|
||||
["zh/workspace/studio.md", "reme_studio/README_ZH.md"],
|
||||
["en/workspace/studio.md", "reme_studio/README.md"],
|
||||
["zh/plugins/daily-paper.md", "plugins/daily_paper/README_ZH.md"],
|
||||
["en/plugins/daily-paper.md", "plugins/daily_paper/README.md"],
|
||||
["zh/plugins/auto-fin.md", "plugins/auto-fin/README_ZH.md"],
|
||||
["en/plugins/auto-fin.md", "plugins/auto-fin/README.md"],
|
||||
["zh/plugins/lme.md", "plugins/lme/README_ZH.md"],
|
||||
["en/plugins/lme.md", "plugins/lme/README.md"],
|
||||
["zh/plugins/beam.md", "plugins/beam/README_ZH.md"],
|
||||
["en/plugins/beam.md", "plugins/beam/README.md"],
|
||||
["zh/benchmarks/beam.md", "benchmark/beam/README_ZH.md"],
|
||||
["en/benchmarks/beam.md", "benchmark/beam/README.md"],
|
||||
["zh/benchmarks/longmemeval.md", "benchmark/longmemeval/README_ZH.md"],
|
||||
["en/benchmarks/longmemeval.md", "benchmark/longmemeval/README.md"],
|
||||
["zh/benchmarks/pibench.md", "benchmark/pibench/README_ZH.md"],
|
||||
["en/benchmarks/pibench.md", "benchmark/pibench/README.md"],
|
||||
["zh/benchmarks/toolmemory.md", "benchmark/toolmemory/README_ZH.md"],
|
||||
["en/benchmarks/toolmemory.md", "benchmark/toolmemory/README.md"],
|
||||
];
|
||||
|
||||
const topicOrder = [
|
||||
"quick_start",
|
||||
"plugin_management",
|
||||
"memory_as_file",
|
||||
"memory_search",
|
||||
"auto_memory",
|
||||
"auto_resource",
|
||||
"auto_link",
|
||||
"auto_dream",
|
||||
"proactive",
|
||||
"reme_scene",
|
||||
"framework",
|
||||
"reme-blog",
|
||||
"contributing",
|
||||
];
|
||||
|
||||
const groups = {
|
||||
quick_start: "start",
|
||||
plugin_management: "start",
|
||||
memory_as_file: "fundamentals",
|
||||
memory_search: "fundamentals",
|
||||
auto_memory: "automation",
|
||||
auto_resource: "automation",
|
||||
auto_link: "automation",
|
||||
auto_dream: "automation",
|
||||
proactive: "automation",
|
||||
reme_scene: "concepts",
|
||||
framework: "concepts",
|
||||
"reme-blog": "concepts",
|
||||
contributing: "development",
|
||||
const externalDocumentRewrites = {
|
||||
"typescript/README.md": [
|
||||
["(./README_ZH.md)", "(/zh/integrations/typescript)"],
|
||||
["(./docs/dsh.md)", "(/en/integrations/dsh)"],
|
||||
["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
|
||||
["(./docs/openclaw.md)", "(/en/integrations/openclaw)"],
|
||||
["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
|
||||
["(./figures/dsh/", "(/figures/dsh/"],
|
||||
],
|
||||
"typescript/README_ZH.md": [
|
||||
["(./README.md)", "(/en/integrations/typescript)"],
|
||||
["(./docs/dsh.md)", "(/en/integrations/dsh)"],
|
||||
["(./docs/dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
|
||||
["(./docs/openclaw.md)", "(/en/integrations/openclaw)"],
|
||||
["(./docs/openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
|
||||
["(./figures/dsh/", "(/figures/dsh/"],
|
||||
],
|
||||
"typescript/docs/dsh.md": [
|
||||
["(./dsh.zh-CN.md)", "(/zh/integrations/dsh)"],
|
||||
["(../figures/dsh/", "(/figures/dsh/"],
|
||||
],
|
||||
"typescript/docs/dsh.zh-CN.md": [
|
||||
["(./dsh.md)", "(/en/integrations/dsh)"],
|
||||
["(../figures/dsh/", "(/figures/dsh/"],
|
||||
],
|
||||
"typescript/docs/openclaw.md": [
|
||||
["(./openclaw.zh-CN.md)", "(/zh/integrations/openclaw)"],
|
||||
],
|
||||
"typescript/docs/openclaw.zh-CN.md": [
|
||||
["(./openclaw.md)", "(/en/integrations/openclaw)"],
|
||||
],
|
||||
};
|
||||
|
||||
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" },
|
||||
auto_resource: { zh: "自动资料整理", en: "Auto Resource" },
|
||||
auto_link: { zh: "自动关联", en: "Auto Link" },
|
||||
auto_dream: { zh: "自动沉淀", en: "Auto Dream" },
|
||||
proactive: { zh: "主动发现", en: "Proactive" },
|
||||
reme_scene: { zh: "ReMe 应用场景", en: "ReMe Application Scenarios" },
|
||||
framework: { zh: "ReMe 代码框架", en: "ReMe Framework" },
|
||||
"reme-blog": { zh: "ReMe 博客", en: "ReMe Blog" },
|
||||
contributing: { zh: "开源与贡献", en: "Open Source and Contributing" },
|
||||
const groupNames = {
|
||||
zh: {
|
||||
system: "系统与诊断",
|
||||
memory: "记忆演化",
|
||||
retrieval: "检索与图谱",
|
||||
daily: "Daily Note",
|
||||
files: "文件操作",
|
||||
},
|
||||
en: {
|
||||
system: "System and diagnostics",
|
||||
memory: "Memory evolution",
|
||||
retrieval: "Retrieval and graph",
|
||||
daily: "Daily notes",
|
||||
files: "File operations",
|
||||
},
|
||||
};
|
||||
|
||||
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: "reme_studio",
|
||||
titles: { zh: "ReMe 工作台", en: "ReMe Studio" },
|
||||
descriptions: {
|
||||
zh: "浏览、编辑和搜索本地记忆,并探索记忆图谱。",
|
||||
en: "Browse, edit, search, and explore local memory from the web workspace.",
|
||||
},
|
||||
group: "workspace",
|
||||
},
|
||||
{
|
||||
slug: "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: "plugins",
|
||||
},
|
||||
{
|
||||
slug: "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: "plugins",
|
||||
},
|
||||
{
|
||||
slug: "beam",
|
||||
source: "benchmark/beam",
|
||||
titles: { zh: "BEAM", en: "BEAM" },
|
||||
descriptions: {
|
||||
zh: "评测大规模记忆检索能力。",
|
||||
en: "Evaluate memory retrieval at scale.",
|
||||
},
|
||||
group: "benchmarks",
|
||||
},
|
||||
{
|
||||
slug: "longmemeval",
|
||||
source: "benchmark/longmemeval",
|
||||
titles: { zh: "LongMemEval", en: "LongMemEval" },
|
||||
descriptions: {
|
||||
zh: "评测跨会话长期记忆问答能力。",
|
||||
en: "Evaluate long-term, cross-session memory question answering.",
|
||||
},
|
||||
group: "benchmarks",
|
||||
},
|
||||
{
|
||||
slug: "pibench",
|
||||
source: "benchmark/pibench",
|
||||
titles: { zh: "π-Bench", en: "π-Bench" },
|
||||
descriptions: {
|
||||
zh: "评测带持久记忆的个人智能体。",
|
||||
en: "Evaluate personal agents with persistent memory.",
|
||||
},
|
||||
group: "benchmarks",
|
||||
},
|
||||
{
|
||||
slug: "toolmemory",
|
||||
source: "benchmark/toolmemory",
|
||||
titles: { zh: "Tool Memory / ExpG", en: "Tool Memory / ExpG" },
|
||||
descriptions: {
|
||||
zh: "通过经验驱动的自适应指导增强 Agent 工具使用。",
|
||||
en: "Improve agent tool use through experience-driven adaptive guidance.",
|
||||
},
|
||||
group: "benchmarks",
|
||||
},
|
||||
];
|
||||
const jobGroups = {
|
||||
version: "system",
|
||||
app_config: "system",
|
||||
chat: "system",
|
||||
health_check: "system",
|
||||
status: "system",
|
||||
help: "system",
|
||||
auto_dream: "memory",
|
||||
auto_memory: "memory",
|
||||
auto_memory_cc: "memory",
|
||||
auto_resource: "memory",
|
||||
proactive: "memory",
|
||||
traverse: "retrieval",
|
||||
graph_snapshot: "retrieval",
|
||||
reindex: "retrieval",
|
||||
search: "retrieval",
|
||||
node_search: "retrieval",
|
||||
daily_list: "daily",
|
||||
daily_reindex: "daily",
|
||||
daily_write: "daily",
|
||||
frontmatter_delete: "files",
|
||||
frontmatter_read: "files",
|
||||
frontmatter_update: "files",
|
||||
stat: "files",
|
||||
list: "files",
|
||||
move: "files",
|
||||
delete: "files",
|
||||
read: "files",
|
||||
load: "files",
|
||||
read_image: "files",
|
||||
write: "files",
|
||||
save: "files",
|
||||
edit: "files",
|
||||
};
|
||||
|
||||
const sharedDocuments = [
|
||||
{
|
||||
id: "agents-guide",
|
||||
path: "AGENTS.md",
|
||||
sourcePath: "AGENTS.md",
|
||||
titles: {
|
||||
zh: "Agent 开发指南",
|
||||
en: "Agent Development Guide",
|
||||
},
|
||||
description: "Repository contracts, lifecycle rules, safety boundaries, and validation.",
|
||||
group: "development",
|
||||
language: "shared",
|
||||
},
|
||||
];
|
||||
|
||||
async function markdownTitle(filePath) {
|
||||
const source = await readFile(filePath, "utf8");
|
||||
return source.match(/^#\s+(.+)$/m)?.[1]?.replace(/[`*_]/g, "") || path.basename(filePath, ".md");
|
||||
function typeLabel(schema = {}) {
|
||||
if (schema.oneOf) return schema.oneOf.map(typeLabel).join(" or ");
|
||||
if (schema.type === "array") return `${typeLabel(schema.items || {})}[]`;
|
||||
return schema.type || "any";
|
||||
}
|
||||
|
||||
async function buildManifest() {
|
||||
const documents = [
|
||||
{
|
||||
id: "readme-zh",
|
||||
path: "README_ZH.md",
|
||||
sourcePath: "README_ZH.md",
|
||||
title: "ReMe 项目介绍",
|
||||
description: "核心理念、快速开始、使用场景与社区入口。",
|
||||
group: "overview",
|
||||
language: "zh",
|
||||
},
|
||||
{
|
||||
id: "readme-en",
|
||||
path: "README.md",
|
||||
sourcePath: "README.md",
|
||||
title: "Introducing ReMe",
|
||||
description: "Core ideas, quick start, use cases, and community resources.",
|
||||
group: "overview",
|
||||
language: "en",
|
||||
},
|
||||
];
|
||||
function markdownCell(value) {
|
||||
if (value === undefined) return "—";
|
||||
const rendered = typeof value === "string" ? value : JSON.stringify(value);
|
||||
return rendered
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("|", "\\|")
|
||||
.replaceAll("\n", " ");
|
||||
}
|
||||
|
||||
for (const language of ["zh", "en"]) {
|
||||
for (const topic of topicOrder) {
|
||||
const sourcePath = `docs/${language}/${topic}.md`;
|
||||
documents.push({
|
||||
id: `${language}-${topic}`,
|
||||
path: sourcePath,
|
||||
sourcePath,
|
||||
title: localizedTitles[topic]?.[language] || (await markdownTitle(path.join(repoDir, sourcePath))),
|
||||
description: "",
|
||||
group: groups[topic],
|
||||
language,
|
||||
});
|
||||
}
|
||||
function buildJobReference(config, language) {
|
||||
const isZh = language === "zh";
|
||||
const jobs = Object.entries(config.jobs || {}).filter(([, job]) => !["background", "cron"].includes(job.backend));
|
||||
const sections = new Map();
|
||||
|
||||
for (const product of productDocuments) {
|
||||
const filename = language === "zh" ? "README_ZH.md" : "README.md";
|
||||
documents.push({
|
||||
id: `${product.slug}-${language}`,
|
||||
path: `${product.source}/${filename}`,
|
||||
sourcePath: `${product.source}/${filename}`,
|
||||
title: product.titles[language],
|
||||
description: product.descriptions[language],
|
||||
group: product.group,
|
||||
language,
|
||||
});
|
||||
}
|
||||
for (const [name, job] of jobs) {
|
||||
const group = jobGroups[name] || "system";
|
||||
if (!sections.has(group)) sections.set(group, []);
|
||||
sections.get(group).push([name, job]);
|
||||
}
|
||||
|
||||
return [...documents, ...sharedDocuments].sort(
|
||||
(left, right) => navigationGroupOrder.indexOf(left.group) - navigationGroupOrder.indexOf(right.group),
|
||||
);
|
||||
const lines = [
|
||||
"---",
|
||||
`title: ${isZh ? "Job API 参考" : "Job API Reference"}`,
|
||||
`description: ${isZh ? "从默认配置自动生成的可调用 Job、参数和服务边界。" : "Callable jobs, parameters, and service boundaries generated from the default configuration."}`,
|
||||
"---",
|
||||
"",
|
||||
`# ${isZh ? "Job API 参考" : "Job API Reference"}`,
|
||||
"",
|
||||
isZh
|
||||
? "本页从 `reme/config/default.yaml` 自动生成。它描述默认应用中的可调用 Job;插件和自定义配置可以增加、删除或覆盖 Job。运行 `reme help` 可查看当前服务的实际能力。"
|
||||
: "This page is generated from `reme/config/default.yaml`. It describes callable jobs in the default application; plugins and custom configurations may add, remove, or override jobs. Run `reme help` to inspect the active service.",
|
||||
"",
|
||||
isZh
|
||||
? "> 后台 Job 和 Cron Job 不通过服务暴露,因此不列入调用参考。"
|
||||
: "> Background and cron jobs are not service-exposed and are omitted from the callable reference.",
|
||||
"",
|
||||
];
|
||||
|
||||
for (const [group, entries] of sections) {
|
||||
lines.push(`## ${groupNames[language][group]}`, "");
|
||||
for (const [name, job] of entries) {
|
||||
const properties = job.parameters?.properties || {};
|
||||
const required = new Set(job.parameters?.required || []);
|
||||
lines.push(`### \`${name}\``, "", markdownCell(job.description || ""), "");
|
||||
lines.push("```bash", `reme ${name}${Object.keys(properties).length ? " ..." : ""}`, "```", "");
|
||||
if (!Object.keys(properties).length) {
|
||||
lines.push(isZh ? "无参数。" : "No parameters.", "");
|
||||
continue;
|
||||
}
|
||||
lines.push(
|
||||
isZh
|
||||
? "| 参数 | 类型 | 必填 | 默认值 | 说明 |"
|
||||
: "| Parameter | Type | Required | Default | Description |",
|
||||
"|---|---|---:|---|---|",
|
||||
);
|
||||
for (const [parameter, schema] of Object.entries(properties)) {
|
||||
lines.push(
|
||||
`| \`${parameter}\` | \`${markdownCell(typeLabel(schema))}\` | ${required.has(parameter) ? (isZh ? "是" : "yes") : (isZh ? "否" : "no")} | ${markdownCell(schema.default)} | ${markdownCell(schema.description || "—")} |`,
|
||||
);
|
||||
}
|
||||
lines.push("");
|
||||
}
|
||||
}
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
await rm(path.join(siteDir, ".generated"), { recursive: true, force: true });
|
||||
await mkdir(outputDir, { recursive: true });
|
||||
await cp(path.join(siteDir, "public", "favicon.svg"), path.join(siteDir, ".generated", "favicon.svg"));
|
||||
await cp(path.join(siteDir, "public", "CNAME"), path.join(siteDir, ".generated", "CNAME"));
|
||||
|
||||
for (const file of ["README.md", "README_ZH.md", "AGENTS.md"]) {
|
||||
await cp(path.join(repoDir, file), path.join(outputDir, file));
|
||||
}
|
||||
await cp(path.join(repoDir, "docs"), path.join(outputDir, "docs"), {
|
||||
await cp(path.join(repoDir, "docs"), outputDir, {
|
||||
recursive: true,
|
||||
filter: (source) => path.basename(source) !== ".DS_Store",
|
||||
filter: (source) => ![".DS_Store", "plans"].includes(path.basename(source)),
|
||||
});
|
||||
for (const product of productDocuments) {
|
||||
await mkdir(path.join(outputDir, product.source), { recursive: true });
|
||||
for (const filename of ["README.md", "README_ZH.md"]) {
|
||||
await cp(path.join(repoDir, product.source, filename), path.join(outputDir, product.source, filename));
|
||||
}
|
||||
await cp(path.join(siteDir, "public", "CNAME"), path.join(outputDir, "public", "CNAME"));
|
||||
await cp(path.join(repoDir, "docs/figure/reme-icon.svg"), path.join(outputDir, "public", "reme-icon.svg"));
|
||||
await cp(path.join(repoDir, "docs/figure/reme-logo-fashion.svg"), path.join(outputDir, "public", "reme-logo.svg"));
|
||||
|
||||
const sourceMap = {};
|
||||
for (const [destination, source] of externalDocuments) {
|
||||
const destinationPath = path.join(outputDir, destination);
|
||||
await mkdir(path.dirname(destinationPath), { recursive: true });
|
||||
let content = await readFile(path.join(repoDir, source), "utf8");
|
||||
for (const [from, to] of externalDocumentRewrites[source] || []) content = content.replaceAll(from, to);
|
||||
await writeFile(destinationPath, content);
|
||||
sourceMap[destination] = source;
|
||||
}
|
||||
await writeFile(
|
||||
path.join(outputDir, "manifest.json"),
|
||||
`${JSON.stringify({ documents: await buildManifest() }, null, 2)}\n`,
|
||||
);
|
||||
|
||||
await cp(path.join(repoDir, "typescript/figures/dsh"), path.join(outputDir, "public/figures/dsh"), {
|
||||
recursive: true,
|
||||
});
|
||||
|
||||
for (const language of ["zh", "en"]) {
|
||||
await cp(
|
||||
path.join(repoDir, "benchmark/toolmemory/gitcha.png"),
|
||||
path.join(outputDir, language, "benchmarks", "gitcha.png"),
|
||||
);
|
||||
}
|
||||
|
||||
const defaultConfig = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
|
||||
for (const language of ["zh", "en"]) {
|
||||
const destination = path.join(outputDir, language, "reference", "jobs.md");
|
||||
await mkdir(path.dirname(destination), { recursive: true });
|
||||
await writeFile(destination, buildJobReference(defaultConfig, language));
|
||||
sourceMap[`${language}/reference/jobs.md`] = "reme/config/default.yaml";
|
||||
}
|
||||
|
||||
await writeFile(path.join(outputDir, ".source-map.json"), `${JSON.stringify(sourceMap, null, 2)}\n`);
|
||||
|
|
|
|||
92
github-pages/scripts/verify-build.mjs
Normal file
92
github-pages/scripts/verify-build.mjs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { access, readFile, readdir } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const outputDir = path.join(siteDir, "dist");
|
||||
|
||||
async function collectFiles(directory, prefix = "") {
|
||||
const files = [];
|
||||
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
||||
const relativePath = path.posix.join(prefix, entry.name);
|
||||
if (entry.isDirectory()) files.push(...await collectFiles(path.join(directory, entry.name), relativePath));
|
||||
else files.push(relativePath);
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
function pageUrl(relativePath) {
|
||||
if (relativePath === "index.html") return "/";
|
||||
if (relativePath.endsWith("/index.html")) return `/${relativePath.slice(0, -"index.html".length)}`;
|
||||
return `/${relativePath.slice(0, -".html".length)}`;
|
||||
}
|
||||
|
||||
function routeExists(pathname, files) {
|
||||
const relativePath = decodeURIComponent(pathname).replace(/^\/+/, "");
|
||||
if (!relativePath) return files.has("index.html");
|
||||
if (relativePath.endsWith("/")) return files.has(`${relativePath}index.html`);
|
||||
return files.has(relativePath) || files.has(`${relativePath}.html`) || files.has(`${relativePath}/index.html`);
|
||||
}
|
||||
|
||||
const requiredFiles = [
|
||||
"index.html",
|
||||
"404.html",
|
||||
"CNAME",
|
||||
"reme-icon.svg",
|
||||
"reme-logo.svg",
|
||||
"hashmap.json",
|
||||
"sitemap.xml",
|
||||
"llms.txt",
|
||||
"llms-full.txt",
|
||||
"zh/index.html",
|
||||
"en/index.html",
|
||||
"zh/configuration.html",
|
||||
"en/configuration.html",
|
||||
"zh/services.html",
|
||||
"en/services.html",
|
||||
"zh/reference/jobs.html",
|
||||
"en/reference/jobs.html",
|
||||
"zh/configuration/llms.txt",
|
||||
"en/configuration/llms.txt",
|
||||
];
|
||||
|
||||
for (const relativePath of requiredFiles) await access(path.join(outputDir, relativePath));
|
||||
|
||||
assert.equal((await readFile(path.join(outputDir, "CNAME"), "utf8")).trim(), "reme.agentscope.io");
|
||||
|
||||
const homepage = await readFile(path.join(outputDir, "index.html"), "utf8");
|
||||
assert.ok(homepage.includes('href="/en/"'), "root language switch must link to /en/");
|
||||
assert.ok(!homepage.includes('href="/en/ex"'), "root language switch must not produce /en/ex");
|
||||
assert.ok(homepage.includes('"studio-en":"/en/workspace/studio"'), "legacy redirects must be embedded");
|
||||
|
||||
const sitemap = await readFile(path.join(outputDir, "sitemap.xml"), "utf8");
|
||||
assert.ok(sitemap.includes("<lastmod>"), "sitemap must include canonical-source update times");
|
||||
assert.ok(!sitemap.includes("<loc>https://reme.agentscope.io/</loc>"), "root redirect must not be indexed");
|
||||
assert.ok(!sitemap.includes('hreflang="zh-CN" href="https://reme.agentscope.io/"'), "root must not duplicate zh-CN");
|
||||
|
||||
const ChineseConfiguration = await readFile(path.join(outputDir, "zh/configuration.html"), "utf8");
|
||||
assert.match(ChineseConfiguration, /搜索文档/);
|
||||
assert.match(ChineseConfiguration, /复制 Markdown/);
|
||||
assert.match(ChineseConfiguration, /在 GitHub 查看源文件/);
|
||||
|
||||
const jobReference = await readFile(path.join(outputDir, "en/reference/jobs.html"), "utf8");
|
||||
assert.match(jobReference, /Job API Reference/);
|
||||
assert.match(jobReference, /auto_memory/);
|
||||
|
||||
const outputFiles = new Set(await collectFiles(outputDir));
|
||||
const missingLinks = [];
|
||||
for (const relativePath of [...outputFiles].filter((file) => file.endsWith(".html"))) {
|
||||
const html = await readFile(path.join(outputDir, relativePath), "utf8");
|
||||
const currentUrl = new URL(pageUrl(relativePath), "https://reme-docs.local");
|
||||
for (const match of html.matchAll(/<a\b[^>]*\bhref="([^"]+)"/g)) {
|
||||
const href = match[1].replaceAll("&", "&");
|
||||
if (href.startsWith("#")) continue;
|
||||
const target = new URL(href, currentUrl);
|
||||
if (target.origin !== currentUrl.origin) continue;
|
||||
if (!routeExists(target.pathname, outputFiles)) missingLinks.push(`${relativePath}: ${href}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(missingLinks, [], `missing internal links:\n${missingLinks.join("\n")}`);
|
||||
|
||||
console.log(`Verified ${requiredFiles.length} documentation build artifacts.`);
|
||||
|
|
@ -1,458 +0,0 @@
|
|||
import DOMPurify from "dompurify";
|
||||
import { marked } from "marked";
|
||||
import { stripMarkdownFrontmatter } from "./markdown.js";
|
||||
import "./styles.css";
|
||||
|
||||
const baseUrl = import.meta.env.BASE_URL;
|
||||
const repositoryUrl = "https://github.com/agentscope-ai/ReMe";
|
||||
|
||||
const copy = {
|
||||
zh: {
|
||||
docs: "文档",
|
||||
home: "首页",
|
||||
search: "搜索文档…",
|
||||
noResults: "没有找到匹配的文档",
|
||||
menu: "打开导航",
|
||||
toc: "本页目录",
|
||||
edit: "在 GitHub 查看源文件",
|
||||
quickStart: "快速开始",
|
||||
groups: {
|
||||
overview: "项目介绍",
|
||||
start: "开始使用",
|
||||
fundamentals: "核心原理",
|
||||
automation: "自动化能力",
|
||||
concepts: "架构与场景",
|
||||
integration: "Agent 集成",
|
||||
workspace: "工作区",
|
||||
plugins: "插件",
|
||||
benchmarks: "评测",
|
||||
development: "开发者规范",
|
||||
},
|
||||
},
|
||||
en: {
|
||||
docs: "Documentation",
|
||||
home: "Home",
|
||||
search: "Search documentation…",
|
||||
noResults: "No matching documents",
|
||||
menu: "Open navigation",
|
||||
toc: "On this page",
|
||||
edit: "View source on GitHub",
|
||||
quickStart: "Quick start",
|
||||
groups: {
|
||||
overview: "Introduction",
|
||||
start: "Get started",
|
||||
fundamentals: "Fundamentals",
|
||||
automation: "Automation",
|
||||
concepts: "Architecture & scenarios",
|
||||
integration: "Agent integration",
|
||||
workspace: "Workspace",
|
||||
plugins: "Plugins",
|
||||
benchmarks: "Benchmarks",
|
||||
development: "Development",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const state = {
|
||||
language: localStorage.getItem("reme-docs-language") || "zh",
|
||||
documents: [],
|
||||
activeDocument: null,
|
||||
query: "",
|
||||
};
|
||||
|
||||
const homeCopy = {
|
||||
zh: {
|
||||
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
|
||||
title: "让 Agent 真正记住,\n也让记忆始终属于你。",
|
||||
description: "ReMe 将对话和资料沉淀为可读、可编辑、可检索、相互链接的 Markdown,并提供从工作区管理到主动研究的一整套工具。",
|
||||
start: "快速开始",
|
||||
project: "了解 ReMe",
|
||||
explore: "按目标探索",
|
||||
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: "Auto Fin 插件", description: "连接最新财联社新闻和本地历史记忆,生成可追溯的研究报告。", tone: "amber" },
|
||||
],
|
||||
benchmark: "验证记忆能力",
|
||||
benchmarkDescription: "从检索规模、跨会话问答、个人智能体到工具经验,查看 ReMe 的四套评测与复现实验。",
|
||||
benchmarkAction: "从 BEAM 开始",
|
||||
},
|
||||
en: {
|
||||
eyebrow: "LOCAL-FIRST · FILE-NATIVE",
|
||||
title: "Memory that works for agents.\nFiles that remain yours.",
|
||||
description: "ReMe turns conversations and resources into readable, editable, searchable, interconnected Markdown—with tools spanning workspace management and proactive research.",
|
||||
start: "Quick start",
|
||||
project: "Meet ReMe",
|
||||
explore: "Explore by goal",
|
||||
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 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.",
|
||||
benchmarkAction: "Start with BEAM",
|
||||
},
|
||||
};
|
||||
|
||||
const app = document.querySelector("#app");
|
||||
|
||||
app.innerHTML = `
|
||||
<header class="topbar">
|
||||
<a class="brand" href="${baseUrl}" aria-label="ReMe documentation home">
|
||||
<span class="brand-mark">R</span>
|
||||
<span>ReMe</span>
|
||||
<span class="brand-divider"></span>
|
||||
<span class="brand-section" data-copy="docs"></span>
|
||||
</a>
|
||||
<nav class="top-actions" aria-label="Global navigation">
|
||||
<div class="language-switch" role="group" aria-label="Language">
|
||||
<button type="button" data-language="zh">中</button>
|
||||
<button type="button" data-language="en">EN</button>
|
||||
</div>
|
||||
<a class="quick-start-link" href="?doc=zh-quick_start" data-doc="zh-quick_start" data-copy="quickStart"></a>
|
||||
<a class="github-link" href="${repositoryUrl}" target="_blank" rel="noreferrer">GitHub ↗</a>
|
||||
<button class="menu-button" type="button" aria-expanded="false" data-action="menu"></button>
|
||||
</nav>
|
||||
</header>
|
||||
<div class="docs-shell">
|
||||
<aside class="sidebar" aria-label="Documentation navigation">
|
||||
<label class="search-box">
|
||||
<span aria-hidden="true">⌕</span>
|
||||
<input type="search" autocomplete="off" />
|
||||
<kbd>⌘K</kbd>
|
||||
</label>
|
||||
<nav class="document-nav"></nav>
|
||||
<div class="sidebar-footer">
|
||||
<span class="status-dot"></span>
|
||||
Local-first · File-native
|
||||
</div>
|
||||
</aside>
|
||||
<main class="article-wrap">
|
||||
<article class="article"><div class="loading-line"></div></article>
|
||||
</main>
|
||||
<aside class="toc-panel"><nav class="toc"></nav></aside>
|
||||
</div>
|
||||
<button class="sidebar-backdrop" type="button" aria-label="Close navigation"></button>
|
||||
`;
|
||||
|
||||
const sidebar = app.querySelector(".sidebar");
|
||||
const docsShell = app.querySelector(".docs-shell");
|
||||
const documentNav = app.querySelector(".document-nav");
|
||||
const article = app.querySelector(".article");
|
||||
const toc = app.querySelector(".toc");
|
||||
const searchInput = app.querySelector("input[type='search']");
|
||||
const menuButton = app.querySelector(".menu-button");
|
||||
const backdrop = app.querySelector(".sidebar-backdrop");
|
||||
|
||||
function slugify(value) {
|
||||
return value
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace(/<[^>]+>/g, "")
|
||||
.replace(/[^\p{Letter}\p{Number}]+/gu, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
function resolveDocumentPath(currentPath, target) {
|
||||
const cleanTarget = target.split("#")[0].split("?")[0];
|
||||
const currentParts = currentPath.split("/");
|
||||
currentParts.pop();
|
||||
for (const part of cleanTarget.split("/")) {
|
||||
if (!part || part === ".") continue;
|
||||
if (part === "..") currentParts.pop();
|
||||
else currentParts.push(part);
|
||||
}
|
||||
return currentParts.join("/");
|
||||
}
|
||||
|
||||
function configureMarkdown(document) {
|
||||
const renderer = new marked.Renderer();
|
||||
const headingIds = new Map();
|
||||
|
||||
renderer.heading = ({ tokens, depth }) => {
|
||||
const text = tokens.map((token) => token.text || token.raw || "").join("");
|
||||
const baseSlug = slugify(text) || "section";
|
||||
const count = headingIds.get(baseSlug) || 0;
|
||||
headingIds.set(baseSlug, count + 1);
|
||||
const id = count ? `${baseSlug}-${count + 1}` : baseSlug;
|
||||
return `<h${depth} id="${id}">${text}</h${depth}>`;
|
||||
};
|
||||
|
||||
renderer.image = ({ href, title, text }) => {
|
||||
const url = /^(https?:|data:)/.test(href)
|
||||
? href
|
||||
: `${baseUrl}content/${resolveDocumentPath(document.path, href)}`;
|
||||
const titleAttribute = title ? ` title="${title}"` : "";
|
||||
return `<img src="${url}" alt="${text}" loading="lazy"${titleAttribute}>`;
|
||||
};
|
||||
|
||||
renderer.link = ({ href, title, tokens }) => {
|
||||
const label = tokens.map((token) => token.text || token.raw || "").join("");
|
||||
const titleAttribute = title ? ` title="${title}"` : "";
|
||||
if (href.startsWith("#")) return `<a href="${href}"${titleAttribute}>${label}</a>`;
|
||||
if (!/^(https?:|mailto:)/.test(href)) {
|
||||
const resolved = resolveDocumentPath(document.path, href);
|
||||
const localDocument = state.documents.find((item) => item.path === resolved);
|
||||
if (localDocument) return `<a href="?doc=${localDocument.id}" data-doc="${localDocument.id}">${label}</a>`;
|
||||
return `<a href="${repositoryUrl}/blob/main/${resolved}" target="_blank" rel="noreferrer">${label}</a>`;
|
||||
}
|
||||
return `<a href="${href}" target="_blank" rel="noreferrer"${titleAttribute}>${label}</a>`;
|
||||
};
|
||||
|
||||
marked.use({ renderer, gfm: true, breaks: false });
|
||||
}
|
||||
|
||||
function availableDocuments() {
|
||||
return state.documents.filter(
|
||||
(document) => document.language === state.language || document.language === "shared",
|
||||
);
|
||||
}
|
||||
|
||||
function documentTitle(document) {
|
||||
return document.titles?.[state.language] || document.title;
|
||||
}
|
||||
|
||||
function renderChrome() {
|
||||
const labels = copy[state.language];
|
||||
app.querySelector("[data-copy='docs']").textContent = labels.docs;
|
||||
const quickStartLink = app.querySelector("[data-copy='quickStart']");
|
||||
quickStartLink.textContent = `${labels.quickStart} →`;
|
||||
quickStartLink.href = `?doc=${state.language}-quick_start`;
|
||||
quickStartLink.dataset.doc = `${state.language}-quick_start`;
|
||||
searchInput.placeholder = labels.search;
|
||||
menuButton.textContent = labels.menu;
|
||||
document.documentElement.lang = state.language === "zh" ? "zh-CN" : "en";
|
||||
app.querySelectorAll("[data-language]").forEach((button) => {
|
||||
button.classList.toggle("active", button.dataset.language === state.language);
|
||||
});
|
||||
}
|
||||
|
||||
function renderNavigation() {
|
||||
const labels = copy[state.language];
|
||||
const query = state.query.trim().toLocaleLowerCase();
|
||||
const filtered = availableDocuments().filter((document) =>
|
||||
`${documentTitle(document)} ${document.title || ""} ${document.description}`.toLocaleLowerCase().includes(query),
|
||||
);
|
||||
const groups = [...new Set(filtered.map((document) => document.group))];
|
||||
|
||||
if (!filtered.length) {
|
||||
documentNav.innerHTML = `
|
||||
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
|
||||
<p class="empty-state">${labels.noResults}</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
documentNav.innerHTML = `
|
||||
<a href="${baseUrl}" data-home class="home-link ${state.activeDocument ? "" : "active"}">${labels.home}</a>
|
||||
` + groups
|
||||
.map(
|
||||
(group) => `
|
||||
<section class="nav-group">
|
||||
<h2>${labels.groups[group]}</h2>
|
||||
${filtered
|
||||
.filter((document) => document.group === group)
|
||||
.map(
|
||||
(document) => `
|
||||
<a href="?doc=${document.id}" data-doc="${document.id}" class="${state.activeDocument?.id === document.id ? "active" : ""}">
|
||||
<span>${documentTitle(document)}</span>
|
||||
</a>`,
|
||||
)
|
||||
.join("")}
|
||||
</section>`,
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
|
||||
function renderHome(pushHistory = true) {
|
||||
const labels = homeCopy[state.language];
|
||||
state.activeDocument = null;
|
||||
docsShell.classList.add("home-view");
|
||||
article.dataset.group = "home";
|
||||
renderNavigation();
|
||||
article.innerHTML = `
|
||||
<section class="home-hero">
|
||||
<p class="home-eyebrow">${labels.eyebrow}</p>
|
||||
<h1>${labels.title.replace("\n", "<br>")}</h1>
|
||||
<p class="home-lead">${labels.description}</p>
|
||||
<div class="home-actions">
|
||||
<a href="${baseUrl}?doc=${state.language}-quick_start" class="primary-action">${labels.start} →</a>
|
||||
<a href="${baseUrl}?doc=readme-${state.language}" class="secondary-action">${labels.project}</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="home-explore">
|
||||
<p class="section-kicker">01 / PRODUCT & PLUGINS</p>
|
||||
<h2>${labels.explore}</h2>
|
||||
<p class="section-lead">${labels.exploreDescription}</p>
|
||||
<div class="feature-grid">
|
||||
${labels.cards.map((card) => `
|
||||
<a href="${baseUrl}?doc=${card.id}" class="feature-card ${card.tone}">
|
||||
<span class="feature-icon">${card.icon}</span>
|
||||
<span class="feature-label">${card.label}</span>
|
||||
<strong>${card.title}</strong>
|
||||
<span class="feature-description">${card.description}</span>
|
||||
<span class="feature-arrow">→</span>
|
||||
</a>`).join("")}
|
||||
</div>
|
||||
</section>
|
||||
<section class="benchmark-callout">
|
||||
<div>
|
||||
<p class="section-kicker">02 / BENCHMARKS</p>
|
||||
<h2>${labels.benchmark}</h2>
|
||||
<p>${labels.benchmarkDescription}</p>
|
||||
</div>
|
||||
<a href="${baseUrl}?doc=beam-${state.language}">${labels.benchmarkAction} →</a>
|
||||
</section>
|
||||
`;
|
||||
toc.innerHTML = "";
|
||||
closeMenu();
|
||||
if (pushHistory) history.pushState({ home: true }, "", baseUrl);
|
||||
window.scrollTo({ top: 0, behavior: "instant" });
|
||||
}
|
||||
|
||||
function renderToc() {
|
||||
const headings = [...article.querySelectorAll("h2, h3")];
|
||||
if (!headings.length) {
|
||||
toc.innerHTML = "";
|
||||
return;
|
||||
}
|
||||
toc.innerHTML = `
|
||||
<h2>${copy[state.language].toc}</h2>
|
||||
${headings
|
||||
.map(
|
||||
(heading) => `<a class="toc-${heading.tagName.toLowerCase()}" href="#${heading.id}">${heading.childNodes[0]?.textContent || heading.textContent}</a>`,
|
||||
)
|
||||
.join("")}
|
||||
`;
|
||||
}
|
||||
|
||||
function rewriteRenderedUrls(document) {
|
||||
article.querySelectorAll("img[src]").forEach((image) => {
|
||||
const source = image.getAttribute("src");
|
||||
if (source && !/^(https?:|data:|\/)/.test(source)) {
|
||||
image.src = `${baseUrl}content/${resolveDocumentPath(document.path, source)}`;
|
||||
}
|
||||
});
|
||||
|
||||
article.querySelectorAll("a[href]").forEach((link) => {
|
||||
const href = link.getAttribute("href");
|
||||
if (!href || /^(https?:|mailto:|#|\/)/.test(href) || link.dataset.doc) return;
|
||||
const resolved = resolveDocumentPath(document.path, href);
|
||||
const localDocument = state.documents.find((item) => item.path === resolved);
|
||||
if (localDocument) {
|
||||
link.href = `?doc=${localDocument.id}`;
|
||||
link.dataset.doc = localDocument.id;
|
||||
link.removeAttribute("target");
|
||||
return;
|
||||
}
|
||||
link.href = `${repositoryUrl}/blob/main/${resolved}`;
|
||||
link.target = "_blank";
|
||||
link.rel = "noreferrer";
|
||||
});
|
||||
}
|
||||
|
||||
async function openDocument(id, pushHistory = true) {
|
||||
const fallbackId = state.language === "zh" ? "readme-zh" : "readme-en";
|
||||
const document = state.documents.find((item) => item.id === id) || state.documents.find((item) => item.id === fallbackId);
|
||||
if (document.language !== "shared" && document.language !== state.language) {
|
||||
state.language = document.language;
|
||||
localStorage.setItem("reme-docs-language", state.language);
|
||||
renderChrome();
|
||||
}
|
||||
state.activeDocument = document;
|
||||
docsShell.classList.remove("home-view");
|
||||
article.dataset.group = document.group;
|
||||
renderNavigation();
|
||||
article.innerHTML = `<div class="loading-line"></div>`;
|
||||
|
||||
const response = await fetch(`${baseUrl}content/${document.path}`);
|
||||
if (!response.ok) throw new Error(`Unable to load ${document.path}`);
|
||||
configureMarkdown(document);
|
||||
const markdown = stripMarkdownFrontmatter(await response.text());
|
||||
const body = DOMPurify.sanitize(await marked.parse(markdown), {
|
||||
ADD_ATTR: ["target"],
|
||||
});
|
||||
article.innerHTML = `
|
||||
<div class="article-meta">
|
||||
<span>${copy[state.language].groups[document.group]}</span>
|
||||
<span>·</span>
|
||||
<span>${document.sourcePath}</span>
|
||||
</div>
|
||||
<div class="markdown-body">${body}</div>
|
||||
<footer class="article-footer">
|
||||
<a href="${repositoryUrl}/blob/main/${document.sourcePath}" target="_blank" rel="noreferrer">${copy[state.language].edit} ↗</a>
|
||||
</footer>
|
||||
`;
|
||||
rewriteRenderedUrls(document);
|
||||
renderToc();
|
||||
closeMenu();
|
||||
if (pushHistory) history.pushState({ doc: document.id }, "", `?doc=${document.id}`);
|
||||
window.scrollTo({ top: 0, behavior: "instant" });
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
sidebar.classList.remove("open");
|
||||
backdrop.classList.remove("visible");
|
||||
menuButton.setAttribute("aria-expanded", "false");
|
||||
}
|
||||
|
||||
function toggleMenu() {
|
||||
const open = !sidebar.classList.contains("open");
|
||||
sidebar.classList.toggle("open", open);
|
||||
backdrop.classList.toggle("visible", open);
|
||||
menuButton.setAttribute("aria-expanded", String(open));
|
||||
}
|
||||
|
||||
app.addEventListener("click", (event) => {
|
||||
const homeLink = event.target.closest("[data-home]");
|
||||
if (homeLink) {
|
||||
event.preventDefault();
|
||||
renderHome();
|
||||
return;
|
||||
}
|
||||
const documentLink = event.target.closest("[data-doc]");
|
||||
if (documentLink) {
|
||||
event.preventDefault();
|
||||
openDocument(documentLink.dataset.doc);
|
||||
}
|
||||
if (event.target.closest("[data-action='menu']")) toggleMenu();
|
||||
const languageButton = event.target.closest("[data-language]");
|
||||
if (languageButton && languageButton.dataset.language !== state.language) {
|
||||
state.language = languageButton.dataset.language;
|
||||
localStorage.setItem("reme-docs-language", state.language);
|
||||
state.query = "";
|
||||
searchInput.value = "";
|
||||
renderChrome();
|
||||
renderHome();
|
||||
}
|
||||
});
|
||||
|
||||
searchInput.addEventListener("input", () => {
|
||||
state.query = searchInput.value;
|
||||
renderNavigation();
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", (event) => {
|
||||
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
|
||||
event.preventDefault();
|
||||
searchInput.focus();
|
||||
}
|
||||
if (event.key === "Escape") closeMenu();
|
||||
});
|
||||
|
||||
backdrop.addEventListener("click", closeMenu);
|
||||
window.addEventListener("popstate", (event) => {
|
||||
const id = event.state?.doc || new URLSearchParams(location.search).get("doc");
|
||||
if (id) openDocument(id, false);
|
||||
else renderHome(false);
|
||||
});
|
||||
|
||||
const manifest = await fetch(`${baseUrl}content/manifest.json`).then((response) => response.json());
|
||||
state.documents = manifest.documents;
|
||||
renderChrome();
|
||||
const initialDocument = new URLSearchParams(location.search).get("doc");
|
||||
if (initialDocument) await openDocument(initialDocument, false);
|
||||
else renderHome(false);
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
const FRONTMATTER_PATTERN = /^\uFEFF?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/;
|
||||
|
||||
/** Remove a leading YAML frontmatter block before rendering Markdown. */
|
||||
export function stripMarkdownFrontmatter(markdown) {
|
||||
return markdown.replace(FRONTMATTER_PATTERN, "");
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
:root {
|
||||
color: #17221d;
|
||||
background: #f4f7f5;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
--ink: #17221d;
|
||||
--muted: #65716a;
|
||||
--line: #dce5e0;
|
||||
--paper: #ffffff;
|
||||
--green: #087f6a;
|
||||
--blue: #3156d9;
|
||||
--green-soft: #e5f5ef;
|
||||
--code: #f3f5f2;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-behavior: smooth; scroll-padding-top: 92px; }
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
background:
|
||||
radial-gradient(circle at 8% 9%, rgba(27, 193, 164, 0.06), transparent 26rem),
|
||||
#f4f7f5;
|
||||
}
|
||||
button, input { font: inherit; }
|
||||
a { color: inherit; }
|
||||
|
||||
.topbar {
|
||||
position: fixed;
|
||||
inset: 0 0 auto 0;
|
||||
z-index: 30;
|
||||
height: 66px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 28px;
|
||||
border-bottom: 1px solid rgba(213, 225, 219, 0.85);
|
||||
background: rgba(250, 252, 251, 0.88);
|
||||
backdrop-filter: blur(18px) saturate(140%);
|
||||
}
|
||||
|
||||
.brand { display: flex; align-items: center; gap: 11px; color: var(--ink); text-decoration: none; font-weight: 760; }
|
||||
.brand-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 11px;
|
||||
color: white;
|
||||
background: linear-gradient(145deg, #19c9b0, #3156d9 82%);
|
||||
box-shadow: 0 7px 18px rgba(24, 123, 114, 0.2);
|
||||
font-family: Georgia, serif;
|
||||
font-size: 21px;
|
||||
}
|
||||
.brand-divider { width: 1px; height: 20px; background: var(--line); margin-left: 2px; }
|
||||
.brand-section { color: var(--muted); font-weight: 520; }
|
||||
.top-actions { display: flex; align-items: center; gap: 18px; }
|
||||
.github-link, .quick-start-link { color: #37443d; text-decoration: none; font-size: 13px; font-weight: 650; }
|
||||
.github-link:hover, .quick-start-link:hover { color: var(--green); }
|
||||
.quick-start-link { padding: 8px 12px; border: 1px solid #d7e5df; border-radius: 9px; background: rgba(255, 255, 255, 0.72); }
|
||||
.language-switch { display: flex; padding: 3px; border: 1px solid var(--line); border-radius: 9px; background: #f5f7f4; }
|
||||
.language-switch button { padding: 5px 9px; border: 0; border-radius: 6px; color: var(--muted); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
|
||||
.language-switch button.active { color: var(--ink); background: white; box-shadow: 0 1px 3px rgba(20, 40, 30, 0.1); }
|
||||
.menu-button { display: none; border: 1px solid var(--line); border-radius: 8px; background: white; padding: 7px 10px; cursor: pointer; }
|
||||
|
||||
.docs-shell { display: grid; grid-template-columns: 276px minmax(0, 1fr) 224px; max-width: 1540px; min-height: 100vh; margin: 0 auto; padding-top: 66px; }
|
||||
.docs-shell.home-view { grid-template-columns: 276px minmax(0, 1fr); }
|
||||
.docs-shell.home-view .toc-panel { display: none; }
|
||||
.sidebar { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 25px 20px 18px; overflow-y: auto; border-right: 1px solid var(--line); background: rgba(247, 250, 248, 0.78); }
|
||||
.search-box { display: flex; align-items: center; gap: 8px; height: 41px; padding: 0 11px; border: 1px solid #d8e3dd; border-radius: 11px; color: #7a857e; background: rgba(255, 255, 255, 0.84); box-shadow: 0 5px 18px rgba(29, 65, 48, 0.035); }
|
||||
.search-box:focus-within { border-color: #78aa8e; box-shadow: 0 0 0 3px rgba(22, 120, 76, 0.1); }
|
||||
.search-box input { width: 100%; border: 0; outline: 0; color: var(--ink); background: transparent; font-size: 13px; }
|
||||
.search-box kbd { padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; background: #f7f8f6; font-size: 10px; }
|
||||
.document-nav { padding: 12px 0 52px; }
|
||||
.home-link { position: relative; display: block; margin-top: 9px; padding: 9px 10px 9px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; }
|
||||
.home-link:hover { color: var(--ink); background: #f0f3ef; }
|
||||
.home-link.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
|
||||
.nav-group { margin-top: 21px; }
|
||||
.nav-group h2 { margin: 0 10px 7px; color: #8a948e; font-size: 10px; font-weight: 800; letter-spacing: 0.11em; text-transform: uppercase; }
|
||||
.nav-group a { position: relative; display: block; padding: 7px 10px 7px 13px; border-radius: 8px; color: #536058; text-decoration: none; font-size: 13px; line-height: 1.4; }
|
||||
.nav-group a:hover { color: var(--ink); background: #f0f3ef; }
|
||||
.nav-group a.active { color: #086b5a; background: linear-gradient(90deg, #dff3ec, #eaf6f2); font-weight: 700; }
|
||||
.nav-group a.active::before { position: absolute; top: 9px; bottom: 9px; left: 0; width: 3px; border-radius: 3px; background: linear-gradient(#17b79d, #3470d8); content: ""; }
|
||||
.empty-state { padding: 24px 10px; color: var(--muted); font-size: 13px; }
|
||||
.sidebar-footer { position: sticky; bottom: -18px; display: flex; align-items: center; gap: 8px; margin: 0 -20px; padding: 14px 22px 18px; border-top: 1px solid var(--line); color: #7c8880; background: #f7faf8; font: 600 10px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.04em; text-transform: uppercase; }
|
||||
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: #29a869; box-shadow: 0 0 0 3px #dff3e8; }
|
||||
|
||||
.article-wrap { min-width: 0; padding: 58px clamp(32px, 5.8vw, 86px) 100px; background: rgba(255, 255, 255, 0.94); }
|
||||
.article { width: 100%; max-width: 820px; margin: 0 auto; }
|
||||
.article[data-group="home"] { max-width: 980px; }
|
||||
.home-hero { padding: 34px 0 76px; }
|
||||
.home-eyebrow, .section-kicker { margin: 0 0 17px; color: #12806d; font: 750 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.13em; }
|
||||
.home-hero h1 { margin: 0; color: #102019; font-size: clamp(46px, 6.3vw, 76px); line-height: 1.15; letter-spacing: -0.055em; }
|
||||
.home-lead { margin: 27px 0 0; color: #526159; font-size: 18px; line-height: 1.72; }
|
||||
.home-actions { display: flex; flex-wrap: wrap; gap: 11px; margin-top: 31px; }
|
||||
.home-actions a { padding: 11px 17px; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 720; }
|
||||
.primary-action { color: white; background: #087f6a; box-shadow: 0 8px 22px rgba(8, 127, 106, 0.2); }
|
||||
.secondary-action { border: 1px solid #d5e2dc; color: #34443c; background: white; }
|
||||
.home-explore { padding-top: 58px; border-top: 1px solid var(--line); }
|
||||
.home-explore h2, .benchmark-callout h2 { margin: 0; color: #15251d; font-size: 30px; letter-spacing: -0.025em; }
|
||||
.section-lead { max-width: 620px; margin: 10px 0 25px; color: var(--muted); line-height: 1.65; }
|
||||
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
|
||||
.feature-card { position: relative; display: flex; min-height: 260px; flex-direction: column; padding: 23px; overflow: hidden; border: 1px solid #dce8e2; border-radius: 16px; color: var(--ink); background: linear-gradient(155deg, #fff, #f4faf7); text-decoration: none; transition: transform 160ms ease, box-shadow 160ms ease; }
|
||||
.feature-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(29, 68, 49, 0.1); }
|
||||
.feature-card.blue { background: linear-gradient(155deg, #fff, #f1f5ff); }
|
||||
.feature-card.amber { background: linear-gradient(155deg, #fff, #fbf7ec); }
|
||||
.feature-icon { display: grid; place-items: center; width: 42px; height: 42px; margin-bottom: 32px; border-radius: 12px; color: #08705e; background: #dbf2ea; font-size: 22px; }
|
||||
.blue .feature-icon { color: #3156b8; background: #e5ebff; }
|
||||
.amber .feature-icon { color: #9b6818; background: #f8eac8; }
|
||||
.feature-label { margin-bottom: 7px; color: #718078; font: 700 10px/1.3 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0.08em; text-transform: uppercase; }
|
||||
.feature-card strong { font-size: 20px; }
|
||||
.feature-description { margin-top: 10px; color: #66736c; font-size: 13px; line-height: 1.6; }
|
||||
.feature-arrow { position: absolute; right: 22px; bottom: 18px; color: #6a7870; font-size: 19px; }
|
||||
.benchmark-callout { display: flex; align-items: end; justify-content: space-between; gap: 35px; margin-top: 62px; padding: 36px; border-radius: 17px; color: white; background: linear-gradient(125deg, #142a22, #1c473b); }
|
||||
.benchmark-callout .section-kicker { color: #74d4bb; }
|
||||
.benchmark-callout h2 { color: white; }
|
||||
.benchmark-callout p:not(.section-kicker) { max-width: 610px; margin: 10px 0 0; color: #c3d4cd; line-height: 1.65; }
|
||||
.benchmark-callout > a { flex: none; padding: 10px 14px; border: 1px solid rgba(255,255,255,.25); border-radius: 9px; color: white; text-decoration: none; font-size: 13px; font-weight: 700; }
|
||||
.article-meta { display: flex; gap: 8px; margin-bottom: 22px; color: #7d8d84; font: 600 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
|
||||
.article-meta span:first-child { padding: 3px 8px; border-radius: 999px; color: #08705e; background: #e6f5ef; }
|
||||
.markdown-body { color: #27332d; font-size: 16px; line-height: 1.78; }
|
||||
.markdown-body > :first-child { margin-top: 0; }
|
||||
.markdown-body h1 { margin: 0 0 30px; color: #112019; font-size: clamp(36px, 5vw, 54px); line-height: 1.06; letter-spacing: -0.04em; }
|
||||
.markdown-body h2 { margin: 58px 0 18px; padding-top: 4px; color: #14241c; font-size: 27px; line-height: 1.25; letter-spacing: -0.02em; }
|
||||
.markdown-body h3 { margin: 35px 0 12px; color: #1f3027; font-size: 20px; line-height: 1.35; }
|
||||
.markdown-body p, .markdown-body ul, .markdown-body ol { margin: 14px 0; }
|
||||
.markdown-body li { margin: 5px 0; }
|
||||
.markdown-body a { color: var(--green); text-decoration-color: #9cc8ae; text-underline-offset: 3px; }
|
||||
.markdown-body a:hover { text-decoration-color: var(--green); }
|
||||
.markdown-body img { display: block; max-width: 100%; height: auto; margin: 30px auto; border-radius: 14px; }
|
||||
/* README badges are linked images. Keep them in a compact row instead of applying the figure layout above. */
|
||||
.article[data-group="overview"] .markdown-body > p:has(> a > img[src*="img.shields.io"]) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 7px;
|
||||
max-width: 690px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
.article[data-group="overview"] .markdown-body > p > a > img[src*="img.shields.io"] {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
max-height: 24px;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.markdown-body p[align="center"] { text-align: center; }
|
||||
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: min(470px, 72%); margin-top: 8px; filter: drop-shadow(0 18px 25px rgba(44, 85, 164, 0.1)); }
|
||||
.markdown-body code { padding: 2px 5px; border-radius: 5px; background: var(--code); color: #315844; font: 0.88em/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
|
||||
.markdown-body pre { margin: 24px 0; padding: 19px 21px; overflow-x: auto; border: 1px solid #dce5e0; border-radius: 12px; background: linear-gradient(145deg, #f5f8f6, #f8faf9); box-shadow: inset 3px 0 0 #7bd0b6; }
|
||||
.markdown-body pre code { padding: 0; color: #263b30; background: none; font-size: 13px; }
|
||||
.markdown-body blockquote { margin: 24px 0; padding: 4px 20px; border-left: 3px solid #68aa85; color: #53635a; background: #f5faf7; }
|
||||
.markdown-body table { display: block; width: 100%; margin: 24px 0; overflow-x: auto; border-collapse: collapse; font-size: 14px; }
|
||||
.markdown-body th, .markdown-body td { padding: 10px 13px; border: 1px solid var(--line); text-align: left; }
|
||||
.markdown-body th { background: #f4f6f3; }
|
||||
.markdown-body hr { margin: 44px 0; border: 0; border-top: 1px solid var(--line); }
|
||||
.article-footer { margin-top: 70px; padding-top: 20px; border-top: 1px solid var(--line); }
|
||||
.article-footer a { color: var(--muted); text-decoration: none; font-size: 13px; }
|
||||
.article-footer a:hover { color: var(--green); }
|
||||
.loading-line { width: 55%; height: 12px; margin-top: 40px; border-radius: 9px; background: linear-gradient(90deg, #edf0ec, #f8faf7, #edf0ec); background-size: 200% 100%; animation: loading 1.2s infinite; }
|
||||
@keyframes loading { to { background-position: -200% 0; } }
|
||||
|
||||
.toc-panel { position: sticky; top: 66px; height: calc(100vh - 66px); padding: 58px 24px; overflow-y: auto; border-left: 1px solid #e9efeb; background: rgba(252, 253, 252, 0.92); }
|
||||
.toc h2 { margin: 0 0 12px; color: #8a948e; font-size: 10px; letter-spacing: 0.1em; text-transform: uppercase; }
|
||||
.toc a { display: block; padding: 5px 0; color: #7a857e; text-decoration: none; font-size: 12px; line-height: 1.45; }
|
||||
.toc a:hover { color: var(--green); }
|
||||
.toc .toc-h3 { padding-left: 12px; }
|
||||
.sidebar-backdrop { display: none; }
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.docs-shell { grid-template-columns: 250px minmax(0, 1fr); }
|
||||
.toc-panel { display: none; }
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.topbar { height: 60px; padding: 0 16px; }
|
||||
.brand-section, .brand-divider, .github-link { display: none; }
|
||||
.quick-start-link { font-size: 12px; }
|
||||
.menu-button { display: block; max-width: 112px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.top-actions { gap: 9px; }
|
||||
.docs-shell { display: block; padding-top: 60px; }
|
||||
.sidebar { position: fixed; z-index: 25; top: 60px; bottom: 0; left: 0; width: min(310px, 86vw); height: auto; transform: translateX(-105%); transition: transform 180ms ease; box-shadow: 16px 0 35px rgba(20, 40, 30, 0.12); }
|
||||
.sidebar.open { transform: translateX(0); }
|
||||
.sidebar-backdrop { position: fixed; z-index: 20; inset: 60px 0 0; width: 100%; border: 0; background: rgba(18, 30, 23, 0.35); }
|
||||
.sidebar-backdrop.visible { display: block; }
|
||||
.article-wrap { padding: 38px 20px 72px; }
|
||||
.article-meta { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
||||
.markdown-body { font-size: 15px; }
|
||||
.markdown-body h1 { font-size: 34px; }
|
||||
.markdown-body h2 { margin-top: 46px; font-size: 24px; }
|
||||
.article[data-group="overview"] .markdown-body > p:first-child img { max-width: 84%; }
|
||||
.home-hero { padding: 20px 0 54px; }
|
||||
.home-hero h1 { font-size: 43px; }
|
||||
.home-lead { font-size: 16px; }
|
||||
.feature-grid { grid-template-columns: 1fr; }
|
||||
.feature-card { min-height: 225px; }
|
||||
.benchmark-callout { align-items: flex-start; flex-direction: column; padding: 28px 24px; }
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
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"));
|
||||
});
|
||||
109
github-pages/tests/generated-content.test.mjs
Normal file
109
github-pages/tests/generated-content.test.mjs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { access, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { legacyRoutes } from "../../docs/.vitepress/legacy-routes.mjs";
|
||||
|
||||
const siteDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const repoDir = path.resolve(siteDir, "..");
|
||||
const generatedDir = path.join(siteDir, ".generated", "site");
|
||||
|
||||
test("generates every required bilingual guide", async () => {
|
||||
const names = [
|
||||
"configuration.md",
|
||||
"services.md",
|
||||
"operations.md",
|
||||
"integrations.md",
|
||||
"plugin_development.md",
|
||||
"faq.md",
|
||||
"reference/cli.md",
|
||||
"reference/jobs.md",
|
||||
];
|
||||
for (const language of ["zh", "en"]) {
|
||||
for (const name of names) await access(path.join(generatedDir, language, name));
|
||||
}
|
||||
await access(path.join(generatedDir, "zh/integrations/claude-code.md"));
|
||||
await access(path.join(generatedDir, "en/integrations/claude-code.md"));
|
||||
await access(path.join(generatedDir, "zh/integrations/hermes.md"));
|
||||
await access(path.join(generatedDir, "en/integrations/hermes.md"));
|
||||
await access(path.join(generatedDir, "zh/integrations/dsh.md"));
|
||||
await access(path.join(generatedDir, "en/integrations/dsh.md"));
|
||||
await access(path.join(generatedDir, "zh/integrations/openclaw.md"));
|
||||
await access(path.join(generatedDir, "en/integrations/openclaw.md"));
|
||||
await access(path.join(generatedDir, "public/figures/dsh/reme-status-overview.png"));
|
||||
});
|
||||
|
||||
test("maps mirrored pages back to their canonical repository sources", async () => {
|
||||
const sourceMap = JSON.parse(await readFile(path.join(generatedDir, ".source-map.json"), "utf8"));
|
||||
assert.equal(sourceMap["zh/integrations/typescript.md"], "typescript/README_ZH.md");
|
||||
assert.equal(sourceMap["en/integrations/dsh.md"], "typescript/docs/dsh.md");
|
||||
assert.equal(sourceMap["zh/integrations/openclaw.md"], "typescript/docs/openclaw.zh-CN.md");
|
||||
assert.equal(sourceMap["en/integrations/claude-code.md"], "integrations/claude_code/README.md");
|
||||
assert.equal(sourceMap["en/integrations/hermes.md"], "integrations/hermes_agent/README.md");
|
||||
assert.equal(sourceMap["en/workspace/studio.md"], "reme_studio/README.md");
|
||||
assert.equal(sourceMap["zh/plugins/lme.md"], "plugins/lme/README_ZH.md");
|
||||
assert.equal(sourceMap["en/reference/jobs.md"], "reme/config/default.yaml");
|
||||
});
|
||||
|
||||
test("publishes portable and accurate DSH instructions", async () => {
|
||||
const english = await readFile(path.join(generatedDir, "en/integrations/dsh.md"), "utf8");
|
||||
const chinese = await readFile(path.join(generatedDir, "zh/integrations/dsh.md"), "utf8");
|
||||
assert.doesNotMatch(english, /\/Users\//);
|
||||
assert.doesNotMatch(chinese, /\/Users\//);
|
||||
assert.match(english, /runtime counters refresh every 5 seconds/);
|
||||
assert.match(chinese, /每 5 秒仅刷新 DSH 插件的运行时计数/);
|
||||
});
|
||||
|
||||
test("generates the callable Job reference from default.yaml", async () => {
|
||||
const config = parseYaml(await readFile(path.join(repoDir, "reme/config/default.yaml"), "utf8"));
|
||||
const callableJobs = Object.entries(config.jobs)
|
||||
.filter(([, job]) => !["background", "cron"].includes(job.backend))
|
||||
.map(([name]) => name);
|
||||
|
||||
for (const language of ["zh", "en"]) {
|
||||
const reference = await readFile(path.join(generatedDir, language, "reference", "jobs.md"), "utf8");
|
||||
for (const job of callableJobs) assert.ok(reference.includes(`### ${"`"}${job}${"`"}`), job);
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps generated content disposable and excludes internal plans", async () => {
|
||||
await assert.rejects(access(path.join(generatedDir, "plans")));
|
||||
await access(path.join(generatedDir, ".vitepress", "config.mts"));
|
||||
await access(path.join(generatedDir, "public", "reme-icon.svg"));
|
||||
await access(path.join(generatedDir, "public", "reme-logo.svg"));
|
||||
assert.equal(
|
||||
(await readFile(path.join(generatedDir, "public", "CNAME"), "utf8")).trim(),
|
||||
"reme.agentscope.io",
|
||||
);
|
||||
});
|
||||
|
||||
test("maps every legacy query-string document ID to a generated page", async () => {
|
||||
assert.equal(Object.keys(legacyRoutes).length, 45);
|
||||
assert.equal(legacyRoutes["studio-en"], "/en/workspace/studio");
|
||||
assert.equal(legacyRoutes["en-quick_start"], "/en/quick_start");
|
||||
assert.equal(legacyRoutes["agents-guide"], "https://github.com/agentscope-ai/ReMe/blob/main/AGENTS.md");
|
||||
|
||||
for (const [id, route] of Object.entries(legacyRoutes)) {
|
||||
if (route.startsWith("https://")) continue;
|
||||
assert.match(route, /^\/(?:zh|en)\//, id);
|
||||
const relative = route.endsWith("/") ? `${route.slice(1)}index.md` : `${route.slice(1)}.md`;
|
||||
await access(path.join(generatedDir, relative));
|
||||
}
|
||||
});
|
||||
|
||||
test("tracks every generated input in documentation CI and deployment", async () => {
|
||||
const requiredPaths = [
|
||||
"reme/config/default.yaml",
|
||||
"integrations/claude_code/README.md",
|
||||
"integrations/hermes_agent/README.md",
|
||||
"typescript/docs/**",
|
||||
"typescript/figures/**",
|
||||
"benchmark/toolmemory/gitcha.png",
|
||||
];
|
||||
for (const workflow of ["ci-docs.yml", "deploy-docs.yml"]) {
|
||||
const source = await readFile(path.join(repoDir, ".github/workflows", workflow), "utf8");
|
||||
for (const requiredPath of requiredPaths) assert.ok(source.includes(requiredPath), `${workflow}: ${requiredPath}`);
|
||||
}
|
||||
});
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { stripMarkdownFrontmatter } from "../src/markdown.js";
|
||||
|
||||
test("strips leading YAML frontmatter before rendering", () => {
|
||||
assert.equal(
|
||||
stripMarkdownFrontmatter("---\nname: reme_memory\ndescription: Memory skill\n---\n\n# ReMe Memory\n"),
|
||||
"\n# ReMe Memory\n",
|
||||
);
|
||||
});
|
||||
|
||||
test("preserves Markdown without frontmatter", () => {
|
||||
const markdown = "# ReMe Memory\n\nContent\n";
|
||||
assert.equal(stripMarkdownFrontmatter(markdown), markdown);
|
||||
});
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
base: "./",
|
||||
publicDir: ".generated",
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: true,
|
||||
},
|
||||
});
|
||||
201
plugins/beam/LICENSE
Normal file
201
plugins/beam/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.
|
||||
38
plugins/beam/README.md
Normal file
38
plugins/beam/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# BEAM plugin
|
||||
|
||||
[中文说明](./README_ZH.md)
|
||||
|
||||
This plugin owns the BEAM memory, agentic-answer and judge Steps, their prompts,
|
||||
and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the
|
||||
shared evaluation Jobs and components. Dataset handling, the runner and results
|
||||
remain in [`benchmark/beam`](../../benchmark/beam/README.md).
|
||||
|
||||
From the repository root, install ReMe and this plugin in editable mode before running the benchmark:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/beam --editable
|
||||
reme plugins validate beam
|
||||
python benchmark/beam/run.py
|
||||
```
|
||||
|
||||
Editable installation registers the `beam` entry point while keeping source changes immediately
|
||||
visible. The runner selects the built-in `benchmark` preset and explicitly enables `beam` for
|
||||
each Application. Installing the plugin makes it discoverable but does not enable it globally.
|
||||
|
||||
`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`,
|
||||
`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with
|
||||
`reme start config=benchmark plugins='["beam"]'`. The shared preset does not inherit
|
||||
`default`: only declared Jobs run, indexing is manual, and neither scheduled dream
|
||||
nor the optional `auto_dream` Job is enabled.
|
||||
The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge`
|
||||
names and model environment variables are unchanged. Explicit application/CLI overrides
|
||||
still take precedence. Installing this plugin does not start an evaluation.
|
||||
|
||||
The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`.
|
||||
The old core-owned `reme.steps.benchmark.beam` Python import path is removed.
|
||||
Custom Python callers should import memory, search and answer Steps from `reme_beam`, and the
|
||||
judge Step from `judge_beam`. After uninstalling,
|
||||
Applications and CLI services must omit the plugin until it is installed again.
|
||||
Uninstallation never removes datasets, workspaces or results.
|
||||
Restart an existing service after changing plugins.
|
||||
32
plugins/beam/README_ZH.md
Normal file
32
plugins/beam/README_ZH.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# BEAM 插件
|
||||
|
||||
[English](./README.md)
|
||||
|
||||
插件包含 BEAM 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。
|
||||
ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在
|
||||
[`benchmark/beam`](../../benchmark/beam/README_ZH.md)。
|
||||
|
||||
在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/beam --editable
|
||||
reme plugins validate beam
|
||||
python benchmark/beam/run.py
|
||||
```
|
||||
|
||||
editable 安装会注册 `beam` entry point,并让源码修改立即生效。runner 选择内置 `benchmark`
|
||||
配置,并为每个 Application 显式启用 `beam`。安装只让插件可被发现,不会在所有应用中全局启用。
|
||||
|
||||
`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、
|
||||
`agentic_answer` 和 `answer_judge` Job。安装后使用
|
||||
`reme start config=benchmark plugins='["beam"]'`。公共评测配置不继承 `default`,只运行声明的 Job:
|
||||
索引手动更新,dream 定时任务和可选的 `auto_dream`
|
||||
均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量
|
||||
保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。
|
||||
|
||||
共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。
|
||||
原 `reme.steps.benchmark.beam` Python 导入路径已移除。自定义 Python 调用应从 `reme_beam`
|
||||
导入记忆、搜索和回答 Step,并从 `judge_beam` 导入评判 Step。
|
||||
卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。
|
||||
卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。
|
||||
28
plugins/beam/pyproject.toml
Normal file
28
plugins/beam/pyproject.toml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
[project]
|
||||
name = "reme-beam"
|
||||
version = "0.1.0"
|
||||
description = "BEAM benchmark plugin for ReMe."
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE"]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"reme-ai[as]>=0.4.1.11",
|
||||
"json-repair",
|
||||
"numpy>=2.2.6",
|
||||
]
|
||||
|
||||
[project.entry-points."reme.plugins"]
|
||||
beam = "reme_beam"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
include = ["reme_beam*", "judge_beam*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
reme_beam = ["plugin.yaml", "*.yaml"]
|
||||
judge_beam = ["*.yaml"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=77", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
5
plugins/beam/src/judge_beam/__init__.py
Normal file
5
plugins/beam/src/judge_beam/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""BEAM benchmark judge backend."""
|
||||
|
||||
from .llm_judge import BeamRubricJudgeStep
|
||||
|
||||
__all__ = ["BeamRubricJudgeStep"]
|
||||
|
|
@ -24,10 +24,9 @@ from typing import List, Tuple
|
|||
import numpy as np
|
||||
from json_repair import repair_json
|
||||
|
||||
from ...base_step import BaseStep, Ref
|
||||
from ....components import R
|
||||
from ....components.as_embedding import BaseAsEmbedding
|
||||
from ....enumeration import ComponentEnum
|
||||
from reme.steps.base_step import BaseStep, Ref
|
||||
from reme.components.as_embedding import BaseAsEmbedding
|
||||
from reme.enumeration import ComponentEnum
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -231,7 +230,6 @@ def _event_ordering_score(
|
|||
}
|
||||
|
||||
|
||||
@R.register("beam_rubric_judge_step")
|
||||
class BeamRubricJudgeStep(BaseStep):
|
||||
"""Judge an LLM response against a list of rubric criteria.
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
"""BEAM benchmark steps."""
|
||||
"""BEAM benchmark backends and application configuration for ReMe."""
|
||||
|
||||
from .agentic_answer import BeamAgenticAnswerStep
|
||||
from .llm_judge import BeamRubricJudgeStep
|
||||
from .auto_memory import BeamAutoMemoryStep
|
||||
from .search_v2 import SearchV2Step
|
||||
|
||||
__all__ = [
|
||||
"BeamAgenticAnswerStep",
|
||||
"BeamRubricJudgeStep",
|
||||
"BeamAutoMemoryStep",
|
||||
"SearchV2Step",
|
||||
]
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
"""BEAM agentic answer step – ReAct agent that answers questions using the search tool."""
|
||||
|
||||
from ....components import R
|
||||
from ..base import BaseAgenticAnswerStep
|
||||
from reme.steps.benchmark import BaseAgenticAnswerStep
|
||||
|
||||
|
||||
@R.register("beam_agentic_answer_step")
|
||||
class BeamAgenticAnswerStep(BaseAgenticAnswerStep):
|
||||
"""Answer a BEAM probing question via ReAct agent with access to the search tool.
|
||||
|
||||
|
|
@ -4,9 +4,8 @@ from datetime import datetime, timedelta
|
|||
|
||||
from agentscope.message import Msg
|
||||
|
||||
from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
|
||||
from ...file_io import validate_session_id
|
||||
from ....components import R
|
||||
from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
|
||||
from reme.steps.file_io import validate_session_id
|
||||
|
||||
# Runtime-context key carrying the 0-based line offset of the current segment
|
||||
# inside the full session file (segmented ingestion of long sessions).
|
||||
|
|
@ -168,7 +167,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]:
|
|||
return result
|
||||
|
||||
|
||||
@R.register("beam_auto_memory_step")
|
||||
class BeamAutoMemoryStep(AutoMemoryStep):
|
||||
"""AutoMemoryStep variant that interpolates timestamps for BEAM sessions.
|
||||
|
||||
120
plugins/beam/src/reme_beam/plugin.yaml
Normal file
120
plugins/beam/src/reme_beam/plugin.yaml
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
backends:
|
||||
beam_auto_memory_step: reme_beam.auto_memory:BeamAutoMemoryStep
|
||||
beam_agentic_answer_step: reme_beam.agentic_answer:BeamAgenticAnswerStep
|
||||
beam_rubric_judge_step: judge_beam.llm_judge:BeamRubricJudgeStep
|
||||
beam_search_v2_step: reme_beam.search_v2:SearchV2Step
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
search:
|
||||
backend: base
|
||||
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "search query"
|
||||
start_date:
|
||||
type: string
|
||||
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
end_date:
|
||||
type: string
|
||||
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: beam_search_v2_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
expand_links: false
|
||||
max_links_per_direction: 10
|
||||
|
||||
agentic_answer:
|
||||
backend: base
|
||||
description: "BEAM agentic answer job (ReAct agent with search tool)"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The query to ask"
|
||||
query_time:
|
||||
type: string
|
||||
description: "ISO timestamp representing the query time"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: beam_agentic_answer_step
|
||||
agent_wrapper: bench
|
||||
|
||||
answer_judge:
|
||||
backend: base
|
||||
description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
llm_response:
|
||||
type: string
|
||||
description: "The model's response to evaluate"
|
||||
rubric:
|
||||
type: array
|
||||
description: "List of rubric criteria to check"
|
||||
items:
|
||||
type: string
|
||||
probing_question:
|
||||
type: string
|
||||
description: "The original probing question"
|
||||
default: ""
|
||||
question_type:
|
||||
type: string
|
||||
description: "BEAM question type (e.g. event_ordering)"
|
||||
default: ""
|
||||
required:
|
||||
- llm_response
|
||||
- rubric
|
||||
steps:
|
||||
- backend: beam_rubric_judge_step
|
||||
agent_wrapper: judge
|
||||
|
||||
auto_memory:
|
||||
backend: base
|
||||
description: "Auto-memory: record conversation facts into a daily note"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
type: array
|
||||
description: "messages"
|
||||
items:
|
||||
type: object
|
||||
session_id:
|
||||
type: string
|
||||
description: "source conversation session identifier"
|
||||
default: ""
|
||||
memory_hint:
|
||||
type: string
|
||||
description: "optional hint"
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
|
||||
default: ""
|
||||
required:
|
||||
- messages
|
||||
steps:
|
||||
- backend: beam_auto_memory_step
|
||||
# Long sessions are split into turn-aligned segments and fed to the
|
||||
# agent incrementally; <= 0 disables splitting.
|
||||
max_segment_words: 10000
|
||||
|
||||
components:
|
||||
as_llm:
|
||||
default:
|
||||
max_retries: 5
|
||||
judge:
|
||||
retry_delay: 5.0
|
||||
|
|
@ -12,14 +12,18 @@ import datetime
|
|||
import os
|
||||
from typing import Final
|
||||
|
||||
from ._dedup import _ToolContextDedupMixin
|
||||
from ._source_format import ALL_RETURNED_MESSAGE, NO_RESULTS_MESSAGE, is_session_path, join_chunk_entries
|
||||
from ._source_format import merge_session_chunk_intervals, render_chunk_entries
|
||||
from ..base_step import BaseStep
|
||||
from ..file_io import extract_daily_date
|
||||
from ...components import R
|
||||
from ...schema import FileChunk
|
||||
from ...utils import expand_links
|
||||
from reme.schema import FileChunk
|
||||
from reme.steps.base_step import BaseStep
|
||||
from reme.steps.file_io import extract_daily_date
|
||||
from reme.steps.index._dedup import _ToolContextDedupMixin
|
||||
from reme.steps.index._source_format import (
|
||||
ALL_RETURNED_MESSAGE,
|
||||
NO_RESULTS_MESSAGE,
|
||||
is_session_path,
|
||||
join_chunk_entries,
|
||||
)
|
||||
from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries
|
||||
from reme.utils import expand_links
|
||||
|
||||
_RRF_K: Final = 60
|
||||
_MAX_CANDIDATES: Final = 200
|
||||
|
|
@ -37,7 +41,6 @@ def _default_limit() -> int:
|
|||
return _DEFAULT_LIMIT
|
||||
|
||||
|
||||
@R.register("search_v2_step")
|
||||
class SearchV2Step(_ToolContextDedupMixin, BaseStep):
|
||||
"""Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate."""
|
||||
|
||||
201
plugins/lme/LICENSE
Normal file
201
plugins/lme/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.
|
||||
38
plugins/lme/README.md
Normal file
38
plugins/lme/README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# LongMemEval plugin
|
||||
|
||||
[中文说明](./README_ZH.md)
|
||||
|
||||
This plugin owns the LongMemEval memory, agentic-answer and judge Steps, their prompts,
|
||||
and their Job defaults in `plugin.yaml`. ReMe's built-in `benchmark.yaml` owns the
|
||||
shared evaluation Jobs and components. Dataset handling, the runner and results remain
|
||||
in [`benchmark/longmemeval`](../../benchmark/longmemeval/README.md).
|
||||
|
||||
From the repository root, install ReMe and this plugin in editable mode before running the benchmark:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/lme --editable
|
||||
reme plugins validate lme
|
||||
python benchmark/longmemeval/run.py
|
||||
```
|
||||
|
||||
Editable installation registers the `lme` entry point while keeping source changes immediately
|
||||
visible. The runner selects the built-in `benchmark` preset and explicitly enables `lme` for
|
||||
each Application. Installing the plugin makes it discoverable but does not enable it globally.
|
||||
|
||||
`plugin.yaml` registers backends and contributes the plugin-owned `auto_memory`,
|
||||
`agentic_answer` and `answer_judge` Job defaults. Start the installed plugin with
|
||||
`reme start config=benchmark plugins='["lme"]'`. The shared preset does not inherit
|
||||
`default`: only declared Jobs run, indexing is manual, and neither scheduled dream
|
||||
nor the optional `auto_dream` Job is enabled.
|
||||
The existing `auto_memory`, `agentic_answer`, `answer_judge`, `bench` and `judge`
|
||||
names and model environment variables are unchanged. Explicit application/CLI overrides
|
||||
still take precedence. Installing this plugin does not start an evaluation.
|
||||
|
||||
The shared answer base class lives in `reme.steps.benchmark.base_agentic_answer`.
|
||||
The old core-owned `reme.steps.benchmark.lme` Python import path is removed.
|
||||
Custom Python callers should import memory, search and answer Steps from `reme_lme`, and the
|
||||
judge Step from `judge_lme`. After uninstalling,
|
||||
Applications and CLI services must omit the plugin until it is installed again.
|
||||
Uninstallation never removes datasets, workspaces or results.
|
||||
Restart an existing service after changing plugins.
|
||||
32
plugins/lme/README_ZH.md
Normal file
32
plugins/lme/README_ZH.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# LongMemEval 插件
|
||||
|
||||
[English](./README.md)
|
||||
|
||||
插件包含 LongMemEval 的记忆、回答、评分 Step、提示词,以及 `plugin.yaml` 中对应的 Job 默认配置。
|
||||
ReMe 内置的 `benchmark.yaml` 负责公共评测 Job 和 Component;数据集处理、runner 和结果仍留在
|
||||
[`benchmark/longmemeval`](../../benchmark/longmemeval/README_ZH.md)。
|
||||
|
||||
在仓库根目录以 editable 模式安装 ReMe 和本插件,再运行评测:
|
||||
|
||||
```bash
|
||||
python -m pip install -e ".[as]"
|
||||
reme plugins install ./plugins/lme --editable
|
||||
reme plugins validate lme
|
||||
python benchmark/longmemeval/run.py
|
||||
```
|
||||
|
||||
editable 安装会注册 `lme` entry point,并让源码修改立即生效。runner 选择内置 `benchmark`
|
||||
配置,并为每个 Application 显式启用 `lme`。安装只让插件可被发现,不会在所有应用中全局启用。
|
||||
|
||||
`plugin.yaml` 注册 backend,并通过 `application_defaults` 提供插件拥有的 `auto_memory`、
|
||||
`agentic_answer` 和 `answer_judge` Job。安装后使用
|
||||
`reme start config=benchmark plugins='["lme"]'`。公共评测配置不继承 `default`,只运行声明的 Job:
|
||||
索引手动更新,dream 定时任务和可选的 `auto_dream`
|
||||
均保持关闭。原有 `auto_memory`、`agentic_answer`、`answer_judge`、`bench`、`judge` 名称及模型环境变量
|
||||
保持不变,显式应用参数和 CLI 覆盖仍优先。安装或启用插件不会自动开始评测。
|
||||
|
||||
共享回答基类位于 `reme.steps.benchmark.base_agentic_answer`。
|
||||
原 `reme.steps.benchmark.lme` Python 导入路径已移除。自定义 Python 调用应从 `reme_lme`
|
||||
导入记忆、搜索和回答 Step,并从 `judge_lme` 导入评判 Step。
|
||||
卸载插件后,Application 和 CLI 服务必须移除插件选择,直到再次安装。
|
||||
卸载不会删除数据集、工作区或结果。修改插件后需重启已有服务。
|
||||
26
plugins/lme/pyproject.toml
Normal file
26
plugins/lme/pyproject.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
[project]
|
||||
name = "reme-lme"
|
||||
version = "0.1.0"
|
||||
description = "LongMemEval benchmark plugin for ReMe."
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
license-files = ["LICENSE"]
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"reme-ai[as]>=0.4.1.11",
|
||||
]
|
||||
|
||||
[project.entry-points."reme.plugins"]
|
||||
lme = "reme_lme"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
include = ["reme_lme*", "judge_lme*"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
reme_lme = ["plugin.yaml", "*.yaml"]
|
||||
judge_lme = ["*.yaml"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=77", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
5
plugins/lme/src/judge_lme/__init__.py
Normal file
5
plugins/lme/src/judge_lme/__init__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
"""LongMemEval benchmark judge backend."""
|
||||
|
||||
from .llm_judge import LmeAnswerJudgeStep
|
||||
|
||||
__all__ = ["LmeAnswerJudgeStep"]
|
||||
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
import re
|
||||
|
||||
from ...base_step import BaseStep
|
||||
from ....components import R
|
||||
from reme.steps.base_step import BaseStep
|
||||
|
||||
|
||||
@R.register("lme_answer_judge_step")
|
||||
class LmeAnswerJudgeStep(BaseStep):
|
||||
"""Evaluate whether an agent answer is correct against a golden answer."""
|
||||
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
"""LongMemEval benchmark steps."""
|
||||
"""LongMemEval benchmark backends and application configuration for ReMe."""
|
||||
|
||||
from .agentic_answer import LmeAgenticAnswerStep
|
||||
from .llm_judge import LmeAnswerJudgeStep
|
||||
from .auto_memory import LmeAutoMemoryStep
|
||||
from .search_v2 import SearchV2Step
|
||||
|
||||
__all__ = [
|
||||
"LmeAgenticAnswerStep",
|
||||
"LmeAnswerJudgeStep",
|
||||
"LmeAutoMemoryStep",
|
||||
"SearchV2Step",
|
||||
]
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
"""LongMemEval agentic answer step – ReAct agent that answers questions using the search tool."""
|
||||
|
||||
from ....components import R
|
||||
from ..base import BaseAgenticAnswerStep
|
||||
from reme.steps.benchmark import BaseAgenticAnswerStep
|
||||
|
||||
|
||||
@R.register("lme_agentic_answer_step")
|
||||
class LmeAgenticAnswerStep(BaseAgenticAnswerStep):
|
||||
"""Answer a LongMemEval query via ReAct agent with access to the search tool.
|
||||
|
||||
|
|
@ -12,7 +10,7 @@ class LmeAgenticAnswerStep(BaseAgenticAnswerStep):
|
|||
``search`` job tool to retrieve relevant memory chunks before generating
|
||||
a final answer.
|
||||
|
||||
Session-transcript compression in ``search_v2_step`` is controlled by the
|
||||
Session-transcript compression in the plugin's search Step is controlled by the
|
||||
``compress_session`` flag in the runtime context (set by the benchmark
|
||||
runner from ``evaluation.compress_session``); it is off by default.
|
||||
"""
|
||||
|
|
@ -4,8 +4,7 @@ from datetime import datetime, timedelta
|
|||
|
||||
from agentscope.message import Msg
|
||||
|
||||
from ...evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
|
||||
from ....components import R
|
||||
from reme.steps.evolve.auto_memory import AutoMemoryStep, _normalize_msg_timestamp
|
||||
|
||||
|
||||
def _parse_iso_seconds(value: str) -> datetime | None:
|
||||
|
|
@ -115,7 +114,6 @@ def _interpolate_timestamps(items: list[dict]) -> list[dict]:
|
|||
return result
|
||||
|
||||
|
||||
@R.register("lme_auto_memory_step")
|
||||
class LmeAutoMemoryStep(AutoMemoryStep):
|
||||
"""AutoMemoryStep variant that interpolates timestamps for LongMemEval sessions.
|
||||
|
||||
113
plugins/lme/src/reme_lme/plugin.yaml
Normal file
113
plugins/lme/src/reme_lme/plugin.yaml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
backends:
|
||||
lme_auto_memory_step: reme_lme.auto_memory:LmeAutoMemoryStep
|
||||
lme_agentic_answer_step: reme_lme.agentic_answer:LmeAgenticAnswerStep
|
||||
lme_answer_judge_step: judge_lme.llm_judge:LmeAnswerJudgeStep
|
||||
lme_search_v2_step: reme_lme.search_v2:SearchV2Step
|
||||
|
||||
application_defaults:
|
||||
jobs:
|
||||
search:
|
||||
backend: base
|
||||
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "search query"
|
||||
start_date:
|
||||
type: string
|
||||
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
end_date:
|
||||
type: string
|
||||
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: lme_search_v2_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
expand_links: false
|
||||
max_links_per_direction: 10
|
||||
|
||||
agentic_answer:
|
||||
backend: base
|
||||
description: "LongMemEval agentic answer job (ReAct agent with search tool)"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The query to ask"
|
||||
query_time:
|
||||
type: string
|
||||
description: "ISO timestamp representing the query time"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: lme_agentic_answer_step
|
||||
agent_wrapper: bench
|
||||
|
||||
answer_judge:
|
||||
backend: base
|
||||
description: "LLM-as-Judge: evaluate agent answer against golden answer"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The question being asked"
|
||||
agent_answer:
|
||||
type: string
|
||||
description: "The model's answer to evaluate"
|
||||
golden_answer:
|
||||
type: string
|
||||
description: "The correct/golden answer"
|
||||
question_type:
|
||||
type: string
|
||||
description: "The question type for prompt selection"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
- agent_answer
|
||||
- golden_answer
|
||||
steps:
|
||||
- backend: lme_answer_judge_step
|
||||
agent_wrapper: judge
|
||||
|
||||
auto_memory:
|
||||
backend: base
|
||||
description: "Auto-memory: record conversation facts into a daily note"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
type: array
|
||||
description: "messages"
|
||||
items:
|
||||
type: object
|
||||
session_id:
|
||||
type: string
|
||||
description: "source conversation session identifier"
|
||||
default: ""
|
||||
memory_hint:
|
||||
type: string
|
||||
description: "optional hint"
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
|
||||
default: ""
|
||||
required:
|
||||
- messages
|
||||
steps:
|
||||
- backend: lme_auto_memory_step
|
||||
|
||||
components:
|
||||
as_llm:
|
||||
default:
|
||||
max_retries: 3
|
||||
333
plugins/lme/src/reme_lme/search_v2.py
Normal file
333
plugins/lme/src/reme_lme/search_v2.py
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
"""Hybrid search (v2) over file_store using RRF fusion of vector + keyword results.
|
||||
|
||||
This is the local fork of the upstream search step. It uses
|
||||
:class:`_ToolContextDedupMixin` for subset-aware interval-merging dedup and
|
||||
:func:`render_chunk_entries` / :func:`join_chunk_entries` for session-aware
|
||||
chunk formatting with
|
||||
:data:`ALL_RETURNED_MESSAGE` / :data:`NO_RESULTS_MESSAGE` notices.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import datetime
|
||||
import os
|
||||
from typing import Final
|
||||
|
||||
from reme.schema import FileChunk
|
||||
from reme.steps.base_step import BaseStep
|
||||
from reme.steps.file_io import extract_daily_date
|
||||
from reme.steps.index._dedup import _ToolContextDedupMixin
|
||||
from reme.steps.index._source_format import (
|
||||
ALL_RETURNED_MESSAGE,
|
||||
NO_RESULTS_MESSAGE,
|
||||
is_session_path,
|
||||
join_chunk_entries,
|
||||
)
|
||||
from reme.steps.index._source_format import merge_session_chunk_intervals, render_chunk_entries
|
||||
from reme.utils import expand_links
|
||||
|
||||
_RRF_K: Final = 60
|
||||
_MAX_CANDIDATES: Final = 200
|
||||
_DEFAULT_LIMIT_ENV: Final = "REME_SEARCH_LIMIT"
|
||||
_DEFAULT_LIMIT: Final = 5
|
||||
|
||||
|
||||
def _default_limit() -> int:
|
||||
value = os.getenv(_DEFAULT_LIMIT_ENV)
|
||||
if value is None:
|
||||
return _DEFAULT_LIMIT
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
return _DEFAULT_LIMIT
|
||||
|
||||
|
||||
class SearchV2Step(_ToolContextDedupMixin, BaseStep):
|
||||
"""Hybrid search: run vector + keyword in parallel, fuse via RRF, filter, truncate."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*args,
|
||||
seen_ttl_hours: float = 24,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.seen_ttl_hours = seen_ttl_hours
|
||||
|
||||
@staticmethod
|
||||
def _rrf_merge(
|
||||
vector: list[FileChunk],
|
||||
keyword: list[FileChunk],
|
||||
vector_weight: float,
|
||||
) -> list[FileChunk]:
|
||||
"""Fuse two ranked lists with Reciprocal Rank Fusion, keyed by chunk.id."""
|
||||
text_weight = 1.0 - vector_weight
|
||||
merged: dict[str, FileChunk] = {}
|
||||
|
||||
for rank, chunk in enumerate(vector, start=1):
|
||||
contrib = vector_weight / (_RRF_K + rank)
|
||||
c = chunk.model_copy(deep=False)
|
||||
c.scores = {**chunk.scores, "vector": chunk.scores.get("vector", chunk.score), "score": contrib}
|
||||
merged[c.id] = c
|
||||
|
||||
for rank, chunk in enumerate(keyword, start=1):
|
||||
contrib = text_weight / (_RRF_K + rank)
|
||||
existing = merged.get(chunk.id)
|
||||
if existing is not None:
|
||||
existing.scores = {
|
||||
**existing.scores,
|
||||
"keyword": chunk.scores.get("keyword", chunk.score),
|
||||
"score": existing.scores["score"] + contrib,
|
||||
}
|
||||
else:
|
||||
c = chunk.model_copy(deep=False)
|
||||
c.scores = {**chunk.scores, "keyword": chunk.scores.get("keyword", chunk.score), "score": contrib}
|
||||
merged[c.id] = c
|
||||
|
||||
results = list(merged.values())
|
||||
results.sort(key=lambda r: r.score, reverse=True)
|
||||
return results
|
||||
|
||||
@staticmethod
|
||||
def _format_scores(scores: dict[str, float], hybrid: bool) -> str:
|
||||
"""Format scores for the answer line: always show fused; show per-branch when hybrid."""
|
||||
parts = [f"score={scores.get('score', 0.0):.4f}"]
|
||||
if hybrid:
|
||||
for k in ("vector", "keyword"):
|
||||
v = scores.get(k)
|
||||
parts.append(f"{k}={v:.4f}" if v is not None else f"{k}=-")
|
||||
return " ".join(parts)
|
||||
|
||||
async def execute(self):
|
||||
assert self.context is not None
|
||||
query: str = (self.context.get("query", "") or "").strip()
|
||||
limit: int = int(self.context.get("limit") or _default_limit())
|
||||
min_score: float = float(self.context.get("min_score") or 0.0)
|
||||
# vector_weight: prefer agent-supplied context value; fallback to YAML kwargs / default 0.7.
|
||||
# Convertible numeric inputs are clipped to [0.0, 1.0]; non-numeric inputs are silently ignored.
|
||||
raw_vw = self.context.get("vector_weight")
|
||||
vector_weight: float | None = None
|
||||
if raw_vw is not None:
|
||||
try:
|
||||
vector_weight = float(raw_vw)
|
||||
except (TypeError, ValueError):
|
||||
self.logger.warning(
|
||||
f"[{self.name}] non-numeric vector_weight={raw_vw!r}; ignoring and using default 0.7",
|
||||
)
|
||||
vector_weight = None
|
||||
if vector_weight is None:
|
||||
vector_weight = float(self.kwargs.get("vector_weight", 0.7))
|
||||
vector_weight = max(0.0, min(1.0, vector_weight))
|
||||
candidate_multiplier: float = float(self.kwargs.get("candidate_multiplier", 5.0))
|
||||
expand_links_enabled: bool = bool(self.kwargs.get("expand_links", True))
|
||||
max_links_per_direction: int = int(self.kwargs.get("max_links_per_direction", 10))
|
||||
tool_context_id: str = (self.context.get("tool_context_id", "") or "").strip()
|
||||
# Injected value takes precedence over YAML kwargs; check existence
|
||||
# (not truthiness) so an explicit False can disable a YAML-true flag.
|
||||
_strict_date_filter = self.context.get("strict_date_filter")
|
||||
if _strict_date_filter is None:
|
||||
_strict_date_filter = self.kwargs.get("strict_date_filter", False)
|
||||
strict_date_filter: bool = bool(_strict_date_filter)
|
||||
|
||||
if not query:
|
||||
self.context.response.success = False
|
||||
self.context.response.answer = "Error: query cannot be empty"
|
||||
return self.context.response
|
||||
assert limit > 0, f"limit must be positive, got {limit}"
|
||||
|
||||
candidates = min(_MAX_CANDIDATES, max(1, int(limit * candidate_multiplier)))
|
||||
search_filter: dict = dict(self.context.get("search_filter", {}) or {})
|
||||
|
||||
# Promote top-level date parameters into search_filter for file_store.
|
||||
for date_key in ("start_date", "end_date"):
|
||||
value = self.context.get(date_key)
|
||||
if value and date_key not in search_filter:
|
||||
search_filter[date_key] = value
|
||||
|
||||
# Validate and normalize date filters before they reach file_store.
|
||||
# _matches_search_filter does lexicographic string comparison against
|
||||
# path_date (always a canonical YYYY-MM-DD), so raw caller values like
|
||||
# "2026-2-28" or "abc" would produce silently wrong results.
|
||||
for date_key in ("start_date", "end_date"):
|
||||
raw = search_filter.get(date_key)
|
||||
if raw is None:
|
||||
continue
|
||||
normalized = extract_daily_date(raw)
|
||||
if normalized is None:
|
||||
# Fallback: accept non-zero-padded dates like "2024-1-5".
|
||||
try:
|
||||
normalized = (
|
||||
datetime.datetime.strptime(
|
||||
str(raw).strip(),
|
||||
"%Y-%m-%d",
|
||||
)
|
||||
.date()
|
||||
.isoformat()
|
||||
)
|
||||
except ValueError:
|
||||
self.logger.warning(
|
||||
f"Ignoring invalid {date_key}={raw!r}; " f"expected a valid YYYY-MM-DD date.",
|
||||
)
|
||||
del search_filter[date_key]
|
||||
continue
|
||||
search_filter[date_key] = normalized
|
||||
|
||||
if strict_date_filter:
|
||||
search_filter["strict_date_filter"] = True
|
||||
|
||||
vector_results, keyword_results = await asyncio.gather(
|
||||
self.file_store.vector_search(query, candidates, search_filter),
|
||||
self.file_store.keyword_search(query, candidates, search_filter),
|
||||
)
|
||||
|
||||
self.logger.info(
|
||||
f"[{self.name}] query={query!r} candidates={candidates} "
|
||||
f"vector_hits={len(vector_results)} keyword_hits={len(keyword_results)}",
|
||||
)
|
||||
|
||||
hybrid = bool(vector_results) and bool(keyword_results)
|
||||
if not vector_results and not keyword_results:
|
||||
fused: list[FileChunk] = []
|
||||
elif not keyword_results:
|
||||
fused = vector_results
|
||||
elif not vector_results:
|
||||
fused = keyword_results
|
||||
else:
|
||||
fused = self._rrf_merge(vector_results, keyword_results, vector_weight)
|
||||
|
||||
if min_score > 0.0:
|
||||
fused = [c for c in fused if c.score >= min_score]
|
||||
|
||||
pre_dedup_count = 0
|
||||
dedup: dict | None = None
|
||||
if tool_context_id:
|
||||
pre_dedup_count = len(fused)
|
||||
fused, dedup = self._dedupe_tool_context(
|
||||
fused,
|
||||
tool_context_id,
|
||||
limit,
|
||||
clock=self.kwargs.get("clock"),
|
||||
ttl_override=self.kwargs.get("tool_context_chunk_ttl_seconds"),
|
||||
)
|
||||
else:
|
||||
fused = fused[:limit]
|
||||
|
||||
unique_paths = list(dict.fromkeys(c.path for c in fused))
|
||||
link_expansion: dict[str, dict] = (
|
||||
await expand_links(self.file_store, unique_paths, max_links_per_direction) if expand_links_enabled else {}
|
||||
)
|
||||
|
||||
session_dir = self.config_value("session_dir")
|
||||
entries = render_chunk_entries(
|
||||
merge_session_chunk_intervals(fused, session_dir),
|
||||
session_dir,
|
||||
score_fn=lambda c: self._format_scores(c.scores, hybrid),
|
||||
link_expansion=link_expansion,
|
||||
)
|
||||
if self._session_compress_enabled():
|
||||
await self._compress_session_entries(entries, query, session_dir)
|
||||
self.context.response.answer = join_chunk_entries(entries)
|
||||
if not fused:
|
||||
self.context.response.answer = ALL_RETURNED_MESSAGE if pre_dedup_count > 0 else NO_RESULTS_MESSAGE
|
||||
self.context.response.metadata["results"] = [
|
||||
c.model_dump(exclude_none=True, exclude={"embedding"}) for c in fused
|
||||
]
|
||||
self.context.response.metadata["link_expansion"] = link_expansion
|
||||
self.context.response.metadata["counts"] = {
|
||||
"vector": len(vector_results),
|
||||
"keyword": len(keyword_results),
|
||||
"returned": len(fused),
|
||||
"hybrid": hybrid,
|
||||
}
|
||||
if dedup is not None:
|
||||
self.context.response.metadata["dedup"] = dedup
|
||||
return self.context.response
|
||||
|
||||
def _session_compress_enabled(self) -> bool:
|
||||
"""True when the injected ``_search._compress.session`` flag is truthy."""
|
||||
assert self.context is not None
|
||||
search_cfg: dict = self.context.get("_search") or {}
|
||||
value = (search_cfg.get("_compress") or {}).get("session")
|
||||
return value is True or str(value).strip().lower() == "true"
|
||||
|
||||
async def _compress_session_entries(self, entries: list[dict[str, str]], query: str, session_dir: str) -> None:
|
||||
"""Compress session-transcript entry bodies in place via the ``compressor`` job.
|
||||
|
||||
Only entries whose ``path`` points at a raw session transcript are
|
||||
compressed; other entries and all non-``body`` fields stay untouched.
|
||||
When ``_search.type`` is ``query-independent`` the compressor runs
|
||||
without queries (generic compression); otherwise (``query-aware``,
|
||||
the default) it receives the injected ``_search.queries`` plus the
|
||||
current search query.
|
||||
|
||||
The compressor receives the already-rendered body (one message per
|
||||
line) stripped. Its output is adopted whenever the compressor
|
||||
succeeded and the result is not longer than the input; adopted bodies
|
||||
get a leading ``compressed session chunk:`` marker so downstream
|
||||
consumers can tell them from verbatim transcripts.
|
||||
|
||||
Degrades gracefully when the ``compressor`` job is missing from the
|
||||
active config: the whole method becomes a no-op and a warning is
|
||||
logged, so search behaves as if compression were disabled. This
|
||||
avoids a hard ``Job compressor not found`` failure when a benchmark
|
||||
config forgets to define the compressor job/component.
|
||||
|
||||
Per-entry exceptions raised by the compressor job (e.g. a temporary
|
||||
LLM outage) are caught inside ``compress`` so they never propagate
|
||||
through ``asyncio.gather``: the failing entry keeps its original body
|
||||
while the remaining entries are still compressed, preserving already
|
||||
retrieved search results.
|
||||
"""
|
||||
assert self.context is not None
|
||||
# Guard: when the compressor job is missing from the active config
|
||||
# (e.g. a benchmark config that forgot to define it), degrade
|
||||
# gracefully to the no-compression behavior instead of raising
|
||||
# "Job compressor not found" from run_job below.
|
||||
# Skipped when there is no app_context (e.g. unit tests that mock
|
||||
# run_job directly), so the mock can still drive compression.
|
||||
if self.app_context is not None and self.get_job("compressor") is None:
|
||||
self.logger.warning(
|
||||
f"[{self.name}] compressor job not found in config; "
|
||||
"skipping session chunk compression (degrading to uncompressed behavior)",
|
||||
)
|
||||
return
|
||||
search_cfg: dict = self.context.get("_search") or {}
|
||||
query_type = str(search_cfg.get("type") or "query-aware").strip().lower()
|
||||
if query_type == "query-independent":
|
||||
queries: list[str] = []
|
||||
else:
|
||||
queries = [str(q).strip() for q in (search_cfg.get("queries") or []) if str(q).strip()]
|
||||
if query and query not in queries:
|
||||
queries.append(query)
|
||||
|
||||
async def compress(entry: dict[str, str]) -> None:
|
||||
path = entry.get("path", "")
|
||||
body = (entry.get("body", "") or "").strip()
|
||||
if not body:
|
||||
return
|
||||
try:
|
||||
response = await self.run_job("compressor", text=body, queries=queries)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
self.logger.warning(
|
||||
f"[{self.name}] session body compression raised path={path!r} " f"error={exc!r}; keeping original",
|
||||
)
|
||||
return
|
||||
compressed = str(response.answer or "").strip()
|
||||
if not response.success or not compressed:
|
||||
self.logger.warning(
|
||||
f"[{self.name}] session body compression failed path={path!r} "
|
||||
f"success={response.success} answer={compressed[:100]!r}; keeping original",
|
||||
)
|
||||
return
|
||||
if len(compressed) > len(body):
|
||||
self.logger.info(
|
||||
f"[{self.name}] compressed body longer than original "
|
||||
f"({len(compressed)} > {len(body)}) path={path!r}; keeping original",
|
||||
)
|
||||
return
|
||||
entry["body"] = f"compressed session chunk:\n{compressed}"
|
||||
|
||||
targets = [e for e in entries if is_session_path(e.get("path", ""), session_dir)]
|
||||
if not targets:
|
||||
return
|
||||
self.logger.info(f"[{self.name}] compressing {len(targets)} session entries with {len(queries)} queries")
|
||||
await asyncio.gather(*(compress(entry) for entry in targets))
|
||||
|
|
@ -1,635 +0,0 @@
|
|||
# BEAM benchmark config — based on longmemeval.yaml (split)
|
||||
# All background/cron jobs are converted to base (manually callable).
|
||||
# Use with: resolve_app_config(config="beam.yaml", ...)
|
||||
|
||||
service:
|
||||
backend: http
|
||||
|
||||
jobs:
|
||||
# ── BEAM agentic answer (ReAct agent + search tool) ──
|
||||
agentic_answer:
|
||||
backend: base
|
||||
description: "BEAM agentic answer job (ReAct agent with search tool)"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The query to ask"
|
||||
query_time:
|
||||
type: string
|
||||
description: "ISO timestamp representing the query time"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: beam_agentic_answer_step
|
||||
agent_wrapper: bench
|
||||
|
||||
# ── BEAM rubric-based LLM-as-Judge ──
|
||||
answer_judge:
|
||||
backend: base
|
||||
description: "BEAM rubric-based LLM-as-Judge: evaluate response against rubric criteria"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
llm_response:
|
||||
type: string
|
||||
description: "The model's response to evaluate"
|
||||
rubric:
|
||||
type: array
|
||||
description: "List of rubric criteria to check"
|
||||
items:
|
||||
type: string
|
||||
probing_question:
|
||||
type: string
|
||||
description: "The original probing question"
|
||||
default: ""
|
||||
question_type:
|
||||
type: string
|
||||
description: "BEAM question type (e.g. event_ordering)"
|
||||
default: ""
|
||||
required:
|
||||
- llm_response
|
||||
- rubric
|
||||
steps:
|
||||
- backend: beam_rubric_judge_step
|
||||
agent_wrapper: judge
|
||||
|
||||
# ── Manual index update (replaces index_update_loop background) ──
|
||||
index_update:
|
||||
backend: base
|
||||
description: "Manually trigger incremental index update for watched dirs."
|
||||
watch_dirs: [daily_dir, digest_dir, session_dir/dialog]
|
||||
watch_suffixes: [md, jsonl]
|
||||
parameters:
|
||||
type: object
|
||||
properties: {}
|
||||
steps:
|
||||
- backend: init_changes_step
|
||||
monitor_type: file_store
|
||||
monitor_name: default
|
||||
dispatch_steps: [update_index_step]
|
||||
|
||||
# ── Manual digest catalog update (replaces digest_watch_loop background) ──
|
||||
digest_update:
|
||||
backend: base
|
||||
description: "Manually trigger digest catalog update."
|
||||
watch_dirs: [daily_dir, digest_dir]
|
||||
watch_suffixes: [md]
|
||||
parameters:
|
||||
type: object
|
||||
properties: {}
|
||||
steps:
|
||||
- backend: init_changes_step
|
||||
monitor_type: file_catalog
|
||||
monitor_name: digest
|
||||
dispatch_steps:
|
||||
- backend: update_catalog_step
|
||||
file_catalog: digest
|
||||
- backend: log_changes_step
|
||||
|
||||
# ── Auto dream (same as default.yaml auto_dream, base mode) ──
|
||||
# auto_dream:
|
||||
# backend: base
|
||||
# description: "Auto-dream: scan today's day-index and daily notes, globally extract merged units/topics, integrate digest units, write interests.yaml, and persist the dream catalog."
|
||||
# parameters:
|
||||
# type: object
|
||||
# properties:
|
||||
# date:
|
||||
# type: string
|
||||
# description: "YYYY-MM-DD to scan; defaults to today in the dreamer's timezone"
|
||||
# default: ""
|
||||
# hint:
|
||||
# type: string
|
||||
# description: "caller guidance passed through to dream extract/integrate"
|
||||
# default: ""
|
||||
# scan_days:
|
||||
# type: integer
|
||||
# description: "number of recent daily directories to scan, ending at date"
|
||||
# default: 2
|
||||
# max_units:
|
||||
# type: integer
|
||||
# description: "maximum number of extracted memory units"
|
||||
# default: 5
|
||||
# topic_count:
|
||||
# type: integer
|
||||
# description: "maximum number of final daily interest topics"
|
||||
# default: 3
|
||||
# topic_diversity_days:
|
||||
# type: integer
|
||||
# description: "number of previous interests.yaml days to avoid repeating"
|
||||
# default: 7
|
||||
# steps:
|
||||
# - backend: dream_extract_step
|
||||
# file_catalog: dream
|
||||
# topic_session_id: interests
|
||||
# scan_days: 2
|
||||
# max_units: 5
|
||||
# - backend: dream_integrate_step
|
||||
# - backend: dream_topics_step
|
||||
# topic_count: 3
|
||||
# topic_diversity_days: 7
|
||||
# - backend: dream_finish_step
|
||||
# file_catalog: dream
|
||||
|
||||
# ── Auto memory (same as default.yaml) ──
|
||||
auto_memory:
|
||||
backend: base
|
||||
description: "Auto-memory: record conversation facts into a daily note"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
type: array
|
||||
description: "messages"
|
||||
items:
|
||||
type: object
|
||||
session_id:
|
||||
type: string
|
||||
description: "source conversation session identifier"
|
||||
default: ""
|
||||
memory_hint:
|
||||
type: string
|
||||
description: "optional hint"
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
|
||||
default: ""
|
||||
required:
|
||||
- messages
|
||||
steps:
|
||||
- backend: beam_auto_memory_step
|
||||
# Long sessions are split into turn-aligned segments and fed to the
|
||||
# agent incrementally; each segment holds at most this many words.
|
||||
# ("segment" here is a slice of the message list, unrelated to file
|
||||
# chunking in the index.) <= 0 disables splitting.
|
||||
max_segment_words: 10000
|
||||
|
||||
# ── Text compression (direct LLM call, no agent) ──
|
||||
compressor:
|
||||
backend: base
|
||||
description: "Compress text via a direct LLM call, optionally guided by queries as relevance filter"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
description: "the text to compress"
|
||||
queries:
|
||||
type: array
|
||||
description: "optional list of queries; content potentially relevant to any query is kept, content certainly irrelevant to all queries may be dropped"
|
||||
items:
|
||||
type: string
|
||||
default: []
|
||||
required:
|
||||
- text
|
||||
steps:
|
||||
- backend: compressor_step
|
||||
as_llm: compressor
|
||||
|
||||
# ── Reindex (derived search indexes only) ──
|
||||
reindex:
|
||||
backend: base
|
||||
description: "rebuild BM25 and/or embedding indexes from current file_chunks"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
scope:
|
||||
type: string
|
||||
enum: [all, bm25, embedding]
|
||||
default: all
|
||||
steps:
|
||||
- backend: reindex_step
|
||||
|
||||
# ── Search ──
|
||||
# start_date:
|
||||
# type: string
|
||||
# description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
# end_date:
|
||||
# type: string
|
||||
# description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
|
||||
search:
|
||||
backend: base
|
||||
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "search query"
|
||||
start_date:
|
||||
type: string
|
||||
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
end_date:
|
||||
type: string
|
||||
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
# vector_weight:
|
||||
# type: number
|
||||
# description: >-
|
||||
# Optional weight balancing vector similarity vs BM25 keyword matching in the
|
||||
# RRF fusion. Recommended value is 0.7, which provides a good balance between
|
||||
# semantic (vector) similarity and lexical (BM25) matching. Values close to 0
|
||||
# emphasize BM25 keyword matching, values close to 1 emphasize vector semantic
|
||||
# similarity.
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: search_v2_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
expand_links: false
|
||||
max_links_per_direction: 10
|
||||
|
||||
add_draft:
|
||||
backend: base
|
||||
description: "Append text to the current draft list."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
text:
|
||||
type: string
|
||||
description: "draft text to append"
|
||||
required:
|
||||
- text
|
||||
steps:
|
||||
- backend: add_draft_step
|
||||
|
||||
read_all_draft:
|
||||
backend: base
|
||||
description: "Read all draft text previously appended in the current tool context."
|
||||
parameters:
|
||||
type: object
|
||||
properties: { }
|
||||
steps:
|
||||
- backend: read_all_draft_step
|
||||
|
||||
python_execute:
|
||||
backend: base
|
||||
description: "Execute Python code and return printed stdout."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
description: "Python code to execute. Print the final result to stdout."
|
||||
timeout:
|
||||
type: number
|
||||
description: "Execution timeout in seconds; defaults to 60."
|
||||
required:
|
||||
- code
|
||||
steps:
|
||||
- backend: python_execute_step
|
||||
|
||||
# ── File I/O jobs (needed by auto_memory agent tools) ──
|
||||
daily_list:
|
||||
backend: base
|
||||
description: "List notes under a single day."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD; empty = today"
|
||||
default: ""
|
||||
steps:
|
||||
- backend: daily_list_step
|
||||
|
||||
daily_reindex:
|
||||
backend: base
|
||||
description: "Rebuild the day-index page daily/<date>.md."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD; empty = today"
|
||||
default: ""
|
||||
steps:
|
||||
- backend: daily_reindex_step
|
||||
|
||||
frontmatter_update:
|
||||
backend: base
|
||||
description: "Merge key-values into a file's frontmatter."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: "workspace-relative path"
|
||||
metadata:
|
||||
type: object
|
||||
description: "key-values to merge"
|
||||
required:
|
||||
- path
|
||||
- metadata
|
||||
steps:
|
||||
- backend: frontmatter_update_step
|
||||
|
||||
move:
|
||||
backend: base
|
||||
description: "Move / rename a workspace file."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
src_path:
|
||||
type: string
|
||||
description: "workspace-relative source"
|
||||
dst_path:
|
||||
type: string
|
||||
description: "workspace-relative destination"
|
||||
overwrite:
|
||||
type: boolean
|
||||
default: false
|
||||
retarget:
|
||||
type: boolean
|
||||
default: true
|
||||
required:
|
||||
- src_path
|
||||
- dst_path
|
||||
steps:
|
||||
- backend: move_step
|
||||
|
||||
read:
|
||||
backend: base
|
||||
description: "Read a markdown file under the workspace."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: "workspace-relative path; markdown only"
|
||||
start_line:
|
||||
type: integer
|
||||
end_line:
|
||||
type: integer
|
||||
required:
|
||||
- path
|
||||
steps:
|
||||
- backend: read_step
|
||||
with_neighbors: false
|
||||
max_neighbors_per_direction: 10
|
||||
|
||||
write:
|
||||
backend: base
|
||||
description: "Write a markdown file."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
required:
|
||||
- path
|
||||
- name
|
||||
- description
|
||||
- content
|
||||
steps:
|
||||
- backend: write_step
|
||||
|
||||
daily_write:
|
||||
backend: base
|
||||
description: "Write a daily markdown note."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
session_id:
|
||||
type: string
|
||||
content:
|
||||
type: string
|
||||
date:
|
||||
type: string
|
||||
default: ""
|
||||
metadata:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
- session_id
|
||||
- content
|
||||
steps:
|
||||
- backend: daily_write_step
|
||||
|
||||
edit:
|
||||
backend: base
|
||||
description: "Find-and-replace in a markdown file."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
old:
|
||||
type: string
|
||||
new:
|
||||
type: string
|
||||
default: ""
|
||||
required:
|
||||
- path
|
||||
- old
|
||||
- new
|
||||
steps:
|
||||
- backend: edit_step
|
||||
|
||||
frontmatter_read:
|
||||
backend: base
|
||||
description: "Read a file's frontmatter as a dict."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
required:
|
||||
- path
|
||||
steps:
|
||||
- backend: frontmatter_read_step
|
||||
|
||||
node_search:
|
||||
backend: base
|
||||
description: "Digest node recall."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
limit:
|
||||
type: integer
|
||||
default: 20
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: node_search_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
|
||||
components:
|
||||
tokenizer:
|
||||
default:
|
||||
backend: regex
|
||||
|
||||
as_embedding:
|
||||
default:
|
||||
backend: ${EMBEDDING_BACKEND:-openai}
|
||||
model: ${EMBEDDING_MODEL_NAME:-text-embedding-v4}
|
||||
credential:
|
||||
api_key: ${EMBEDDING_API_KEY:-}
|
||||
base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
|
||||
dimensions: 1024
|
||||
|
||||
embedding_store:
|
||||
default:
|
||||
backend: local
|
||||
as_embedding: default
|
||||
|
||||
as_llm:
|
||||
default:
|
||||
backend: ${LLM_BACKEND:-openai}
|
||||
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
|
||||
stream: true
|
||||
context_size: 200000
|
||||
max_retries: 5
|
||||
retry_delay: 5.0
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
parameters:
|
||||
max_tokens: 65536
|
||||
thinking_enable: false
|
||||
judge:
|
||||
backend: ${LLM_BACKEND:-openai}
|
||||
model: ${JUDGE_MODEL_NAME:-qwen3.7-max}
|
||||
stream: false
|
||||
context_size: 200000
|
||||
max_retries: 5
|
||||
retry_delay: 5.0
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
parameters:
|
||||
max_tokens: 65536
|
||||
thinking_enable: false
|
||||
bench:
|
||||
backend: ${LLM_BACKEND:-openai}
|
||||
model: ${BENCH_MODEL_NAME:-qwen3.7-max}
|
||||
stream: true
|
||||
context_size: 400000
|
||||
max_retries: 5
|
||||
retry_delay: 5.0
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
parameters:
|
||||
max_tokens: 65536
|
||||
thinking_enable: true
|
||||
compressor:
|
||||
backend: ${LLM_BACKEND:-openai}
|
||||
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
|
||||
stream: false
|
||||
context_size: 200000
|
||||
max_retries: 5
|
||||
retry_delay: 5.0
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
base_url: ${LLM_BASE_URL:-}
|
||||
parameters:
|
||||
max_tokens: 65536
|
||||
thinking_enable: false
|
||||
|
||||
agent_wrapper:
|
||||
default:
|
||||
backend: agentscope
|
||||
as_llm: default
|
||||
permission_mode: bypass
|
||||
react_config:
|
||||
max_iters: 30
|
||||
context_config:
|
||||
trigger_ratio: 0.8
|
||||
reserve_ratio: 0.1
|
||||
tool_result_limit: 50000
|
||||
model_config:
|
||||
max_retries: 1
|
||||
judge:
|
||||
backend: agentscope
|
||||
as_llm: judge
|
||||
permission_mode: bypass
|
||||
react_config:
|
||||
max_iters: 1
|
||||
context_config:
|
||||
trigger_ratio: 0.8
|
||||
reserve_ratio: 0.1
|
||||
tool_result_limit: 50000
|
||||
model_config:
|
||||
max_retries: 1
|
||||
bench:
|
||||
backend: agentscope
|
||||
as_llm: bench
|
||||
permission_mode: bypass
|
||||
react_config:
|
||||
max_iters: 30
|
||||
context_config:
|
||||
trigger_ratio: 0.8
|
||||
reserve_ratio: 0.1
|
||||
tool_result_limit: 50000
|
||||
model_config:
|
||||
max_retries: 1
|
||||
|
||||
file_graph:
|
||||
default:
|
||||
backend: local
|
||||
|
||||
file_catalog:
|
||||
default:
|
||||
backend: local
|
||||
resource:
|
||||
backend: local
|
||||
digest:
|
||||
backend: local
|
||||
dream:
|
||||
backend: local
|
||||
|
||||
file_chunker:
|
||||
markdown:
|
||||
backend: markdown
|
||||
supported_extensions: [ "md" ]
|
||||
embed_toc: true
|
||||
max_ast_sections: 100
|
||||
include_frontmatter_in_metadata: false
|
||||
include_frontmatter_keys_in_metadata: [] # empty = all non-empty frontmatter keys
|
||||
json:
|
||||
backend: json
|
||||
supported_extensions: [ "json" ]
|
||||
jsonl:
|
||||
backend: jsonl
|
||||
supported_extensions: [ "jsonl" ] # noqa: keep #314 chunker scope intact after #325
|
||||
max_chars: 4000
|
||||
default:
|
||||
backend: default
|
||||
supported_extensions: ["txt","log"]
|
||||
|
||||
keyword_index:
|
||||
default:
|
||||
backend: bm25
|
||||
tokenizer: default
|
||||
|
||||
file_store:
|
||||
default:
|
||||
backend: local
|
||||
store_name: local
|
||||
embedding_store: default
|
||||
keyword_index: default
|
||||
file_graph: default
|
||||
|
|
@ -1,63 +1,11 @@
|
|||
# LongMemEval benchmark config — based on longmemeval.yaml (split)
|
||||
# All background/cron jobs are converted to base (manually callable).
|
||||
# Use with: resolve_app_config(config="lme.yaml", ...)
|
||||
# Shared benchmark application preset; independent of the default service config.
|
||||
# Background and cron jobs are omitted in favor of manually callable base jobs.
|
||||
# Benchmark plugins contribute their own jobs through plugin.yaml.
|
||||
|
||||
service:
|
||||
backend: http
|
||||
|
||||
jobs:
|
||||
# ── LongMemEval agentic answer (ReAct agent + search tool) ──
|
||||
agentic_answer:
|
||||
backend: base
|
||||
description: "LongMemEval agentic answer job (ReAct agent with search tool)"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The query to ask"
|
||||
query_time:
|
||||
type: string
|
||||
description: "ISO timestamp representing the query time"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: lme_agentic_answer_step
|
||||
agent_wrapper: bench
|
||||
|
||||
# ── LLM-as-Judge for evaluating answers ──
|
||||
answer_judge:
|
||||
backend: base
|
||||
description: "LLM-as-Judge: evaluate agent answer against golden answer"
|
||||
watch_dirs: []
|
||||
watch_suffixes: []
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "The question being asked"
|
||||
agent_answer:
|
||||
type: string
|
||||
description: "The model's answer to evaluate"
|
||||
golden_answer:
|
||||
type: string
|
||||
description: "The correct/golden answer"
|
||||
question_type:
|
||||
type: string
|
||||
description: "The question type for prompt selection"
|
||||
default: ""
|
||||
required:
|
||||
- query
|
||||
- agent_answer
|
||||
- golden_answer
|
||||
steps:
|
||||
- backend: lme_answer_judge_step
|
||||
agent_wrapper: judge
|
||||
|
||||
# ── Manual index update (replaces index_update_loop background) ──
|
||||
index_update:
|
||||
backend: base
|
||||
|
|
@ -135,34 +83,6 @@ jobs:
|
|||
# - backend: dream_finish_step
|
||||
# file_catalog: dream
|
||||
|
||||
# ── Auto memory (same as default.yaml) ──
|
||||
auto_memory:
|
||||
backend: base
|
||||
description: "Auto-memory: record conversation facts into a daily note"
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
messages:
|
||||
type: array
|
||||
description: "messages"
|
||||
items:
|
||||
type: object
|
||||
session_id:
|
||||
type: string
|
||||
description: "source conversation session identifier"
|
||||
default: ""
|
||||
memory_hint:
|
||||
type: string
|
||||
description: "optional hint"
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD daily note date; empty = infer from message timestamps or today"
|
||||
default: ""
|
||||
required:
|
||||
- messages
|
||||
steps:
|
||||
- backend: lme_auto_memory_step
|
||||
|
||||
# ── Text compression (direct LLM call, no agent) ──
|
||||
compressor:
|
||||
backend: base
|
||||
|
|
@ -199,46 +119,6 @@ jobs:
|
|||
steps:
|
||||
- backend: reindex_step
|
||||
|
||||
# ── Search ──
|
||||
# start_date:
|
||||
# type: string
|
||||
# description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
# end_date:
|
||||
# type: string
|
||||
# description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
|
||||
search:
|
||||
backend: base
|
||||
description: "Hybrid workspace search (vector + BM25, RRF-fused) with deduplication."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "search query"
|
||||
start_date:
|
||||
type: string
|
||||
description: "optional inclusive start date filter (YYYY-MM-DD); results earlier than this date are excluded"
|
||||
end_date:
|
||||
type: string
|
||||
description: "optional inclusive end date filter (YYYY-MM-DD); results later than this date are excluded"
|
||||
# vector_weight:
|
||||
# type: number
|
||||
# description: >-
|
||||
# Optional weight balancing vector similarity vs BM25 keyword matching in the
|
||||
# RRF fusion. Recommended value is 0.7, which provides a good balance between
|
||||
# semantic (vector) similarity and lexical (BM25) matching. Values close to 0
|
||||
# emphasize BM25 keyword matching, values close to 1 emphasize vector semantic
|
||||
# similarity.
|
||||
required:
|
||||
- query
|
||||
steps:
|
||||
- backend: search_v2_step
|
||||
vector_weight: 0.7
|
||||
candidate_multiplier: 5.0
|
||||
expand_links: false
|
||||
max_links_per_direction: 10
|
||||
|
||||
add_draft:
|
||||
backend: base
|
||||
description: "Append text to the current draft list."
|
||||
|
|
@ -326,7 +206,7 @@ jobs:
|
|||
|
||||
move:
|
||||
backend: base
|
||||
description: "Move / rename a workspace file."
|
||||
description: "Move / rename a workspace file; rewrites inbound wikilinks by default."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
|
|
@ -338,9 +218,11 @@ jobs:
|
|||
description: "workspace-relative destination"
|
||||
overwrite:
|
||||
type: boolean
|
||||
description: "overwrite if dst exists"
|
||||
default: false
|
||||
retarget:
|
||||
type: boolean
|
||||
description: "rewrite [[src]] → [[dst]] across the workspace"
|
||||
default: true
|
||||
required:
|
||||
- src_path
|
||||
|
|
@ -359,8 +241,10 @@ jobs:
|
|||
description: "workspace-relative path; markdown only"
|
||||
start_line:
|
||||
type: integer
|
||||
description: "first line (1-based, inclusive)"
|
||||
end_line:
|
||||
type: integer
|
||||
description: "last line (1-based, inclusive)"
|
||||
required:
|
||||
- path
|
||||
steps:
|
||||
|
|
@ -370,20 +254,25 @@ jobs:
|
|||
|
||||
write:
|
||||
backend: base
|
||||
description: "Write a markdown file."
|
||||
description: "Write a markdown file (create or overwrite) with name/description frontmatter."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: "workspace-relative path; markdown only"
|
||||
name:
|
||||
type: string
|
||||
description: "frontmatter name"
|
||||
description:
|
||||
type: string
|
||||
description: "frontmatter description"
|
||||
content:
|
||||
type: string
|
||||
description: "body"
|
||||
metadata:
|
||||
type: object
|
||||
description: "Optional extra frontmatter fields (md only)."
|
||||
required:
|
||||
- path
|
||||
- name
|
||||
|
|
@ -394,23 +283,29 @@ jobs:
|
|||
|
||||
daily_write:
|
||||
backend: base
|
||||
description: "Write a daily markdown note."
|
||||
description: "Write a daily markdown note with conversation source frontmatter."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: "daily note filename stem and frontmatter name"
|
||||
description:
|
||||
type: string
|
||||
description: "frontmatter description"
|
||||
session_id:
|
||||
type: string
|
||||
description: "source conversation session identifier"
|
||||
content:
|
||||
type: string
|
||||
description: "body"
|
||||
date:
|
||||
type: string
|
||||
description: "YYYY-MM-DD daily note date; empty = today"
|
||||
default: ""
|
||||
metadata:
|
||||
type: object
|
||||
description: "Optional extra frontmatter fields."
|
||||
required:
|
||||
- name
|
||||
- description
|
||||
|
|
@ -421,16 +316,19 @@ jobs:
|
|||
|
||||
edit:
|
||||
backend: base
|
||||
description: "Find-and-replace in a markdown file."
|
||||
description: "Find-and-replace in a markdown file (all occurrences)."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: "workspace-relative path"
|
||||
old:
|
||||
type: string
|
||||
description: "text to find"
|
||||
new:
|
||||
type: string
|
||||
description: "replacement"
|
||||
default: ""
|
||||
required:
|
||||
- path
|
||||
|
|
@ -447,6 +345,7 @@ jobs:
|
|||
properties:
|
||||
path:
|
||||
type: string
|
||||
description: "workspace-relative path"
|
||||
required:
|
||||
- path
|
||||
steps:
|
||||
|
|
@ -454,14 +353,16 @@ jobs:
|
|||
|
||||
node_search:
|
||||
backend: base
|
||||
description: "Digest node recall."
|
||||
description: "Digest node recall — given a candidate abstraction's name+description, surface existing digest nodes similar enough to either dedup against or link to as related."
|
||||
parameters:
|
||||
type: object
|
||||
properties:
|
||||
query:
|
||||
type: string
|
||||
description: "search query"
|
||||
limit:
|
||||
type: integer
|
||||
description: "max digest nodes to return"
|
||||
default: 20
|
||||
required:
|
||||
- query
|
||||
|
|
@ -495,7 +396,6 @@ components:
|
|||
model: ${LLM_MODEL_NAME:-qwen3.6-flash}
|
||||
stream: true
|
||||
context_size: 200000
|
||||
max_retries: 3
|
||||
retry_delay: 5.0
|
||||
credential:
|
||||
api_key: ${LLM_API_KEY:-}
|
||||
|
|
@ -1,17 +1,5 @@
|
|||
"""Benchmark steps."""
|
||||
"""Shared benchmark steps; concrete implementations live in plugins."""
|
||||
|
||||
from . import base, lme, beam
|
||||
from .base import BaseAgenticAnswerStep
|
||||
from .lme import LmeAgenticAnswerStep, LmeAnswerJudgeStep
|
||||
from .beam import BeamAgenticAnswerStep, BeamRubricJudgeStep
|
||||
from .base_agentic_answer import BaseAgenticAnswerStep
|
||||
|
||||
__all__ = [
|
||||
"BaseAgenticAnswerStep",
|
||||
"LmeAgenticAnswerStep",
|
||||
"LmeAnswerJudgeStep",
|
||||
"BeamAgenticAnswerStep",
|
||||
"BeamRubricJudgeStep",
|
||||
"base",
|
||||
"lme",
|
||||
"beam",
|
||||
]
|
||||
__all__ = ["BaseAgenticAnswerStep"]
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
"""Shared base classes for benchmark steps."""
|
||||
|
||||
from .agentic_answer import BaseAgenticAnswerStep
|
||||
|
||||
__all__ = [
|
||||
"BaseAgenticAnswerStep",
|
||||
]
|
||||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
import os
|
||||
|
||||
from ...base_step import BaseStep
|
||||
from ...index._dedup import _ToolContextDedupMixin
|
||||
from ....enumeration import ChunkEnum
|
||||
from ....utils.counter import global_counter_inc
|
||||
from ..base_step import BaseStep
|
||||
from ..index._dedup import _ToolContextDedupMixin
|
||||
from ...enumeration import ChunkEnum
|
||||
from ...utils.counter import global_counter_inc
|
||||
|
||||
|
||||
class BaseAgenticAnswerStep(BaseStep):
|
||||
"""Base ReAct-agent answer step shared by BEAM and LongMemEval benchmarks.
|
||||
"""ReAct-agent answer implementation shared by benchmark plugins.
|
||||
|
||||
Subclasses only need to set:
|
||||
TOOL_CONTEXT_PREFIX (str): prefix used to build the unique tool_context_id.
|
||||
|
|
@ -18,7 +18,7 @@ class BaseAgenticAnswerStep(BaseStep):
|
|||
tool call via ``injected_job_kwargs``; override the attribute or the
|
||||
``_injected_job_kwargs`` hook to customize.
|
||||
|
||||
And apply their own ``@R.register(...)`` decorator and docstring.
|
||||
Concrete subclasses are registered by the plugin manifest.
|
||||
|
||||
Inputs (from RuntimeContext):
|
||||
query (str, required): The question to answer.
|
||||
|
|
@ -43,7 +43,7 @@ class BaseAgenticAnswerStep(BaseStep):
|
|||
``INJECTED_JOB_KWARGS`` with per-request values derived from ``query``.
|
||||
|
||||
When the runtime context carries a truthy ``compress_session`` flag,
|
||||
session-transcript compression is enabled in ``search_v2_step`` by
|
||||
session-transcript compression is enabled in the benchmark plugin's search Step by
|
||||
injecting a ``_search._compress.session`` marker plus the current
|
||||
``query`` as the query-aware relevance filter. Default (falsy) leaves
|
||||
session chunks uncompressed.
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue