Update feishu.py - #22316
Conversation
PR: fix: render markdown tables as interactive cards in Feishu gateway
问题描述
当回复消息包含 markdown 表格时,当前 Feishu gateway 检测到 _MARKDOWN_TABLE_RE 匹配后,将整条消息降级为 msg_type='text'(纯文本)。 结果是表格显示为原始 |...| markdown 语法,用户无法阅读。
修改内容
1. 新增模块级函数(gateway/platforms/feishu.py,~L165-285)
_card_format_cell() — 清理表格单元格内的 markdown 标记
_card_parse_table() — 将 markdown 表格解析为 Feishu table 组件结构
_build_card_payload() — 将含表格的 markdown 内容转为 interactive card JSON
2. 修改 _build_outbound_payload(~L4124)
检测到表格时,返回 ("interactive", card_json) 而非 ("text", plain_text)。 任何异常自动 fallback 到纯文本,确保消息不丢失。
3. 发送管道的 fallback(3处)
send_message 和 edit_message 中对 "interactive" 类型添加了与 "post" 相同的 API 拒绝回退机制。
设计说明
非表格内容(标题、列表、引用、代码块等)渲染在 lark_md div 中,保留富文本格式
表格单元仅支持纯文本(Feishu table 组件限制)
所有新增代码为独立模块级函数,不侵入现有类结构
单卡片最多 5 个 table 组件(Feishu API 限制)
需要 Feishu 客户端 v7.4+
测试覆盖
已在本地验证 9 种场景:
纯表格
文字 + 表格混合
多表格
表格 + 富文本(粗体/引用/链接)
纯文本(无表格,走原路径)
纯 markdown 无表格(走原路径)
多行列表格
富文本 + 表格 + 分隔线 + 注释
长表格(11行 → page_size clamp)
操作步骤
在 GitHub 网页上提交
打开 https://github.com/NousResearch/hermes-agent
点右上角 Fork → 创建自己的 fork
在 fork 中进入 gateway/platforms/feishu.py
点右上角 ✏️ Edit this file
在 ~L165(_POST_CONTENT_INVALID_RE 之后)插入以下代码
修改 ~L4124 的 _build_outbound_payload 方法
修改 ~L1840 和 ~L1890 的 fallback 判断
提交 → Contribute → Open Pull Request
补丁文件
补丁文件位于:/tmp/feishu_table_fix.diff
也可在下方直接查看完整 diff。
liuhao1024
left a comment
There was a problem hiding this comment.
Good improvement — using interactive cards with native table components is much better than falling back to plain text. Two things I noticed:
1. Empty cells get dropped, causing column misalignment
In `_card_parse_table`:
```python
cells = [c.strip() for c in s.strip("|").split("|")]
cells = [c for c in cells if c] # ← drops empty cells
```
For a table like `| A | | B |` (empty middle cell), this produces `["A", "B"]` instead of `["A", "", "B"]`. The row data then maps to the wrong columns — `B` ends up in `col_1` instead of `col_2`.
Fix: Remove the empty-cell filter and instead align by column count:
```python
cells = [c.strip() for c in s.strip("|").split("|")]
row = {}
for i, cell in enumerate(cells):
if i < len(columns):
row[columns[i]["name"]] = _card_format_cell(cell)
```
2. Accidental leading whitespace on line 1
The diff shows extra whitespace being added at the very top of the file (before the docstring). Likely an editor artifact:
```diff
+
"""
Feishu/Lark platform adapter.
```
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real Feishu rendering limitation. Current main still deliberately falls back to text for Markdown tables at plugins/platforms/feishu/adapter.py:4524-4534, so the native-card direction remains relevant.
Problems
- The PR targets
gateway/platforms/feishu.py, but current main migrated the live adapter toplugins/platforms/feishu/adapter.pyin552adbe0827c32df8ed9bb19e908c26eff43add7; this needs a port rather than a clean cherry-pick. gateway/platforms/feishu.py:218drops empty cells before positional mapping, so| A | | B |shiftsBinto the wrong column. This confirms the earlier review feedback.- The proposed interactive fallback remains gated by the literal post-type error expression at
gateway/platforms/feishu.py:1842-1855; other interactive-card rejections do not fall back. - The builder appends every matched table (
gateway/platforms/feishu.py:233-265) despite the stated five-table limit, and the PR adds no tests.
Suggested changes
- Port to
plugins/platforms/feishu/adapter.py, preserve empty cells, define an interactive-card error fallback, and add payload/send/edit tests for empty cells, multi-table input, and rejection fallback. - Validate the generated Card v2 payload with a real Feishu bot before merge.
Automated hermes-sweeper review.
| continue | ||
| if s.startswith("|") and s.endswith("|"): | ||
| cells = [c.strip() for c in s.strip("|").split("|")] | ||
| cells = [c for c in cells if c] |
There was a problem hiding this comment.
Dropping empty cells shifts every subsequent value left. Preserve the empty strings and map cells by their original index; add a regression case for | A | | B |.
| ) | ||
| except Exception as exc: | ||
| if msg_type != "post" or not _POST_CONTENT_INVALID_RE.search(str(exc)): | ||
| if msg_type not in ("post", "interactive") or not _POST_CONTENT_INVALID_RE.search(str(exc)): |
There was a problem hiding this comment.
This still requires a literal post-type validation error before falling back. An interactive card rejected with any other error bypasses the advertised plain-text fallback; define an interactive-specific rejection condition and cover it with a test.
| elements = [] | ||
| parts = [] | ||
| last_end = 0 | ||
| for match in _CARD_TABLE_RE.finditer(content): |
There was a problem hiding this comment.
The PR description states a maximum of five table components per card, but this loop processes every match. Enforce or split at that limit and add a multi-table regression test.
Title: fix: render markdown tables as interactive cards in Feishu gateway
Description:
Current behavior: when a message contains markdown tables, _build_outbound_payload detects them via _MARKDOWN_TABLE_RE and falls back to msg_type='text', which renders raw |...| markdown syntax literally.
Fix: add _build_card_payload() and helpers that parse markdown content with tables into Feishu interactive card messages using native table components. Table cells are plain text (Feishu limitation), but surrounding text renders normally via lark_md divs. Falls back to plain text on any exception.
Changes:
New module-level functions: _card_format_cell, _card_parse_table, _build_card_payload
_build_outbound_payload now returns ('interactive', card_json) for table content, with try/except fallback to plain text
send_message and edit_message fallback handlers extended to cover 'interactive' type alongside existing 'post'
Limitations:
Requires Feishu client v7.4+ for table component rendering
Table cells are plain text only (markdown stripped)
Max 5 tables per card (Feishu API limit)
PR: fix: render markdown tables as interactive cards in Feishu gateway 问题描述
当回复消息包含 markdown 表格时,当前 Feishu gateway 检测到 _MARKDOWN_TABLE_RE 匹配后,将整条消息降级为 msg_type='text'(纯文本)。 结果是表格显示为原始 |...| markdown 语法,用户无法阅读。
修改内容
_card_format_cell() — 清理表格单元格内的 markdown 标记
_card_parse_table() — 将 markdown 表格解析为 Feishu table 组件结构 _build_card_payload() — 将含表格的 markdown 内容转为 interactive card JSON
2. 修改 _build_outbound_payload(~L4124)
检测到表格时,返回 ("interactive", card_json) 而非 ("text", plain_text)。 任何异常自动 fallback 到纯文本,确保消息不丢失。
send_message 和 edit_message 中对 "interactive" 类型添加了与 "post" 相同的 API 拒绝回退机制。
设计说明
非表格内容(标题、列表、引用、代码块等)渲染在 lark_md div 中,保留富文本格式
表格单元仅支持纯文本(Feishu table 组件限制)
所有新增代码为独立模块级函数,不侵入现有类结构
单卡片最多 5 个 table 组件(Feishu API 限制)
需要 Feishu 客户端 v7.4+
测试覆盖
已在本地验证 9 种场景:
纯表格
文字 + 表格混合
多表格
表格 + 富文本(粗体/引用/链接)
纯文本(无表格,走原路径)
纯 markdown 无表格(走原路径)
多行列表格
富文本 + 表格 + 分隔线 + 注释
长表格(11行 → page_size clamp)
操作步骤
在 GitHub 网页上提交
打开 https://github.com/NousResearch/hermes-agent
点右上角 Fork → 创建自己的 fork
在 fork 中进入 gateway/platforms/feishu.py
点右上角 ✏️ Edit this file
在 ~L165(_POST_CONTENT_INVALID_RE 之后)插入以下代码
修改 ~L4124 的 _build_outbound_payload 方法
修改 ~L1840 和 ~L1890 的 fallback 判断
提交 → Contribute → Open Pull Request
补丁文件
补丁文件位于:/tmp/feishu_table_fix.diff
也可在下方直接查看完整 diff。
What does this PR do?
Related Issue
Fixes #
Type of Change
Changes Made
How to Test
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — or N/Acli-config.yaml.exampleif I added/changed config keys — or N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — or N/AFor New Skills
hermes --toolsets skills -q "Use the X skill to do Y"Screenshots / Logs