用 AI 玩角色扮演的人,大概都见过这种文字:

文笔的问题,交给另一篇里说的那几个 Skill。这篇说的是另一件更日常的烦心事:标签。
标签糊了一屏
现在的角色扮演预设,常常要求模型在正文之外再输出几段结构化内容:先在 <build> 里写一段剧情构思,正文写完后在 <choices> 里列出几个可选的下一步,有的还要用 <updatevariable> 更新好感度之类的变量。
这对模型是好事,先想清楚再写,剧情不容易崩。可在 Operit 这类手机端 Agent 里,这些标签会原样显示出来:一屏里一半是正文,一半是尖括号。想选下一步,还得自己把选项抄进输入框。
rp-xml-renderer(插件里叫"RP 标签渲染器")就是为这个写的,一个 Operit 插件,现在是 2.3.4 版。
渲染成面板和卡片
每个标签可以单独选一种显示方式:
- 折叠面板:适合
<build>这种"后台过程",默认收起,标题上显示一共几行,想看再点开。 - 选项卡片:适合
<choices>,每个选项一张卡片,点一下就填进回复框,不用再手抄。 - 卡片、Markdown:普通内容换个排版显示。
- 隐藏:
<updatevariable>这种只给程序看的,直接藏起来。


图标和颜色也能改,图标填 Material Icons 的名字,颜色从宿主主题里挑,深浅色模式下都跟着变。
每条规则还可以挂几条正则替换,只作用在这个标签里面。比如模型老在选项前面加"姓名:",写一条 姓名[::]([^\n]+) 替换成 $1 就清爽了。原始消息不会被改,"复制原文"复制的仍然是模型的原话。
规则默认不绑定任何角色卡,装好后要先在"编辑规则"里选好角色、保存,不然会以为插件没生效。
每次对话前,注入一段 JS 的结果
写着写着,又加了一个和渲染不太相干的功能:每次给模型发消息之前,先跑一段 JS,把返回的字符串作为提示词一起发过去。
比如掷个骰子:
const roll = 1 + Math.floor(Math.random() * 20);
const dc = 12;
return "本轮可选检定:D20=" + roll + ",难度=" + dc + ",结果=" + (roll >= dc ? "成功" : "失败") + "。仅在当前行动需要检定时采用,不替用户选择行动。";与其让模型自己编一个点数,不如算好了交给它。
内置了四套预设,同一时间只启用一套:
- 随机码:每轮附上几行随机字符,给模型一点随机扰动。
- 时间:按你设的时区告诉模型现在几点,省得它半夜跟你说早上好。
- 每日主题 + 微事件:七个日常主题按日期轮换,每次请求还有 15% 的概率附带一个小插曲,比如"注意到一件先前忽略的小物件"。
- 自定义:自己写。仓库里另外放了日程、写作挑战、每日练习等 10 个示例,复制进编辑框就能用。
脚本跑在一个受限的 QuickJS 环境里:不能联网、不能读文件、不能读聊天记录,每次最多跑 1.5 秒,输出也有上限。保存之前可以先检查语法、试运行,看看它到底会给模型塞什么。默认是关着的,打开总开关、选好角色才会生效。
最麻烦的是"什么时候不该渲染"
功能本身不复杂,花时间最多的是判断哪些标签不该碰。
模型会在思考过程里提到 <choices>,会在代码块里举例写标签,有时标签写到一半就断了;Operit 有时还会在两段思考之间插一条警告,再接着生成。哪一种都不能当成真正的选项去渲染。所以插件只在确认这段内容完整、确实属于这条回复的正文时才渲染,拿不准就原样显示。宁可难看一点,也不能把内容弄丢。
最近两个版本修的都是这类边界:2.3.3 处理思考块和正文紧挨着的情况,2.3.4 处理"思考 → 警告 → 再思考 → 正文"这种重试。现在本地有 312 项测试,最近几个版本也都在手机上实测通过后才发布。
顺带一提,README 是 gpt-6-astra 帮我写的,所以里面会出现"本地测试不等于实际手机视觉验收"这种句子,我在 de-astra 那篇里还拿它当过反面例子。
安装
- 到 GitHub Releases 下载
.toolpkg安装包,在 Operit 里导入。 - 插件详情 → 标签渲染配置 → 编辑规则,选好要生效的角色卡,保存。
- 想用动态 JS 的话,再进"动态 JS 注入",选预设、选角色,试运行没问题再打开开关。
更多玩法写在仓库的使用指南里。用着有问题欢迎提 issue,或者直接给我写邮件。