ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用LLM从沟通记录中结构化提取客户信息并写入CRM的工程实践

用LLM从沟通记录中结构化提取客户信息并写入CRM的工程实践 我接这个需求的时候客户那边给到的诉求其实很简单市场部和销售部每天产生大量的企微聊天记录、邮件往来和通话转写文本以前全靠业务助理一条条看完再手工把客户信息、商机进展、下一步跟进时间录进CRM。一个月下来光录单就占掉一个助理大半的工作量而且录错、漏录是常态。所以目标就变成了——用LLM对非结构化沟通记录做结构化提取再批量写入CRM。听起来很直接真正开工才发现这里的坑比预想的多得多提示词怎么约束、返回的JSON不稳定怎么办、批量写入怎么保证不重复、Token成本会不会失控。这篇文章把我从设计到落地全过程的思路、踩坑和最终方案完整复现一遍给正在做类似事情的人一个可参考的工程路径。1. 为什么规则引擎做不了这件事从一次失败的抽取说起1.1 沟通记录的多样性远比想象中复杂先看一条典型的沟通记录这种文本在真实业务里每天都在产生“李总说下周三下午来公司看演示预算大概三十万左右主要看我们的私有化部署能力还说如果合适会带技术负责人一起来。对了王哥说他已经在OA上提交了合作意向但具体金额还没定让我周三前给个初步方案。”如果让你手动抽取字段你会得到客户联系人“李总”、时间“下周三下午”、金额“三十万左右”、意向产品“私有化部署”、关键动作“带技术负责人来看演示”、辅助联系人“王哥”、任务“周三前给初步方案”。但让规则引擎来做这件事麻烦就来了“下周三”是一个相对时间需要基于消息当天日期换算正则只能匹配“周几”换算逻辑要额外写。“三十万左右”不是精确值带模糊修饰词直接抽出来是“三十万左右”入库前要清洗。“主要看我们的私有化部署能力”和“客户预算”是什么关系规则无法理解这层语义。还有隐含信息——“如果合适会带技术负责人一起来”这说明跟进阶段可能在“方案演示”和“技术评审”之间这种阶段判断规则引擎基本做不了。类似文本再叠加上不同销售的表达习惯、不同地域的口语化措辞、键盘输入导致的错别字规则引擎的匹配字典会无限膨胀。1.2 规则引擎的维护成本曲线在决定上LLM之前我们其实先用正则表达式做过一轮抽取原型。第一批20条规则处理100条测试样本准确率能到80%出头。我当时觉得还行毕竟写的都是核心字段漏的也都是边角信息。问题出在第二个团队接入之后。每个团队带来的沟通记录风格差异很大有的销售习惯用短句分条发消息有的习惯大段文字还有人喜欢在中间插语音转写片段。规则从20条涨到80条准确率反而跌到70%以下。原因很典型——规则冲突和误匹配一条匹配客户电话的正则在某个文本里误伤了一个QQ号导致客户手机号被清洗成错误值。新增的金额规则和原有的日期规则在“预算30万下周三定”这种句子里相互干扰抽取结果前后矛盾。维护这套正则的时间每周都要投入三四个小时而且每次调整都存在“修了A场景踩了B场景”的风险。这个成本曲线是不可持续的。所以我做了一个ROI对比继续维护规则引擎 vs 引入LLM抽取。对比下来LLM在一次性接入成本上更高但长期维护成本和跨团队复制成本都低一个量级。1.3 转向LLM后的能力边界判断这里必须强调一个工程上的清醒认识——LLM不是用来解决所有问题的。我的方案里LLM只承担“从自由文本中抽取信息并映射为标准化中间字段”这一件事其他工作全部交给代码收到文本先做清洗去签名、去转发链、URL归一化。LLM输出的原始结果先做JSON Schema校验不合格就触发修复链路。字段里的枚举值客户来源、产品线由代码再做一轮映射不依赖LLM的记忆。CRM写入做幂等和去重和LLM完全无关。为什么这样切因为LLM擅长的是“把语义理解转化为结构化表达”但它的输出天然带有概率性不擅长做精确匹配和状态判断。把所有确定性逻辑从LLM中剥离出去系统的可观测性和稳定性才会真正可控。2. 整体链路设计从碎片文本到CRM字段的全管道2.1 输入侧的清洗与上下文组装原始沟通记录不能直接扔给LLM。我踩过的第一个教训就是喂进去什么很大程度决定了输出的质量。原始文本里有大量与业务无关的内容典型的有邮件签名档、公司免责声明。转发链里的“-----原始邮件-----”内容和历史上下文。企微表情代码、URL、无关的自动通知。这些噪音不仅浪费Token还会干扰抽取准确率。所以输入侧我加了一个清洗层规则不复杂但收益很明显去掉回复引用链中超过两层的部分只保留最近一层的上下文。用正则剔除URL、邮箱签名、固定免责声明文本。Emoji统一替换为对应语义标签比如替换为[时间标记]避免特殊字符打乱切分。超过1400字的文本先做摘要压缩压缩时要求保留“客户、金额、时间、联系人、异议、承诺”等关键信息。清洗之后的文本干净很多后面的提示词和输出稳定性都有了基础。2.2 关键设计决策一次一记录而不是批量抽取这是整个方案里我认为最核心的决策——每条沟通记录单独调用一次LLM而不是把10条记录打包成一个数组让LLM一次性输出。我知道批量调用的单次成本低、吞吐量高看起来是更优解但在工程实践里有三个绕不开的问题第一是错误隔离。10条记录拼进一个Prompt只要其中一条触发幻觉输出的整个JSON数组可能全部解析失败或者某条数据污染了相邻记录。单条调用时失败只会影响当前这一条重试成本和控制逻辑都简单。第二是上下文干扰。连续多条不同客户的记录放在一起LLM在抽取时容易出现字段串位把A客户的时间安到B客户头上。尤其当记录里出现“王哥”“李总”这种相似称呼时串位概率会明显上升。第三是稳定性。我在测试阶段做过对比单条记录抽取时JSON合法率在98%以上批量拼接输出数组时合法率掉到90%左右。批量时一旦中间某条文本触发了模型的不稳定行为整个返回都作废。对于生产系统来说5%的解析失败率意味着每天要多出大量重试任务省下的Token又被重试吃回去了。2.3 输出侧的目标CRM字段映射策略LLM直接输出CRM字段是不可取的。各家CRM的字段体系高度定制化而且经常出现“一个字段在系统里拆成两个”“两个不同来源的值要合并到一个字段”这类情况。所以我在LLM和CRM之间加了一个标准中间层。LLM的输出统一走一套中间Schema字段全部用英文命名值只能是字符串、数字或null。然后映射层负责枚举翻译LLM输出“私有化部署”映射为产品线ID3。日期解析LLM输出“下周三下午”由代码结合消息发送日期计算成标准ISO时间。负责人归属根据消息发送人、客户群归属计算CRM中的负责人ID。这样设计的最大好处是哪天换了CRM系统只需要重写映射层LLM侧的提示词和Schema完全不用动。3. 提示词与JSON Schema约束让模型稳定输出结构化数据的核心3.1 提示词结构设计角色、任务、格式、示例缺一不可提示词是我调试时间最长的地方。一个稳定可用的结构化抽取提示词我的最终结构长这样你是客户信息抽取助手。我会给你一段真实的客户沟通记录请提取其中与客户跟进相关的字段。 要求 1. 只输出JSON不输出任何解释性文字。 2. 如果某个字段在原文中没有对应信息输出null禁止猜测。 3. 保持字段值使用原文中的原文表述不要改写。 4. 金额字段只输出数字和单位不要添加判断性语言。 请严格按照以下JSON Schema输出 {...schema内容...} 以下是几个抽取示例 {...few-shot示例...} 待抽取的沟通记录 {清洗后的原始文本}几个细节值得展开说第2条“禁止猜测”非常关键。LLM有一种倾向就是字段缺失时会根据常识补一个“合理值”这在抽取场景里是灾难。这条指令配合Schema里的null约束能把幻觉率压到很低。第3条“保持原文表述”是为了避免模型把“三十万左右”改写成“300000”因为改写过程可能引入计算错误。金额换算统一放到映射层做。强调“只输出JSON”是为了减少后续解析时的文本清理工作量。3.2 JSON Schema校验与Temperature参数的关系Schema不只是给模型看的更是给程序看的。我在提示词里内嵌了完整的JSON Schema定义同时在后端用校验器对模型输出做二次校验。这里有个很容易忽略的点校验器必须做字段级类型检查而不仅是检查“这段文本能不能被解析成JSON”。举个例子模型输出了{budget: 三十万左右}JSON解析是成功的Schema里如果定义budget为string类型校验也通过了。但映射层如果想计算总金额这个字符串就完全没法参与计算。所以中间Schema的设计里budget我用的是number类型加percent提示如果模型输出非数字校验直接失败进入修复链路。Temperature参数也值得说说。很多人知道抽取场景要把Temperature调低但不知道为什么。简单解释一下LLM生成时通过Softmax把token得分转换为概率分布Temperature就是用来缩放这个分布的。Temperature越高分布越平坦低概率token被采样到的可能性越大输出就越“发散”。在结构化抽取里我们需要的是高概率的确定性输出所以Temperature设在0到0.2之间最合适。设成0时输出基本是贪心解码稳定但可能略显生硬0.2左右会保留一点点多样性更适合有少量口语化变体的字段。3.3 few-shot示例的选样原则Few-shot示例不是越多越好。我最初放了6个完整示例覆盖各种场景结果Token消耗大且边际收益几乎为零。后来压到3个示例准确率反而没下降这说明了选样比数量重要。我的选样原则是让示例之间有区分度示例一字段齐全的标准记录让模型看到完整输出的样子。示例二一半字段缺失输出里对应字段为null教模型“宁可空着也不要编”。示例三包含模糊表达和相对时间如“这两天”、“下周左右”教模型保留原文表述而不自行推断。少放重复度高的完整模板因为模型看多了同质化示例并不会提升理解能力只会增加Token成本。4. JSON解析容错LLM返回不合法JSON时怎么办4.1 常见的非法JSON类型与根因不管提示词怎么约束LLM偶尔还是会返回不合法JSON。生产环境跑了一个月之后我汇总了非法返回的主要类型非法类型出现频率典型示例输出被Markdown围栏包裹高json {…}前面还带了一个“结果如下”字段值包含未转义引号中remark: 他说好的然后挂了布尔值大小写混用中has_decision: TruePython风格输出在结尾被截断低JSON最后一个括号缺失前后混入解释性文本低JSON前后夹杂“我已抽取完毕”等说明根因上Markdown围栏和解释文本大多是模型“过拟合”了训练时的格式习惯尤其当训练语料里大量出现“以下是结果”这类过渡句时。未转义引号多出现在原文包含对话场景时模型没做好字符串内部的转义。4.2 修复方案字符串层级清理、容错解析库与重试自修复我不建议直接在代码里写一个加载依赖的第三方库就完事而是把修复过程分层设计每一层只解决一层问题。第一层是字符串清理。不管模型输出了什么先把返回文本标准化去掉首尾空白提取第一个{到最后一个}之间的内容。这一步能解决Markdown围栏和前后解释文本。第二层是容错解析。字符串清理之后仍可能不是合法JSON。Java生态里我用过json-sanitizer做防御式清洗它能处理未转义引号、单引号包裹、缺失逗号这类常见问题。Python生态对应的是json-repair库。这两个库都不是万能药但对于LLM输出场景里的历史错误模式覆盖度很高。第三层是重试自修复。前两层都失败后我把解析错误信息直接回传模型你的上一次输出未通过JSON解析错误信息如下 {error_message} 请根据错误信息修正只输出合法的JSON不要解释。这个自修复链路能把绝大多数单次失败在第二轮转成功。需要提醒的是自修复的Prompt里必须带上明确的错误信息让模型知道具体哪里不合法笼统地说“输出不正确”效果很差。4.3 自修复链路要控制的止损机制自修复不是无限重试的。我在链路里设置了max_retries2两轮修复还没通过校验这条记录就被标记为EXTRACT_FAILED进入人工处理队列。为什么控制在两轮因为实测数据显示第一轮自修复成功率在60%左右第二轮只有15%左右第三轮基本在5%以下。继续重试不仅浪费Token而且模型在反复纠错中容易产生新的幻觉越修越离谱。另外我记录了一个retry_count字段这个数据后期很有价值——如果某个团队的消息记录retry_count普遍偏高说明清洗层的预处理还需要加强或者该团队的沟通风格需要新增few-shot示例。5. 批量写入CRM的幂等与去重工程5.1 字段映射中隐藏类型陷阱拿到LLM输出并通过校验之后写入CRM前还有一堆隐蔽的坑。最容易踩的是类型不匹配。LLM输出的budget是字符串“三十万左右”CRM里的金额字段是Decimal类型映射层必须先做转换。问题在于“三十万”和“300000”之间不是简单的格式化关系中文金额单位、口语表达“大概三十多”、逗号分隔符“300,000”都需要专门处理。日期字段同理。LLM输出“下周三下午”我需要拿到消息本身的发送时间计算出具体的日期和时间点。这里有个细节计算“下周三”时如果发送时间本身就是周三“下周三”应该理解为下个周三而不是当天这个判断代码里要写清楚。还有一个我不小心踩过的地方CRM里负责人字段存储的是用户ID而LLM输出的是人名。映射层需要做人名到ID的查询。如果两个销售同名就要求输入侧清洗时补充“部门”或“区域”信息否则无法唯一确定。5.2 写入幂等与重复记录识别批量写入最怕的不是写入失败而是同一批数据被重复执行。我们设计了两层幂等保护。第一层是源消息ID。每条沟通记录在进入管道时就生成了source_message_id写入CRM时这个ID会写到客户记录或跟进记录的自定义字段上。数据库层面建唯一索引如果同一条源消息被重复执行写入数据库直接拒绝。第二层是业务自然键。有些场景下同一条沟通内容可能通过不同渠道进入管道比如企微消息和通话转写里都提到了同一件事源消息ID不同但业务意义相同。这种情况就需要在CRM侧先做自然键查重比如“客户名称大致时间负责人”能匹配上就跳过新建改为更新补充字段。实测下来两层幂等把重复数据率从3%左右降到了接近于零。多写这几行代码非常值得。5.3 批处理失败的部分回滚策略批量写入时我还有一张处理状态表记录每条消息在管道中每个环节的状态source_message_id | extract_status | write_status | crm_record_id | error_msg | retry_count写入CRM时以10条为一个批次逐条执行写入。如果批次中间某几条失败我不会把整个批次回滚而是让成功的写入保留失败的标记为WRITE_FAILED并根据错误类型决定是立即重试还是转人工。这么设计的原因很简单CRM写入大多数失败是单条数据特有的字段超长、必填缺失、枚举值不合法回滚整个批次会导致明明成功的部分也丢掉了得不偿失。而保留成功后重试时只需要处理失败的那几条逻辑清晰。6. 成本与延迟优化批量场景下的Token控制实测6.1 Token消耗的大头在哪里上线一周后我统计了Token消耗发现一个反直觉的结论真正的大头不是待抽取的文本而是提示词本身。一次调用总共消耗Token大约1600个其中沟通记录正文只有500个左右系统提示词加few-shot示例占了800多个输出JSON占300个。也就是说提示词的固定开销接近单次调用的一半。如果每天处理3000条记录单是提示词固定开销就是240万Token。这说明在批量场景里优化提示词的长度比压缩输入文本更能显著降低成本。6.2 上下文裁剪与摘要重写策略我的解决方案是三层裁剪。第一层是精简系统提示词。把可以用一句话表述的规则用一句话few-shot从6个压到3个。实测准确率没变Token却省了40%。第二层是输入侧长度限制。超过1400字的记录先用一个小模型做摘要保留客户、金额、时间、异议点然后只把摘要传给抽取模型。摘要模型成本更低做压缩非常划算。这一点和很多人遇到的“上下文越长输出越不稳定”是同一个道理——我在实践里也观察到超过2000字的上下文会让JSON合法率下降5到8个百分点所以该截断时就要截断。第三层是Prompt缓存。系统提示词和few-shot示例是固定不变的LLM服务商如果支持Prompt缓存这部分Token每次调用都能命中缓存。我接入缓存后稳定场景的Token成本下降了近30%。6.3 缓存、模型分级与并发控制成本优化的另一条思路是模型分级。短文本200字以内、字段明确的高置信度记录走更便宜的小模型长文本、信息模糊、重试过一次的记录走能力更强的大模型。分级之后综合成本降了25%左右准确率波动可以忽略。并发控制方面我做了两步一是把批量写入安排在夜间低峰期二是调用LLM时采用半并发模式——同一时间最多10个并发请求超出部分排队。原因很简单LLM接口有速率限制盲目提高并发只会触发限流然后陷入“退避-重试-再限流”的恶性循环。用指数退避的策略处理限流异常实测比固定重试的稳定得多。7. 踩坑清单与可复用的经验7.1 我踩过的几个典型坑第一个坑是字段被补充成了“合理的假数据”。有一次模型把负责人口中的“可能下个月”抽取成了具体日期“2025-06-15”Schema也通过了但一看就知道是编的。原因是提示词里没有强调“不能将模糊时间转换为具体时间”。加了这条约束之后类似问题几乎绝迹。第二个坑是把金额设计成了字符串。早期版本里金额字段用string存储后面做商机汇总统计时才发现转换各种格式的字符串有多痛苦。重构成number类型后虽然解析失败率微微上升但数据可用性提升巨大。第三个坑是在校验不到位时就先上线了写入。第一周有几十条脏数据直接写进了生产CRM虽然靠幂等和标记字段能识别但清洗历史数据非常费劲。后来我在写入前加了一道强制校验必填字段缺失、日期格式错误、金额非数字这三种情况一律拦截不允许绕过。7.2 可复用的检查清单检查维度具体检查项建议提示词是否明确禁止猜测并允许输出null缺失字段必须输出null提示词是否要求保留原文表述模糊表达保持原样不要换算参数Temperature是否设置在0~0.2结构化抽取勿用高温度解析是否有多层容错清理词、容错库、自修复三层缺一不可校验是否有字段级Schema类型校验必须做类型检查非仅解析幂等是否有源消息ID唯一索引必须有幂等是否有业务自然键查重防止跨渠道重复映射是否有人名到ID、枚举到编码转换必须做不能依赖LLM成本是否精简few-shot并开启缓存提示词固定开销是成本大头重试是否设置最大重试次数建议2轮避免无限重试7.3 如果重新做一次我会怎么改进整套系统跑稳定之后我回过头审视整个方案最值得改进的是两点。第一一开始就引入人工修正反馈闭环。现在抽取失败的记录进入了人工处理队列但处理结果没有回灌到系统中。如果当时做一个反馈标注界面让人工修正后的数据成为few-shot示例的候选池模型准确率会随运行时间持续提升。第二把中间Schema设计得再薄一点。当前中间层有部分字段是从CRM字段直接复制过来的导致Schema里出现了几个不太合理的耦合字段。如果重新设计我会先和CRM团队一起梳理字段血缘再定Schema避免后期来回改提示词。我在实际运行中还有一个体会这类系统的核心不在于模型有多强而在于工程结构能否把模型的不可控性限制在一个小范围内。提示词控制格式校验器拦截非法输出映射层做确定性转换幂等机制保证数据不脏——每一层都解决一个具体问题最终整个管道才是可依赖的。如果你也在搭类似的抽取管道希望这篇实践记录能让你少走一些弯路。
返回列表