通道适配器
LibreFang 通过 45 个通道适配器 连接各类消息平台,让用户可以在所有主流通信平台上与 Agent 交互。适配器覆盖消费级即时通讯、企业协作、社交媒体、社区平台、隐私优先协议、通用 Webhook 以及可扩展的 Sidecar 进程。
所有适配器共享统一的基础能力:通过 watch::channel 实现优雅关闭、连接失败时指数退避重试、使用 Zeroizing<String> 保护密钥、超出平台长度限制时自动拆分消息、支持按通道覆盖模型/提示词、DM/群组策略控制、按用户速率限制,以及输出格式化(Markdown、TelegramHTML、SlackMrkdwn、PlainText)。
目录
- 核心消息渠道 -- Telegram、Discord、Slack、WhatsApp、微信、Signal、Matrix、Email、WebChat
- 企业协作 -- Teams、Mattermost、Google Chat、Webex、飞书、Rocket.Chat、Zulip、Flock、Twist、Pumble、Guilded
- 社交与社区 -- Reddit、Mastodon、Bluesky、LinkedIn、Twitch、Discourse、Gitter、Revolt、Keybase、Nostr
- 集成与协议 -- LINE、Viber、Messenger、Threema、钉钉、QQ、企业微信、IRC、XMPP、MQTT、Ntfy、Gotify、Nextcloud、Mumble、Webhook、Voice
- 自定义适配器 -- 编写自定义适配器
通道配置
所有通道凭证支持四种存储方式(优先级从高到低):
- 系统环境变量:
export TELEGRAM_BOT_TOKEN=... - 加密金库(推荐):
librefang vault set TELEGRAM_BOT_TOKEN - .env 文件:
librefang config set-key telegram或直接编辑~/.librefang/.env - secrets.env: 通过仪表板 "Set API Key" 按钮写入
~/.librefang/secrets.env
以下示例展示全部四种方式。生产环境建议使用 vault。
所有通道配置位于 ~/.librefang/config.toml 的 [channels] 段落下。每个通道是一个子段落:
[channels.telegram]
bot_token_env = "TELEGRAM_BOT_TOKEN"
default_agent = "assistant"
allowed_users = ["123456789"]
[channels.discord]
bot_token_env = "DISCORD_BOT_TOKEN"
default_agent = "coder"
[channels.slack]
bot_token_env = "SLACK_BOT_TOKEN"
app_token_env = "SLACK_APP_TOKEN"
default_agent = "ops"
# 企业示例
[channels.teams]
app_id_env = "TEAMS_APP_ID"
app_secret_env = "TEAMS_APP_SECRET"
default_agent = "ops"
# 社交示例
[channels.mastodon]
token_env = "MASTODON_TOKEN"
instance = "https://mastodon.social"
default_agent = "social-media"
# IoT/MQTT 示例(需要 all-channels feature flag)
[channels.mqtt]
host = "broker.example.com"
port = 1883
client_id = "librefang-bot"
subscribe_topics = ["home/agents/input", "home/agents/commands"]
response_topic = "home/agents/output"
qos = "at_least_once" # at_most_once | at_least_once | exactly_once
# username_env = "MQTT_USERNAME" # 可选
# password_env = "MQTT_PASSWORD" # 可选
keep_alive_secs = 30
default_agent = "home-assistant"
通用字段
bot_token_env/token_env-- 保存 Bot/访问令牌的环境变量名。LibreFang 在启动时从该环境变量读取令牌。所有密钥以Zeroizing<String>存储,释放时自动清零。default_agent-- 当没有特定路由规则时,接收消息的 Agent 名称(或 ID)。allowed_users-- 可选的平台用户 ID 白名单。留空表示允许所有用户。overrides-- 可选的按通道行为覆盖(参见下方通道覆盖)。
环境变量参考(核心通道)
| 通道 | 必需的环境变量 |
|---|---|
| Telegram | TELEGRAM_BOT_TOKEN |
| Discord | DISCORD_BOT_TOKEN |
| Slack | SLACK_BOT_TOKEN, SLACK_APP_TOKEN |
WA_ACCESS_TOKEN, WA_PHONE_ID, WA_VERIFY_TOKEN | |
| Matrix | MATRIX_TOKEN |
EMAIL_PASSWORD |
其他通道的环境变量详见上方全部 45 个通道表格。
通道覆盖
每个通道适配器都支持 ChannelOverrides,可在两个层级自定义按通道行为:
- 通道级 在
config.toml—— 对使用该通道的所有 agent 生效。加[channels.<name>.overrides]段。 - Agent 级 在
agent.toml—— 只对该 agent 在所有通道上生效。加顶层[channel_overrides]段。
解析顺序:agent 级 → 通道级 → 内置默认。哪一层设了哪一层赢;没设的字段穿透到下一层(跟 exec_policy 一样的模式)。
# agent.toml —— 每个 agent 自己的覆盖(比如 "这个 agent 永远回 DM")
[channel_overrides]
dm_policy = "always"
group_policy = "trigger_only"
group_trigger_patterns = ["(?i)\\bmy-bot\\b"]
# config.toml —— 该通道上对所有 agent 的默认
[channels.telegram.overrides]
model = "gemini-2.5-flash"
system_prompt = "You are a concise Telegram assistant. Keep replies under 200 words."
dm_policy = "respond"
group_policy = "mention_only"
rate_limit_per_user = 10
threading = true
output_format = "telegram_html"
usage_footer = "compact"
覆盖字段
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
model | Option<String> | Agent 默认值 | 覆盖此通道使用的 LLM 模型。 |
system_prompt | Option<String> | Agent 默认值 | 覆盖此通道的系统提示词。 |
dm_policy | DmPolicy | Respond | 如何处理私信。 |
group_policy | GroupPolicy | MentionOnly | 如何处理群组/频道消息。 |
rate_limit_per_user | u32 | 0(无限制) | 每用户每分钟最大消息数。 |
threading | bool | false | 以线程回复形式发送响应(仅限支持的平台)。 |
output_format | Option<OutputFormat> | Markdown | 此通道的输出格式。 |
usage_footer | Option<UsageFooterMode> | 无 | 是否在响应末尾附加 token 用量信息。 |
格式化器、速率限制器与策略
输出格式化器
formatter 模块(librefang-channels/src/formatter.rs)将 LLM 输出的 Markdown 转换为平台原生格式:
| OutputFormat | 目标格式 | 说明 |
|---|---|---|
Markdown | 标准 Markdown | 默认值;原样传递。 |
TelegramHtml | Telegram HTML 子集 | 将 **bold** 转换为 <b>、`code` 转换为 <code> 等。 |
SlackMrkdwn | Slack mrkdwn | 将 **bold** 转换为 *bold*、链接转换为 <url|text> 等。 |
PlainText | 纯文本 | 去除所有格式。 |
按用户速率限制器
ChannelRateLimiter(librefang-channels/src/rate_limiter.rs)使用 DashMap 跟踪每用户消息计数。当通道覆盖中设置了 rate_limit_per_user 时,限制器以滑动窗口方式限制每分钟最多 N 条消息。超出限制的消息会收到友好的拒绝提示。
DM 策略
控制适配器如何处理私信:
| DmPolicy | 行为 |
|---|---|
Respond | 回复所有私信(默认)。 |
AllowedOnly | 仅回复 allowed_users 中用户的私信。 |
Ignore | 静默丢弃所有私信。 |
群组策略
控制适配器如何处理群聊、频道和房间中的消息:
| GroupPolicy | 行为 |
|---|---|
All | 回复群组中的所有消息。 |
MentionOnly | 仅在 Bot 被 @提及时回复(默认)。 |
CommandsOnly | 仅回复 /command 命令消息。 |
Ignore | 静默忽略所有群组消息。 |
策略在 dispatch_message() 中执行,早于消息到达 Agent 循环。这意味着被忽略的消息不会消耗任何 LLM token。
Agent 路由
AgentRouter 决定哪个 Agent 接收传入的消息。路由逻辑如下:
- 按通道默认值:每个通道配置都有一个
default_agent字段。来自该通道的消息会发送到该 Agent。 - 用户-Agent 绑定:如果用户之前已与特定 Agent 关联(通过命令或配置),来自该用户的消息会路由到该 Agent。
- 命令前缀:用户可以在聊天中发送类似
/agent coder的命令来切换 Agent。后续消息将路由到 "coder" Agent。 - 兜底:如果没有匹配的路由规则,消息会发送到第一个可用的 Agent。
当渠道配置了 default_agent 时,来自该渠道的消息会跳过语义路由和关键词路由,直接发送到指定的 Agent。用户仍可通过 /agent 命令手动切换 Agent。
出站标记
在 ChannelOverrides 里设 prefix_agent_name 给每条出站消息加 agent 名前缀。多个 agent 共享一个频道时有用——读者一眼能看出是哪个 agent 回的。
| 风格 | 包装效果 |
|---|---|
Off(默认) | 不变——跟 agent 文本字节相同 |
Bracket | [agent-name] 回复内容 |
BoldBracket | **[agent-name]** 回复内容(粗体渲染依赖通道的输出格式) |
[channels.telegram.overrides]
prefix_agent_name = "BoldBracket"
包装在每个非流式成功路径上跑一次(auto_reply、kernel-streaming-with-status accumulated、streaming-fallback buffered_text、非流式 fallback、re-resolution 后重试、dispatch_with_blocks)。streaming tee(每个 chunk 到达就转发)不包装——前缀会跟 chunk 边界穿插。
Signal 默认纯文本
Signal adapter 默认 OutputFormat::PlainText。signal-cli 把 Markdown 星号下划线原样渲染,留着会产生可见噪音。要重启 Markdown,在 agent 或频道上覆盖:
[channels.signal.overrides]
output_format = "markdown_v2"
其他通道保留各自的 formatter 默认值——只翻了 Signal。
反应表情与处理状态
几个 adapter 可以贴一个反应到用户消息表示"我在处理",发送回复时移除。各通道独立:
- Slack —
reactions_enabled开关(环境变量SLACK_REACTIONS,默认true)。收消息加 👀,回复时换成 ✅。already_reacted/no_reaction错误静默忽略(fail-open)。 - Feishu — 收消息加
Typingreaction,回复发送时移除。两个调用都 fire-and-forget;API 失败warn!但绝不阻塞消息处理。
要在自定义 adapter 上加同样能力,把 reaction 调用 tokio::spawn 出去,把 (reaction_id, message_id) 存在按 chat id 索引的 map 里。
Feishu @提及保留
Feishu 之前会静默删掉传入文本里的 @_user_N 占位符。Feishu adapter 现在用 mention payload 解析出的 @<display-name> 替换(name 缺失时回退 open_id),并把 @_all 重写为 @all。agent 看到原始对话语气——"@alice 你能 check 下吗?"——而不是裸标点。
Signal 媒体附件
Signal adapter 现在支持发送 Image、Voice、Video、Audio、Animation、File、FileData、MediaGroup content block。媒体 URL 被下载并 base64 编码进 /v2/send 的 base64_attachments。MediaGroup 递归调用 send()。不支持的类型(Poll、Sticker …)warn! 优雅降级——纯文本 fallback 或跳过——而不是失败整个回复。
WhatsApp 语音 + DM/群消息策略
WhatsApp adapter 暴露:
send_voice(to, audio, mime_type)— Cloud API 模式经/{phone_number_id}/media上传,再用返回的media_id发audio消息;Gateway 模式 base64 编码后 POST 到/message/send-voice。- adapter 上的
dm_policy: DmPolicy(默认Respond)和group_policy: GroupPolicy(默认MentionOnly),形状跟 Telegram 一致。Builder 方法:with_dm_policy()、with_group_policy()、with_bot_phone()、with_bot_name()。 WhatsAppAdapter::should_handle_message(is_group, text) -> bool给 bridge / webhook 入口过滤用。
MentionOnly 检查消息文本是否含 bot phone 或 bot name;TriggerOnly 检查 group_trigger_patterns;Always 和 Never 短路。
Webhook deliver_only 模式
webhook 通道可作为 pass-through 跑:传入 payload 完全绕过 LLM(无 sanitizer、无 rate limiter、无 agent 查找),直接转发到目标通道。适合只需要让消息落到 Telegram / Slack 等地方的推送通知和带外告警。
[channels.webhook]
enabled = true
deliver_only = true
deliver = "telegram:123456789" # deliver_only 为 true 时必填
deliver_only = true 但 deliver 未设时,librefang 启动时打 warn,webhook 保持不激活。扇出在内部用 __deliver_only__ / __deliver_target__ metadata 信号化,adapter trait 签名保持不变。
通道文件下载
Telegram(及其他通道)发的文件以临时认证 URL 形式过来,LLM 无法直接访问。bridge 现在下载到可配置目录,把消息重写成本地路径 content block——非图片文件变成 ContentBlock::Text 带保存路径(agent 调 file_read),图片变成 ContentBlock::ImageFile。
[channels]
file_download_dir = "/var/lib/librefang/channel-files" # 默认:~/.librefang/data/channel-files
file_download_max_bytes = 33554432 # 默认:32 MiB
启动 sweep 移除 24h 以上的过期文件;下载过程中还有概率 1/256 的额外 sweep,长跑的守护进程不会堆积孤儿附件。