先说结论
website_helpdesk_livechat 里的 chatbot 自动建单,核心不是“最后一步生成一张 ticket”这么简单。
把 website_helpdesk_livechat/models/chatbot_script.py、chatbot_script_step.py、相关视图和测试一起看,官方实际上在同时守住 4 条边界:
- 脚本顺序有硬约束:
create_ticket前面必须先出现question_email; - 建单目标要明确:创建步骤必须选定
helpdesk_team_id,不是随便落到一个模糊队列; - 访客资料会先被整理:邮箱、电话、partner 更新并不是事后补录,而是建单前流程的一部分;
- 会话历史要完整落账:ticket 描述不只是一句摘要,而是把 chatbot 采集到的信息和 livechat 历史一起写进去。
所以更准确的理解是:
Odoo 的网站 chatbot 自动建单,本质上是把“前台对话”转成“可追溯服务记录”的一条受控升级链,而不是聊天结束后顺手插一张工单。
这也是为什么它放在企业版 website_helpdesk_livechat 里,而不是做成一个随便触发的轻量按钮功能。
一、create_ticket 不是任意步骤,而是 chatbot 的新步骤类型
在 models/chatbot_script_step.py 里,企业版给 chatbot.script.step 扩展了一个新的 step_type:
create_ticket
这说明自动建单不是 controller 里额外写个 if,而是直接进入 chatbot 脚本引擎的一等步骤类型。
这件事很关键,因为它意味着:
- 建单可以和前面的问答步骤串成同一条脚本;
- 它会遵循现有 sequence / triggering answer 的流转机制;
- 它不是“聊天结束后后台猜一下要不要建单”,而是脚本明确走到了建单节点才触发。
从产品设计上看,这比很多“关键词命中就开 ticket”的方案稳很多。
二、为什么 Odoo 强制“邮箱步骤必须在建单步骤之前”
models/chatbot_script.py 里最值得注意的是 _validate_script_steps()。
它会按 sequence 顺序遍历脚本步骤,只要在看到 question_email 之前先遇到 create_ticket,就直接抛 ValidationError:
- 必须先有 Email 步骤,之后才能有 Create Ticket 步骤。
这不是表单层的小提醒,而是模型层约束。
这条约束解决的,不只是“少一个字段”
很多人第一反应是:
- 没邮箱就不方便回访。
但源码表达得更深一层:
1. ticket 需要最基本的可联系身份
如果网站访客是 public user,系统本来没有稳定身份。没有邮箱,后续工单几乎无法形成可靠跟进对象。
2. chatbot 建单不是匿名留言箱
它要落到 helpdesk 体系,而 helpdesk 默认期待的是:
- 谁提的问题;
- 后面怎么联系;
- 工单与客户对象怎么关联。
3. 这也保证了后面的 partner 补全逻辑有锚点
后面 _chatbot_prepare_customer_values() 会尝试创建或更新 partner。邮箱先出现,等于前面的身份采集已经建立了最基础的客户锚点。
所以这条校验本质是在保护:
chatbot 自动建单必须先具备最低可运营性,再允许进入 helpdesk。
三、为什么 create_ticket 步骤还要强制绑定 helpdesk_team_id
视图 views/chatbot_script_step_views.xml 很直白:
- 当
step_type == 'create_ticket'时,helpdesk_team_id字段会显示; - 而且是
required="step_type == 'create_ticket'"。
这代表官方拒绝一种很常见但很危险的设计:
- 先把 ticket 建出来,之后再人工分队列。
Odoo 的做法是反过来:
- 脚本设计阶段就要决定,这条网站对话最终归哪个 helpdesk team 接。
这样做有几个明显好处。
1. 服务归属一开始就明确
不同 livechat 入口可能对应:
- 售前咨询
- 售后支持
- 技术支持
- 特定网站频道
如果团队归属不在脚本里提前定死,后面的 SLA、负责人、阶段流转和邮箱模板都会变得不稳定。
2. chatbot 不需要猜路由
它不是靠关键词分类后再随机建单,而是靠脚本设计者预先定义目标团队。
3. 测试也验证了“有 team / 无 team”是有区别的
在 tests/test_chatbot_create_ticket.py 里:
- public user 场景里,未指定 team 时,创建出来的 ticket 可以没有
team_id; - portal user 场景里,把
step_helpdesk_create_ticket.helpdesk_team_id指到helpdesk_team后,ticket 就会明确落入该团队。
这说明官方允许“未选 team 的最小建单”,但整个企业版 UI 又在强推你建脚本时就把 team 填上。换句话说:
- 技术上能留空;
- 产品上不鼓励留空。
四、建单标题为什么不是固定模板,而是优先取访客第一次自由输入
_prepare_ticket_values() 里还有个很容易被忽略、但体验上很聪明的细节。
默认 ticket 名称会先走:
"%(name)s's Ticket",名字来自访客显示名或脚本标题。
但如果系统能在会话里找到第一条用户自由输入消息:
- 就会把那条消息转成纯文本;
- 并截到前 100 个字符;
- 直接作为 ticket 的
name。
这一步非常有产品感。
为什么这比“默认工单标题”更好
因为客服侧真正需要的,不是“Visitor's Ticket” 这种抽象名字,而是:
- 打印机无法工作
- 我想知道企业版知识库能否公开给访客
- Portal 登录后文件下载失败
也就是说,Odoo 让标题尽量来自访客自己的第一句核心问题,而不是后台帮他起一个泛化标签。
这会直接提升:
- helpdesk 列表可读性;
- 坐席分派效率;
- 后续搜索命中率。
五、ticket 描述为什么要把“客户补全信息 + 整段聊天历史”一起写进去
_process_step_create_ticket() 的真正重点,不只是 self.env['helpdesk.ticket'].create(...)。
在创建之前,它先调用:
_chatbot_prepare_customer_values(discuss_channel, create_partner=True, update_partner=True)
然后再把返回的 customer_values['description'] 和:
discuss_channel.sudo()._get_channel_history()
拼在一起,写进 ticket 的 description。
也就是说,最终描述不是一段简短摘要,而是两块内容叠加:
- chatbot 结构化采集到的客户信息;
- 完整 livechat 会话历史。
这条设计特别像“把前台上下文压缩成后台可执行记录”
它解决的是 helpdesk 实操里的两个老问题:
1. 坐席接单时不需要回放现场
哪怕最初对话发生在网站右下角,ticket 一打开,核心上下文已经落进描述区。
2. 结构化字段和原始语境能同时保留
- partner / email / phone 是结构化数据;
- 聊天记录则保留原始表达、时间顺序和问题演化。
很多系统只保留其中一个:
- 要么只留字段,不留语境;
- 要么只留 transcript,不形成客户对象。
Odoo 这里两者都要。
六、public user 和 portal user 的处理为什么不同
测试 test_chatbot_helpdesk_ticket_public_user 与 test_chatbot_helpdesk_ticket_portal_user 很能说明官方边界。
public user:更像“从网站陌生访客生成可跟进对象”
测试验证:
- ticket 上会得到
partner_email = helpme@example.com - 会得到
partner_phone = +32499112233 - 描述里也能看到问题文本、邮箱、电话
- 若步骤没指定 team,
team_id可以为空
也就是说,对 public user,系统更像在把一段陌生对话整理成最小可服务对象。
portal user:更像“在已有客户对象上补齐缺信息”
portal 用户场景里,测试明确说明:
- 若 base partner 已有 email,则不会被 chatbot 里输入的邮箱覆盖;
- 但 phone 若原本为空,则会被更新成聊天中采到的号码;
- 若步骤指定了 team,ticket 会落到对应 helpdesk team。
这说明 Odoo 的策略不是“聊天里输入什么就无脑覆盖主数据”,而是更保守:
- 已有稳定客户主数据,优先信已有值;
- 缺口字段,再用聊天采集值补齐。
这很像企业系统应有的态度:
chatbot 可以补数据,但不该轻易篡改成熟客户档案。
七、ticket 为什么还要记住 origin_channel_id 和 source_id
建单值里还有两个很关键的来源字段:
origin_channel_id = discuss_channel.idsource_id = self.chatbot_script_id.source_id.id
这两个字段决定了 ticket 不是一张“凭空出现”的工单。
origin_channel_id:追溯到哪段 livechat 会话
它把工单和具体 discuss.channel 连起来。
后续在 helpdesk.ticket 上就能:
- 追到原始聊天来源;
- 从 ticket 侧再打开 livechat;
- 让服务人员回到对话现场而不是只看脱水摘要。
source_id:追溯到哪条 chatbot 脚本 / 入口来源
这意味着你之后可以从运营维度分析:
- 哪个网站 livechat 入口最常产出工单;
- 哪条 chatbot 脚本转单最多;
- 某个脚本版本是否更容易产生无效 ticket。
models/chatbot_script.py 里的 ticket_count 和 action_view_tickets(),其实就是围绕这个来源归集做的:
- 一个 chatbot script 可以在后台直接看到它生成了多少 tickets;
- 点进去还能查看这些 ticket。
所以 Odoo 不只是“允许 chatbot 建单”,它还顺手把脚本效果统计一起做了。
八、这套设计和普通 /ticket 指令建单有什么本质不同
同一个模块里,discuss.channel 也扩展了 /ticket 命令。
但 /ticket 更像:
- 聊天进行中由内部使用者显式下命令;
- 直接根据频道历史生成工单;
- 是一种人工触发升级。
而 chatbot 的 create_ticket 步骤更像:
- 网站访客沿脚本一步步填写问题;
- 系统先采集身份与上下文;
- 到达特定步骤后自动触发建单;
- 同时保留团队、来源和脚本统计。
两者都能把会话转成 ticket,但产品心智完全不同:
/ticket是人工升级命令;create_ticket是脚本化前台分诊流程。
这也是为什么这篇文章不能简单归到“网站 livechat 基础路由”那类已有选题里:它讲的是chatbot 如何把前台分诊转成正式服务对象,切口更细,也更偏企业版服务运营设计。
九、对实施与二开的启发
1. 不要把自动建单理解成“少打一遍字”
真正的价值在于:
- 先采信息;
- 再定归属;
- 最后完整落单。
2. 如果你删掉邮箱前置约束,后面客户跟进成本会明显上升
技术上你当然能魔改,但会破坏官方对“最低可联系性”的保护。
3. ticket 标题最好保留“访客第一句问题”的思路
这对坐席检索和派单真的有帮助,比统一模板名更实用。
4. 不要只存结构化字段,不存会话历史
客服接手时,历史语境通常比单个字段更能解释问题。
5. 对已有 portal 客户,更新策略要保守
聊天数据适合补齐缺口,不适合直接覆盖权威主数据。
一句话记忆法
Odoo 企业版的 website_helpdesk_livechat chatbot 自动建单,不是“聊完自动生成 ticket”,而是“先收邮箱、定团队、补客户、存历史,再把 livechat 升级成可追溯 helpdesk 记录”。
参考源码
enterprise/website_helpdesk_livechat/models/chatbot_script.pyenterprise/website_helpdesk_livechat/models/chatbot_script_step.pyenterprise/website_helpdesk_livechat/models/helpdesk_ticket.pyenterprise/website_helpdesk_livechat/views/chatbot_script_step_views.xmlenterprise/website_helpdesk_livechat/views/chatbot_script_views.xmlenterprise/website_helpdesk_livechat/tests/helpdesk_livechat_chatbot_common.pyenterprise/website_helpdesk_livechat/tests/test_chatbot_create_ticket.py
DISCUSSION
评论区