WeCom.AiBot 0.1.0-preview.1

This is a prerelease version of WeCom.AiBot.
dotnet add package WeCom.AiBot --version 0.1.0-preview.1
                    
NuGet\Install-Package WeCom.AiBot -Version 0.1.0-preview.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="WeCom.AiBot" Version="0.1.0-preview.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="WeCom.AiBot" Version="0.1.0-preview.1" />
                    
Directory.Packages.props
<PackageReference Include="WeCom.AiBot" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add WeCom.AiBot --version 0.1.0-preview.1
                    
#r "nuget: WeCom.AiBot, 0.1.0-preview.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package WeCom.AiBot@0.1.0-preview.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=WeCom.AiBot&version=0.1.0-preview.1&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=WeCom.AiBot&version=0.1.0-preview.1&prerelease
                    
Install as a Cake Tool

wecom-aibot-.net-sdk

企业微信智能机器人 .NET SDK —— 基于 WebSocket 长连接通道,提供消息收发、流式回复、模板卡片、事件回调、文件下载解密、媒体素材上传等核心能力。

本 SDK 从官方 @wecom/aibot-node-sdk(https://github.com/WecomTeam/aibot-node-sdk) 协议对齐的 .NET 实现,无需 Node 网关进程,API 设计保持一致。

特性

  • 🔗 WebSocket 长连接 —— 客户端主动外连,不需要公网 IP、域名或回调 URL
  • 🔐 自动认证 —— 连接建立后自动发送认证帧
  • 💓 心跳保活 —— 连续未收到回执即判定连接已死并重连
  • 🔄 断线重连 —— 指数退避(1s → 2s → 4s → … → 30s 上限);认证失败与网络断开使用独立计数器
  • 🚫 顶下线保护 —— 收到 disconnected_event 后不再重连,避免多实例互踢形成风暴
  • 🌊 流式回复 —— StreamSession 自动把增量累积成协议要求的全量帧,并按 ack 节流
  • 🃏 模板卡片 —— 5 种卡片类型、流式 + 卡片组合、卡片原地更新
  • 📤 主动推送 —— 向指定会话推送 Markdown / 卡片 / 媒体,不依赖回调帧
  • ⏩ 串行回复队列 —— 同一 req_id 串行发送并等待回执
  • 🔒 文件下载解密 —— 内置 AES-256-CBC(32 字节块 PKCS#7)
  • 📎 媒体素材上传 —— 三步分片上传,自动分级并发与重试
  • 🧩 DI / 托管集成 —— AddWeComAiBot + IWeComAiBotHandler,每条消息独立作用域
  • 📦 双目标框架 —— net8.0 / net10.0,源生成序列化、零反射、AOT 友好

安装

dotnet add package WeCom.AiBot
dotnet add package WeCom.AiBot.Extensions.Hosting   # 需要 DI / IHostedService 时

快速开始

using Microsoft.Extensions.Hosting;
using WeCom.AiBot.Extensions.Hosting;
using WeCom.AiBot.Streaming;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddWeComAiBot(options =>
{
    options.BotId = Environment.GetEnvironmentVariable("WECOM_BOT_ID")!;
    options.Secret = Environment.GetEnvironmentVariable("WECOM_BOT_SECRET");
});
builder.Services.AddWeComAiBotHandler<MyBotHandler>();

await builder.Build().RunAsync();

// ------------------------------------------------------------

public sealed class MyBotHandler(IMyLlm llm) : IWeComAiBotHandler
{
    public async Task OnTextAsync(TextMessageContext ctx, CancellationToken ct)
    {
        await using StreamSession session = ctx.BeginStream();

        // 只喂增量,SDK 负责累积成全量帧、按 ack 节流、保证结束帧送达
        await foreach (string delta in llm.StreamAsync(ctx.Content, ct))
            await session.AppendAsync(delta, ct);

        await session.CompleteAsync(ct);
    }

    public Task OnEnterChatAsync(EventContext ctx, CancellationToken ct) =>
        ctx.ReplyWelcomeAsync("您好!有什么可以帮您的?", ct);   // 须在 5 秒内完成
}

IWeComAiBotHandler 的所有方法都有默认空实现,只覆写关心的部分即可。

两个必须知道的协议特性

1. 流式内容是「全量刷新」,不是增量追加

同一 stream.id 的后续帧会覆盖之前的内容。把大模型的 delta 逐个直接发出去, 用户会看到内容不断被替换成最后一小段。

StreamSession 内部累积完整文本再发,因此你只需要喂增量。若要直接用底层 API, ReplyStreamAsync 的 content 必须是到目前为止的完整文本。

2. 同一 req_id 的回复必须串行等回执

逐个 delta 发送会在队列里积压,延迟越拉越大。StreamSession 在「上一帧未回执」 或「距上次刷帧不足最小间隔」时跳过中间帧,结束帧则一定送达。

API 一览

连接

成员 说明
StartAsync 启动后台连接循环(不等待认证)
StopAsync 停止连接
WaitForAuthenticatedAsync 等待认证完成;主动推送前应先等待
IsConnected / IsAuthenticated / IsDisplaced 连接状态
ReadInboundAsync 以异步流读取收到的消息与事件

回复(被动,需透传 req_id)

方法 说明
BeginStream 推荐。创建流式会话,自动处理全量刷新与节流
ReplyStreamAsync 发送一帧流式回复(content 须为完整内容)
ReplyStreamNonBlockingAsync 同上,但上一帧未回执时跳过
ReplyStreamWithCardAsync 流式 + 模板卡片组合
ReplyWelcomeTextAsync / ReplyWelcomeCardAsync 欢迎语,5 秒窗口
ReplyTemplateCardAsync 回复模板卡片
UpdateTemplateCardAsync 更新卡片,5 秒窗口,task_id 须一致
ReplyMediaAsync 回复媒体消息

主动推送

方法 说明
SendMarkdownAsync 推送 Markdown
SendTemplateCardAsync 推送模板卡片
SendMediaAsync 推送媒体消息

媒体

方法 说明
UploadMediaAsync 三步分片上传,返回 media_id(3 天有效)
DownloadFileAsync 下载并用消息自带的 aeskey 解密

事件

事件 说明
Connected WebSocket 已连接(认证未完成)
Authenticated 认证成功
Disconnected 连接断开
Reconnecting 即将重连(含序号、延迟、是否由认证失败触发)
Error 发生错误
Displaced 被新连接顶下线,此后不再重连

配置

属性 默认值 说明
BotId — 机器人 ID(必填)
Secret — 机器人 Secret,与 SecretAlias 二选一
SecretAlias — 别名如 alias:wecom-bot,运行时解析
WebSocketUrl wss://openws.work.weixin.qq.com 私有化部署时改为管理端提供的地址
ReconnectBaseDelay 1s 重连基础延迟
ReconnectMaxDelay 30s 重连延迟上限
MaxReconnectAttempts 10 断线重连上限,-1 无限
MaxAuthFailureAttempts 5 认证失败重试上限,-1 无限
HeartbeatInterval 30s 心跳间隔
MaxMissedHeartbeats 2 连续未回执次数达到即判死
ReplyAckTimeout 5s 等待回执超时
MaxReplyQueueLength 500 单个 req_id 排队上限
RemoteCertificateValidationCallback — 自签证书校验(私有化部署)
ConfigureWebSocket — 底层 ClientWebSocketOptions 逃生舱

Secret 别名

配置文件里只出现标识符,真实密钥留在进程环境:

options.SecretAlias = "alias:wecom-bot";

默认解析器读环境变量 WECOM_BOT_SECRET_WECOM_BOT(去掉 alias: 前缀、转大写、 非字母数字换成下划线)。可用 AddWeComSecretResolver<T>() 换成 KeyVault 等实现。

示例

示例 说明
samples/WeCom.AiBot.Sample.Echo 流式回显、欢迎语、卡片交互、文件下载解密
samples/WeCom.AiBot.Sample.WebConsole 浏览器控制台:实时看收到的消息 + 主动推送 + 人工接管
samples/WeCom.AiBot.Sample.EuCoreBridge 桥接 EU.Core.Agent:SSE delta → StreamSession

运行 Echo 示例

需要 .NET SDK 10.0.100+(见 global.json),以及在企业微信管理端创建好的机器人。

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'

dotnet run --project samples/WeCom.AiBot.Sample.Echo

$env: 只在当前 PowerShell 会话内有效。凭据不要写进代码或提交进仓库。

启动后看到这几行就说明通了:

info: WeCom.AiBot.WeComAiBotClient[0] 正在连接 wss://openws.work.weixin.qq.com …
info: WeCom.AiBot.WeComAiBotClient[0] 连接已建立,发送认证帧。
info: WeCom.AiBot.WeComAiBotClient[0] 认证成功。

然后在企微里找这个机器人对话:

操作 预期
当天首次进入会话 5 秒内收到欢迎语
发任意文本 逐字流式回显,内容逐步刷新而非重复堆叠
发含「卡片」二字的文本 收到按钮交互卡片
点卡片按钮 卡片原地更新为「已确认 ✅ / 已取消 ❌」
发图片或文件 日志打出解密后字节数,并回一条确认消息
断网 30 秒再恢复 自动重连并恢复收发
另起一个同 botId 实例 旧实例收到 disconnected_event 后干净退出、不重连

⚠️ 同一个 botId 只允许一条活跃长连接。调试时别同时开两个实例 —— 它们会互相顶下线。

把 WECOM_BOT_SECRET 留空则回落到别名方式,示例内置别名 alias:echo-bot:

$env:WECOM_BOT_SECRET          = ''
$env:WECOM_BOT_SECRET_ECHO_BOT = '你的机器人Secret'

运行 Web 控制台示例

调试时最缺的两件事——看见机器人收到了什么、随手发一条测试消息——都在这个页面上。

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'

dotnet run --project samples/WeCom.AiBot.Sample.WebConsole

启动后自动打开 http://127.0.0.1:5080(加 --no-browser 可关掉)。页面分两栏:

  • 左栏实时流水 —— 收到的消息、发出的回复、连接状态变化,经 SSE 实时推送。 点任意一条可把它的会话 ID 回填到右栏;图片与文件带「下载」按钮,取的是本机解密后的副本 (企微原始链接 5 分钟失效且内容加密,链不过去)。只留最近 20 个,进程重启即清空。
  • 右栏发送面板 —— Markdown、模板卡片(带「填入示例卡片」按钮)、媒体文件上传后推送。

右上角的人工接管开关决定收到消息后怎么办:

模式 行为
关(默认) 机器人自动流式回显,页面同时记录收发两侧
开 不自动回复,消息挂在页面上等你手动回

人工回复走的是主动推送而不是被动回复。被动回复必须透传 req_id,而人打字要几十秒 甚至几分钟,那时 req_id 早已过期。代价是回复在企微里不与原消息形成引用关系。

模板卡片在提交前会本地校验 card_type、card_action(文本通知型与图文展示型必填) 与 task_id 字符集,直接给中文提示,省得拿服务端错误码去翻文档。

⚠️ 这个页面能以机器人身份发消息,因此只绑回环地址 127.0.0.1 且不做鉴权。 需要给同事用就得自己加鉴权并改 WECOM_CONSOLE_URL——别直接把它暴露到公网。

别名方式为 alias:web-console,对应 WECOM_BOT_SECRET_WEB_CONSOLE。

被顶下线时本示例不会退出进程(StopHostWhenDisplaced = false),页面上会留一条醒目记录 说明为什么不再收消息——与 Echo 示例的默认行为刻意不同。

运行 EuCoreBridge 示例

$env:WECOM_BOT_ID     = '你的机器人ID'
$env:WECOM_BOT_SECRET = '你的机器人Secret'
$env:EUCORE_BASE_URL  = 'http://localhost:5000'
$env:EUCORE_AGENT_ID  = '已发布 Agent 的 GUID'

dotnet run --project samples/WeCom.AiBot.Sample.EuCoreBridge

EUCORE_RUN_PATH 可直接指定完整路径(优先于 EUCORE_AGENT_ID); EUCORE_SHOW_TOOL_TRACE=false 关闭「🔧 正在调用」回显。别名为 alias:bridge-bot。

看原始协议帧

企微的实际下发结构偶尔与文档对不上,排查时原始帧是唯一可信的依据。 模板卡片事件的原始体 Echo 示例默认就会打出来;要看全部推送帧,把日志级别降到 Debug:

$env:Logging__LogLevel__Default = 'Debug'

排查

构建报 MSB3027 / MSB3021「文件被锁定」 —— 示例还在跑,bin 下的 DLL 被占用。 先 Ctrl+C 停掉,或:

Get-Process -Name 'WeCom.AiBot.Sample.Echo' -ErrorAction SilentlyContinue | Stop-Process

更新卡片被拒 errcode=42045(card_action Missing or Invalid) —— 文本通知型 (text_notice)与图文展示型(news_notice)必填 card_action,且 type 只能取 1(跳转 URL)或 2(打开小程序),不填或取 0 都会被拒。

更新卡片被拒 errcode=40058 —— task_id 必须与回调收到的完全一致,取不到就发出去 该字段会被整个省略,服务端必拒。且更新有 5 秒窗口,收到事件后别做耗时操作再回。

文件下载报 aeskey 不是合法的 Base64 字符串 —— 确认 aeskey 取自同一条消息: 每个下载链接的密钥都不同,链接本身也只有 5 分钟有效期。

认证失败 —— 日志会打出 errcode / errmsg。认证失败与网络断开用独立的重试预算, 到上限后抛 WeComAuthFailureException 并停止重连 —— 这种情况重试无益,去核对 botId 与 secret。

欢迎语没收到 —— enter_chat 只在用户当天首次进入单聊会话时触发,当天再进不会重复触发。

与官方 Node SDK 的差异

方面 Node SDK 本 SDK
回复队列竞态 定时器回调 + 自增 seq 防过期超时误杀 每 req_id 一把互斥门 + 作用域超时,该竞态结构上不存在
流式全量刷新 调用方自行拼接完整内容 StreamSession 自动累积
分片上传失败 收集全部错误后汇总抛出 首个分片彻底失败即快速终止,不再白跑流量
日志 自定义 Logger 接口 标准 ILogger
chunk_index 类型注释写 1 基,实现是 0 基 与实现一致(0 基),并在注释中说明
模板卡片事件 声明 event_key / task_id 平铺在 event 下 线上实际嵌在 event.template_card_event 里,两种形态都能解析
aeskey 解码 Buffer.from(k,'base64') 恰好兼容 URL 安全字母表 显式宽松解码,兼容 - _ 与缺失填充
card_action 声明为可选,README 示例未带 注释标明文本通知型 / 图文展示型必填,否则 errcode=42045

构建与测试

dotnet build WeCom.AiBot.sln
dotnet test  WeCom.AiBot.sln     # net8.0 与 net10.0 各跑一遍

单元测试使用内存假传输,不需要真实机器人凭据即可覆盖认证、心跳判死、退避序列、 顶下线禁重连、队列串行与超时、流式累积与截断、加解密往返等行为。

作者

xiaochanghai — https://github.com/xiaochanghai

仓库地址:https://github.com/xiaochanghai/wecom-aibot-dotnet-sdk,欢迎提 Issue 与 PR。

License

MIT © xiaochanghai

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on WeCom.AiBot:

Package Downloads
WeCom.AiBot.Extensions.Hosting

WeCom.AiBot 的依赖注入与托管集成:AddWeComAiBot / AddWeComAiBotHandler、IHostedService 生命周期、有界并发消息分发。

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.1 96 7/31/2026