ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

AI客服不自由发挥:硬规则引擎+LLM结构化约束实战方案

2026/9/1 22:28:16 拓冰建站 浏览量
AI客服不自由发挥:硬规则引擎+LLM结构化约束实战方案 这次我们来看一个实战问题AI 客服上线后“自由发挥”怎么办业务部门要求所有话术必须按规章来但大模型一开口就经常跑偏甚至承诺“可以无理由退款 100%”。问题不在模型本身而在你把客服的“规则”全部塞进了提示词让它自己判断。AI Agent 实战里最核心的一条原则是能确定的东西不要让模型做决定先把规则抽出来。这篇文章会围绕“让 AI 严格按业务规则做事”这个场景拆解一套可落地的技术方案。重点聊三件事为什么提示词工程压不住客服幻觉RAG、微调、规则引擎分别解决什么问题以及怎么用“硬规则优先 LLM 理解 结构化输出 人工兜底”的模式把 AI 客服的行为锁死。最后附上完整的 Python 代码示例、接口调用方法和排查清单。1. 核心能力速览先给一张速览表方便判断这套方案适不适合你的场景。能力项说明项目类型AI Agent 实战方法论 可运行代码模板非现成成品软件核心功能让 AI 客服严格按业务规则执行不自由发挥关键技术规则引擎、提示词工程、结构化输出、RAG 可选、模型微调可选适用场景电商售后、金融咨询、政务客服、企业内部问答等强规则场景推荐硬件仅调用 API 时无需 GPU本地部署需要按模型选择显卡显存占用依赖具体 LLM纯 API 方案显存占用为 0支持平台跨平台Python 3.9启动方式命令行启动 / FastAPI 接口服务是否支持 API支持给出 FastAPI 调用示例是否支持批量任务支持提供批量处理脚本思路适合读者AI 应用开发者、Agent 产品经理、客服系统技术负责人2. 适用场景与使用边界2.1 这个方案解决什么问题AI 客服最常见的失败案例是“一本正经地胡说八道”。客服话术一旦涉及价格、时效、赔偿、售后政策就不允许出现概率性回答。业务方的要求通常非常明确未发货订单申请退款必须全额原路退回。已发货订单要申请退货需在签收后 7 天内提出。生鲜、定制类商品不支持 7 天无理由退货。超过 30 天未申请售后的订单客服只能引导用户走人工审核。这些规则是确定性的理论上不需要 LLM 来判断。如果你把规则文字写进 system prompt 里让模型自己“理解后执行”遇到复杂上下文就很容易出错。模型对规则的理解是概率性的它不会去查“这个订单什么时候签收的”“这件商品属不属于生鲜”它只会根据文字的相似度去生成回答。所以这套方案的第一个目标是把确定性规则从模型手里剥离出来交给代码去执行。2.2 不适合什么场景这套“强规则约束”方案并不适合所有客服场景。如果业务本身就是开放式的例如售前产品咨询、闲聊陪伴、创意建议强行套规则引擎反而会让对话显得死板。规则引擎擅长的是“判断题”和“计算题”不擅长“开放题”。另一个极端是如果业务方希望完全由大模型自由发挥那这篇文章的思路完全不适用。另外如果业务规则本身还在频繁变动例如运营每周调整优惠策略这需要把规则配置化否则每次都要改代码。2.3 合规与安全边界这里必须强调合规问题。AI 客服涉及用户订单、手机号、地址、聊天记录等敏感数据在生产环境部署时要确保优先使用私有化部署或企业级 API避免敏感数据外泄。对话日志脱敏后再入库。涉及退款、赔偿等操作AI 客服只能给出答复不能直接执行资金操作除非有完整的审批链路。话术必须经过业务方审核AI 不能生成“额外承诺”或“合同变更”类表述。人工客服兜底是必须的AI 没有权限处理的东西要明确转接。3. 三层技术路线对比提示词、RAG 与模型微调热搜词里有个很典型的问题“AI 人工智能客服是属于提示词工程、RAG 检索、模型微调这三个层级里的哪一个”严格来说一个成熟可用的 AI 客服往往是三层同时存在的只看你解决什么问题。3.1 提示词工程层提示词工程管的是“模型以什么身份、按什么逻辑说话”。它能约束语气、角色、回答格式但对硬性业务规则的约束力很弱。原因是提示词只是文字模型对文字的理解存在偏差。同一个规则“7 天无理由退货”在不同上下文里模型可能执行成 7 天、8 天、或者“具体以签收时间为准”。另外模型输入长度有限你不可能把几千条业务规则全部塞进提示词。提示词工程适合做的是定义客服人格、规定回答风格、要求输出 JSON 结构化结果、设定拒绝回答的兜底话术。3.2 RAG 检索增强层RAG 解决的是“模型不知道你的业务知识”的问题。你可以把完整的售后规则文档、商品说明、常见问题导入向量数据库用户提问时先检索相关内容再把检索结果拼进 prompt 让模型回答。RAG 比单纯提示词强的地方在于它能覆盖大规模知识库且更新知识只需要换文档不需要重新部署模型。但 RAG 也有两个明显限制检索质量不稳定召回不到正确文档时模型还是会答错。对“条件判断”类规则支持不好。例如“用户申请退款订单是否发货是否超过退款时限商品是否在退货范围内”RAG 可以搜到规则文本但它不负责执行逻辑判断模型可能只看到第一条相关规则就生成答案。3.3 模型微调层用户问 AI 客服是不是需要做模型微调这要分场景看如果目标是让模型学会“模仿你的回答风格”微调有作用。如果目标是让模型学会“严格遵守业务规则”微调的作用很有限。原因是微调改变的是模型权重和输出风格它并不能真正“记住”几百条具体规则。模型在微调后依然可能有幻觉和遗忘问题而且微调成本高、迭代周期长业务规则一变就得重新训练不适合高频变动的场景。真正需要微调的场景是模型对某一类任务的基础能力不够。例如你希望客服模型在识别用户情绪时更准确或者希望它始终用简称称呼你的产品这些适合微调。硬规则约束请交给代码。3.4 我的结论AI 客服要让业务规则“不可违背”正确路线是硬规则引擎 ------- 前置判断和拦截 RAG/知识检索 ----- 给模型提供业务知识依据 提示词工程 ------- 管模型的语言风格和输出格式 模型微调 --------- 可选用于提升特定任务的能力这四层不是替代关系是配合关系。下面我们来写代码。4. 环境准备与前置条件本项目用的技术栈比较简单属于“轻量可直接跑”的模板。建议在 Linux 或 macOS 下开发Windows 的 WSL 也可以。4.1 环境清单依赖版本建议用途Python3.9主开发语言FastAPI0.100提供 HTTP APIuvicorn0.20ASGI 服务openai最新版调用大模型 APIpydantic2.x数据校验pytest7.x编写测试用例langchain可选按需安装Agent 编排本示例不强制使用4.2 安装命令# 创建虚拟环境 python3 -m venv venv source venv/bin/activate # 安装依赖 pip install fastapi uvicorn openai pydantic pytest如果使用本地模型例如通过 Ollama 或 vLLM 提供的 OpenAI 兼容接口只需要把代码中的base_url改成本地地址即可。4.3 目录结构规划ai_agent_customer_service/ ├── main.py # FastAPI 入口 ├── config.py # 全局配置 ├── rules/ │ ├── __init__.py │ ├── policy.py # 硬规则引擎 │ └── policy_data.py # 规则数据 ├── llm/ │ ├── __init__.py │ ├── client.py # LLM 调用封装 │ └── prompts.py # 提示词模板 ├── agent/ │ ├── __init__.py │ └── pipeline.py # 智能体编排主流程 ├── tests/ │ └── test_policy.py # 规则引擎测试 └── requirements.txt5. 硬规则引擎把确定性判断从模型剥离这是整个方案的核心。我们要把“客服不能自由发挥”的诉求从一句口号变成代码逻辑。5.1 规则数据结构用一个电商售后客服来做例子。定义业务规则如下# rules/policy_data.py from dataclasses import dataclass, field dataclass class ReturnPolicy: refund_days: int 7 no_return_categories: list field(default_factorylambda: [生鲜, 虚拟商品, 定制商品]) max_refund_days: int 30 quality_issue_support: bool True规则不要写死在提示词里建议放在独立的 Python 模块或 JSON 配置中运营可以直接改。5.2 规则判断引擎# rules/policy.py from datetime import datetime, timedelta class PolicyEngine: def __init__(self, policy): self.policy policy def check_refund_eligibility(self, category: str, order_status: str, signed_at: str | None) - dict: 检查退货退款资格。 # 先判断类目是否支持退货 if category in self.policy.no_return_categories: return { eligible: False, reason: f该商品属于{category}类目不支持无理由退货。, next_action: human } # 未发货订单直接全额退款 if order_status pending: return { eligible: True, reason: 订单未发货可以全额退款。, refund_type: full, next_action: fulfill } # 已签收订单判断是否在退货时限内 if order_status signed: if signed_at is None: return { eligible: None, reason: 缺少签收时间信息无法自动判断转人工处理。, next_action: human } signed_dt datetime.fromisoformat(signed_at) now datetime.now() days_since_signed (now - signed_dt).days if days_since_signed self.policy.max_refund_days: return { eligible: False, reason: f订单签收已超过 {self.policy.max_refund_days} 天无法在线申请售后。, next_action: human } if days_since_signed self.policy.refund_days: return { eligible: True, reason: 在无理由退货时限内可以申请退货退款。, refund_type: refund, next_action: guide_return } return { eligible: False, reason: 已超过7天无理由退货时限请走人工审核流程。, next_action: human } # 其他状态未知 return { eligible: None, reason: 无法判断订单状态转人工处理。, next_action: human }这段代码的价值在于它把“能不能退款”“该怎么答复”变成了确定性逻辑模型没有机会乱猜。所有可能让模型自由发挥的关键判断点都优先走这里。5.3 单元测试先锁规则再写业务规则引擎必须用测试用例锁住否则后续改代码容易改出 bug# tests/test_policy.py from datetime import datetime, timedelta from rules.policy import PolicyEngine from rules.policy_data import ReturnPolicy def test_pending_order_full_refund(): engine PolicyEngine(ReturnPolicy()) result engine.check_refund_eligibility(数码, pending, None) assert result[eligible] is True assert result[refund_type] full def test_no_return_category(): engine PolicyEngine(ReturnPolicy()) result engine.check_refund_eligibility(生鲜, pending, None) assert result[eligible] is False def test_expired_return_window(): engine PolicyEngine(ReturnPolicy()) signed_at (datetime.now() - timedelta(days45)).isoformat() result engine.check_refund_eligibility(数码, signed, signed_at) assert result[eligible] is False assert result[next_action] human写测试的必要性这里不展开但请记住AI 客服上线前如果规则测试不过等于是在给用户送钱。6. LLM 模块只负责理解和表达不负责决策规则引擎处理完确定性判断后我们把“用户说的话”转成结构化信息再喂给 LLM 生成最终话术。6.1 信息抽取让 LLM 从对话里提取结构化字段用户说一句“我要退货我买的麦克风前天签收了”我们需要提取用户意图退货商品类目数码或具体商品名订单状态signed签收时间前天这种信息抽取可以用 prompt 让 LLM 完成输出 JSON# llm/prompts.py SYSTEM_PROMPT 你是电商客服的信息抽取助手。 请从用户消息中提取以下字段并输出JSON { intent: 退货|退款|查询物流|人工客服|闲聊, category: 商品类目如数码、生鲜、服饰、未知, order_status: pending|signed|unknown, signed_at: 用户提到的签收时间格式ISO8601如果没提到则为null } 如果没有足够信息字段值用unknown或null表示。只输出JSON不要输出其他文字。LLM 抽取字段即使偶尔抽错规则引擎也能兜底。例如抽取结果为order_statusunknown规则引擎会返回“无法判断转人工”。这就形成了第一层保护。6.2 话术生成规则引擎给定“边界”LLM 只说边界内的话拿到规则引擎的判断结果后我们要求 LLM 在这个边界内生成回复# llm/prompts.py GENERATE_REPLY_PROMPT 你是电商售后客服。 系统已经判断出本次售后结果请你用礼貌、简洁的口吻基于结果生成回复。 禁止添加任何系统没有给出的承诺、金额、时限。 判断结果 {policy_result} 用户原话 {user_message} 请生成不超过100字的客服回复。这里的关键是模型的职责是“把结果说得好听”不是“决定结果”。决策是规则引擎做的模型只是翻译。这种设计对模型的服从性要求极低甚至用一个小模型就能完成。7. Agent 主流程编排7.1 主流程实现# agent/pipeline.py import json from rules.policy import PolicyEngine from llm.client import LLMClient from llm.prompts import SYSTEM_PROMPT, GENERATE_REPLY_PROMPT class CustomerServiceAgent: def __init__(self, policy_engine: PolicyEngine, llm_client: LLMClient): self.policy policy_engine self.llm llm_client def handle_message(self, user_message: str) - dict: # 1. LLM 抽取结构化信息 extracted self.llm.extract(user_message) # 2. 规则引擎判断 decision self.policy.check_refund_eligibility( categoryextracted.get(category), order_statusextracted.get(order_status), signed_atextracted.get(signed_at) ) # 3. 转人工场景 if decision.get(next_action) human: return { reply: 这个问题我需要为您转接人工客服请稍等。, decision: decision, extracted: extracted } # 4. 生成最终话术 reply self.llm.generate_reply(decision, user_message) return { reply: reply, decision: decision, extracted: extracted }这个编排方式叫“Rule-based routing”规则引擎前置LLM 后置。用户和 AI 客服的对话严格走这条链路模型没有任何机会在规则之外发表意见。7.2 为什么不用 LangGraph 也能做很多 Agent 开发教程喜欢直接上 LangGraph 或 CrewAI但在这个场景里编排逻辑非常简单线性用户消息 - 信息抽取 - 规则判断 - 转人工/生成回复用 LangGraph 可以做但没必要。真正的工程重点在规则设计和 prompt 边界控制先把这个做好再考虑框架。如果你已经有 Agent 框架完全可以把这个流程封装成框架里的一个 Worker 或 Tool编排思路是一样的。8. 接口 API 与批量任务8.1 FastAPI 接入实际生产中客服系统需要一个 HTTP 接口# main.py from fastapi import FastAPI from pydantic import BaseModel from agent.pipeline import CustomerServiceAgent from rules.policy import PolicyEngine from rules.policy_data import ReturnPolicy from llm.client import LLMClient app FastAPI() agent CustomerServiceAgent( policy_enginePolicyEngine(ReturnPolicy()), llm_clientLLMClient() ) class MessageRequest(BaseModel): user_message: str session_id: str default class MessageResponse(BaseModel): reply: str decision: dict extracted: dict app.post(/api/chat, response_modelMessageResponse) async def chat(req: MessageRequest): result agent.handle_message(req.user_message) return MessageResponse( replyresult[reply], decisionresult[decision], extractedresult[extracted] ) if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)启动服务python main.py验证接口curl -X POST http://127.0.0.1:8000/api/chat \ -H Content-Type: application/json \ -d {user_message: 我买的生鲜想要退货, session_id: 001}预期返回结果decision.eligible False消息提醒“生鲜不支持无理由退货”。这样规则永远先于模型生效。8.2 批量任务有些客服场景不是实时对话而是离线工单处理。例如客服每天要处理 5000 条留言我们可以走批量脚本# batch_process.py import json import csv from pathlib import Path def batch_process(input_path: str, output_path: str): with open(input_path, r, encodingutf-8) as f: rows list(csv.DictReader(f)) results [] for row in rows: try: result agent.handle_message(row[message]) results.append({ id: row[id], reply: result[reply], next_action: result[decision].get(next_action), eligible: result[decision].get(eligible) }) except Exception as e: results.append({ id: row[id], reply: f处理失败: {e}, next_action: error }) with open(output_path, w, encodingutf-8) as f: json.dump(results, f, ensure_asciiFalse, indent2) if __name__ __main__: batch_process(data/input.csv, data/output.json)批量处理要注意三点必须加超时和重试单条 LLM 调用异常不能拖垮整个批次。每条记录要保存状态处理完一批不要重复处理。批量结果需要人工抽检特别是next_action human的条目。8.3 失败重试建议给一个通用重试模板import time from functools import wraps def retry(max_retries3, delay2.0): def decorator(func): wraps(func) def wrapper(*args, **kwargs): for attempt in range(max_retries): try: return func(*args, **kwargs) except Exception as e: if attempt max_retries - 1: raise time.sleep(delay * (attempt 1)) return None return wrapper return decorator对超时、限流类异常使用指数退避重试对参数错误类异常不要重试直接记录日志并转人工。9. 资源占用与性能观察纯 API 方案下本机资源占用非常低主要消耗在 FastAPI 服务和规则引擎上内存几百 MB 以内显存占用为 0。如果你把 LLM 换成本地模型比如使用 Ollama 加载一个 7B 或 14B 的对话模型显存占用会取决于模型量化版本和上下文长度。这时候需要重点观察模型加载后的基础显存占用。并发请求到达后显存是否会持续增长。信息抽取任务的 prompt 长度对延迟的影响。是否需要把信息抽取和话术生成分成两个独立模型实例。我的建议是信息抽取模型可以稍微弱一点话术生成模型可以稍强一点。如果算力有限两个任务共用一个模型也没问题只要规则引擎的逻辑正确。性能观察时用 FastAPI 自带的日志或接入 Prometheus 都可以。关键指标是P95 响应时间。单请求 LLM 耗时占比。规则引擎耗时通常可忽略不计。转人工率。如果 P95 响应时间过高优先看是不是信息抽取 prompt 太长或者模型并发度不够。10. 常见问题与排查方法问题现象可能原因排查方式解决方案客服还是会承诺额外退款规则引擎没有覆盖该分支模型直接生成话术检查decision日志看是否有next_actionfulfill但未被规则拦截补充分支规则确保所有退款场景都走规则引擎LLM 信息抽取字段为空prompt 不清晰或模型能力不足打印extracted原始输出检查 JSON 是否合法调整 prompt增加 few-shot 示例换更强的模型大量请求转人工规则过于严格或字段缺失查看next_actionhuman的原因分布优先排查缺失字段看是否可以通过追问补全接口响应很慢LLM 推理耗时高或并发不够查看 API 日志压测单接口增加并发减少 max_tokens启用缓存用户问售前问题被规则拦截规则引擎只处理了售后意图检查 intent 识别结果增加售前意图分支或将非售后类对话直接交给 LLM规则更新后没有生效配置缓存未刷新检查进程内存引入配置文件版本号或改成数据库存储规则批量任务卡住单条 LLM 调用未设置超时检查任务日志是否有异常增加超时和重试逻辑客服语气过于生硬话术生成 prompt 约束太强调整生成 prompt 的风格描述在规则边界内开放语气自由度11. 最佳实践与使用建议这一节汇总工程落地时的关键实践。11.1 先建规则清单再写代码不要边写代码边定规则。上线前和业务方开一次会把“能自动处理”和“必须转人工”的边界全部理清楚。至少覆盖可自动处理的条件。必须转人工的条件。无法判断时默认怎么处理。客服话术的统一口径。11.2 所有决策留痕每次对话响应都记录user_message脱敏文本。extracted抽取结果。decision规则判断结果。reply最终回复。model_name和prompt_version。这样事后出现客诉时可以快速定位是规则漏了还是模型输出有问题。11.3 线上灰度用分阶段上线策略第一阶段AI 只做辅助回答所有回复都有人工审核。第二阶段AI 可自动回复但退款类操作必须人工确认。第三阶段规则覆盖率足够高后再开放完全自动回复。11.4 定期回去看转人工日志转人工率是判断规则完备度的重要指标。每周看一次next_actionhuman的日志把高频出现的 case 逐步补充进规则引擎就能把 AI 客服的自主空间一点点压缩到合理范围。11.5 涉及敏感操作必须加守护如果客服系统要对接退款回调、工单创建等操作AI 生成的指令必须经过第二层校验不能直接执行。例如规则引擎判断“可以退款”仍要调用独立的订单服务校验订单号、金额和用户权限防止 prompt 注入或参数异常。12. 总结与下一步AI 客服之所以“自由发挥”根源不是某个模型不行而是你在让它做不擅长的事用概率决策去执行确定性规则。解决思路是让规则引擎做决策让 LLM 做表达。用硬规则引擎前置拦截所有确定性判断再用 LLM 抽取信息和生成话术把模型的发挥空间锁死在业务边界内。这个方案不依赖昂贵硬件不需要微调大模型改动现有客服系统的成本也低。如果这篇文章对你有用建议收藏备用。下一步你可以做的事情是把手头的业务规则按“判断项”和“执行项”拆出来先做一个最小规则引擎再接入一个便宜的对话模型试试效果。等跑通之后再考虑要不要引入 RAG 扩充知识库或者在特定能力上做模型微调。