Skip to content

fix(feishu): render Markdown tables as v2 cards instead of degrading to text#37728

Open
315zuicai wants to merge 1 commit into
NousResearch:mainfrom
315zuicai:feat/feishu-markdown-table-as-card
Open

fix(feishu): render Markdown tables as v2 cards instead of degrading to text#37728
315zuicai wants to merge 1 commit into
NousResearch:mainfrom
315zuicai:feat/feishu-markdown-table-as-card

Conversation

@315zuicai

Copy link
Copy Markdown

Problem

Feishu IM post messages do not render Markdown tables — they appear blank or as raw | a | b | source characters on the client. Before this change, FeishuAdapter._build_outbound_payload reacted by force-degrading any table-bearing reply to plain text, which still surfaced the raw pipe/dash characters to the user.

This makes any agent that emits a markdown table (very common — comparison tables, status reports, structured data) look broken on Feishu.

Fix

Two parts:

A) gateway/platforms/feishu.py — when the outbound content contains a Markdown table, wrap it into a minimal v2 interactive card (schema: 2.0) whose body is one or more tag: markdown elements. Feishu's card markdown elements DO render tables correctly. If the card build raises for any reason, the existing plain-text fallback is preserved so the send pipeline cannot crash.

Two helpers are added:

  • _split_markdown_for_card_elements() — splits long content into chunks that fit Feishu's per-element soft limit (~3400 chars) and per-card element cap (40), respecting fenced code blocks.
  • _build_table_card_payload() — wraps the (chunked) markdown into the minimal card v2 envelope.

B) agent/prompt_builder.py — tightens the Feishu directive injected into the system prompt so the model knows which Markdown features Feishu IM actually renders. The previous wording (“Feishu renders Markdown … bold, italic, code blocks, and links are supported”) was misleading because it implied tables/headings work too. New wording explicitly lists what works (**bold**, *italic*, code, links, lists) and what doesn’t (tables, ATX headings, block quotes, hr), and recommends key-value bullet lists or bold lines as safe alternatives. This is best-effort steering — the code-side fix in (A) catches the cases where the model still emits a table.

Behaviour change

  • Before: a reply containing a Markdown table → user sees raw | a | b | source.
  • After: same reply → user sees a rendered interactive card with the table laid out properly.
  • No-table replies are unchanged (regression-tested).
  • Plain text is unchanged.

Tests

Adds 4 cases to TestFeishuAdapterMessaging in tests/gateway/test_feishu.py:

  1. test_build_outbound_payload_wraps_table_into_v2_card — table input produces ("interactive", <v2 card with tag:markdown elements>).
  2. test_build_outbound_payload_falls_back_to_text_when_card_build_fails_build_table_card_payload raising → returns ("text", ...) instead of crashing.
  3. test_build_outbound_payload_non_table_markdown_still_uses_post — non-table markdown still routes to the existing post path.
  4. test_build_outbound_payload_plain_text_unchanged — plain text stays as text.

All 361 existing tests in test_feishu.py + test_feishu_bot_admission.py + test_stream_consumer.py continue to pass.

Validation

Verified live in a real Feishu IM session: a reply containing a 3-row × 4-col Markdown table previously rendered as raw source; with this change it renders as an interactive card with the table laid out correctly.

Diff size

  • agent/prompt_builder.py: +13 / -2
  • gateway/platforms/feishu.py: +124 / -3 (two new module-level helpers + a small change in _build_outbound_payload)
  • tests/gateway/test_feishu.py: +78 / -0

…to text

Feishu IM 'post' messages do not render Markdown tables — a reply that
contains a `| a | b |` table previously surfaced the raw source characters
to the user, because `_build_outbound_payload` force-degraded any
table-bearing content to plain text.

This wraps replies containing a Markdown table into a minimal v2
interactive card with `tag: markdown` elements (which DO render tables
correctly on Feishu clients). If card construction raises, the existing
text fallback is preserved so the send pipeline cannot crash.

Also tightens the Feishu prompt directive in `agent/prompt_builder.py`
to explicitly enumerate which Markdown features Feishu IM does and does
not render, steering the model toward formats that survive the IM
renderer (e.g. bullet-list key-value pairs instead of pipe tables, bold
lines instead of ATX headings).

Tests: adds 4 cases to `TestFeishuAdapterMessaging` covering the table
→ card wrap, the build-failure fallback to text, the no-table
regression (still uses post), and the plain-text passthrough.
@alt-glitch alt-glitch added type/bug Something isn't working platform/feishu Feishu / Lark adapter comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants