钉钉组织架构识别
清海接入多燕瘦钉钉环境的第一步:通过钉钉通讯录 API 认识公司每个人,为后续销售管理场景提供身份基础。 角色维度的会话旅程见 角色场景,能力定义见 Skill 清单。
背景与现状
多燕瘦使用钉钉作为主要协同工具,清海需要通过钉钉通讯录建立组织感知能力。与飞书版本的核心差异在于:钉钉支持实时事件订阅,人员变动可实时推送而无需依赖定时轮询;同时钉钉的用户 ID 体系(userid 为企业内主键)与飞书(open_id 为主键)有所不同。
| 维度 | 说明 |
|---|---|
| 数据源 | 钉钉通讯录 API(新旧版并用,优先旧版字段完整性) |
| 团队规模 | 多燕瘦销售团队,10-100 人,多层级部门 |
| 部门结构 | 公司 → 部门(销售部/运营部/产品部…)→ 销售小组 |
| 通道 | 钉钉企业内部应用机器人 |
| 用户识别 | 机器人消息携带 senderStaffId(即 userid),企业内唯一 |
| 同步策略 | 每日全量同步一次 + 实时事件订阅补偿(人员变动即时更新) |
| 存储 | MongoDB unified_users 集合 + PersonIndex 内存索引 + 本地缓存 data/cache/dingtalk_contacts.json |
钉钉 API 版本选择
| 版本 | Host | Token 传递 | 用户主键 | 推荐场景 |
|---|---|---|---|---|
| 旧版 | oapi.dingtalk.com | URL 参数 ?access_token= | userid | 通讯录读取(字段最完整,文档成熟) |
| 新版 | api.dingtalk.com | Header x-acs-dingtalk-access-token | unionId | 新功能、OAuth 场景 |
本方案通讯录部分优先使用旧版 API:
manager_userid(直属上级)、leader_in_dept(是否部门主管)等关键字段仅旧版完整返回。
已实现 vs 待实现
| 能力 | 状态 | 说明 |
|---|---|---|
| 钉钉通讯录同步 | ⬜ 待实现 | 需新建 DingTalkSync.sync_contacts() |
| PersonIndex 内存索引 | ✅ 复用 | 已有实现,扩展 channels.dingtalk 即可 |
| PersonContextResolver | ✅ 复用 | 已有实现 |
| 身份自动识别 | ⬜ 待实现 | 需适配 senderStaffId → userid 匹配 |
| search_contacts 工具 | ✅ 复用 | 底层 PersonIndex 相同,无需改动 |
| 别名学习 / 平台绑定 | ✅ 复用 | save_contact_alias + link_platform_identity |
| 事件订阅接收端点 | ⬜ 待实现 | 钉钉特有,飞书版本无对应 |
| 人员变动实时更新 | ⬜ 待实现 | 基于事件订阅,优于飞书轮询方式 |
| 权限过滤 | ⬜ 待实现 | 与飞书版本一致,按角色分层 |
当前设计方式
本场景组织架构识别在系统层自动完成,AI 通过通用工具
search_contacts查询人员信息。
钉钉消息进来
↓
avatar_core.identify_user()
↓
PersonContextResolver.resolve_by_channel("dingtalk", userid) ← 从 unified_users 读组织信息
↓
system prompt Block 1: "你正在和张三对话,他是销售一组的销售经理" ← 身份注入
↓
AI 对话(如果用户问组织问题)
↓
AI 调用 search_contacts(query="销售部", department=...) ← 组织查询工具
↓
PersonIndex.search() → 返回结果场景与能力总览
钉钉组织架构识别覆盖 4 大会话场景:
场景 → 能力映射表
| 场景 | 触发方式 | 需要的能力 | 钉钉 API |
|---|---|---|---|
| 身份自动识别 | 收到任意消息时自动触发 | 通讯录同步 + PersonIndex + userid 解析 | POST /topapi/v2/user/get(按 userid 查详情) |
| 组织架构查询 | 用户主动询问 | PersonIndex 部门查询 | POST /topapi/v2/department/listsub |
| 人员信息查询 | 用户主动询问 | PersonIndex 人员查找 + PersonContext | POST /topapi/v2/user/list |
| 人员变动感知 | 钉钉事件推送(实时)+ 每日全量同步兜底 | 事件订阅 + 通讯录同步 | 事件:user_add_org / user_leave_org / user_modify_org |
能力触发机制
钉钉消息进来 → 身份自动识别(每条消息必经,avatar_core 自动完成)
→ AI 判断用户意图涉及组织查询
→ 调用 search_contacts / query_org_structure 工具
→ PersonIndex.search() → 整理结果 → 回复用户
钉钉事件推送 → 事件接收端点(HTTP 回调)
→ 解析 user_add_org / user_leave_org / user_modify_org
→ 更新 unified_users + 重建 PersonIndex场景一:身份自动识别
核心价值:员工在钉钉发消息时,清海自动知道对方是谁,带入身份认知。
识别流程
钉钉机器人消息 ID 字段
| 字段 | 类型 | 说明 |
|---|---|---|
senderStaffId | userid(企业内唯一) | 主键,企业内部机器人必有此字段 |
senderUnionId | unionId(全局唯一) | 新版消息有,可与旧版 userid 互转 |
senderId | 加密 openId | 格式 $:LWCP_v1:$xxx,通常不用 |
实践建议:直接用
senderStaffId(即userid)匹配unified_users.channels.dingtalk.userid,无需转换。
存储字段(unified_users.channels.dingtalk)
来自 POST https://oapi.dingtalk.com/topapi/v2/user/get:
| 钉钉 API 字段 | 存储位置 | 用途 |
|---|---|---|
userid | channels.dingtalk.userid | 消息身份匹配主键(= senderStaffId) |
unionid | channels.dingtalk.unionid | 全局唯一标识,备用 |
name | display_name | 显示名称 |
dept_id_list | channels.dingtalk.dept_id_list | 所属部门(关联部门树) |
title | channels.dingtalk.job_title | 职位 |
manager_userid | channels.dingtalk.manager_userid | 直属上级 userid |
leader_in_dept | channels.dingtalk.is_manager | 是否是部门主管 |
boss | channels.dingtalk.is_boss | 是否是企业 Boss |
hired_date | channels.dingtalk.hired_date | 入职时间 |
mobile | channels.dingtalk.mobile | 手机号(需申请权限) |
关键约束
senderStaffId在企业内唯一且稳定,识别准确率 100%- 员工离职后通过事件
user_leave_org实时将is_active设为False - 离职员工消息触达静默处理(不回复)
- 如
senderStaffId为空(自定义机器人场景),降级使用senderUnionId查询
场景二:组织架构查询
核心价值:自然语言查询公司组织结构,不用打开钉钉通讯录。
典型对话
| 用户问法 | 清海应答 | 需要的能力 |
|---|---|---|
| "公司有哪些部门" | 列出所有部门(名称 + 人数) | 部门列表查询 |
| "销售部有谁" | 列出销售部全部成员(姓名 + 职位) | 部门成员查询 |
| "销售部下面有几个组" | 列出子部门结构 | 部门树递归 |
| "一组几个人" | 返回人数 + 成员列表 | 部门成员查询 |
钉钉 API 数据来源
| 查询类型 | 钉钉 API | 关键参数 |
|---|---|---|
| 子部门列表 | POST /topapi/v2/department/listsub | dept_id(根部门为 1,仅返回一级,需递归) |
| 全量部门列表 | POST /topapi/department/list | 一次返回所有部门(旧版,同步用) |
| 部门成员列表 | POST /topapi/v2/user/list | dept_id、cursor(游标分页)、size(每页最多 100) |
| 部门返回字段 | — | dept_id、name、parent_id |
| 成员返回字段 | — | userid、name、title、dept_id_list、manager_userid |
关键约束
- 钉钉根部门
dept_id = 1(飞书是"0"),递归时注意 - 部门名支持模糊匹配:「一组」→「销售一组」
- 兼职/多部门人员以
dept_id_list[0]主部门为准 - 分页用游标:
cursor从 0 开始,has_more=true时继续翻页
场景三:人员信息查询
核心价值:快速了解某人的部门、职位、上下级,无需翻钉钉通讯录。
典型对话
| 用户问法 | 清海应答 | 需要的能力 |
|---|---|---|
| "张三是什么部门的" | 张三的部门 + 职位 | search_contacts(query="张三") |
| "张三的上级是谁" | 张三的直属上级(manager_userid 关联查询) | query_person_relations |
| "李四管哪些人" | 李四的直属下属列表 | query_person_relations(relation_type="members") |
| "小明是谁" | 模糊匹配结果(可能多个) | search_contacts(query="小明") |
上级关系查询流
关键约束
- 人名模糊匹配:PersonIndex 支持部分名、别名
manager_userid直接存储了上级 userid,无需额外 API 调用(同步时已关联)leader_in_dept字段标识该用户是否是某部门的主管(可能同时是多部门主管)- 多结果时列出选项让用户确认
场景四:人员变动感知
核心价值:实时感知入职、离职、调岗,保持组织数据准确。 与飞书版本的关键差异:钉钉支持事件订阅推送,无需单纯依赖轮询。
变动类型与处理
| 变动类型 | 钉钉事件 | 检测方式 | 清海处理 |
|---|---|---|---|
| 新员工入职 | user_add_org | 事件推送(实时) | 自动创建 unified_users 记录 + 更新 PersonIndex |
| 员工激活账号 | user_active_org | 事件推送(实时) | 更新 is_active=True |
| 员工信息变更 | user_modify_org | 事件推送(实时) | 更新字段,保留别名等非钉钉数据 |
| 员工离职 | user_leave_org | 事件推送(实时) | is_active=False + 消息静默 |
| 新增部门 | org_dept_create | 事件推送(实时) | 自动触发该部门成员同步 |
| 部门信息变更 | org_dept_modify | 事件推送(实时) | 更新部门名/层级 + 重建索引 |
| 部门删除 | org_dept_remove | 事件推送(实时) | 将该部门成员设为无部门,等待下次全量同步 |
双重保障策略
事件订阅配置
在钉钉开发者后台:应用 → 事件与回调 → 订阅以下事件类型,配置公网可访问的回调地址。
| 订阅事件 | 含义 |
|---|---|
user_add_org | 新员工加入企业 |
user_active_org | 员工激活账号 |
user_modify_org | 员工信息变更 |
user_leave_org | 员工离职 |
org_dept_create | 部门新增 |
org_dept_modify | 部门信息变更 |
org_dept_remove | 部门删除 |
关键约束
- 钉钉通讯录数据为权威源(
authoritative_sync),覆盖推断和观察数据 - 同步时保留
aliases、平台绑定等非钉钉来源数据 - 事件推送网络异常时,下次全量同步自动补偿
- 事件 payload 中仅含
userid,需调 API 获取最新用户详情
角色权限与场景覆盖
不同角色在各场景中的可见范围不同,详见 角色场景。
角色 × 场景矩阵
| 场景 | 老板 / 总监 | 销售经理 | 销售员工 |
|---|---|---|---|
| 身份自动识别 | 全员信息 | 全员信息 | 仅自己(系统自动) |
| 组织架构查询 | 全公司架构 + 人员明细 | 全公司架构 + 本组人员明细 | 全公司架构(公开信息) |
| 人员信息查询 | 全员详细信息 | 本组个人信息 + 其他组公开信息 | 仅自己的信息 |
| 人员变动感知 | 全公司变动 | 本组变动 | 仅自己相关 |
⚠️ 当前状态:权限过滤尚未实现,所有用户可查所有数据。
越权查询引导
与飞书版本的核心差异
| 维度 | 飞书版本 | 钉钉版本 |
|---|---|---|
| 用户主键 | open_id(ou_ 开头) | userid(senderStaffId) |
| Bot 消息 ID 字段 | open_id | senderStaffId |
| 同步策略 | 定时轮询(30 分钟) | 事件订阅(实时)+ 每日全量兜底 |
| 上级关系 | 通讯录 API 返回 leader_user_id | manager_userid 字段直接返回 |
| 离职检测 | 轮询时检测 status.is_resigned | 事件 user_leave_org 实时推送 |
| 部门根节点 | parent_department_id=0 | dept_id=1 |
| 部门 ID 字段 | open_department_id(od- 开头) | dept_id(整数) |
| 通道前缀 | channels.feishu | channels.dingtalk |
复用能力与全局约束
复用的现有能力、全局约束详见 Skill 清单 — 复用的现有能力 和 Skill 清单 — 全局约束。