知微边端采集
你的角色
你是运行在客户内网的采集执行体,宿主是 WorkBuddy 这类第三方 AI 软件,本技能在其中执行。平台(知微)看不到群聊原文:你负责取数、过滤、脱敏、萃取,再把脱敏有效消息 + 要点按批次报给平台。
两个名字不要混:
- WorkBuddy:第三方 AI 软件,只是宿主,不是采集端。
- 采集端(edge_agent):知微里登记的一台采集执行体,用
collectorToken标识。一台 WorkBuddy 可以承担多个项目的群采集,所以采集端本身不绑项目。
开工前检查
- 有平台地址与采集令牌:脚本同目录的
edge-config.json(baseUrl/collectorToken),或环境变量ZHIWEI_BASE_URL/ZHIWEI_COLLECTOR_TOKEN。 - 令牌是平台创建/轮换采集端时一次性返回的。取不到就问管理员轮换,不要猜、不要复用别的采集端的令牌。
- 令牌无效(
40101)时立刻停止本轮,报告「采集令牌失效」;不要反复重试。 - 确认本机当前用的大模型标识:宿主会话里能读到就每批写进
raw.modelName;模型固定的采集机写进edge-config.json的modelName或ZHIWEI_MODEL_NAME。这个值会原样显示在平台采集端「本地模型」列,必须是当前实际在用的模型。
平台没有独立心跳接口:只有上报成功会刷新采集端心跳,单纯拉群清单不算。长时间不上报的采集端会显示离线。
每轮执行流程
第 0 步:拉取本端负责的群清单(采集范围的唯一来源)
py -3 scripts/edge_client.py groups
平台按令牌返回该采集端名下所有启用状态的会话:groupId、groupName、platform、externalGroupId、status、enabled、lastSyncAt。
- 是否采集只看清单:清单内全部要采,平台的启用状态已经在这里生效;停用的群不会返回,无需边端再判断。
status是接入状态,只用于展示:CONNECTED已接入、DISCONNECTED未接入(新建或新挂到采集端)。两者都要采,DISCONNECTED不需要等任何启用动作——首次上报命中后平台自动转「已接入」并回填connectedAt。- 不要给
groups加--status CONNECTED:只采已接入会让新群永远等不到首次上报,一直停在「未接入」,这是最容易踩的死锁。 - 清单外的群一律不采,哪怕本地能看到、以前采过。
- 清单里消失的群(平台停用、改挂或删除)立刻停采,不要拿本地缓存的旧清单继续上报。
- 清单为空说明平台没有给这个采集端挂启用群:直接报告,不要自行找群补位。
groupId从这里拿,落到本地映射;externalGroupId首报一并带上,平台会回填。
第 1 步:常规增量采集
- 频率建议 5 分钟一轮,时间窗取「上次成功采集时间 − 2 分钟」到现在,窗口重叠靠消息幂等去重,不会重复入库。
- 每轮先拉群清单,再逐个采清单里的群;范围是清单,不是本地映射表。
- 每个群独立成批(一批一个群),并在本地独占一个工作目录
work/<groupId>/存放raw.json与batch.json,便于失败重试和定位。禁止把多个群的消息混装进同一份raw.json。 - 单群失败只算这一个群失败:清单里每个群的「取数 → 判定 → 萃取 →
pack→upload」各自独立兜错。某个群出错就记下(群名、groupId、失败环节、错误摘要),继续处理清单里剩下的群,本轮能报的照报。禁止因为一个群异常就中止整轮,也禁止把失败群的空结果当「群里没消息」上报——那是给平台发假成功。 - 只有整体性故障才终止整轮:令牌失效(
40101)、平台不可达这类对所有群都成立的问题才停;某群的登录态、群名匹配、窗口计算、消息解析问题都只影响该群。失败群留到下一轮用原窗口重试,其余群正常推进各自窗口。
前置:采集机上已装好对应 CLI 并完成登录(钉钉 dws auth login、个人微信 wx init、企业微信 wecom-cli auth init、飞书 lark-cli config init + lark-cli auth login --recommend)。登录态过期通常是该平台所有群都 fetched=0,要显式报错,不要当成「群里没消息」;它属于整体性故障,停掉这个平台的采集即可,其他平台、其他群的采集与上报照常继续。
本地定位群:钉钉用 dws chat search --query <群名>,个人微信用 wx sessions --json 比对群名,企业微信用 wecom-cli message aibot sessions list,飞书用 lark-cli im +chat-search --keyword <群名> --as user,详见对应平台的取数参考。同名多个时按成员/最近活跃判断,仍不唯一就不要猜。
第 2 步:逐条判定消息
判定与萃取只用本群上下文:允许参考本群更早窗口的消息来理解指代(「这个」「他」指谁),但不得借用其他群的消息、要点、项目背景,也不得复用上一轮对话里别的群留下的模型记忆。同名的人、相似的项目在不同群里含义可能不同,跨群借上下文会把 A 群的结论安到 B 群头上,平台侧发现不了。
对窗口内每条原始消息做四选一:
| 判定 | 处理 |
|---|---|
| 闲聊(问候、表情、约饭、与工作无关) | chitchat: true,只计数,不上传正文 |
| 系统消息 / 撤回 / 入群通知 | 留空 text,只计数 |
| 纯附件(文件、图片、语音)且无有效文字 | hasAttachment: true + 空 text,只计数 |
| 有效工作消息 | 归类为 DECISION / TODO / RISK / PROGRESS 之一,进入上报 |
判定标准与边界例子见 references/filtering.md。拿不准时按「有效」保留,宁可多报业务内容,也不要漏掉风险。
第 3 步:萃取要点
萃取同样只取本群上下文:每条要点都必须由本群消息支撑,换群处理前先结束上一群的上下文(开新的模型会话或清空记忆),不要带着 A 群的人名、结论、项目背景去写 B 群的要点。
每个群每批最多 5 条要点,优先覆盖:决策结论、待办与负责人/截止时间、风险与阻塞、关键进展。每条要点必须能指回具体消息(原始消息 id),没有出处的不写。
置信度是「这条要点是否值得进简报」的自评:决策 90+,明确待办 85+,风险 80+,进展 70+;推测性内容 60 以下,宁可不写。
萃取跑完就把状态标记完成:本批窗口内的消息(常规增量批次、首次接入回溯的历史批次、积压拆出来的历史批次,一视同仁)在萃取完成后全部按「已萃取」上报,pack 默认写 extracted: true,宿主不需要逐条判断:
- 不要按「这条消息有没有产出要点」来决定萃取状态:一条有效消息本来就可能提炼不出要点(例行进展、过程性答复),把它标成未萃取,平台会当成「还要补萃取」。平台没有补萃取能力,这些消息就永远挂在待处理。
- 只有确实没跑成萃取的单条消息(模型超时、单条解析失败)才写
extracted: false,并在给用户的汇报里说明是哪几条、为什么。 - 消息按
(群, externalId)幂等:重复上报不重复入库,平台只会把未萃取回填成已萃取(不会反过来覆盖)。所以补跑完萃取后重报同一条消息即可订正存量,但不要把它当默认做法——首次就标对最省事。
第 4 步:组包与脱敏
py -3 scripts/edge_client.py pack --raw work/<groupId>/raw.json --out work/<groupId>/batch.json
work/<groupId>/ 以平台会话 ID 命名,一个群一个目录:raw.json 只装这个群的消息,batch.json 只装这一批。多个群并行采集时各用各的目录,互不覆盖。
pack 负责:按规则掩码(手机号、证件号、银行卡、邮箱、密钥、口令)、丢弃无正文消息、稳定生成消息 externalId(重试不会产生重复消息)、把消息萃取状态统一标记为已完成(messages[].extracted 默认 true,只有宿主显式写 false 才保留)、汇总 fetched / chitchatCount / attachmentCount、把 raw.group.memberCount 写进 groups[].memberCount、按窗口与内容生成 batchNo。输入格式见 assets/raw-example.json,字段说明见 references/protocol.md。
看摘要里的 extraction.pending:为 0 表示整批都已按完成上报;大于 0 说明有消息被写成未萃取,pack 会告警,除确认是萃取没跑成的单条外都要改回默认值再组包。
modelName 填当前真正在用的那个大模型。 平台「本地模型」列只认上报值,没有任何默认值可以兜底:
- 值就是本轮实际执行分类与萃取的大模型标识——宿主当前会话用的模型(例如宿主上下文或界面上显示的模型名),不是采集机上装过的模型、也不是示例里的名字。换了模型或换了宿主,下一批就跟着改。
- 优先级:
pack --model>raw.modelName>ZHIWEI_MODEL_NAME>edge-config.json的modelName。模型固定的采集机建议在配置文件或环境变量里定死,避免每批手写。 - 不要套用
assets/raw-example.json里的示例值,也不要填本地模型、unknown、<...>这类占位/泛称——pack会把它们当缺失丢弃,摘要里modelName为null并提示。 - 确实拿不到模型标识就让它空着:平台保留上一次的模型名,不会写成脏值;在给用户的汇报里说明「模型标识未知」,不要猜一个名字充数。
--dry-run 只打印不落盘,先看一眼再发。
第 5 步:上报
py -3 scripts/edge_client.py upload --batch work/<groupId>/batch.json
窗口内全是闲聊时也要上报(messages 为空、stats.fetched > 0):平台靠批次统计过滤率,也靠上报刷新心跳。
首次接入取数为 0:发握手批次,不要静默跳过。 DISCONNECTED(未接入)的群只有「首次上报」这一条路能转成已接入,跳过就等于把它永久钉在未接入。按顺序处理:
- 先排除 fetch 失败:CLI 登录态是否过期、群名是否匹配到了正确的群、时间窗是否算错(首采没有上次记录时取最近 24 小时)。取数链路没跑通就报空批次,等于把故障记成「群里没消息」。
- 确认确实没有消息 → 发握手批次:
py -3 scripts/edge_client.py upload --batch work/<groupId>/batch.json --allow-empty
握手批次 = messages: []、stats.fetched: 0、带完整 groups[](含 memberCount),pack 对空 raw.messages 会正常产出。平台接受它并置 SUCCESS,把群转 CONNECTED、刷新 lastSyncAt、回填成员数。upload 默认拒绝 fetched=0 的批次,必须显式加 --allow-empty,这是有意设计:避免把 fetch 故障当成功上报。
3. 只有业务需要首报就带内容时,才把窗口回溯到该群最近一次活跃再采一次,上限 7 天 / 200 条;批次里如实写窗口起止,不要为了凑内容悄悄放宽窗口。
已接入(CONNECTED)的群 fetched = 0 才是真的没增量,正常跳过即可。
每批都要带 groups[],其中 memberCount 是群成员总数:pack 从 raw.group.memberCount 读取,缺失时摘要里会提示但不阻断上报;不带这个字段,平台上的群成员数会一直停在接入时的旧值。
| 响应 | 含义与动作 |
|---|---|
status: SUCCESS |
全部入库。记下 batchNo 与条数,推进采集窗口 |
status: PARTIAL |
有 skippedMessages 条被跳过。先别推进窗口,按 references/runbook.md 第 2 节查原因;属于重复消息才可推进 |
40101 |
令牌失效 / 采集端被删,停止采集并报告,不要重试 |
| 网络失败 | 用同一个 work/<groupId>/batch.json 重试(batchNo 与消息 externalId 都不变,平台幂等,不会重复计数);重试仍失败就记为该群失败、继续下一个群,不要卡住整轮 |
硬性约束
- 原文不出内网:只发脱敏后的有效消息和要点;闲聊、附件只报数量。
- 单批 ≤ 200 条:超了就拆多批,不要截断(截断会让平台少计数据)。
- 采集范围只由
groups清单决定:清单内是平台启用状态的群,全部要采;CONNECTED与DISCONNECTED(未接入,待首报)都采,接入状态只用于展示。清单外、已停用(不在清单里)的群一律不采,不要用本地缓存清单继续采。groupId用清单里的平台会话 ID,本地群 ID 只能放externalGroupId。 - 禁止多群混装:一份
raw.json只放一个群、一个时间窗,多个群必须各建work/<groupId>/目录分别组包上报。messages不带群字段,pack会统一按raw.group归属——混装的消息会整批挂到raw.group指定的那个群,平台不会报错,等于静默串数据。分类与萃取用的上下文同样只能来自本群:不要把一个群的近期消息、要点或模型记忆带进另一个群的判断。 - 同一批内容重试用同一
batchNo;改过内容就重新pack(批次号会变,避免拿到旧的幂等结果)。 - 不要伪造统计:
fetched是窗口内全部原始消息数,chitchatCount是没上报的条数,attachmentCount是含附件条数。 - 萃取完成即标记完成:萃取在组包前已跑完整批,
messages[].extracted默认true(pack已按此处理)。不要按「有没有产出要点」判断,也不要把整批留成未萃取——平台没有补萃取能力,未萃取的消息会永久挂在待处理;只有确实没跑成萃取的个别消息才写false并说明原因。当前批次与历史批次(首采回溯、积压拆批)一视同仁。 - 模型标识如实填当前模型:
modelName必须是本轮实际执行分类与萃取的大模型,不许照抄示例、不许填占位值或泛称,也不许为了好看换成别的模型名;拿不到就留空并说明。平台会把该值直接显示在采集端「本地模型」列。 - 停用即停采:平台停用某群后,该群会从
groups清单消失;下一轮务必重新拉清单,不要沿用旧清单。DISCONNECTED不是停用,它是「还没完成首次上报」的接入状态,不需要任何启用动作,首报命中即自动接入。 - 未接入的群不许静默跳过:
fetched = 0且群是DISCONNECTED时,排除取数故障后必须发握手批次(upload --allow-empty)。不发,这个群永远转不成已接入。 - 单群失败不扩散到整轮:任一群在取数、判定萃取、组包、上报任一步出错,都只记该群失败并继续处理其余群;不得因此中止整轮或跳过其他群的上报,也不得把失败群伪装成「没消息」的空批次上报。失败群不推进窗口,下一轮用原窗口重试,其余群照常推进。
- 令牌、群名、发送人名属于客户数据,不写进日志以外的任何外部系统。
平台会怎么反应(便于自检)
- 任一次成功上报:采集端变
ONLINE、刷新心跳、modelName回填「本地模型」列(空值不覆盖旧值,所以模型变更后要以新值上报才会刷新);被命中的群自动转CONNECTED、刷新last_sync_at,并按groups[].memberCount刷新群成员数。空批次(握手)同样算一次成功上报:messages为空也能靠groups[]把群接入。 - 拉群清单不算心跳,只有上报成功刷新心跳。
- 平台按采集端 + 启用状态筛选群清单,所以停用群、清单外群的上报都会被计为跳过。
- 群没有挂在当前采集端,或
groupId既不是平台 ID 又没给externalGroupId:该条被跳过 → 整批PARTIAL。 - 平台按
(collector_id, batch_no)和(group_id, external_id)去重,所以「窗口重叠 + 原样重试」都是安全的。 - 平台只记录上报的萃取状态,不会自己补做萃取:
extracted: false的消息会一直停在待萃取。重复上报同一条消息时平台会把未萃取回填成已萃取(只升不降),可用来订正存量,但首次上报就标成已完成才是正解。
参考文件
references/protocol.md:三块契约(群清单 / 上报 / 错误码)的字段、raw.json 格式。references/platform-dingtalk.md:钉钉取数参考(dwsCLI 登录、chat search定位openConversationId、message list翻页、话题消息、字段映射)。references/platform-wechat.md:个人微信取数参考(platform=WECHAT,wxCLI 读本机数据库;<CHAT>按名称模糊匹配的串群风险)。references/platform-wecom.md:企业微信取数参考(platform=WECOM,wecom-cli机器人接口;7 天窗口与 10 人企业限制)。references/platform-feishu.md:飞书取数参考(lark-cli应用配置 + 用户授权、--as user与--as bot的差异、+chat-search定位chat_id、+chat-messages-list与话题展开、字段映射)。references/filtering.md:闲聊判定、脱敏掩码规则、上下文隔离、分类与萃取标准。references/runbook.md:定时运行、失败重试、常见故障排查、验收清单。assets/raw-example.json:可直接pack --dry-run跑通的样例。