inline 标签 renderer 注册表落地:@all 迁移 + 新增 @user/@face(ADR-0003 遗留缺口) #28

Closed
opened 2026-07-09 06:34:45 +00:00 by KumaAgent · 1 comment
KumaAgent commented 2026-07-09 06:34:45 +00:00 (Migrated from codeberg.org)

Parent

#2

Background

ADR-0003 的更新节描述了一套"括号协议收缩为仅管 inline 元素(如 @,在 send_message 的 text 参数内由 core 解析)"的机制——core 拥有 inline 标签的解析,host 注册具体标签的 renderer/风味。

实现 #23(富媒体发送能力)时发现:这套机制从未被实际实现过——代码库里没有任何 inline 标签解析器、host 风味注册接口,或标签 renderer 抽象。ADR-0003 描述的是设计意图,不是已落地的代码。#23 只需要一个具体标签(@all),已经用最小实现方式解决(send_messagecontent 里直接检测字面字符串 "@all"capabilities.at_all 门控是否转成真实 @全体效果,不建通用框架)。

本次会内 grill(2026-07-09)排查发现:#22(poke/reaction/profile_like)、#23 的 sticker/voice/quote 全部选择了独立工具调用或 send_message 结构化参数,无一使用 inline 标签——"host 注册标签 renderer"此前只是未落地的意图。用户明确提出还需要 @user:<id>(指定用户)与 @face:<id>(平台特殊表情/内联表情)两个标签,遂借此把机制补齐,@all 一并迁移。完整决策记录见 ADR-0003 更新节

范围锁定为 output 侧send_messagecontent 文本 inline 标签解析)。ingress 侧 ConversationInput.render()<message>/<poke>/<reaction> XML 序列化不受影响,未来若要统一格式另开新 issue,本 issue 不覆盖。

What to build

  1. 语法@ 前缀标签族。@all 裸写(无参数);带参数标签用冒号,如 @user:<sender_id>@face:<face_id>

  2. ArisePorts 新增 tag_renderers: dict[str, TagRenderer] 字段,host 在 configure() 时随其它 port 一并注册。TagRenderer 是一个小结构:同步 render(param: str | None) -> str | None@all 场景 param=None)+ 一句供拼进 send_message schema 的用法说明 description: strrender(param) 返回替换文本,原地拼回 content 里标签原来的位置;返回 None 表示这次渲染失败/参数不合法,core 把该标签整体从 content 剔除(不留占位文字,区别于 sticker/voice 的整条内容降级)。未在 tag_renderers 里注册的标签名同样整体剔除(容错处理模型幻觉出未声明的标签,不抛异常)。

  3. @all 迁移到新机制EgressPort.send() 签名去掉 at_all: bool 参数——这是一次破坏性协议改动,但目前没有任何 host 实现消费这个 port(dxkuma-bot-ob11 尚未接入 nonebot_plugin_arise),无需兼容层。runtime_loop._resolve_at_all 的硬编码字面量检测逻辑删除,改为统一的标签解析走 tag_renderers 注册表。

  4. 不新增 CapabilitySet 布尔位CapabilitySet.at_all 移除(迁移后无引用)。标签是否支持完全由 tag_renderers 是否注册决定,单一信源,避免布尔位与注册表状态不同步。

  5. send_message 工具 schema 动态生成SEND_MESSAGE_SCHEMAcontent 字段描述里关于 inline 标签的部分(现在硬编码"文本内容里可以包含「@all」标记整个群")改为按当前 tag_renderers 动态拼接每个已注册标签的 descriptiontag_renderers 为空时不包含任何 inline 标签用法提示,未注册的标签不会出现在模型可见的描述里(沿用 #22 定的"能力为假 = 模型看不到"哲学)。

Acceptance criteria

  • TagRenderer(或等价结构)定义:同步 render(param: str | None) -> str | None + description: str
  • ArisePorts 新增 tag_renderers: dict[str, TagRenderer] 字段,configure()/get_ports() 正常透传,默认可以是空字典
  • content@tagname / @tagname:param 被正确识别并替换为 tag_renderers[tagname].render(param) 的返回值,原地替换(保留标签前后文本,不改变其它内容)
  • 同一条 content 中出现多个标签(含重复同名标签)全部被正确处理
  • render() 返回 None 时,对应标签从 content 中整体剔除,不留任何占位字符
  • content 中出现未在 tag_renderers 注册的标签名时,同样整体剔除,不抛异常、不中断本次 send_message 处理
  • EgressPort.send() 签名移除 at_all 参数;CapabilitySet.at_all 字段移除;代码库中无任何残留引用
  • runtime_loop.py_resolve_at_all 被通用的标签解析逻辑取代(旧的 @all 字面量检测代码删除)
  • build_tool_schemas(或 SEND_MESSAGE_SCHEMA 构造点)按当前 tag_renderers 动态生成 content 字段里 inline 标签的用法说明;未注册任何标签时不包含相关提示文字
  • FakeEgress/测试 fixture 更新为不再需要 at_all 参数;新增测试覆盖 @all(作为普通注册标签之一)、@user:<id>@face:<id> 三种标签在"已注册"与"未注册"两种场景下的行为
  • 现有 test_runtime_loop.py(及其它引用 at_all/_resolve_at_all 的测试)全部迁移到新机制后通过,无回归
  • ruff/basedpyright 通过

Out of scope(本次会内 grill 明确排除)

  • ingress 侧 ConversationInput.render() 的 XML→自然语言格式统一(另开新 issue)
  • EgressPort 具名方法形式的替代设计(仿 poke_send 逐个加方法,已否决,见 ADR-0003 更新节)
  • 保留 at_all 作为永久特例、与新注册表并行两套机制(已否决,选择统一迁移)

Blocked by

None——不阻塞、不被阻塞。

## Parent #2 ## Background [ADR-0003](https://codeberg.org/ProjectKuma/arise/src/branch/docs/docs/adr/0003-reply-protocol-ownership.md) 的更新节描述了一套"括号协议收缩为仅管 inline 元素(如 `@`,在 `send_message` 的 text 参数内由 core 解析)"的机制——core 拥有 inline 标签的解析,host 注册具体标签的 renderer/风味。 实现 #23(富媒体发送能力)时发现:这套机制**从未被实际实现过**——代码库里没有任何 inline 标签解析器、host 风味注册接口,或标签 renderer 抽象。ADR-0003 描述的是设计意图,不是已落地的代码。#23 只需要一个具体标签(`@all`),已经用最小实现方式解决(`send_message` 的 `content` 里直接检测字面字符串 `"@all"`,`capabilities.at_all` 门控是否转成真实 @全体效果,不建通用框架)。 本次会内 grill(2026-07-09)排查发现:#22(poke/reaction/profile_like)、#23 的 sticker/voice/quote 全部选择了独立工具调用或 `send_message` 结构化参数,无一使用 inline 标签——"host 注册标签 renderer"此前只是未落地的意图。用户明确提出还需要 `@user:<id>`(指定用户)与 `@face:<id>`(平台特殊表情/内联表情)两个标签,遂借此把机制补齐,`@all` 一并迁移。完整决策记录见 [ADR-0003 更新节](https://codeberg.org/ProjectKuma/arise/src/branch/docs/docs/adr/0003-reply-protocol-ownership.md#更新2026-07-09见-issue-28标签-renderer-注册表落地)。 **范围锁定为 output 侧**(`send_message` 的 `content` 文本 inline 标签解析)。ingress 侧 `ConversationInput.render()` 的 `<message>`/`<poke>`/`<reaction>` XML 序列化不受影响,未来若要统一格式另开新 issue,本 issue 不覆盖。 ## What to build 1. **语法**:`@` 前缀标签族。`@all` 裸写(无参数);带参数标签用冒号,如 `@user:<sender_id>`、`@face:<face_id>`。 2. **`ArisePorts` 新增 `tag_renderers: dict[str, TagRenderer]` 字段**,host 在 `configure()` 时随其它 port 一并注册。`TagRenderer` 是一个小结构:同步 `render(param: str | None) -> str | None`(`@all` 场景 `param=None`)+ 一句供拼进 `send_message` schema 的用法说明 `description: str`。`render(param)` 返回替换文本,原地拼回 `content` 里标签原来的位置;返回 `None` 表示这次渲染失败/参数不合法,core 把该标签**整体从 content 剔除**(不留占位文字,区别于 sticker/voice 的整条内容降级)。未在 `tag_renderers` 里注册的标签名同样整体剔除(容错处理模型幻觉出未声明的标签,不抛异常)。 3. **`@all` 迁移到新机制**:`EgressPort.send()` 签名去掉 `at_all: bool` 参数——这是一次破坏性协议改动,但目前没有任何 host 实现消费这个 port(`dxkuma-bot-ob11` 尚未接入 `nonebot_plugin_arise`),无需兼容层。`runtime_loop._resolve_at_all` 的硬编码字面量检测逻辑删除,改为统一的标签解析走 `tag_renderers` 注册表。 4. **不新增 `CapabilitySet` 布尔位**:`CapabilitySet.at_all` 移除(迁移后无引用)。标签是否支持完全由 `tag_renderers` 是否注册决定,单一信源,避免布尔位与注册表状态不同步。 5. **`send_message` 工具 schema 动态生成**:`SEND_MESSAGE_SCHEMA` 的 `content` 字段描述里关于 inline 标签的部分(现在硬编码"文本内容里可以包含「@all」标记整个群")改为按当前 `tag_renderers` 动态拼接每个已注册标签的 `description`;`tag_renderers` 为空时不包含任何 inline 标签用法提示,未注册的标签不会出现在模型可见的描述里(沿用 #22 定的"能力为假 = 模型看不到"哲学)。 ## Acceptance criteria - [ ] `TagRenderer`(或等价结构)定义:同步 `render(param: str | None) -> str | None` + `description: str` - [ ] `ArisePorts` 新增 `tag_renderers: dict[str, TagRenderer]` 字段,`configure()`/`get_ports()` 正常透传,默认可以是空字典 - [ ] `content` 中 `@tagname` / `@tagname:param` 被正确识别并替换为 `tag_renderers[tagname].render(param)` 的返回值,原地替换(保留标签前后文本,不改变其它内容) - [ ] 同一条 `content` 中出现多个标签(含重复同名标签)全部被正确处理 - [ ] `render()` 返回 `None` 时,对应标签从 `content` 中整体剔除,不留任何占位字符 - [ ] `content` 中出现未在 `tag_renderers` 注册的标签名时,同样整体剔除,不抛异常、不中断本次 `send_message` 处理 - [ ] `EgressPort.send()` 签名移除 `at_all` 参数;`CapabilitySet.at_all` 字段移除;代码库中无任何残留引用 - [ ] `runtime_loop.py` 中 `_resolve_at_all` 被通用的标签解析逻辑取代(旧的 `@all` 字面量检测代码删除) - [ ] `build_tool_schemas`(或 `SEND_MESSAGE_SCHEMA` 构造点)按当前 `tag_renderers` 动态生成 `content` 字段里 inline 标签的用法说明;未注册任何标签时不包含相关提示文字 - [ ] `FakeEgress`/测试 fixture 更新为不再需要 `at_all` 参数;新增测试覆盖 `@all`(作为普通注册标签之一)、`@user:<id>`、`@face:<id>` 三种标签在"已注册"与"未注册"两种场景下的行为 - [ ] 现有 `test_runtime_loop.py`(及其它引用 `at_all`/`_resolve_at_all` 的测试)全部迁移到新机制后通过,无回归 - [ ] `ruff`/`basedpyright` 通过 ## Out of scope(本次会内 grill 明确排除) - ingress 侧 `ConversationInput.render()` 的 XML→自然语言格式统一(另开新 issue) - `EgressPort` 具名方法形式的替代设计(仿 `poke_send` 逐个加方法,已否决,见 ADR-0003 更新节) - 保留 `at_all` 作为永久特例、与新注册表并行两套机制(已否决,选择统一迁移) ## Blocked by None——不阻塞、不被阻塞。
Yushu commented 2026-07-09 08:50:49 +00:00 (Migrated from codeberg.org)

#2 遗留

#2 遗留
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ProjectKuma/arise#28
No description provided.