refactor(config): remove daily_cookbook and streamline plugin configs

- Delete the entire daily_cookbook.yaml standalone application config
- Remove qwenpaw dependencies verification and related CI workflow steps
- Simplify release workflows by removing qwenpaw verification and enforcing reme-ai >=0.4.1.9
- Update plugin start commands and examples to use 'default' or 'demo' configs instead of daily_cookbook
- Adjust imports and tests related to daily_cookbook removal and injected_job_kwargs enhancements
- Refactor agent wrapper to support injected_job_kwargs for job parameter injection in auto-fin and daily-paper
- Improve daily_paper digest prompt to include configured daily directory and correct historical search constraints
- Update dependency versions in pyproject.toml files to require reme-ai >=0.4.1.9 and remove qwenpaw optional dependencies
- Clean up unused environment variables and obsolete test cases related to daily_cookbook and verification steps
This commit is contained in:
jinli.yl 2026-08-27 22:00:07 +08:00
parent 34cc579b97
commit fbbc5d2715
23 changed files with 114 additions and 539 deletions

View file

@ -13,11 +13,6 @@ on:
required: false
default: false
type: boolean
verify_qwenpaw_dependencies:
description: Verify that independently published qwenpaw plugins are installable
required: false
default: true
type: boolean
permissions:
contents: read
@ -84,22 +79,6 @@ jobs:
assert (static_dir() / "index.html").is_file()
PY
# qwenpaw composes independently released plugins. Bootstrap releases may
# skip this check to publish the reme-ai version required by those plugins.
- name: Verify released qwenpaw dependencies
if: inputs.expected_version != '' && inputs.verify_qwenpaw_dependencies
run: |
REME_WHEEL="$(pwd)/$(ls dist/reme/reme_ai-[0-9]*.whl)"
python -m venv "${RUNNER_TEMP}/reme-qwenpaw-package-smoke"
"${RUNNER_TEMP}/reme-qwenpaw-package-smoke/bin/python" -m pip install "${REME_WHEEL}[qwenpaw]"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-qwenpaw-package-smoke/bin/python" - <<'PY'
from importlib.metadata import distribution
assert distribution("reme-auto-fin")
assert distribution("reme-daily-paper")
PY
- name: Upload ReMe distributions
if: inputs.upload_artifacts
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6

View file

@ -75,6 +75,8 @@ jobs:
reme_requirement = Requirement(requirements[0])
if reme_requirement.name != "reme-ai" or reme_requirement.extras:
raise SystemExit(f"Expected a base reme-ai dependency, found {requirements[0]!r}")
if Version("0.4.1.8") in reme_requirement.specifier or Version("0.4.1.9") not in reme_requirement.specifier:
raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {requirements[0]!r}")
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
print(f"reme_requirement={reme_requirement}", file=output)
print(f"Publishing {project['name']} {actual}")
@ -104,7 +106,8 @@ jobs:
python -m zipfile -l "${AUTO_FIN_WHEEL}" | grep 'dist-info/licenses/LICENSE'
python -m tarfile -l "${AUTO_FIN_SDIST}" | grep '/LICENSE'
python -m venv "${RUNNER_TEMP}/reme-auto-fin-smoke"
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install "${AUTO_FIN_WHEEL}"
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" -m pip install \
"agentscope[model-ollama]==2.0.7" "${AUTO_FIN_WHEEL}"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-auto-fin-smoke/bin/python" - <<'PY'
from importlib.metadata import distribution

View file

@ -72,6 +72,8 @@ jobs:
reme_requirements = [requirement for requirement in requirements if requirement.name == "reme-ai"]
if len(reme_requirements) != 1 or reme_requirements[0].extras:
raise SystemExit(f"Expected one base reme-ai dependency, found {reme_requirements!r}")
if Version("0.4.1.8") in reme_requirements[0].specifier or Version("0.4.1.9") not in reme_requirements[0].specifier:
raise SystemExit(f"Expected reme-ai>=0.4.1.9, found {reme_requirements!r}")
if sum(requirement.name == "pypdf" for requirement in requirements) != 1:
raise SystemExit("Expected exactly one pypdf dependency")
with Path(os.environ["GITHUB_OUTPUT"]).open("a", encoding="utf-8") as output:
@ -105,7 +107,8 @@ jobs:
python -m zipfile -l "${DAILY_PAPER_WHEEL}" | grep 'dist-info/licenses/LICENSE'
python -m tarfile -l "${DAILY_PAPER_SDIST}" | grep '/LICENSE'
python -m venv "${RUNNER_TEMP}/reme-daily-paper-smoke"
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install "${DAILY_PAPER_WHEEL}"
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" -m pip install \
"agentscope[model-ollama]==2.0.7" "${DAILY_PAPER_WHEEL}"
cd "${RUNNER_TEMP}"
"${RUNNER_TEMP}/reme-daily-paper-smoke/bin/python" - <<'PY'
from importlib.metadata import distribution

View file

@ -1,8 +1,5 @@
name: Release / Python packages
# reme-ai[qwenpaw] is normally verified before publication. For a bootstrap
# release where the plugins require this new reme-ai version, disable that
# verification, publish reme-ai first, and then publish the plugins.
# Configure a PyPI Trusted Publisher for this repository, workflow, and its
# pypi environment before running the manual release.
@ -13,11 +10,6 @@ on:
description: Release version
required: true
type: string
verify_qwenpaw_dependencies:
description: Verify already-published Auto Fin and Daily Paper packages
required: true
default: true
type: boolean
permissions:
contents: read
@ -33,7 +25,6 @@ jobs:
with:
expected_version: ${{ inputs.version }}
upload_artifacts: true
verify_qwenpaw_dependencies: ${{ inputs.verify_qwenpaw_dependencies }}
publish-reme:
needs: build

View file

@ -64,7 +64,7 @@ reme plugins list --json
To compare installed plugins with one application config:
```bash
reme plugins list --config daily_cookbook
reme plugins list --config default
```
The optional `ENABLED` column reflects only the `plugins` list resolved from that config. A command-line override used
@ -176,7 +176,7 @@ When the application uses an MCP service, service-enabled plugin Jobs appear as
To add the plugin to another application config, select it explicitly:
```bash
reme start config=daily_cookbook plugins='["auto-fin"]'
reme start config=demo plugins='["auto-fin"]'
```
## Uninstall a plugin

View file

@ -61,7 +61,7 @@ reme plugins list --json
对照某个应用配置查看启用状态:
```bash
reme plugins list --config daily_cookbook
reme plugins list --config default
```
可选的 `ENABLED` 列只反映该配置解析出的 `plugins` 列表。其他运行中进程使用的 CLI override 不是全局启用状态。
@ -170,7 +170,7 @@ curl -s http://127.0.0.1:2333/auto_fin \
如果需要将插件叠加到其他应用配置,则显式选择该配置:
```bash
reme start config=daily_cookbook plugins='["auto-fin"]'
reme start config=demo plugins='["auto-fin"]'
```
## 卸载插件

View file

@ -62,7 +62,7 @@ reme start plugins='["auto-fin"]' \
To add Auto Fin to another application instead, select that config explicitly, for example:
```bash
reme start config=daily_cookbook plugins='["auto-fin"]'
reme start config=demo plugins='["auto-fin"]'
```
## Pipeline
@ -109,7 +109,7 @@ refreshes the daily index. No JSONL, intermediate Markdown, or structured Agent
| `request_interval` | `10` | Minimum delay in seconds after every CLS request attempt; may be zero |
| `max_retries` | `3` | Maximum attempts for each CLS page request; must be at least one |
The three plugin cron Jobs start with the application and run daily at 09:30, 11:30, and 18:00 in `Asia/Shanghai`.
The plugin cron Job starts with the application and runs daily at 18:00 in the application timezone.
## Output

View file

@ -57,7 +57,7 @@ reme start plugins='["auto-fin"]' \
如果需要将 Auto Fin 叠加到其他应用,则显式选择相应配置,例如:
```bash
reme start config=daily_cookbook plugins='["auto-fin"]'
reme start config=demo plugins='["auto-fin"]'
```
## 流程
@ -100,7 +100,7 @@ workspace 的 Markdown 目标。不存在、绝对路径、越界、带反斜杠
| `request_interval` | `10` | 每次财联社请求尝试后的最小等待秒数,可设为 0 |
| `max_retries` | `3` | 每页财联社请求的最大尝试次数,至少为 1 |
插件的三个 cron Job 随应用启动,并按 `Asia/Shanghai` 时区在每天 09:30、11:30 和 18:00 运行。
插件的 cron Job 随应用启动,并按应用配置的时区在每天 18:00 运行。
## 产物

View file

@ -7,7 +7,7 @@ license = "Apache-2.0"
license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"reme-ai",
"reme-ai>=0.4.1.9",
]
[project.entry-points."reme.plugins"]

View file

@ -76,6 +76,7 @@ class AutoFinStep(BaseStep):
prompt_name: str,
model: type[BaseModel],
job_tools: list[str] | None = None,
injected_job_kwargs: dict[str, Any] | None = None,
**values: str,
) -> BaseModel:
if self.agent_wrapper is None:
@ -89,6 +90,8 @@ class AutoFinStep(BaseStep):
kwargs: dict[str, Any] = {"output_schema": model}
if job_tools:
kwargs["job_tools"] = job_tools
if injected_job_kwargs:
kwargs["injected_job_kwargs"] = injected_job_kwargs
result = await self.agent_wrapper.reply(prompt, **kwargs)
if not isinstance(result, dict) or result.get("structured_output") is None:
raise ValueError(f"Auto Fin Agent returned no structured output: {self._preview(result)}")

View file

@ -4,7 +4,7 @@ from __future__ import annotations
import json
import re
from datetime import date
from datetime import date, timedelta
from pathlib import Path
from types import SimpleNamespace
@ -107,10 +107,17 @@ class AutoFinMergeStep(AutoFinStep):
if self.context.get("auto_fin_skipped"):
return self.context.response
run_date = date.fromisoformat(str(self._required("auto_fin_date")))
historical_search = {
"limit": 5,
"min_score": 0.0,
"start_date": None,
"end_date": (run_date - timedelta(days=1)).isoformat(),
}
output = await self._reply(
"merge_user",
AutoFinReportOutput,
job_tools=list(self.kwargs.get("job_tools") or []),
injected_job_kwargs=historical_search,
decision_at=str(self._required("auto_fin_decision_at")),
window_start=str(self._required("auto_fin_window_start")),
topics=json.dumps(self._required("auto_fin_topics"), ensure_ascii=False),

View file

@ -202,7 +202,16 @@ async def test_merge_writes_only_final_report_and_validates_historical_links(tmp
assert "end_date" not in prompt
assert "调用 `search`" in prompt
assert "调用 `read`" in prompt
assert kwargs == {"output_schema": AutoFinReportOutput, "job_tools": ["search", "read"]}
assert kwargs == {
"output_schema": AutoFinReportOutput,
"job_tools": ["search", "read"],
"injected_job_kwargs": {
"limit": 5,
"min_score": 0.0,
"start_date": None,
"end_date": "2026-08-09",
},
}
report = (tmp_path / "daily" / "2026-08-10" / "auto_fin.md").read_text(encoding="utf-8")
assert "[[daily/2026-08-01/auto_fin.md|历史黄金观察]]" in report
assert "](daily/2026-08-01/auto_fin.md)" not in report

View file

@ -8,7 +8,7 @@ license-files = ["LICENSE"]
requires-python = ">=3.11"
dependencies = [
"pypdf>=5.0.0",
"reme-ai",
"reme-ai>=0.4.1.9",
]
[project.entry-points."reme.plugins"]

View file

@ -80,14 +80,23 @@ class DailyPaperDigestStep(DailyPaperStep):
documents = [{"title": item.title, "desc": item.desc, "body": item.body} for item in analyses]
wikilinks = [f"[[{item.note_path}]]" for item in analyses]
run_day = dt.date.fromisoformat(self._run_day())
daily_dir = str(self.config_value("daily_dir")).strip("/")
self.logger.info(f"[{self.name}] agent start notes={len(analyses)}")
result = await self.agent_wrapper.reply(
self.prompt_format(
"digest_user",
documents=json.dumps(documents, ensure_ascii=False, indent=2),
daily_dir=daily_dir,
),
output_schema=DailyPaperMarkdownOutput,
job_tools=list(self.kwargs.get("job_tools") or []),
injected_job_kwargs={
"limit": 20,
"min_score": 0.0,
"start_date": None,
"end_date": (run_day - dt.timedelta(days=1)).isoformat(),
},
)
self.logger.info(f"[{self.name}] agent done notes={len(analyses)}")
output = structured_output(result, DailyPaperMarkdownOutput)
@ -95,8 +104,7 @@ class DailyPaperDigestStep(DailyPaperStep):
if not output.desc.strip() or not body:
raise ValueError("Agent returned an empty daily paper brief")
day = self._run_day()
daily_dir = str(self.config_value("daily_dir")).strip("/")
day = run_day.isoformat()
title = normalize_chinese_title(output.title, f"每日论文简报-{day}")
existing_rel = str(self._state("existing_digest_path") or "").strip()
existing_path = self.workspace_path / existing_rel if existing_rel else None
@ -109,7 +117,7 @@ class DailyPaperDigestStep(DailyPaperStep):
existing=existing_path,
)
digest_rel = digest_path.relative_to(self.workspace_path).as_posix()
body = self._validate_historical_wikilinks(body, dt.date.fromisoformat(day), digest_path)
body = self._validate_historical_wikilinks(body, run_day, digest_path)
body += "\n\n## 详细论文\n\n" + "\n".join(f"- {link}" for link in wikilinks)
selected_ids = [item.arxiv_id for item in analyses]
await write_markdown(

View file

@ -4,12 +4,12 @@ digest_user: |
内容只能依据输入文档,不得补充文档中没有提供的事实。
保留技术准确性,同时解释三篇论文为什么值得关注,以及它们之间有什么联系。
在写作前,先调用 `search` 检索以前的文章:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询
使用 limit=20。主题跨度较大时可以多次检索。只把 `daily/` 下日期早于今天、
在写作前,先调用 `search` 检索以前的文章:围绕三篇论文的核心问题、方法、关键词和同义表达组织查询
主题跨度较大时可以多次检索。只把 `{daily_dir}/` 下日期早于今天、
且与本期内容确实相似或互补的 Markdown 文章作为候选;必要时调用 `read` 核验全文,不要仅凭标题判断。
将确认相关的旧文章以 Wikilink 自然织入正文,并用句子说明关联(延续、对比、补充或方法相似);
链接必须采用带 `.md` 的完整 workspace-relative 路径,例如
`[[daily/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。
`[[{daily_dir}/2026-07-01/旧文章.md|此前的相关解读]]`。不要输出裸链接、独立关系字段,也不要虚构搜索未命中的路径。
旧文章只用于判断关联和建立链接,不得用来补充本期事实。如果没有真正相关的旧文章,不要强行添加;
当日三篇详细解读的链接会由系统统一附在文末。

View file

@ -627,6 +627,26 @@ def test_daily_paper_cron_hf_mirror_defaults_enabled_with_environment_override(m
assert _plugin_config()["jobs"]["daily_paper_cron"]["use_hf_mirror"] is False
def test_digest_prompt_uses_configured_daily_directory(tmp_path: Path):
"""Use the host application's daily directory in historical-link guidance."""
step = DailyPaperDigestStep(
app_context=ApplicationContext(
workspace_dir=str(tmp_path),
daily_dir="memory",
),
)
prompt = step.prompt_format(
"digest_user",
documents="[]",
daily_dir=str(step.config_value("daily_dir")).strip("/"),
)
assert "`memory/`" in prompt
assert "[[memory/2026-07-01/旧文章.md" in prompt
assert "[[daily/2026-07-01/" not in prompt
def test_paper_pick_list_uses_an_object_root_for_tool_output():
"""AgentScope function arguments require an object-root JSON schema."""
schema = PaperPickList.model_json_schema()
@ -887,6 +907,12 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
assert cc_wrapper.calls[-1]["kwargs"] == {
"output_schema": DailyPaperMarkdownOutput,
"job_tools": ["search", "read"],
"injected_job_kwargs": {
"limit": 20,
"min_score": 0.0,
"start_date": None,
"end_date": "2026-07-20",
},
}
assert [call["kwargs"]["output_schema"] for call in cc_wrapper.calls] == [
PaperPickList,
@ -908,6 +934,7 @@ async def test_pipeline_filters_strict_yesterday_and_writes_outputs(
assert "长期记忆" not in digest_prompt
assert "先调用 `search` 检索以前的文章" in digest_prompt
assert "end_date" not in digest_prompt
assert "limit=" not in digest_prompt
assert "Wikilink" in digest_prompt
rerun = RuntimeContext(date="2026-07-21")

View file

@ -62,11 +62,6 @@ core = [
"polars>=1.43.0",
"reme_studio",
]
qwenpaw = [
"reme-ai",
"reme-auto-fin>=0.1.2",
"reme-daily-paper>=0.1.2",
]
dev = [
"packaging>=24.2",
"pre-commit>=4.6.1",

View file

@ -1,416 +0,0 @@
app_name: ReMe Daily Cookbook
workspace_dir: ${DAILY_PAPER_WORKSPACE_DIR:-reme_workspace}
timezone: Asia/Shanghai
language: zh
# This is a standalone application config. It intentionally does not inherit
# default.yaml and listens on a separate port so it can run beside ReMe.
service:
backend: http
host: ${DAILY_PAPER_HOST:-127.0.0.1}
port: ${DAILY_PAPER_PORT:-8001}
jobs:
index_update_loop:
backend: background
watch_dirs: [daily_dir, digest_dir]
watch_suffixes: [md, jsonl]
steps:
- backend: init_changes_step
monitor_type: file_store
monitor_name: default
dispatch_steps: [update_index_step]
- backend: watch_changes_step
dispatch_steps:
- backend: update_index_step
persist: false
auto_dream:
backend: base
description: "Auto-dream: consolidate recent daily notes into digest memory and interest topics."
parameters:
type: object
properties:
date:
type: string
description: "YYYY-MM-DD to scan; defaults to today in the configured timezone."
default: ""
hint:
type: string
description: "Optional guidance for extraction and integration."
default: ""
scan_days:
type: integer
description: "Number of recent daily directories to scan."
default: 2
max_units:
type: integer
description: "Maximum number of extracted memory units."
default: 5
topic_count:
type: integer
description: "Maximum number of interest topics to write."
default: 3
topic_diversity_days:
type: integer
description: "Previous interest-topic days used for de-duplication."
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:
backend: base
description: "Auto-memory: record conversation facts into a daily note."
parameters:
type: object
properties:
messages:
type: array
description: "Conversation messages."
items:
type: object
session_id:
type: string
description: "Source conversation session identifier."
default: ""
memory_hint:
type: string
description: "Optional memory-writing guidance."
date:
type: string
description: "YYYY-MM-DD daily-note date; empty infers it from messages or current time."
default: ""
required: [messages]
steps:
- backend: auto_memory_step
reindex:
backend: base
description: "Wipe the derived search store and rebuild it from memory files."
watch_dirs: [daily_dir, digest_dir]
watch_suffixes: [md, jsonl]
parameters:
type: object
properties: {}
steps:
- backend: clear_store_step
- backend: init_changes_step
monitor_type: file_store
monitor_name: default
dispatch_steps: [update_index_step]
memory_search:
backend: base
description: "Long-term memory retrieval via hybrid workspace search (vector + BM25, RRF-fused)."
parameters:
type: object
properties:
query:
type: string
description: "Search query."
limit:
type: integer
description: "Maximum number of results."
default: 5
min_score:
type: number
description: "Minimum fused score."
default: 0.0
start_date:
type: string
description: "Optional inclusive start date (YYYY-MM-DD)."
end_date:
type: string
description: "Optional inclusive end date (YYYY-MM-DD)."
required: [query]
steps:
- backend: search_step
vector_weight: 0.7
candidate_multiplier: 5.0
expand_links: true
max_links_per_direction: 10
node_search:
backend: base
description: "Recall digest nodes for auto-dream de-duplication and linking."
parameters:
type: object
properties:
query:
type: string
description: "Candidate memory-node name and description."
limit:
type: integer
description: "Maximum number of digest nodes."
default: 20
required: [query]
steps:
- backend: node_search_step
vector_weight: 0.7
candidate_multiplier: 5.0
daily_list:
backend: base
description: "List notes under one day."
parameters:
type: object
properties:
date:
type: string
description: "YYYY-MM-DD; empty means today."
default: ""
steps:
- backend: daily_list_step
frontmatter_read:
backend: base
description: "Read a file's frontmatter."
parameters:
type: object
properties:
path:
type: string
description: "Workspace-relative path."
required: [path]
steps:
- backend: frontmatter_read_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 or rename a workspace file and retarget inbound wikilinks."
parameters:
type: object
properties:
src_path:
type: string
description: "Workspace-relative source path."
dst_path:
type: string
description: "Workspace-relative destination path."
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 markdown path."
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: "Create or overwrite a markdown file with frontmatter."
parameters:
type: object
properties:
path:
type: string
description: "Workspace-relative markdown path."
name:
type: string
description: "Frontmatter name."
description:
type: string
description: "Frontmatter description."
content:
type: string
description: "Markdown body."
metadata:
type: object
description: "Optional extra frontmatter fields."
required: [path, name, description, content]
steps:
- backend: write_step
daily_write:
backend: base
description: "Write a daily markdown note linked to its source conversation."
parameters:
type: object
properties:
name:
type: string
description: "Filename stem and frontmatter name."
description:
type: string
description: "Frontmatter description."
session_id:
type: string
description: "Source conversation session identifier."
content:
type: string
description: "Markdown body."
date:
type: string
description: "YYYY-MM-DD; empty means today."
default: ""
metadata:
type: object
description: "Optional extra frontmatter fields."
required: [name, description, session_id, content]
steps:
- backend: daily_write_step
edit:
backend: base
description: "Find and replace text in a markdown file."
parameters:
type: object
properties:
path:
type: string
description: "Workspace-relative path."
old:
type: string
description: "Text to replace."
new:
type: string
description: "Replacement text."
default: ""
required: [path, old, new]
steps:
- backend: edit_step
dingtalk_wait:
backend: background
supervisor: true
close_timeout: 10
steps:
- backend: dingtalk_wait_step
app_key: ${DINGTALK_APP_KEY:-}
app_secret: ${DINGTALK_APP_SECRET:-}
robot_code: ${DINGTALK_ROBOT_CODE:-}
worker_count: 4
builtin_tools: [bash]
job_tools:
- memory_search
- read
- write
- edit
- daily_list
- daily_write
- frontmatter_read
- frontmatter_update
components:
tokenizer:
default:
backend: regex
as_llm:
default:
backend: openai
model: ${LLM_MODEL_NAME:-qwen3.7-plus}
stream: true
context_size: 200000
max_retries: 3
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
builtin_tools: false
# as_embedding:
# default:
# backend: openai
# model: ${EMBEDDING_MODEL_NAME:-text-embedding-v4}
# dimensions: 1024
# max_retries: 0
# credential:
# api_key: ${EMBEDDING_API_KEY:-}
# base_url: ${EMBEDDING_BASE_URL:-https://dashscope.aliyuncs.com/compatible-mode/v1}
# parameters: {}
#
# embedding_store:
# default:
# backend: local
# as_embedding: default
# max_retries: 3
# quota_retry_delay: 60.0
file_graph:
default:
backend: local
file_catalog:
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: []
jsonl:
backend: jsonl
supported_extensions: [jsonl]
max_lines_per_chunk: 1
keyword_index:
default:
backend: bm25
tokenizer: default
file_store:
default:
backend: local
store_name: local
# embedding_store: default
embedding_store: ""
keyword_index: default
file_graph: default

View file

@ -123,14 +123,6 @@ def test_default_config_keeps_frontmatter_chunk_metadata_opt_in():
) in (None, [])
def test_daily_cookbook_chunks_jsonl_one_line_at_a_time():
"""Daily cookbook keeps JSONL records as individually addressable chunks."""
cfg = _load_config("daily_cookbook.yaml")
jsonl = cfg["components"]["file_chunker"]["jsonl"]
assert jsonl["max_lines_per_chunk"] == 1
def test_parse_args_rejects_non_key_value_extra_argument():
"""Extra CLI arguments must use key=value syntax."""
with pytest.raises(ValueError, match="expected key=value"):

View file

@ -9,10 +9,8 @@ from unittest.mock import MagicMock
import pytest
from reme.components import ApplicationContext, R
from reme.components import ApplicationContext
from reme.components.agent_wrapper.base_agent_wrapper import BaseAgentWrapper
from reme.config.config_parser import _load_config
from reme.enumeration import ComponentEnum
from reme.steps.cookbook.dingtalk.wait import DingTalkWaitStep, _session_key
@ -203,54 +201,6 @@ async def test_final_reply_injects_only_configured_tools(tmp_path):
]
def test_daily_cookbook_registers_one_step_background_wait_job(monkeypatch):
for name in ("DINGTALK_APP_KEY", "DINGTALK_APP_SECRET", "DINGTALK_ROBOT_CODE"):
monkeypatch.delenv(name, raising=False)
config = _load_config("daily_cookbook")
job = config["jobs"]["dingtalk_wait"]
assert job["backend"] == "background"
assert job["steps"] == [
{
"backend": "dingtalk_wait_step",
"app_key": "",
"app_secret": "",
"robot_code": "",
"worker_count": 4,
"builtin_tools": ["bash"],
"job_tools": [
"memory_search",
"read",
"write",
"edit",
"daily_list",
"daily_write",
"frontmatter_read",
"frontmatter_update",
],
},
]
assert config["components"]["agent_wrapper"] == {
"default": {
"backend": "agentscope",
"as_llm": "default",
"builtin_tools": False,
},
}
assert R.get(ComponentEnum.STEP, "dingtalk_wait_step") is DingTalkWaitStep
def test_daily_cookbook_passes_dingtalk_environment_to_step(monkeypatch):
monkeypatch.setenv("DINGTALK_APP_KEY", "app-key")
monkeypatch.setenv("DINGTALK_APP_SECRET", "app-secret")
monkeypatch.setenv("DINGTALK_ROBOT_CODE", "robot-code")
step = _load_config("daily_cookbook")["jobs"]["dingtalk_wait"]["steps"][0]
assert (step["app_key"], step["app_secret"], step["robot_code"]) == (
"app-key",
"app-secret",
"robot-code",
)
@pytest.mark.asyncio
async def test_stream_client_closes_when_background_stop_is_set(monkeypatch):
websocket = _WebSocket()

View file

@ -65,6 +65,31 @@ def test_strip_injected_parameters_hides_keys_from_schema():
assert "date" in job.parameters["properties"]
def test_search_injection_exposes_only_query():
parameters = {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer"},
"min_score": {"type": "number"},
"start_date": {"type": "string"},
"end_date": {"type": "string"},
},
"required": ["query"],
}
injected = {
"limit": 20,
"min_score": 0.0,
"start_date": None,
"end_date": "2026-07-20",
}
stripped = BaseAgentWrapper._strip_injected_parameters(parameters, injected)
assert stripped["properties"] == {"query": {"type": "string"}}
assert stripped["required"] == ["query"]
# -- AgentScope wrapper -----------------------------------------------------------
@ -139,7 +164,7 @@ class _RecordingWrapper(BaseAgentWrapper):
super().__init__(**kwargs)
self.calls: list[dict] = []
async def reply(self, inputs, **kwargs) -> dict:
async def reply(self, _inputs, **kwargs) -> dict:
self.calls.append(kwargs)
return {"session_id": "s-1", "last_message": {}, "result": "ok"}

View file

@ -7,6 +7,7 @@ import tomllib
from types import ModuleType
from packaging.requirements import Requirement
from packaging.version import Version
import pytest
REPOSITORY = Path(__file__).resolve().parents[2]
@ -45,11 +46,7 @@ def test_studio_packages_have_independent_identity() -> None:
assert main_config["project"]["optional-dependencies"]["web"] == ["reme_studio"]
assert main_config["project"]["optional-dependencies"]["core"].count("reme-ai[as]") == 1
assert main_config["project"]["optional-dependencies"]["core"].count("reme_studio") == 1
assert main_config["project"]["optional-dependencies"]["qwenpaw"] == [
"reme-ai",
"reme-auto-fin>=0.1.2",
"reme-daily-paper>=0.1.2",
]
assert "qwenpaw" not in main_config["project"]["optional-dependencies"]
assert auto_fin_config["project"]["version"] == "0.1.2"
assert daily_paper_config["project"]["version"] == "0.1.2"
assert main_config["tool"]["setuptools"]["packages"]["find"]["include"] == ["reme", "reme.*"]
@ -161,7 +158,8 @@ def test_auto_fin_requires_reme_base() -> None:
assert len(reme_requirements) == 1
assert not reme_requirements[0].extras
assert not reme_requirements[0].specifier
assert Version("0.4.1.8") not in reme_requirements[0].specifier
assert Version("0.4.1.9") in reme_requirements[0].specifier
def test_daily_paper_license_matches_repository() -> None:
@ -178,7 +176,8 @@ def test_daily_paper_declares_runtime_dependencies() -> None:
by_name = {requirement.name: requirement for requirement in requirements}
assert not by_name["reme-ai"].extras
assert not by_name["reme-ai"].specifier
assert Version("0.4.1.8") not in by_name["reme-ai"].specifier
assert Version("0.4.1.9") in by_name["reme-ai"].specifier
assert "pypdf" in by_name

View file

@ -76,7 +76,7 @@ def test_list_plugins_marks_configured_plugins(monkeypatch, tmp_path, capsys):
)
monkeypatch.setattr(plugin_cli_module, "_enabled_plugins", lambda _config: {"auto-fin"})
assert plugin_cli_module.plugin_cli(["list", "--config", "daily_cookbook"]) == 0
assert plugin_cli_module.plugin_cli(["list", "--config", "default"]) == 0
output = capsys.readouterr().out
assert "ENABLED" in output