845 lines
31 KiB
Python
845 lines
31 KiB
Python
"""
|
||
微信读书(wechat_reading)bench 任务定义。
|
||
|
||
各 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.coinBalance;ground 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。
|
||
|
||
注意:operate;expected_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)
|