feat: support DingTalk streaming AI cards - #8890
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for DingTalk streaming interactive cards by adding configuration options, implementing card creation and update methods in the DingTalk adapter, and updating the event handler to stream message updates. The review feedback highlights a potential memory leak if the streaming generator raises an exception, which can be resolved by wrapping the loop in a try...finally block, and a bug where a configured update interval of 0.0 is overridden due to a falsy check.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
11b0ca7 to
9b36e52
Compare
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
DingtalkMessageEvent._send_streaming_as_plain_text, the previous call tosuper().send_streaming(...)has been removed and theuse_fallbackparameter is now ignored; if you intended to preserve the base-class streaming/fallback behavior, consider still delegating tosuper()after buffering or dropping the unused parameter. - The card streaming path repeatedly sends the full accumulated
full_contenton each update; if payload size or rate limits become a concern, consider tracking and sending only the newly appended text (e.g., via a diff or slice) instead of the entire history each time.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `DingtalkMessageEvent._send_streaming_as_plain_text`, the previous call to `super().send_streaming(...)` has been removed and the `use_fallback` parameter is now ignored; if you intended to preserve the base-class streaming/fallback behavior, consider still delegating to `super()` after buffering or dropping the unused parameter.
- The card streaming path repeatedly sends the full accumulated `full_content` on each update; if payload size or rate limits become a concern, consider tracking and sending only the newly appended text (e.g., via a diff or slice) instead of the entire history each time.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
DingtalkMessageEvent.send_streaming, theuse_fallbackparameter is now unused and the method no longer delegates tosuper().send_streaming; consider either removing the parameter or making the intended behavior explicit so callers are not misled about fallback semantics. - The card streaming implementation always sends the full accumulated
full_contenton each update, which can lead to increasingly large payloads for long responses; consider introducing a maximum content length or a configurable option to switch to delta-based updates once DingTalk reliably supports append semantics.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `DingtalkMessageEvent.send_streaming`, the `use_fallback` parameter is now unused and the method no longer delegates to `super().send_streaming`; consider either removing the parameter or making the intended behavior explicit so callers are not misled about fallback semantics.
- The card streaming implementation always sends the full accumulated `full_content` on each update, which can lead to increasingly large payloads for long responses; consider introducing a maximum content length or a configurable option to switch to delta-based updates once DingTalk reliably supports append semantics.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
|
@sourcery-ai review |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- In
DingtalkMessageEvent.send_streaming, the generator is passed tosuper().send_streamingand then consumed again locally; ifAstrMessageEvent.send_streamingiterates the generator, this will result in an exhausted generator and inconsistent behavior, so consider either moving thesuper()call after your own streaming logic or using a separate notification mechanism that doesn’t reuse the same generator. - Both
DingtalkPlatformAdapter.send_by_sessionandDingtalkMessageEvent.sendcall the card-sending helpers and then unconditionally delegate tosuper().send_by_session/super().send, which may cause duplicate side effects depending on the base implementations; it would be safer to clearly separate “send card only” vs “send normal message” paths and ensure the base method isn’t invoked in cases where the card send fully replaces the original message delivery.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `DingtalkMessageEvent.send_streaming`, the generator is passed to `super().send_streaming` and then consumed again locally; if `AstrMessageEvent.send_streaming` iterates the generator, this will result in an exhausted generator and inconsistent behavior, so consider either moving the `super()` call after your own streaming logic or using a separate notification mechanism that doesn’t reuse the same generator.
- Both `DingtalkPlatformAdapter.send_by_session` and `DingtalkMessageEvent.send` call the card-sending helpers and then unconditionally delegate to `super().send_by_session`/`super().send`, which may cause duplicate side effects depending on the base implementations; it would be safer to clearly separate “send card only” vs “send normal message” paths and ensure the base method isn’t invoked in cases where the card send fully replaces the original message delivery.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
792332d to
83f2bba
Compare
|
已将本 PR 更新并重放到最新
本地验证:钉钉相关测试 新的 GitHub Actions 当前显示 |
中文说明
Related to #5785
这个 PR 为 AstrBot 内置钉钉平台适配器增加原生“互动 AI 卡片流式回复”支持。
目前 WebUI 里的钉钉设置已经有
card_template_id配置项,并提示“启用后将使用互动卡片进行流式回复”,但实际DingtalkMessageEvent.send_streaming()仍然会把模型输出缓冲到结束后再作为普通消息发送。用户在钉钉里提问后需要等待完整生成结束才看到回复,体验上不是流式输出。本 PR 基于已经在外部插件中验证通过的适配逻辑迁移到官方钉钉 adapter:填写钉钉互动卡片模板 ID 后,流式回复会创建钉钉 AI 卡片,并把模型生成的文本持续更新到卡片内容变量中;未配置模板或创建卡片失败时,会自动回退到原来的普通消息发送方式。
改动内容
card_template_id作为钉钉流式卡片开关。dingtalk_stream.AICardReplier创建并投递钉钉互动 AI 卡片。card_content_key,用于指定卡片模板中的内容变量名,默认content。card_update_interval,用于控制卡片更新最小间隔,默认0.35秒。send_plain_text_as_card(默认关闭),开启后将未来任务、定时任务、插件context.send_message等通过send_by_session主动发送的纯文本回复也投递为互动卡片。createAndDeliver创建空卡片,再调用/v1.0/card/streaming写入正文并完成卡片,避免卡片已创建但内容为空。animate_proactive_card(默认开启),让已经生成完成的主动消息分段更新到同一张卡片,提供“正在输出”的视觉反馈;单次最多更新 20 次。主动消息与未来任务
AstrBot 的普通对话仍使用模型生成器提供的真实流式输出;未来任务、定时任务和插件主动消息通常只能在任务执行完成后拿到完整文本,因此这一类消息无法获得模型原始 token 流。本实现会先立即创建空卡片,再把完整结果按自适应分段更新到同一张卡片,最后发送完整正文并将卡片标记为完成,从视觉上保留“处理中 → 正在输出 → 输出完成”的连续体验。
卡片更新使用完整累计内容和覆盖语义(
isFull=True),避免只发送 delta 时前文被替换。主动消息最多执行 20 次更新,并复用card_update_interval控制更新间隔;关闭animate_proactive_card后会直接写入最终内容。任何创建或更新失败都会返回原有普通消息路径,不影响现有功能。达到的效果
1. 处理中
收到用户问题后,先创建钉钉互动 AI 卡片,并展示“该问题正在处理中...”的占位状态。这样用户发送问题后能立即看到机器人已经接收并开始处理请求,不需要等到模型完整生成结束才看到第一条回复。
2. 输出结果中
模型开始生成内容后,卡片进入流式输出状态,标题显示“正在输出结果...”,内容区域会随着模型生成不断刷新已经产出的文本。用户可以边等边看当前回答进度,避免普通消息模式下长时间无反馈的割裂感。
3. 输出结果
模型生成完成后,卡片更新为最终状态,标题显示“输出结果”,内容区域展示完整回复。最终结果仍保留在同一张卡片中,不会额外刷出多条普通消息。
卡片的具体标题、布局、按钮和视觉样式均可在钉钉开发者平台的互动卡片模板中自定义;本 PR 只负责将 AstrBot 的流式输出接入钉钉 AI 卡片,并把内容写入配置的模板变量,例如默认的
content。关于完整内容更新
当前实现会在每次卡片更新时发送完整的
full_content,这是有意保留的行为。钉钉互动 AI 卡片这里使用的是覆盖式内容更新语义;在append=False的更新方式下,如果只发送新增 delta,卡片内容可能只保留最新片段,导致前文丢失。为了控制请求频率和 payload 增长,本 PR 通过
card_update_interval对更新节奏做限流,默认最小间隔为0.35秒。后续如果钉钉侧确认可稳定使用追加式更新,也可以再切换为 delta 追加模式。验证情况
master(d2d7e5a,2026-08-27)重新整理提交并验证兼容性。pytest tests/test_dingtalk_adapter.py -q:21 passed。2210 passed;仅tests/test_dashboard.py::test_plugins因本地受限网络无法访问插件市场接口而失败,与本 PR 无关。scripts/smoke_startup_check.py:通过。ruff check、ruff format --check、py_compile和git diff --check均通过。Checklist / 检查清单
This is NOT a breaking change. / 这不是一个破坏性变更。
😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
/ 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。相关讨论见 [Feature]功能请求:适配钉钉打字机效果流式 AI 卡片 #5785。
👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
/ 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。
🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in
requirements.txtandpyproject.toml./ 我确保没有引入新依赖库,或者引入新依赖库的同时将其添加到
requirements.txt和pyproject.toml文件相应位置。😮 My changes do not introduce malicious code.
/ 我的更改没有引入恶意代码。
English Summary
This PR adds native DingTalk interactive AI card streaming support to the built-in DingTalk platform adapter. When
card_template_idis configured, streaming text chunks are delivered through a DingTalk AI card viadingtalk_stream.AICardReplier; otherwise the adapter falls back to the existing buffered normal-message behavior.The user experience has three visible states: a processing card is shown immediately after the user asks a question, the same card is continuously updated while the model is generating, and the final card displays the complete answer. The card title, layout, buttons, and visual style can be customized in the DingTalk developer platform's interactive card template; this PR only connects AstrBot's streaming output to the configured card content variable.
The card update path intentionally sends the full accumulated content on each update because the current DingTalk AI card update flow is used with overwrite semantics (
append=False). Sending only delta text in that mode can cause the card to display only the latest chunk instead of the whole generated answer. Update frequency is throttled bycard_update_interval.Optional
send_plain_text_as_cardsupport also covers proactive plain-text replies sent by future tasks, scheduled jobs, and plugins. Proactive messages are created as empty cards and then populated through DingTalk's streaming endpoint; whenanimate_proactive_cardis enabled, the completed text is replayed in at most 20 accumulated updates to provide continuous visual feedback. Mixed or media messages and any failed card operation retain the existing normal-message fallback.Summary by Sourcery
Enable configurable DingTalk interactive AI cards for streaming and proactive plain-text replies while preserving reliable normal-message fallbacks.
New Features:
Bug Fixes:
Enhancements:
Tests: