拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

大模型+查询工具:工单处理自动化实战入门

大模型+查询工具:工单处理自动化实战入门 先说一个我每天都会遇到的场景客服团队对着几百条文字工单发愁“用户说账号登不上”“用户说扣款了没到账”“用户说界面报错”每一条都要人工读一遍、分类、提取关键词、判断优先级、再复制到表格里。这套动作重复了无数次之后我终于决定不再写更多的Excel函数而是换一种思路用大模型做文字理解用一个查询工具做数据管理两条腿一起跑。这个标题里的“01”不是随便写的它是一个系列的开始核心词是“先跑起来”。什么意思呢就是不追求一步到位做出一套完美系统先把一条最基础的链路打通——大模型负责读工单、做判断查询工具负责把结果存起来、随时查出来、按需统计出来。它能解决的问题很具体工单分类、关键信息抽取、优先级判断、后续检索和统计。适合谁看呢适合那些手上有一堆文本工单、表格已经堆不下了、又不想一上来就上重系统的运营、技术、产品朋友。1. 先想清楚为什么是“大模型查询工具”的组合1.1 文字工单处理的核心痛点工单和普通文本最大的区别在于它有明确的“业务结果”诉求。它不是一个让模型读一读、感叹一下的文本而是需要被处置、被分发、被跟踪的文本。原始工单是这样的“你好我昨晚充值了100元但是余额没有变化订单号是20240612001麻烦快点帮我处理”。这种工单如果只靠关键词正则去匹配你能抓到“充值”“余额”但抓不到用户的情绪也判断不了这是支付延迟还是充值失败更别提区分“催办”和“投诉”的优先级差别。人工处理的问题就更直接了。工单量少的时候还可以靠人量一上来纯粹就是重复劳动。我在实际项目里见过一个团队每天花三个小时手动给工单打标签、填Excel、再群发给对应处理人。这不是能力问题是人力结构性错配的问题。真正需要人去判断的复杂工单没有时间看反而在简单重复的分类上浪费了大量精力。1.2 “先跑起来”背后的核心思维做这个项目最容易犯的错误是想一来就做一个“智能工单系统”又是知识库、又是微调、又是全自动分发、又是实时监控大屏。不是说这些不对而是对于一个从0开始的场景来说这套东西太重了。大模型接入有成本微调需要数据准备和算力知识库要做向量检索这套链路没有两周搭建和两周调优根本稳不下来。我这次刻意反着来。只解决一个最核心的问题把“读工单”这件最重复、最耗时的事情交给大模型把“存工单、查工单、统计工单”交给查询工具。至于后续要不要微调、要不要上更复杂的调度等基础链路跑通了再说。这个思路借鉴的是软件工程里的最小可行产品MVP逻辑先用一个最简单的闭环去验证价值再迭代。你不需要预测所有未来的需求你只需要确保现在这一版能用、能跑、能改。等你真的积累了几个月的数据再来谈“我们要不要针对某个细分场景做微调”“要不要把工单自动派给指定的人”那时候的判断才是基于真实的业务反馈而不是凭空猜的。1.3 方案选型的三个考量选型的时候我主要卡在三个问题上。第一个是成本大模型调用是按token计费的工单每天几百上千条每条又长如果设计不好提示词月底账单会让你怀疑人生。第二个是响应速度工单处理不是一个在线聊天场景用户可以等但在交互上我仍然希望单条处理在几秒内完成这样调试的时候体验才顺。第三个是可追溯性这也是我坚持引入查询工具而不是让大模型一次算完的原因。大模型拿来做判断没问题但你在它返回的一堆JSON里核对状态、统计类型分布就很痛苦而SQL天然就是干这个的。查了一下行业的常规做法最后我选了两件套一个大模型的API本地部署也可以稍后讲一个MySQL实例。MySQL几乎是这个场景里最稳妥的选择免费、轻量、团队里所有人都能用客户端连上后续即使换其他工具迁移也容易。当然你也可以用更轻的SQLite但考虑到后面要多账号并发查询、后续要接报表工具MySQL的通用性更好。2. 核心拆解大模型和查询工具分别吃什么2.1 大模型语义判断器不是计算器很多人把大模型当成一个“万能对话器”这是初期的常见误判。在我这个方案里大模型扮演的角色更像是一个“语义判断器”你给它一段工单文本它告诉你这是什么类型的工单、涉及哪些关键信息、用户情绪是急切还是不急切、应该优先处理还是常规处理就行。为什么这件事适合大模型因为工单的语言极度不标准。“我上不去账号”和“登录页面一直转圈半天不出验证码”这两句话从关键词上看几乎没有重合但语义上都属于登录类问题。传统的规则匹配在这里会遇到组合爆炸正则表达式写到后面对新增写法永远反应不过来。大模型基于语义判断天然能容忍这种表达差异。但在做“计算”这件事上大模型的可靠性就比较差了。你可以让它数出一段话有几句话它经常数错你可以让它判断某字段在数据库里出现多少次它不会告诉你它其实是在胡编。所以基础统计、聚合计算、精确匹配这类工作我从一开始就明确不交给它交给查询工具。2.2 查询工具结构化存储与灵活检索查询工具在方案里承担的职责用六个字概括存得下、查得出。工单的结构不是固定的系统内可能没有专门的工单表或者工单本来就在一个文本记录里所以我们第一步要建一张表把工单转成结构化数据。每个字段是什么、属于什么类型、什么时候创建、当前什么状态这些信息一旦结构化后续不管是按时间汇总、按分类统计、按优先级筛选WHERE 条件都能秒级完成。有一次我印象特别深业务方问“这周有多少工单是财务问题用户留言里提到退款这个词的有多少”。如果原始数据是Word文档或者聊天记录这个问题人工翻得翻到天黑。现在只要一条SQL几秒出结果。这就是查询工具存在的意义——它把“分析调研”类比为“查阅档案”大模型是大脑负责理解查询工具是档案室负责存取。2.3 两者边界怎么划如果你只用大模型结果是一个充满不确定性的来回对话如果你只用SQL你无法理解工单的文字内容。两者必须配合。我给自己定了一条铁律大模型只负责“文本到标签”的转换查询工具只负责“标签到判断”的检索统计。具体来说是三个安全边界。第一大模型不直接改数据库。它对工单的理解结果先作为JSON返回由我们的代码校验了格式再写库避免模型幻觉污染主数据。第二SQL不处理模糊语义。所有涉及“哪个工单更紧急”的判断都基于已经被标记的优先级数值来比较不会让SQL去读原文判断情绪。第三人工复核永远保留。大模型标注完的结果在正式处理之前留一个review队列每天由专人过一遍前一周每天看后面可以调整为抽查。2.4 工单建模的细节表格设计是整套方案的骨架这里分享一个我踩过坑之后的建模版本仅供参考。工单原始表maintaining字段包括id、ticket_no工单号、source来源渠道例如APP、电话、邮件、content_text原始工单文本、created_at创建时间。这个表尽量少动它是审计底稿保留用户最原始的文本内容。另外建一张ai_tagged表用来放大模型的处理结果ticket_no、category分类标签、priority优先级1-3、keywords关键信息建议存JSON或独立关联表、review_statusreview状态未处理/待复核/已确认、ai_model记录用的是哪个模型、ai_raw_output大模型返回的原始结果。好处是底稿和标注分离你可以随时对照“模型的判断”和“工单原文”不至于把模型幻觉污染进唯一数据源。这一点后面在排查问题的环节会反复讲到。3. 实操从零跑通一条工单处理流水线3.1 环境准备这一步其实很简单不需要多豪华的配置。我用了三样东西一台能联网的普通开发机Windows、Mac、Linux都可以一个MySQL实例本地或者云上都行一个支持Python的终端环境大模型接口我用的是当前市面上比较常见的商用API本地部署也完全可行。标题里提到“模型部署”如果你手头有支持推理的显卡部署一个7B或者14B参数的开源模型比如Qwen2.5系列同样可以完成这个流程里的分类和抽取任务。区别在于商用API省心本地部署数据不出内网、长期费用更低。小团队入门我建议先用API等量上来了再考虑迁移。还有一点要强调无论在什么网络环境下调用模型服务都必须符合服务商的使用规范部署环境要合规。3.2 建表与数据导入在MySQL里执行下面建表语句。注意字段类型的选择这是一个高频踩坑点工单原文如果是中文VARCHAR的长度要给足否则会有长度溢出报错时间字段直接用DATETIME未来做统计聚合方便ticket_no无论是数字还是带前缀字符串统一用VARCHAR避免后续系统对接时的类型问题。CREATE TABLE tickets_original ( id INT AUTO_INCREMENT PRIMARY KEY, ticket_no VARCHAR(64) NOT NULL UNIQUE, source VARCHAR(32) NOT NULL DEFAULT manual, content_text TEXT NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, INDEX idx_created_at (created_at) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE tickets_ai_tagged ( id INT AUTO_INCREMENT PRIMARY KEY, ticket_no VARCHAR(64) NOT NULL, category VARCHAR(64) NULL, priority TINYINT NULL, keywords JSON DEFAULT NULL, review_status ENUM(pending,reviewed,confirmed) NOT NULL DEFAULT pending, ai_model VARCHAR(64) NULL, ai_raw_output JSON NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_ticket_no (ticket_no), KEY idx_category (category), KEY idx_priority (priority), KEY idx_review_status (review_status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;导入数据的时候我建议一次性从旧的Excel或CSV里批量灌进tickets_original表之后新工单可以继续往这张表INSERT。有一点要注意不要用Excel本身当数据库长期运行Excel做小规模数据清洗很爽但数据一多文件打开就卡更没法多并发查询。3.3 提示词设计让大模型输出结构化结果项目的灵魂在提示词这里。我做了很多次实验后发现最稳妥的提示词结构是“角色设定任务说明输出约束示例”。直接把我的提示词模板放出来中文版你根据业务自行调整。你是一个工单分析助手。请分析下面这条工单只输出JSON格式不要附加任何解释文字。 工单内容{工单原文} 要求 1. category字段从以下选项中选择一个最合适的账号登录、支付充值、网络连接、设备故障、投诉建议、其他。 2. priority字段填1、2或31表示影响核心功能且用户明确催促或情绪激动2表示功能可用但有异常或部分受阻3表示咨询、建议或非紧急问题。 3. keywords字段列出工单中关键的实体信息标准是“能帮助客服快速定位问题”例如账号、订单号、报错码、时间、金额最多列5个每个都是字符串。 4. 如果工单内容不足以判断category填“其他”priority填3keywords填[]不要编造。 示例输出 {category: 账号登录, priority: 2, keywords: [登录失败, 密码错误]}必须给输出约束的原因很简单大模型如果不被明确要求它可能输出一长段解释也可能把“category”叫成“类型”甚至会有不同调用之间字段名不一致的情况。为了避免每次都需要重新解析我在代码里也做了兜底把模型返回结果直接做json.loads如果失败就整条置为“pending”让人工重新看绝不让坏数据直接流转。初始时我对“输出约束”持半信半疑的态度但实测跑了两百条工单之后发现加了示例输出和不加示例输出JSON解析失败率从接近10%下降到接近0%。这个差距非常显著属于必须写的部分。3.4 Python脚本调用模型并写库这个脚本做的事情是把上一步建好的tickets_original表里还没有处理过的工单逐条取出来调用模型然后把模型返回的结果写入tickets_ai_tagged表。为了降低复杂度脚本里采用最简单的“逐条处理”方式。import json import mysql.connector import requests def build_prompt(content): template 你是一个工单分析助手。请分析下面这条工单只输出JSON格式不要附加任何解释文字。 工单内容{content} 要求 1. category字段从以下选项中选择一个最合适的账号登录、支付充值、网络连接、设备故障、投诉建议、其他。 2. priority字段填1、2或31表示影响核心功能且用户明确催促或情绪激动2表示功能可用但有异常或部分受阻3表示咨询、建议或非紧急问题。 3. keywords字段列出工单中关键的实体信息最多列5个每个都是字符串。 4. 如果工单内容不足以判断category填“其他”priority填3keywords填[]不要编造。 示例输出 {{category: 账号登录, priority: 2, keywords: [登录失败, 密码错误]}} return template.format(contentcontent) def call_llm(prompt): # 这里使用你采购的模型API按服务商要求填写endpoint和key # 也可以用本地部署模型的接口注意保持返回格式一致 resp requests.post( https://your-api-endpoint/v1/chat/completions, headers{Authorization: Bearer your-api-key}, json{ model: your-model-name, messages: [{role: user, content: prompt}], temperature: 0.0, response_format: {type: json_object} }, timeout30 ) data resp.json() return data[choices][0][message][content] def process_tickets(): conn mysql.connector.connect(hostlocalhost, userroot, passwordyourpass, databaseticket_db) cursor conn.cursor(dictionaryTrue) cursor.execute(SELECT ticket_no, content_text FROM tickets_original WHERE ticket_no NOT IN (SELECT ticket_no FROM tickets_ai_tagged) LIMIT 200) rows cursor.fetchall() for row in rows: raw_output call_llm(build_prompt(row[content_text][:2000])) # 截断保护 try: parsed json.loads(raw_output) except Exception: cursor.execute( INSERT INTO tickets_ai_tagged (ticket_no, review_status, ai_raw_output) VALUES (%s, pending, %s), (row[ticket_no], raw_output) ) conn.commit() continue cursor.execute( INSERT INTO tickets_ai_tagged (ticket_no, category, priority, keywords, review_status, ai_model, ai_raw_output) VALUES (%s, %s, %s, %s, pending, %s, %s), (row[ticket_no], parsed.get(category), parsed.get(priority), json.dumps(parsed.get(keywords, []), ensure_asciiFalse), your-model-name, raw_output) ) conn.commit() conn.close() if __name__ __main__: process_tickets()几个我在调试时踩过的坑需要单说。第一个是JSON解析问题大模型偶尔会在JSON前后加markdown代码块标记解析前要先做strip再替换掉多余的反引号。第二个是中文编码写库时连接串要带charsetutf8mb4否则keywords里的中文会变成问号这个问题很经典。第三个是超时问题工单文本比较长时单次调用可能超过默认超时时间我把timeout设到30秒一旦超时就跳过不给整批处理造成卡顿。关于tempareture这里我强烈建议调成0或者接近0的低值这能让输出更稳定、更贴近示例输出的格式。如果你发现同一批工单被来回判定成不同类型检查一下是否忘了把tempereture降下来。3.5 用查询工具让结果真正可用工单处理完并写库之后查询工具就派上用场了。这是整个项目里最直接见效的部分你不再需要人工去统计今天新增了多少登录类工单也不需要去翻聊天记录找某个订单号一个SQL查询可以算出想知道的任何聚合数字。-- 今天各类工单的数量分布 SELECT category, COUNT(*) AS cnt FROM tickets_ai_tagged WHERE DATE(created_at) CURRENT_DATE() GROUP BY category ORDER BY cnt DESC; -- 待复核的高优先级工单 SELECT o.ticket_no, o.content_text, t.category, t.priority FROM tickets_ai_tagged t JOIN tickets_original o ON o.ticket_no t.ticket_no WHERE t.review_status pending AND t.priority 1 LIMIT 50; -- 搜索包含某个订单号的工单 SELECT o.ticket_no, o.content_text, t.category FROM tickets_ai_tagged t JOIN tickets_original o ON o.ticket_no t.ticket_no WHERE JSON_CONTAINS(t.keywords, 20240612001) OR o.content_text LIKE %20240612001%;第三个SQL里我同时用了JSON_CONTAINS和LIKE因为模型抽取的keywords可能没包含用户原始输入的完整字符串LIKE作为兜底查询可以查到原始文本里出现过该编号的工单两条路径互为补充。这就是“大模型查询工具”组合里最让人舒服的地方大模型把非结构化文字翻译成了结构化标签然后你在SQL里用各种聚合、过滤、关联把工单变成一张随时能算的宽表。没有大模型这条SQL的前提条件没有着落没有查询工具模型的输出就只能躺在日志文件里积灰。4. 常见问题与排查技巧实录4.1 大模型输出不稳定怎么处理这是所有基于大模型做业务的人都会遇到的第一个坎。分类结果飘忽不定同样的工单在两次调用里被分成不同类别甚至关键词抽取时漏掉核心订单号。我排查下来常见原因有四个temperature值过高、提示词里缺少示例、工单文本截断导致关键信息丢失、模型本身能力对中文细粒度分类不够敏感。对应的解法也很直接把temperature降到0在提示词中固定好分类和示例工单截断时保留开头和结尾而不是只取前2000字符关键信息往往在开头和结尾出现。如果还是不够稳定你需要审视这个分类体系是不是太难了比如“支付充值”和“订单异常”两者边界模糊这种问题换什么模型都难解决本质上是分类体系设计的问题。我自己的心态是大模型不是算出准确率100%结果的机器它是一个“把需要人工读一遍的工作降级为人工抽查一遍”的工具。出错的边界你接受不了就加人工复核能接受就通过多数投票同一工单调多次取众数来缓解。4.2 工单文本超长导致调用失败工单不是每次只有一句话有些用户会洋洋洒洒写几百字甚至粘贴完整聊天记录。这种情况下把全文丢给模型会涨token费还可能直接触达模型的上下文长度上限。我的处理方案是分段优先先把工单正文按换行拆成段落取前3段和后2段如果总长度还超过800字符就把中间部分先省略并明确告诉模型“工单正文中间部分已省略”。这对分类和关键词抽取的准确率影响很小因为工单的首尾承载了最核心的信息。另外建议固定写入一条保护逻辑无论怎么截断模型的输入长度都不要超过2000字按字符计这是在调用成本、上下文限制、信息完整性三重约束下找出来的一个较为稳妥的平衡点。4.3 统计结果和人工认知对不上这种情况很常见尤其是刚上线两周的时候。业务方告诉你“今天应该有15条支付问题”你SQL查出来只有8条。追问一步一看大模型的判定结果才发现有3条工单被分到了“网络连接”因为这批用户实际描述是“支付时网页一直转圈”模型把重点判断成了网络问题还有几条被分成了“其他”因为工单内容只有“麻烦看一下”这种模糊文本。排查这一类问题我是严格对照ai_raw_output和原始工单来定位模型误判的。如果是分类体系的问题调整提示词的示例如果是模型能力缺陷考虑换更强的模型如果只是少数模糊案例保持原状并让人工复核队列去兜底。一句话总结不要试图把AI的错误全部消灭在模型侧那是高成本低收益的路径不如在流程里设置人工兜底。4.4 批量处理时的成本与吞吐平衡批量处理多条工单的时候我有过两次踩坑教训。一是并发太高导致API限流报错后来加上重试机制和并发控制比如用threading.Semaphore限定信号量为4之后解决了。二是计费成本比预想高了不少查了一下发现没必要把所有工单全文都送进大模型尤其是那些超长工单全部送进去纯属浪费。省钱的做法有两个一是去重系统里如果同一用户对同一问题反复提交相似工单可以在预处理阶段做一次文本相似度判断重复工单直接复用上一次的标签不用再调模型。二是按需处理不是每条工单都需要完整的三标签输出有些咨询类工单只需分类不需要优先级可以在提示词里动态裁剪要求让模型省下生成无用字段的token。这批小优化做完我这边成本下降了接近30%。5. 一点个人经验这个项目上线跑通后我最大的体会是“先跑起来”不是一个偷懒的借口反而是认清现实之后最踏实的选择。你在没有数据积累的时候硬上微调大概率是事倍功半你在没有清晰边界的时候做全自动分发大概率会漏单。先用大模型加查询工具把底层数据盘活让每一张工单都有分类、有优先级、可检索、可统计之后你想做任何复杂的功能都有了一个干净的地基。最后再分享一个我一直沿用的习惯每次修改提示词我都会先拿历史工单里的固定10条做回归测试确保旧问题没有被改坏。这套“提示词版本管理”虽然土但在没有正式评测集之前它已经是性价比最高的护身符了。下一篇我打算把这10条回归测试文本的来源和标注过程展开聊如果你们在落地过程中遇到什么问题也可以沿着这个方向先自查一遍。
返回列表