Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

845 lines
31 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
微信读书(wechat_readingbench 任务定义。
各 Task 类 docstring 遵循 bench_env/docs/task/TASK_CODE_SPEC.md §12:第一段概括验证目标;第二段「判定」说明
ground truth 来源、check 结构与匹配方式;第三段「注意」仅在边界非显然时补充。
实现仍须与 docstring 一致;代码是最终依据。
"""
# -- Task Index (auto-generated, do not edit) --
# 22 tasks | L1×4 L2×8 L3×9 L4×1
#
# [L1] CheckCoinBalance 微信读书里书币还有多少
# [L3] CheckHotSearchRank 微信读书热搜榜第{rank}名是什么书
# [L1] CheckBookRating 帮我看看微信读书里《{book_title}》推荐值多少
# [L2] AddBookToShelf 把《{book_title}》加到微信读书书架
# [L1] ManageShelf 把微信读书书架里《{book_title}》移出去
# [L2] SearchBookAuthor 微信读书里《{book_title}》是谁写的
# [L3] TogglePrivateReading 把书架里的《{book_title}》设成私密阅读
# [L2] EditProfileName 把微信读书的昵称改成{new_name}
# [L3] SetDarkMode 把微信读书深色模式改成{dark_mode}
# [L2] FindAudiobookPlays 微信读书里《{book_title}》有声版播放量多少
# [L3] AnalyzeReadingHabit 最近一周在微信读书上哪天读的时间最长
# [L3] CheckCalendarMonthReading 微信读书{year}年{month}月总共读了多少天
# [L2] CompareBookLengths 对比微信读书里《{book1}》和《{book2}》的字数,告诉我字数多的那本,然后加到书架
# [L3] FindHighestRatedBookInCategory 微信读书{category}分类里评分最高的书是哪本
# [L2] ConfigureReaderSettings 把微信读书的阅读器字体大小调成{font_size},翻页方式改成{style}
# [L1] UnfollowUser 在微信读书取消关注{user_name}
# [L2] SetProfileVisibility 把微信读书主页可见范围改成{visibility}
# [L3] ReadBookProgress 把微信读书里《{book_title}》翻到{percentage}%的位置
# [L4] OrganizeShelfByRecommendation 整理微信读书书架,把推荐值不高于{recommendation}%的书都删掉
# [L2] AddBookAndReadTo 帮我在微信读书找到《{book_title}》加到书架,调整读书进度到{percentage}%
# [L3] FindLowestProgressAndRead 微信读书书架里哪本书我读的进度最低,帮我翻到{percentage}%的位置
# [L3] PrivacyAndThemeBundle 把微信读书的阅读颜色换成{theme_color},开启"{privacy_label}",再把翻页方式改成{style}
# -- End Task Index --
from __future__ import annotations
import datetime
import re
from typing import Any
from bench_env.task.base import BaseTask
from bench_env.task.common_tasks import AnswerTask, CriteriaTask, build_answer_checks, match_duration
from bench_env.task.judge import JudgeInput
from bench_env.task.utils import sim_today
from bench_env.task.wechat_reading.app import (
WECHAT_READING_FONT_SIZE_VALUES,
WECHAT_READING_PAGE_TURN_STYLE_VALUES,
WECHAT_READING_PROFILE_NAME_VALUES,
WECHAT_READING_PROFILE_VISIBILITY_VALUES,
WECHAT_READING_THEME_COLOR_PARAM,
WechatReading,
)
# =============================================================================
# L1 — 基础覆盖
# =============================================================================
class CheckCoinBalance(AnswerTask):
"""验证 Agent 能否正确读出当前用户书币余额。
判定:宣告式 answer=.user.coinBalanceground truth 来自 App state
AnswerTask 默认 match_value 比对回答中的数值。
注意:objective=query,仅答案判定,无状态变更要求。
"""
templates = ["微信读书里书币还有多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "atomic"
difficulty = "L1"
capabilities = ["extract"]
answer = ".user.coinBalance"
answer_fields = [{"type": "number", "label": "书币余额"}]
class CheckHotSearchRank(AnswerTask):
"""验证 Agent 能否读出热搜榜指定名次对应的书名。
判定:answer=.hotSearch[rank={rank}].title;单一路径、单值匹配。
"""
templates = ["微信读书热搜榜第{rank}名是什么书"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "atomic"
difficulty = "L3"
max_steps = 15
capabilities = ["extract"]
parameters = {
"rank": {
"type": "integer",
"default": 1,
"description": "热搜排名",
},
}
answer = ".hotSearch[rank={rank}].title"
answer_fields = [{"type": "text", "label": "书名", "hint": "如:围城"}]
class CheckBookRating(AnswerTask):
"""验证 Agent 能否读出书城内某书的推荐值。
判定:answer=.store[title={book_title}].recommendedValue;按 AnswerTask
默认数值匹配(match_value 自动提取回答中的数字,容忍 % 后缀)。
"""
templates = ["帮我看看微信读书里《{book_title}》推荐值多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L1"
capabilities = ["extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.store[title]",
"default": "活着",
"description": "书籍标题",
},
}
answer = ".store[title={book_title}].recommendedValue"
answer_fields = [{"type": "number", "label": "推荐值(%"}]
# =============================================================================
# L2 — 核心原子操作
# =============================================================================
class AddBookToShelf(BaseTask):
"""验证是否将初始不在架的书加入书架。
判定:一项 check — WechatReading.check_on_shelf(book_title)expected=True。
注意:operateexpected_changes 含 shelf、bookProgress、readingBookIds、allProgressBookIds。
"""
templates = [
"把《{book_title}》加到微信读书书架",
"Add 《{book_title}》 to my WeChat Read bookshelf",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "create"]
parameters = {
"book_title": {
"type": "string",
"default": "三体",
"sampler": WechatReading.sample_book_title_not_on_shelf,
"description": "书籍标题(必须初始不在书架中)",
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_on_shelf(self.p.book_title)]
class ManageShelf(BaseTask):
"""验证是否将指定书从书架移出。
判定:check_on_shelf(book_title, expected=False)。
"""
templates = [
"把微信读书书架里《{book_title}》移出去",
"Remove 《{book_title}》 from my WeChat Read bookshelf",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L1"
max_steps = 30
capabilities = ["delete"]
parameters = {
"book_title": {
"type": "string",
"default": "红楼梦",
"sampler": WechatReading.sample_shelf_title,
"description": "书籍标题(必须初始在书架中)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_on_shelf(self.p.book_title, expected=False)]
class SearchBookAuthor(AnswerTask):
"""验证 Agent 能否答出某书的作者。
判定:answer=.store[title={book_title}].author。
"""
templates = ["微信读书里《{book_title}》是谁写的"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.store[title]",
"default": "活着",
"description": "书籍标题",
},
}
answer = ".store[title={book_title}].author"
answer_fields = [{"type": "text", "label": "作者", "hint": "如:鲁迅"}]
class TogglePrivateReading(BaseTask):
"""验证是否将书架上的书设为私密阅读。
判定:check_private_reading(book_title),校验 shelf 项 isPrivate。
"""
templates = [
"把书架里的《{book_title}》设成私密阅读",
"Set 《{book_title}》 on my bookshelf to private reading",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
max_steps = 30
capabilities = ["settings", "edit"]
parameters = {
"book_title": {
"type": "string",
"default": "苏菲的世界",
"sampler": WechatReading.sample_public_shelf_title,
"description": "书籍标题(必须初始在书架里且不是私密阅读)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_private_reading(self.p.book_title)]
class EditProfileName(CriteriaTask):
"""验证用户昵称是否被改为目标值。
判定:criteria user.name == 参数 new_name_post_sample 调用 _invert_criteria。
"""
templates = [
"把微信读书的昵称改成{new_name}",
"Change my WeChat Read nickname to {new_name}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["edit"]
parameters = {
"new_name": {
"type": "enum",
"values": WECHAT_READING_PROFILE_NAME_VALUES,
"default": "阿青",
},
}
criteria = {"user.name": "{new_name}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class SetDarkMode(CriteriaTask):
"""验证深色模式是否为浅色 / 深色 / 跟随系统之一。
判定:criteria settings.darkMode == 参数内部值(模板展示为「深色模式」等,
由 enum 映射到 store 值);_post_sample_invert_criteria。
"""
templates = ["把微信读书深色模式改成{dark_mode}"]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
max_steps = 30
capabilities = ["settings"]
parameters = {
"dark_mode": {
"type": "enum",
"values": {
"深色模式": "深色",
"浅色模式": "浅色",
"跟随系统": "跟随系统",
},
"default": "深色",
},
}
criteria = {"settings.darkMode": "{dark_mode}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class FindAudiobookPlays(AnswerTask):
"""验证 Agent 能否读出某有声书的播放量文案。
判定:answer=.audiobooks[title={book_title}].plays(如「25万」)。
"""
templates = ["微信读书里《{book_title}》有声版播放量多少"]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "extract"]
parameters = {
"book_title": {
"type": "string",
"source": "apps.wechat_reading.audiobooks[title]",
"default": "红楼梦",
"description": "有声书标题",
},
}
answer = ".audiobooks[title={book_title}].plays"
answer_fields = [{"type": "text", "label": "播放量", "hint": "如:8万"}]
expected_changes = ["recommendedAudiobooks"]
# =============================================================================
# L3 — 多步推理与跨功能
# =============================================================================
class AnalyzeReadingHabit(BaseTask):
"""验证能否指出最近一周(7 天窗口)阅读时长最长的那一天。
判定:best_reading_dates_in_last_week 返回所有并列最高日期;
回答须命中其中任一日期的 date_labels(多日并列时答任一天均可)。
注意:与 AnswerTask 不同,需自定义日期回答匹配(§4.6)。
并列时 ground truth 为多个日期,无法用单一 answer 路径表达。
"""
templates = [
"最近一周在微信读书上哪天读的时间最长",
"看看微信读书这一周的阅读记录里哪天读得最久",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
answer_fields = [{"type": "choice", "label": "阅读时间最长的一天",
"options": ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]}]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps_init["wechat_reading"])
best_dates = wr.best_reading_dates_in_last_week(input.os_init)
answer_text = re.sub(r"\s+", "", str(input.answer or ""))
all_labels: list[str] = []
for d in best_dates:
all_labels.extend(WechatReading.date_labels(d, input.os_init))
return [{
"field": "answer",
"expected": all_labels,
"actual": input.answer,
"passed": any(label in answer_text for label in all_labels),
}]
class CheckCalendarMonthReading(AnswerTask):
"""验证指定自然月内有阅读记录的天数。
判定:get_answer 为 month_reading_day_count(year, month);按 readingRecords
去重日期;返回 int。
"""
templates = [
"微信读书{year}{month}月总共读了多少天",
"查下{year}{month}月我在微信读书上有几天阅读记录",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
parameters = {
"year": {
"type": "integer",
"default": 2026,
"description": "年份",
},
"month": {
"type": "integer",
"default": 1,
"description": "月份",
},
"_record_month": {
"sampler": WechatReading.sample_year_month_with_records,
"fields": {"year": "year", "month": "month"},
},
}
answer_fields = [{"type": "number", "label": "阅读天数"}]
def get_answer(self, input: JudgeInput) -> Any:
wr = WechatReading(input.apps_init["wechat_reading"])
return wr.month_reading_day_count(self.p.year, self.p.month)
class CompareBookLengths(BaseTask):
"""对比两书字数,将字数更多的一本上架,并在回答中体现该书书名。
判定:① check_on_shelf(字数多者);② build_answer_checks(书名)。
totalWords 来自 store;采样保证两书字数不等。
注意:objective=hybrid;状态与回答均需通过。
"""
templates = [
"对比微信读书里《{book1}》和《{book2}》的字数,告诉我字数多的那本,然后加到书架",
"告诉我《{book1}》和《{book2}》在微信读书里哪本更厚,帮我把厚的那本收到书架",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "hybrid"
composition = "deep_dive"
difficulty = "L2"
max_steps = 45
capabilities = ["extract", "reasoning", "create"]
parameters = {
"book1": {
"type": "string",
"default": "三体",
"description": "书籍1",
},
"book2": {
"type": "string",
"default": "活着",
"description": "书籍2",
},
"_book_pair": {
"sampler": WechatReading.sample_two_books_unequal_word_counts,
"fields": {"book1": "book1", "book2": "book2"},
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
answer_fields = [{"type": "choice", "label": "字数更多的书名", "options": ["{book1}", "{book2}"]}]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
b1 = wr.require_book_by_title(self.p.book1)
b2 = wr.require_book_by_title(self.p.book2)
w1, w2 = int(b1["totalWords"]), int(b2["totalWords"])
thicker = self.p.book1 if w1 > w2 else self.p.book2
checks = [wr.check_on_shelf(thicker, field="shelf_thicker_book")]
checks.extend(build_answer_checks(thicker, input.answer))
return checks
class FindHighestRatedBookInCategory(AnswerTask):
"""验证某分类下评分最高的书名(同分并列时多答案均可)。
判定:get_answer 用 highest_rated_books_in_category;唯一最高返回 str
并列返回 re.Pattern 匹配任一书名(§4.3)。
"""
templates = [
"微信读书{category}分类里评分最高的书是哪本",
"看看微信读书{category}类的书哪本评分最高",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "query"
composition = "deep_dive"
difficulty = "L3"
capabilities = ["extract", "reasoning"]
parameters = {
"category": {
"type": "string",
"default": "文学",
"sampler": WechatReading.sample_category_with_multiple_books,
"description": "书籍分类",
},
}
answer_fields = [{"type": "text", "label": "评分最高的书名", "hint": "如:围城"}]
def get_answer(self, input: JudgeInput) -> Any:
wr = WechatReading(input.apps_init["wechat_reading"])
best_books = wr.highest_rated_books_in_category(self.p.category)
if len(best_books) == 1:
return str(best_books[0]["title"])
return re.compile("|".join(re.escape(str(book["title"])) for book in best_books))
def get_expected_response(self, input):
wr = WechatReading(input.apps_init["wechat_reading"])
best_books = wr.highest_rated_books_in_category(self.p.category)
# In grounded mode, return the first tied book's title
return [str(best_books[0]["title"])]
class ConfigureReaderSettings(CriteriaTask):
"""同时设置阅读器字号与系统级翻页方式。
判定:criteria 同时检查 readerPrefs.fontSize 与 settings.pageTurnStyle
_post_sample_invert_criteria。
"""
templates = [
"把微信读书的阅读器字体大小调成{font_size},翻页方式改成{style}",
"在微信读书里把阅读器字体大小改成{font_size},顺便把翻页方式换成{style}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["settings"]
parameters = {
"font_size": {
"type": "enum",
"values": WECHAT_READING_FONT_SIZE_VALUES,
"default": 22,
},
"style": {
"type": "enum",
"values": WECHAT_READING_PAGE_TURN_STYLE_VALUES,
"default": "仿真翻页",
},
}
criteria = {
"readerPrefs.fontSize": "{font_size}",
"settings.pageTurnStyle": "{style}",
}
async def _post_sample(self, env):
await self._invert_criteria(env)
class UnfollowUser(BaseTask):
"""验证是否取消关注指定用户(初始为已关注)。
判定:check_following(user_id, expected=False)。
注意:user_id / user_name 由 sample_following_user 协同采样。
"""
templates = [
"在微信读书取消关注{user_name}",
"帮我取关微信读书上的{user_name}",
"Unfollow {user_name} on WeChat Read",
"Stop following {user_name} on WeChat Read",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L1"
capabilities = ["edit"]
parameters = {
"user_id": {
"type": "string",
"default": "user_508",
},
"user_name": {
"type": "string",
"default": "508",
},
"_following_user": {
"sampler": WechatReading.sample_following_user,
"fields": {"user_id": "user_id", "user_name": "user_name"},
},
}
expected_changes = ["user.following"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_following(self.p.user_id, expected=False)]
class SetProfileVisibility(CriteriaTask):
"""验证主页可见范围是否为目标枚举(仅自己 / 互关 / 所有人)。
判定:criteria settings.privacy.profile.visibility_post_sample_invert_criteria。
"""
templates = [
"把微信读书主页可见范围改成{visibility}",
"微信读书的主页可见性帮我调成{visibility}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L2"
capabilities = ["settings"]
parameters = {
"visibility": {
"type": "enum",
"values": WECHAT_READING_PROFILE_VISIBILITY_VALUES,
"default": "仅自己可见",
},
}
criteria = {"settings.privacy.profile.visibility": "{visibility}"}
async def _post_sample(self, env):
await self._invert_criteria(env)
class ReadBookProgress(BaseTask):
"""验证是否将指定书阅读进度推进到不低于某百分比。
判定:check_progress(book_title, min_pct);进度由 bookProgress.charOffset
与 store.totalWords 推导。
"""
templates = [
"把微信读书里《{book_title}》翻到{percentage}%的位置",
"打开微信读书里的《{book_title}》读到{percentage}%左右",
"Turn to the {percentage}% position of 《{book_title}》 in WeChat Read",
"Read 《{book_title}》 on WeChat Read to about {percentage}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["edit"]
parameters = {
"book_title": {
"type": "string",
"default": "红楼梦",
"description": "书籍标题(必须在书架中)",
},
"percentage": {
"type": "integer",
"default": 20,
"description": "阅读进度百分比",
},
"_progress_target": {
"sampler": WechatReading.sample_progress_target,
"fields": {"book_title": "book_title", "percentage": "percentage"},
},
}
expected_changes = ["bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [wr.check_progress(self.p.book_title, self.p.percentage)]
# =============================================================================
# L4 — 复杂组合与条件分支
# =============================================================================
class OrganizeShelfByRecommendation(BaseTask):
"""按推荐值阈值清理书架:删除推荐值不高于阈值的书,保留更高推荐的书。
判定:一项自定义 check — 当前书架 bookId 集合应等于初始架中
recommendedValue > threshold 的子集(与 check_goals 实现一致)。
注意:依赖 init 与 curr 快照对比,非单一 criteria。
"""
templates = [
"整理微信读书书架,把推荐值不高于{recommendation}%的书都删掉",
"微信读书书架里推荐值不超过{recommendation}%的书帮我清理掉",
"Clean up my WeChat Read bookshelf by removing all books with a recommendation score of {recommendation}% or below",
"Organize my WeChat Read bookshelf and delete books with a recommendation value no higher than {recommendation}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "deep_dive"
difficulty = "L4"
capabilities = ["delete", "reasoning"]
parameters = {
"recommendation": {
"type": "float",
"default": 95.0,
"description": "推荐值阈值(百分比)",
},
}
expected_changes = ["shelf"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
init_wr = WechatReading(input.apps_init["wechat_reading"])
curr_wr = WechatReading(input.apps["wechat_reading"])
expected_ids = sorted(
str(item["bookId"])
for item in init_wr.shelf
if float(init_wr.require_store_book(str(item["bookId"]))["recommendedValue"]) > self.p.recommendation
)
actual_ids = sorted(str(item["bookId"]) for item in curr_wr.shelf)
return [{
"field": "shelf",
"expected": expected_ids,
"actual": actual_ids,
"passed": actual_ids == expected_ids,
}]
class AddBookAndReadTo(BaseTask):
"""加书到架并将该书读到不低于目标进度。
判定:① check_on_shelf;② check_progress;两项均须通过。
注意:objective=hybrid;初始书不在架由采样保证。
"""
templates = [
"帮我在微信读书找到《{book_title}》加到书架,调整读书进度到{percentage}%",
"Find 《{book_title}》 on WeChat Read, add it to my bookshelf, and set the reading progress to {percentage}%",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "hybrid"
composition = "sequential"
difficulty = "L2"
capabilities = ["search", "create", "edit"]
parameters = {
"book_title": {
"type": "string",
"default": "三体",
"description": "书籍标题(初始不在书架)",
},
"percentage": {
"type": "integer",
"default": 20,
"description": "阅读进度百分比",
},
"_add_read": {
"sampler": WechatReading.sample_add_book_and_read,
"fields": {"book_title": "book_title", "percentage": "percentage"},
},
}
expected_changes = ["shelf", "bookProgress", "readingBookIds", "allProgressBookIds"]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
wr = WechatReading(input.apps["wechat_reading"])
return [
wr.check_on_shelf(self.p.book_title, field="shelf_after_add"),
wr.check_progress(self.p.book_title, float(self.p.percentage), field="read_progress"),
]
class FindLowestProgressAndRead(BaseTask):
"""将书架中进度最低的书读到目标进度。
判定:先在 **初始 state** 上用 WechatReading.shelf_book_title_with_lowest_progress()
锁定「起始进度最低」的书名,然后在最终 state 上只检查这本书的进度
>= percentage。并列最低时与 App 内 shelf 遍历顺序一致。
注意:percentage 由 sample_percentage_for_lowest_progress_read 单独采样,
保证相对采样时刻的最低进度书存在可达目标;判定时书名与初始 state 绑定,
避免用户先把最低那本读高后判定目标“跳到下一本”。objective=operate。
"""
templates = [
"微信读书书架里哪本书我读的进度最低,帮我翻到{percentage}%的位置",
"看看微信读书书架上我进度最落后的是哪本,翻到{percentage}%的位置",
"Which book on my WeChat Read bookshelf has the lowest reading progress? Jump to the {percentage}% position for me",
"Find the book with the lowest progress on my WeChat Read bookshelf and jump to the {percentage}% position",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "deep_dive"
difficulty = "L3"
max_steps = 60
capabilities = ["reasoning", "edit"]
parameters = {
"percentage": {
"type": "integer",
"default": 50,
"description": "当前最低进度书需达到的目标百分比",
},
"_lowest_pct": {
"sampler": WechatReading.sample_percentage_for_lowest_progress_read,
"fields": {"percentage": "percentage"},
},
}
expected_changes = [
"bookProgress",
"readingBookIds",
"allProgressBookIds",
"finishedBookIds",
"homeFinishedBookIds",
]
def check_goals(self, input: JudgeInput) -> list[dict[str, Any]]:
init_wr = WechatReading(input.apps_init["wechat_reading"])
curr_wr = WechatReading(input.apps["wechat_reading"])
title = init_wr.shelf_book_title_with_lowest_progress()
return [curr_wr.check_progress(title, float(self.p.percentage))]
class PrivacyAndThemeBundle(CriteriaTask):
"""同时设置阅读器颜色(themeColor)、开启一项隐私开关、设置翻页方式。
判定:criteria 三条 — readerPrefs.themeColor、settings.privacy.{setting_key}=True、
settings.pageTurnStyle;隐私项由 sample_privacy_setting_bundle 采样。
_post_sample_invert_criteria。
注意:与 App 阅读器「主题」面板首行「颜色」一致(readerPrefs.themeColor
white/yellow/green/dark),不是第二行「背景」(themeBg)。展示名白色/米黄/
绿色/深色的内部值见 WECHAT_READING_THEME_COLOR_PARAM。
"""
templates = [
"把微信读书的阅读颜色换成{theme_color},开启\"{privacy_label}\",再把翻页方式改成{style}",
"在微信读书里把阅读颜色调成{theme_color}、开启\"{privacy_label}\"、翻页方式设成{style}",
]
apps = ["wechat_reading"]
scope = "S1"
objective = "operate"
composition = "sequential"
difficulty = "L3"
capabilities = ["settings"]
parameters = {
"theme_color": {
"type": "enum",
"values": WECHAT_READING_THEME_COLOR_PARAM,
"default": "yellow",
},
"privacy_label": {
"type": "string",
"default": "关注你须获得你的同意",
},
"setting_key": {
"type": "string",
"default": "requireFollowRequest",
},
"style": {
"type": "enum",
"values": WECHAT_READING_PAGE_TURN_STYLE_VALUES,
"default": "仿真翻页",
},
"_privacy_setting": {
"sampler": WechatReading.sample_privacy_setting_bundle,
"fields": {"privacy_label": "privacy_label", "setting_key": "setting_key"},
},
}
criteria = {
"readerPrefs.themeColor": "{theme_color}",
"settings.privacy.{setting_key}": True,
"settings.pageTurnStyle": "{style}",
}
async def _post_sample(self, env):
await self._invert_criteria(env)