Skip to content

[Improvement] Rex 系统提示中硬编码的 IM Send Protocol 应当移除/降级为按需载入的 skill #378

@fourele

Description

@fourele

Related Component

Backend (Python/FastAPI)

Category

Enhancement

Problem / Motivation

[Improvement] Rex 系统提示中硬编码的 IM Send Protocol 应当移除/降级为按需载入的 skill

摘要

flocks/agent/agents/rex/prompt_builder.py 中的 _build_im_send_section()(约 90 行 prompt 文本)被无条件地拼接进 Rex 的系统提示。这段内容存在两个问题:

  1. 对绝大多数会话来说没有用——大部分用户/任务并不涉及"发消息到 IM",这段 prompt 是纯粹的负担
  2. Token 浪费明显——粗算 ~1.4k token / 每次 Rex 调用,按 Claude Opus 输入 $15/M、1000 次会话/天计,约 $21/天浪费在告诉模型如何向飞书发消息

更深层的问题是:这段 prompt 把 channel 的运行时知识(channel id、title 前缀、channel_type 映射)硬编码进了 agent 的系统提示,自定义 channel 完全没有任何接入点。这与Issue 377 反映的是同一种设计倾向:框架开发者把"内置的少数 channel"当作了"channel 的全部"


现状

涉及代码

  • flocks/agent/agents/rex/prompt_builder.py:208 —— 占位符 __IM_SEND_SECTION__ 的替换点
  • flocks/agent/agents/rex/prompt_builder.py:431 —— _build_im_send_section() 函数定义

Prompt 节选(节选展示问题,完整内容见源码)

### IM Send Protocol (MANDATORY when user asks to send a message to WeCom/Feishu/DingTalk)

**Trigger**: Any request that involves sending a message to an IM platform
(企业微信/WeCom、飞书/Feishu、钉钉/DingTalk).

**Execute this exact sequence — no deviations:**

#### Step 1 — Identify how the user is talking to you
... ...

#### Step 4 — Map title prefix to channel_type

| Title prefix | channel_type |
|--------------|--------------|
| `[Wecom]`    | `wecom`      |
| `[Feishu]`   | `feishu`     |
| `[Dingtalk]` | `dingtalk`   |

#### Step 5 — Send
... ...

### IM Session Resolution for schedule_task_create (MANDATORY)
... ...

整段是一个6 步业务 SOP(Identify → Discover → Ask → Map → Send → Report),并且后面还跟着一个针对 schedule_task_create 的相似版本。


问题逐条分析

1. 实用价值低 —— 不发 IM 的会话也要带

无论:

  • 用户当前是否启用了任何 IM channel
  • 当前会话是否来自 IM
  • 用户的请求是否与"发消息"有关
  • 当前是否在做安全分析、查日志、写检测规则等完全不相关的任务

这段 ~1.4k token 都会被注入。Rex 是项目的核心 orchestrator,几乎所有会话都经过它,这段提示的"命中率"极低——保守估计 < 5%。

2. Token 浪费成本

项目 数值
IM Send Section 体积 ~1.4k token(中英混排,含表格、代码块、两套独立协议)
Claude Opus 输入价格 $15 / 1M token
单次 Rex 调用浪费 ≈ $0.021
团队规模 1000 会话/天 ≈ $21/天
一年 ≈ $7,665

更隐性的成本:上下文窗口被占用、模型注意力被分散、跟"如何发 IM"无关的任务里模型也要扫一遍这段(虽然不会用,但仍要消耗 attention)。

3. 硬编码 channel 类型 —— 把"发现机制"伪装成"知识"

| Title prefix | channel_type |
| [Wecom]    | wecom       |
| [Feishu]   | feishu      |
| [Dingtalk] | dingtalk    |

这种映射本来应该由 ChannelRegistry 自己暴露——agent 应当询问框架,而不是依赖框架开发者手写进 prompt

后果:

  • 加新 channel 必须改 prompt_builder.py 这个与 agent 业务逻辑相关的文件
  • prompt 与 channel 实现强耦合,违反开闭原则
  • 第三方/自定义 channel 永远无法被 Rex 自然识别——即使一个用户级 channel 注册成功,Rex 也不知道 [Foo] 前缀对应什么 channel_type
  • 提示里写 "only WeCom/Feishu/DingTalk" 等于在用户面前否认了自定义 channel 的存在

4. 把业务流程"冻结"在系统提示里

整段是 6 步业务 SOP,不是 prompt 应当承载的内容。它本质上应该是:

  • 一个工具:im_send_message(message, target=None) 内部封装"发现 session → 询问用户 → 发送"
  • 或者一个 skill:im-send,按需 skill_load

把流程逻辑塞进系统提示的具体危害:

  • 每次会话都要让 LLM "重新读懂"流程,LLM 推理执行 SOP 不如代码可靠——会跳步、会幻觉
  • 流程更新需要发新版 flocks(动 prompt template),而不是发布一个新工具/skill
  • 无法做单元测试
  • 无法被用户/agent 关闭或替换

5. Prompt 自身也有几个具体问题

  • Step 2 让模型 "filter sessions whose title starts with [Wecom] / [Feishu] / [Dingtalk]" —— 用 session 标题字符串前缀 作为 channel 类型判断,这种类型信息应当是 session 元数据的一等字段,不应靠 LLM 解析字符串
  • "我不知道" 选项 被硬编码成中文,prompt 其它部分对多语言用户使用英文,混用导致非中文用户看到机器人界面会突兀
  • Step 6 "Report" 完全是空话——LLM 本来就会在结尾汇报结果,不需要单独写一步

Issue 377 的关联

这两个 issue 反映的是同一种设计倾向

子系统 表现
Channel 加载(Issue 377 内置 channel 写死在 _register_builtin_channels,自定义 channel 走 plugin loader 出 bug
Channel prompt(本 issue) 内置 IM 协议写死在 prompt_builder,自定义 channel 没有扩展点

根因都是ChannelRegistry 没有作为"channel 真相之源"被尊重——其他子系统(gateway、prompt builder)都各自维护了一份对 channel 的局部认知。

我并不主张推翻 ChannelPlugin 抽象本身(meta / start / send / handle_webhook 是合理的),主张的是项目组没把 channel 抽象用到底——内置 channel 跳过 plugin loader、prompt 里硬编码 channel 类型,这些都是"知道有抽象但绕开它"的反例。


修复方案

方向:从 Rex 系统提示中移除 IM Send Section,降级为按需载入的 skill + 元数据驱动

第 1 步:从 Rex 系统提示中删除 __IM_SEND_SECTION__

flocks/agent/agents/rex/prompt_builder.py

  • 删除占位符 __IM_SEND_SECTION__ 的注入(L208)
  • 保留 _build_im_send_section() 函数实体或移到 skill(见第 2 步)

Rex 系统提示中只保留一句话作为"指针":

When the user wants to send messages to IM platforms,
load the `im-send` skill first and follow its guidance.

预期:~1.4k token → ~30 token,节省约 98%

第 2 步:把 SOP 落到 skill 或 tool 里

选项 2a(推荐):im-send skill

新建 flocks/skills/im-send/SKILL.md,内容是当前的 6 步 SOP。Rex 在用户明确请求"发消息到 IM"时调用 skill_load("im-send") 才注入相关内容。

选项 2b(更进一步):im_send_message tool

新增内置工具 im_send_message(message, target=None, schedule=None),内部封装:

  • 检查 current session 是否来自 IM
  • 列出可用 IM sessions
  • 必要时通过 question 工具向用户提问选哪个
  • 调用 channel_message 发送
  • 返回结果

Rex prompt 里只需要一句话:

When the user wants to send messages to IM platforms,
call `im_send_message` and follow its guidance.

将"业务流程"从 LLM 推理转移到代码执行,准确率反而更高。

第 3 步:Channel 类型由 ChannelRegistry 提供,而非硬编码

ChannelRegistry 提供运行时元数据查询接口:

class ChannelRegistry:
    def list_im_channels(self) -> list[ChannelMeta]:
        """返回所有支持 IM 语义的 channel 元数据。"""
        ...

skill 模板(或 tool 实现)通过这个接口动态生成"title 前缀 → channel_type"映射,而不是写死。

这样自定义 channel 只要正确实现 ChannelMeta,就能自动被 Rex 识别和路由。

第 4 步(独立改进):Session 元数据中 channel_type 改为一等字段

当前从 session.title[Wecom] / [Feishu] / [Dingtalk] 前缀判断 channel 类型,是脆弱的字符串解析。

建议在 session 创建时就把 channel_typechannel_id 作为一等字段持久化,所有上游(prompt / tool / route)直接读字段,不再解析 title。

第 4 步可以独立推进,与本 issue 主体不强绑定。


优先级建议

改进 收益 风险 推荐顺序
第 1 步:从系统提示中移除 IM Send Section 高(直接省 token + 解耦) 先做
第 2 步:SOP 落地为 skill 或 tool 中高 紧随第 1 步
第 3 步:ChannelRegistry 暴露 IM 元数据 第 2 步选 2b 时同步做;选 2a 时可延后
第 4 步:session.channel_type 一等字段 中(可能动到 DB schema) 独立排期

期望

希望项目组能:

  1. 确认对"系统提示中硬编码 channel 业务流程"这一设计取向的看法
    2.如果决定保留 IM Send Section,至少把它做成条件注入——比如检测当前会话来自 IM 时才注入,非 IM 会话不注入

附:与其他 prompt 段落的对比

prompt_builder.py 中其他段落(_build_anti_patterns_section_build_skills_section_build_workflows_section)都是根据当前环境动态生成的:

  • skills_section 只列出已安装的 skill
  • workflows_section 只列出已注册的 workflow

唯独 _build_im_send_section完全静态、与环境无关的硬编码字符串。这种不一致本身就值得修复。

Proposed Solution

希望项目组能:

  1. 确认对"系统提示中硬编码 channel 业务流程"这一设计取向的看法
  2. 如果决定保留 IM Send Section,至少把它做成条件注入——比如检测当前会话来自 IM 时才注入,非 IM 会话不注入

Alternatives Considered

No response

Suggested Priority

Medium - Worth improving

Willingness to Contribute

  • I am willing to submit a PR to implement this feature

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions