ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从零构建AI驱动CRM:多源数据融合与动态画像引擎实战

从零构建AI驱动CRM:多源数据融合与动态画像引擎实战

先说个背景。我之前在一个做企业服务的团队里待过一阵,手头有个客户是做教育产品的,他们原来的CRM系统里存了上万条客户信息,字段不少——姓名、电话、来源渠道、最后跟进时间、备注,该有的都有。可销售每天打开这个系统,基本只干一件事:查一下客户上次聊到哪了。至于这个客户现在最可能需要什么、是不是快流失了、用什么话术跟进成功率更高,系统一概给不出答案。说白了,那套CRM只是把客户记录从纸质换成了电子,并没有把数据变成判断力。

后来我们在这个系统上做了个大改造,核心就三条线:把散落在业务系统、行为日志、客服记录里的数据全部揉在一起,形成多源数据融合的客户视图;在融合数据之上,用一套带时效衰减的标签引擎,实时生成每个客户的动态画像;最后基于画像,给销售和运营提供"该对谁说什么、什么时候说"的建议。整套系统的源码是我们一行行从零写的,过程中踩了不少坑,也沉淀了一些能直接复用的设计思路。这篇文章就把整个构建过程拆开讲,从架构设计讲到核心代码,再到上线三个月后的实际效果,适合正在做CRM升级、想引入AI能力但不想直接套用大厂产品的技术团队参考。

1. 传统CRM的"有数无用"困局:为什么要做AI驱动

1.1 旧系统最大的问题不是缺数据,而是缺"算数据"的能力

先说结论:绝大多数传统CRM里的客户数据,是"死"的。死的意思是,数据被存下来之后,除了人工翻看,没有任何程序会主动利用它去做推理。

我们接手那家教育产品客户时,第一件事是盘点他们手头有什么。一盘点发现数据真不少:

  • 报名表里的静态资料:年龄、地区、职业、意向课程
  • 销售跟进记录:几百条通话备注、微信聊天摘要
  • 线上行为日志:官网浏览了哪些课程页、试听视频看了几分钟
  • 订单数据:买过什么课、客单价多少、续费周期多长
  • 客服工单:投诉过什么、咨询过什么

这些数据散在四个不同的系统里,客户ID都不统一。销售A用手机号打标记,销售B用姓名加备注,客服那边又是另一套工单号。想跨系统去回答"这个客户最近三周频繁看留学课程但没下单,是不是快被竞品抢走了"这种问题,人肉几乎做不到——不是不想做,是数据链路压根没打通。

我把这个阶段总结成"有数无用":数据量足够,但每个数据孤岛都只能回答一个非常窄的问题。报表系统能告诉你"上周新增线索500条",却回答不了"这500条里哪些人最快有可能成交"。

1.2 AI驱动CRM和传统CRM的本质区别:从"记录系统"变"决策系统"

传统CRM的模型本质是一张张静态表:客户表、跟进表、订单表。系统做的事情是录、存、查、改。AI驱动的CRM,底层模型完全变了,我习惯这么理解:传统CRM把客户当成一条记录,AI驱动的CRM把客户当成一个不断演化的向量。

什么意思呢?传统CRM里,一个客户的画像等于他报名时填的那张表:王小明,28岁,北京,想学Python。这个画像从录入那天起就不变了,哪怕王小明三个月后已经学完基础课、开始看进阶课程、甚至已经出现明显的续费意向,系统里他的画像还是那四个字段。

AI驱动的思路是:画像不是存出来的,是算出来的。每次产生新的行为数据,比如王小明今天点开了"数据分析进阶班"的详情页、停留了8分钟,系统就要用这个新事件去更新他整个画像向量——学习阶段是基础还是进阶、需求强度是低还是高、购买倾向是观望还是强烈。这个"算出来"的动作背后,就是多源数据融合和动态画像生成这两个核心模块。

1.3 项目边界设定:不做什么,比做什么更重要

这类项目最容易翻车的地方,一上来就想着把推荐系统、智能外呼、自动化营销全部做了。我们这次一开始就划清了边界,只做两件最核心的事:

第一,多源数据融合,目标是构建一个统一、干净、可查询的客户特征数据集; 第二,动态画像生成,目标是对每一个客户实时输出一份"当前状态说明书"——他是谁、现在处于什么阶段、接下来最可能做什么。

至于画像出来之后怎么用——是给销售推话术,还是给运营做自动营销——那是下游系统的事,不在这一次的范围内。这个边界设定非常关键,它让整个团队的精力能聚焦在数据链路和画像引擎这两个地基上,而不是被各种花哨的业务功能拖着跑。

提示:如果你的团队也想从零构建AI驱动的CRM,我建议第一版也只做数据融合和画像引擎,把"用画像做事"的环节留给第二期。地基不打牢,上面盖什么都会歪。

2. 多源数据融合:从"数据堆"到"数据湖"再到"特征集合"

多源数据融合这一步,听起来高大上,拆开就是三件事:把数据接进来、把同一个客户的记录合并成一个人、把合并后的人变成一张特征宽表。我一步步说。

2.1 数据源盘点:先搞清楚你要融合的到底是什么

动手写代码之前,我们花了整整一周做数据源盘点。这个过程不能省,因为后面所有的ID映射、字段标准化,都得基于这份盘点清单来做。盘点下来,数据源大概能分成四类:

数据源类型具体内容举例主要价值
业务系统数据报名信息、订单记录、合同数据回答"客户是谁"
交互行为数据网页浏览、视频观看、表单填写回答"客户现在对什么感兴趣"
沟通记录数据销售跟进备注、客服工单、聊天记录回答"客户对服务是否满意"
外部补充数据企业公开信息、公开招标动态等回答"客户背后还有什么潜力"

教育这个案例里,前三类是主力,外部数据相对少。如果换到B2B销售场景,外部数据反而会成为重头戏。不管哪种情况,核心原则一样:数据源盘点时必须把"数据从哪来、什么格式、更新频率多高、质量如何"四件事写清楚,否则后面做数据同步一定会被频繁返工。

2.2 第一层融合:接入层的标准化处理

数据接进来之后,第一件要做的事就是标准化,不标准化的数据后面根本没法融合。我们当时遇到的情况非常典型:

同一个字段,四个系统的叫法都不一样。业务系统叫"客户名",行为日志叫"user_name",客服系统叫"contact_person"。同一个时间,有的是字符串"2024-03-15 14:23",有的是时间戳"1710483780"。同一个来源渠道,CRM里写"线上广告",行为系统里写"ads_baidu",一个中文一个英文。

这种混乱如果不在接入层解决,后面每次写SQL查数据都要带一堆CASE WHEN,痛苦不堪。

我们的做法是定义一套统一的数据模型,所有源数据进来先过一遍转换器,把字段名统一成标准命名,把时间统一成ISO 8601格式,把枚举值统一映射到标准字典。这一步没什么技术含量,但全部做完之后,数据仓库里的表终于能让人看懂了。代码层面的核心是这样的:

def standardize_contact(record, source_system): """ 不同系统的客户记录标准化入口 source_system: "crm" / "behavior" / "support" """ mapping = { "crm": { "客户名": "customer_name", "手机号": "phone", "跟进状态": "follow_up_status", }, "behavior": { "user_name": "customer_name", "user_phone": "phone", "page_url": "page_url", }, "support": { "contact_person": "customer_name", "contact_phone": "phone", "ticket_status": "support_status", }, } field_map = mapping.get(source_system, {}) standardized = {} for src_field, value in record.items(): std_field = field_map.get(src_field, src_field) standardized[std_field] = value # 统一时间格式:无论输入是字符串还是时间戳,都转成 datetime if "event_time" in standardized: standardized["event_time"] = normalize_time(standardized["event_time"]) return standardized

这里面的关键不是代码本身,而是field_map这张映射表。这张表是我们和四个系统的对接人一个个字段核对出来的,前后对了好几轮。做数据融合,花在沟通上的时间永远比写代码的时间多。

2.3 第二层融合:实体解析与客户合并

标准化做完之后,最头大的问题出现了——同一个真人,在不同系统里长着完全不同的样子。举几个真实遇到的例子:

  • 手机号一样,但业务系统里名字是"张伟",客服系统里是"张先生"
  • 电话号码填的座机,但邮箱和行为系统的登录账号能对上
  • 同一个客户,因为后缀多了空格,被存成了两条记录

这个环节在数据工程里叫实体解析,简单说就是判断两条记录到底是不是同一个人。我们第一版想直接上一个相似度计算,对所有记录两两比对,但很快发现一个残酷的现实:客户总量几万条,两两比对的计算量是几亿次,跑一轮要几个小时,而且误判率很高——叫"张伟"的人全城可能好几个,光看姓名和手机号根本分不清。

后来我们换了策略,搞了一个"规则引擎 + 相似度评分"的两步走方案。第一步用高置信规则直接合并,比如手机号完全一致、并且姓名拼音一致的直接合并成一个人,这种规则查得快、误判率极低。第二步才轮到相似度计算,而且只在规则没法判定的"疑似对"上跑。

def merge_candidates(records): """ 返回潜在相同的客户记录对 """ # 第一步:精确规则索引 phone_index = {} for rec in records: phone = rec.get("phone", "").strip() if phone and len(phone) == 11: phone_index.setdefault(phone, []).append(rec) # 第二步:只有同一手机号下超过1条记录时,才需要做合并判断 candidates = [] for phone, recs in phone_index.items(): if len(recs) > 1: candidates.append(recs) return candidates def similarity_score(r1, r2): """ 名字相似度 + 邮箱相似度 + 地区相似度的加权组合 """ score = 0.0 name_score = 1.0 if r1.get("customer_name") == r2.get("customer_name") else 0.3 email_score = 1.0 if r1.get("email") == r2.get("email") else 0.0 region_score = 1.0 if r1.get("region") == r2.get("region") else 0.4 score = name_score * 0.4 + email_score * 0.4 + region_score * 0.2 return score

这个阶段我最大的体会有两个。第一,不要迷信算法,规则永远优先。第二,合并逻辑一定要做人工抽检。我们第一版跑完合并后,随机抽了200组结果人工比对,发现有差不多5%的合并是错的,后来加了更严格的姓名一致性校验,才把误合并率降到1%以内。

2.4 第三层融合:从"人"到"特征宽表"

客户合并完成后,进入了第三个环节:构建特征集合。我们用的是业界比较成熟的宽表方案。

宽表,简单说就是把一个客户所有相关的特征,揉成一行超宽的表,行的粒度是一个客户,列就是各种特征字段。这样下游不管是做规则判断还是训练模型,直接取一行数据就行,不需要再到处关联查表。

这张宽表里的特征,我们分成了三层:

  • 原子特征:直接来自原始数据的字段,比如年龄、城市、注册时间、课程类型。这些不做加工,是什么就存什么。
  • 派生特征:对多个原子特征做简单计算得到的,比如"距上次登录天数""累计购买课程数""近30天访问次数"。
  • 模型特征:通过统计或者算法生成的指标,比如"近90天活跃度趋势斜率""高意向课程类型概率",这类特征往往需要跑批任务计算。

举一个实际的特征计算例子。教育场景里,"活跃度"是一个关键特征,但活跃度不能只看登录次数,得结合行为的类型和频次算一个加权分。

def compute_activity_score(events, active_days=30): """ 计算客户在指定时间窗口内的活跃度得分 访问官网 = 1分,播放试听视频 = 3分,提交报名表单 = 5分 """ window_start = datetime.now() - timedelta(days=active_days) score = 0.0 for ev in events: if ev["event_time"] < window_start: continue if ev["event_type"] == "page_view": score += 1 elif ev["event_type"] == "video_play": score += 3 elif ev["event_type"] == "form_submit": score += 5 return score

如果你仔细观察会发现,这个活跃度不只是"最近来没来",还暗含了"来干了什么"。"逛页面"和"提交报名"显然不是一个量级的行为,它们体现的需求强度差着档次。这种设计思想在特征工程里叫"行为编码"——不是记录行为本身,而是把行为编码成能反映意图的数值。

到这一步,多源数据融合的骨架就搭完了。数据从四面八方进来,经过标准化、合并、特征计算,最终沉淀到一张宽表里。这张宽表,就是动态画像生成的地基。

3. 动态画像生成:三层标签体系与时效衰减机制

有了统一特征宽表,画像引擎的上层建筑就可以开工了。先说清楚我理解的"动态画像"和传统标签有什么不一样。

3.1 画像不是一堆静态属性:三层标签体系

很多厂商说自己的CRM有"客户画像",点开一看,就是"年龄、性别、地域、兴趣偏好"这几个静态字段。这种画像有一个致命问题:它描述的是客户昨天的样子,而不是客户今天的状态。

我们这次设计画像标签时,定了一个硬要求:画像里的标签必须分成三层来管理。

第一层叫静态属性层。就是那些基本不变的客观事实,比如姓名、性别、所在城市、学历、加入时间。这些字段来源于报名表,几乎不需要算法参与,定期从宽表同步即可。

第二层叫动态状态层。这一层描述的是客户近期的状态,比如"近30天活跃度评分""当前学习阶段""最近一次互动距今X天"。这些标签的核心特征是"会变",而且变得很快。客户一周没来,他的活跃度标签就要往下掉。

第三层叫预测意图层。这一层完全靠模型推理,比如"续费意向评分""流失风险等级""最可能感兴趣的课程品类"。它不是对事实的描述,而是对未来的推测。预测层是动态画像最核心的价值——销售最想知道的不是客户过去买了什么,而是客户接下来最可能做什么。

三层标签体系的落地,靠的是一套标签计算引擎。这个引擎的核心功能就一个:根据宽表里的特征数据,实时算出一个客户当前所有标签的值。

3.2 标签权重与时间衰减:为什么客户画像必须"保鲜"

动态画像和静态画像最大的分水岭,就在于时间衰减。

我们一开始犯过一个错误:客户的"留学意向"标签,三个月前他看了大量留学课程,模型给他打了"高意向"分。结果三个月后他其实已经报了一家线下机构,不打算在线上买了,但因为中间没留下新的行为数据,他的画像标签一直挂着"高意向"。销售按这个画像去打回访电话,话术完全是错的。

解决这个问题,需要给所有动态标签加一个时间衰减机制。核心逻辑是:客户过去的行为对他们当前状态的预测价值,会随着时间推移越来越低。

衰减函数我们用的是指数衰减。公式很简单:

score_now = score_original * exp(-lambda * days_since_event)

lambda是衰减系数,控制多快衰减。对于不同的行为,lambda应该不一样:

  • 浏览行为衰减最快,三天不看就掉一半,lambda约等于0.23
  • 试听视频衰减适中,两周内还有效,lambda约等于0.05
  • 正式订单衰减最慢,半年还有影响,lambda约等于0.004

代码实现起来并不复杂,但选对每个事件的lambda值,需要和业务方反复沟通确认。我们的经验是,第一版先给一个经验值,上线后跑两周,对比衰减前后的画像准确率,再逐步调参。

import math from datetime import datetime, timedelta DECAY_CONFIG = { "page_view": 0.23, # 浏览行为,3天半衰期 "video_play": 0.05, # 视频试听,14天半衰期 "form_submit": 0.02, # 表单提交,35天半衰期 "purchase": 0.004, # 正式购买,173天半衰期 } def decayed_score(event_score, event_type, event_time): """ 对历史事件分数做时间衰减 """ lambda_val = DECAY_CONFIG.get(event_type, 0.05) days_since = max((datetime.now() - event_time).days, 0) return round(event_score * math.exp(-lambda_val * days_since), 4)

这个函数写完之后,整个画像引擎的"动态"就真正落地了。每一次行为的发生,都会触发这个客户所有相关标签的重新计算,标签值会随着时间自动往下降,直到有新的行为事件把它重新拉起来。客户在系统里就不再是一个静态的单据,而是一个随时会变化的活模型。

3.3 从标签到画像:场景化视图的组装

标签算出来之后,还有一个细节容易忽略:不同角色对画像的需求完全不同。

销售关心的是客户近期的意向状态和即将流失的风险,他们希望开门见山一眼看到"这个客户值不值得跟进"。运营关心的是一批客户的共性特征,他们要的是"最近一个月高意向客户集中在哪个城市、哪类课程"。管理层关心的则是宏观变化趋势,比如"全网客户的健康度是在上升还是下降"。

如果所有角色都去看同一张标签宽表,信息过载会很严重。我们的做法是做了一套场景化视图组装器:底层还是同一套标签数据,但根据不同场景,只暴露该场景需要的标签子集和视图。

比如销售工作台展示的客户画像视图,就是一个极简化的卡片:

王小明 | 高意向客户

  • 意向课程:数据分析进阶班(评分 87)
  • 活跃趋势:近7天活跃度下降30%
  • 风险提示:线下面试课程两周未访问,可能已经被竞品触达
  • 建议动作:优先回访,推荐新一轮试听体验课

这背后调用的是一段视图组装逻辑,从标签库里按需取数据。这样的好处是,画像引擎整个逻辑都在后端统一维护,前端的展示层只是按角色做过滤和排序,不会出现"销售看到的画像和运营看到的画像对不上"这种各说各话的情况。

4. 核心源码实现:画像引擎的骨架代码

前面讲了不少概念和设计,这一节把关键代码骨架放出来,方便想复现的团队有个入手点。这部分代码不是完整生产版本,但核心链路是通的,直接改改就能跑起来。

4.1 数据融合管线的核心链路

融合管线我们用Python写,数据同步用定时任务触发,每五分钟增量拉取一次。管线的主流程分五步:拉取 → 标准化 → 实体匹配 → 宽表更新 → 画像重算。

def run_ingestion_pipeline(): """ 增量数据融合主流程 """ # 1. 从各数据源拉取增量数据 crm_records = fetch_incremental("crm", since=last_sync_time) behavior_records = fetch_incremental("behavior", since=last_sync_time) support_records = fetch_incremental("support", since=last_sync_time) # 2. 标准化所有记录 all_std = [] for src, records in [ ("crm", crm_records), ("behavior", behavior_records), ("support", support_records), ]: for rec in records: all_std.append(standardize_contact(rec, src)) # 3. 实体匹配:找到同一客户,合并到统一客户ID unified_customers = merge_to_unified_id(all_std) # 4. 更新特征宽表 for customer_id, records in unified_customers.items(): feature_row = build_feature_row(customer_id, records) upsert_feature_row(feature_row) # 5. 触发画像重算 for customer_id in unified_customers.keys(): refresh_customer_profile(customer_id)

这段代码没什么花活,但它代表了一种很重要的工程原则:每条数据从进来到产生业务价值的路径,必须是确定且可追踪的。每五分钟一轮,每一轮都有日志记录,哪条数据从哪个系统进来、有没有匹配上客户ID、有没有进入宽表,出了脏数据能直接回溯。没有这种可观测性,数据融合管线后期会变成一团乱麻,出了问题完全没法查。

4.2 画像打分引擎的实现

画像打分引擎是动态画像的核心,它的任务是接收一个客户ID,读取该客户的特征宽表数据,结合时间衰减机制,输出所有标签的当前值。

def refresh_customer_profile(customer_id): """ 重算指定客户的动态画像 """ customer_features = load_feature_row(customer_id) events = load_customer_events(customer_id, days=180) # 1. 活跃度标签 activity_score = compute_activity_score(events) # 2. 兴趣标签:统计近90天各课程类别的事件分数,并做衰减 interest_scores = {"数据分析": 0, "机器学习": 0, "产品经理": 0} for ev in events: if ev["event_type"] in DECAY_CONFIG: category = ev.get("course_category", "") if category in interest_scores: decayed = decayed_score(ev.get("event_weight", 1), ev["event_type"], ev["event_time"]) interest_scores[category] += decayed # 3. 流失风险:活跃度低 + 近30天无互动 + 曾经高频 risk_score = compute_churn_risk(customer_features, activity_score, events) # 4. 购买意向:综合兴趣强度、活跃度、历史购买周期 intent_score = compute_purchase_intent(interest_scores, activity_score, customer_features) # 写入画像存储 profile = { "customer_id": customer_id, "activity_score": activity_score, "interest_scores": interest_scores, "churn_risk": risk_score, "purchase_intent": intent_score, "updated_at": datetime.now(), } save_profile(profile) return profile

这个引擎在设计上有个核心思路叫"可解释的画像"。每个画像标签不仅存了最终的分值,还存了计算它的依据——哪些事件贡献了分数、贡献了多大数据是向后兼容的,画像引擎的兄弟模块可以直接沿用同一套数据模型。延迟要求的场景,把画像接口做成准实时,策略就灵活很多。

上线两周后,我们把慢路径和快路径的画像一致性做了对比,发现90%以上的客户两条路径算出来的画像是一致的。剩下不一致的主要集中在活跃度极高或极低的极端客户身上,触发不够灵。后来把这部分客户加白名单,强制定时重算,差异基本消失。

5.3 业务方"信任"问题:画像要给理由,而不是只给分数

这个坑从项目一开始就有,我们花了很大力气才解决。业务方一开始看到"购买意向评分87"这种数字,第一反应不是"好,我去跟进",而是"这87是哪来的?凭什么说这个客户意向高?"如果不回答这个问题,再准的画像也推不动业务落地。

我们的解法是做了一套画像解释器:每一个标签分值,都必须附带它的主要贡献因子和可读的说明。

放到实际场景里演示一下。销售打开客户王小明的主页,看到"课程意向评分 87",他不会一头雾水,页面上会同步展示:

该评分主要基于以下依据:

  1. 近7天3次访问"数据分析进阶班"详情页
  2. 近14天完整观看2场直播试听课
  3. 历史购课周期距离上次已满75天,进入复购窗口
  4. 近30天活跃度上升40%

这样业务方对画像的接受度就完全不一样了。分数只是结论,理由才是决策依据。构建这套解释器的时候,等于是为每个画像标签做了一个小型归因模块,它记录了"标签值=哪些特征的函数"。技术上不复杂,但对项目能否真正落地,起到了决定性的作用。

6. 实测效果与扩展思路

6.1 跑了三个月的对比数据

改造完成、上线运行三个月后,我们对效果做了一次复盘。对比维度用的是同一批销售、同一批客户池,只看画像系统上线前后的差别。数据表现如下:

指标上线前上线后变化幅度
线索平均响应时间42分钟11分钟-73%
线索到试听的转化率8.2%13.6%+65%
沉醒客户唤醒成功率3.1%7.8%+151%
单个销售月度有效跟进量58次83次+43%
客户流失率(月度)6.4%4.7%-26%

有几个数字需要解释一下。线索响应时间为什么能降这么多?因为画像系统会在新线索进入时,实时打上意向评分,评分超过阈值的线索会自动置顶到销售工作台,销售不需要自己翻列表判断先跟谁。沉醒客户唤醒成功率的提升,靠的是画像的"流失预警"功能——以前是客户彻底不来了才被当成沉睡用户,现在画像能提前2到3周发出风险提示,运营有充足时间去做挽回动作。

6.2 从画像到推荐:后续的扩展方向

画像引擎上线稳定后,很自然会走向下一步:基于画像的推荐与自动化。这个方向我们已经在做,核心思路是画像做"懂客户",推荐做"喂内容"。

具体链路是:客户画像标签 → 匹配内容标签 → 生成触达策略。比如画像判断客户是"数据分析方向、近30天活跃度上升、当前处于观望期",推荐引擎就从课程库里匹配出"数据分析进阶班+本周直播试听",通过销售或自动触达推给客户。

这个扩展方向一旦跑通,CRM就不再只是一个销售工具,而是变成一个客户运营的自动化中枢。但我不建议一上来就做完整推荐系统,推荐的效果高度依赖画像的准确度,地基没打好就上推荐,只会让客户收到一堆不痛不痒的推送,反而损伤体验。

6.3 个人体会:这套系统的本质,是让数据替业务人员先读一遍客户

最后说点个人的真实感受。做完这个项目,我对"AI驱动CRM"这件事的理解变了很多。以前觉得AI驱动就是要上大模型、做智能交互,做完才明白,大部分客户关系管理的智能化,最关键的其实不是模型有多强,而是底层数据有多通、画像有多准。

多源数据融合解决的是"让数据互通",动态画像解决的是"让数据说话"。这两件事做扎实后,哪怕接一个非常简单的规则引擎,也能让业务效率产生质的变化。反过来,如果底层数据还是一堆孤岛,就算接上最先进的模型,喂进去的也是残缺不全的信息,输出自然不会好看。

这套系统对技术团队的启发,说到底是六个字:**先治理,再智能。**每一步技术选型,都要先问问它能不能沉淀到客户数据资产里。数据资产厚了,AI才有得算。这个路子,走完之后再看回原来的CRM,就像从黑白电视换到了高清屏——客户不再是那一行行枯燥的记录,而是有温度、有意图、活生生的画像。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进