首页 / 资讯中心 / 文章详情

Odoo社交集成实战:统一消息与AI智能体

Odoo社交集成实战:统一消息与AI智能体 ★ FEATURED ARTICLE
简介Odoo社交集成模块是一套面向企业级客户关系管理与全渠道营销自动化的解决方案整合微信服务号、微信小程序、企业微信、抖音号、抖音小程序、飞书H5、钉钉等多平台渠道并融入AI智能体可完成智能问答、客户数据分析与自动化服务响应。资源共267个文件约3.12MB以Python代码96个py、XML视图与权限配置49个xml、JavaScript交互逻辑11个js为核心搭配CSS样式、CSV权限数据、HTML页面、MD说明文档、PO翻译文件及PNG/JPG界面截图目录结构清晰便于按平台定位功能模块。已有50人学习下载适合需要统一管理多社交渠道的企业技术团队和Odoo二次开发人员既可用于生产环境部署也可作为学习Odoo多渠道集成的参考。通过这份资源包可以掌握各平台OAuth对接、渠道消息同步、AI智能体融合等核心实现并基于源码与配置快速复用大幅缩短从零搭建社交集成模块的周期。1. Odoo 社交集成模块到底在解决什么做销售或服务型业务的人消息往往散在微信服务号、企业微信、抖音私信、飞书和钉钉好几个后台客服每天来回切换才能把一条咨询搬进 Odoo 建商机、写跟进。这个模块解决的核心问题是把所有社交渠道的消息收发统一收敛进 Odoo入站消息自动变成 CRM 会话记录出站按模板和定时任务批量触达中间再挂一个 AI 智能体做意图识别与自动回复让企业级社交 CRM 真正跑起来。适合已经在用 Odoo、想把渠道数据沉淀成客户资产又不愿意把数据留在第三方平台的人。下面按「架构 → 接入 → 自动化与 AI → 避坑 → 验证」讲一条可复现的落地路径。2. 平台能力与架构设计先看清渠道边界再动手写代码多平台集成的第一课不是写代码而是把每个渠道的接口能力边界列清楚。微信生态里的服务号、小程序、企业微信虽然同源接口权限完全不同抖音、飞书、钉钉的开放策略也各有侧重。这些差异直接决定消息模型设计、路由规则和出站触达的可行性。我见过不少团队上来就写 controller结果做到一半发现某个渠道根本不允许主动推送整个营销自动化方案推翻重来。先把能力边界拆开再给一套能落库的表结构是省时间的唯一路径。2.1 微信生态三件套服务号、小程序、企业微信的接口差异微信是大部分企业社交 CRM 的第一站但「微信」不是一个接口。服务号走公众号平台小程序走开放平台企业微信走企业微信服务商接口三者入口、协议和授权模型各自独立不能混用一套凭证。服务号最核心的能力是客服消息接口用户主动发消息后的 48 小时窗口内服务端可以主动推送消息窗口外只能靠模板消息触达。消息回调是 XML 格式开启安全模式后还要做 AES 解密。小程序客服消息本质上走同一条管道但入站消息类型更多事件类消息进入会话、订单支付需要单独判断。企业微信走另一套体系客户联系相关回调是 JSON 格式核心标识是 external_userid——这是企业维度生成的客户 ID同一个微信用户在不同企业下会有不同的 external_userid没法直接当 OpenID 用。三个渠道的授权方式也不一样。服务号和小程序用 AppID AppSecret 获取 access_token企业微信额外需要企业 CorpID 和某个应用或成员的 Secret。集成时最常犯的错是以为「微信生态统一接入」实际上三套凭证、三套回调、三套消息格式必须各建各的配置。下面是接入前值得先拉出来的对比形态入站协议主动触达核心标识凭证服务号XML 回调 AES48h 客服消息 / 模板消息OpenIDAppID AppSecret小程序XML 回调同链路订阅消息 / 客服消息OpenIDAppID AppSecret企业微信JSON 事件回调客户联系 / 群发external_useridCorpID 应用 Secret2.2 抖音、飞书、钉钉与 H5主动触达的三个层次抖音开放平台的私信回调走事件订阅入站消息通过事件推送到达服务端回复要调用客服消息接口同样存在会话窗口限制窗口外的主动触达基本只能靠模板卡片。飞书和钉钉是典型的办公协同渠道机器人 webhook 可以主动推送入站依赖事件订阅或机器人回调企业客户用它承接内部审批、服务工单通知的占比最高。H5 渠道比较特殊它没有平台回调本质是一个嵌在浏览器里的网页。常见做法是直接用 Odoo 的 controller 提供一套 JSON API前端聊天窗口通过这套 API 收发消息复用同一套统一消息模型。H5 进来的消息和微信进来的消息在业务层没有任何区别路由、归档、坐席工作台全都通用。按主动触达能力可以把六个渠道分成三个层次第一层是随时可主动推的飞书、钉钉的机器人属于这类第二层是会话窗口内可主动推的微信客服消息、抖音私信属于这类第三层是几乎只能被动响应的。设计营销自动化任务时先给每个渠道标注触达层次再决定这个渠道能不能跑定时群发避免上线后才知道接口不支持。2.3 统一消息模型与渠道抽象层四张核心表怎么设计渠道差异这么大不能每个渠道一套表否则路由、搜索、排班全部要写分支。我一般会先定四张核心表social_channel 存渠道凭证social_contact 存外部联系人social_message 存消息流水social_session 存会话状态。凭证和消息分开存渠道配置配错时不需要动消息表数据。from odoo import models, fields class SocialChannel(models.Model): _name social.channel name fields.Char(requiredTrue) # 渠道显示名微信服务号 / 企业微信 / 抖音 ... channel_type fields.Selection([ (wechat_mp, 微信服务号), (wechat_ma, 微信小程序), (wecom, 企业微信), (douyin, 抖音), (feishu, 飞书), (dingtalk, 钉钉), (h5, H5), ], requiredTrue) app_id fields.Char() # 各平台的 AppID / CorpID / client_key app_secret fields.Char() # 各平台的 Secret存库前用 Odoo 加密字段或外部密钥服务 token fields.Char() # 回调验签 Token微信、飞书 verify_token aes_key fields.Char() # 微信安全模式的 EncodingAESKey state fields.Selection([ (draft, 未启用), (active, 已启用), (error, 异常) ], defaultdraft) message_ids fields.One2many(social.message, channel_id) class SocialMessage(models.Model): _name social.message _rec_name content channel_id fields.Many2one(social.channel, ondeletecascade, requiredTrue) contact_id fields.Many2one(social.contact, ondeletecascade) direction fields.Selection([(inbound, 入站), (outbound, 出站)], requiredTrue) msg_type fields.Char() # text / image / event / template 等 content fields.Text() external_msg_id fields.Char(indexTrue) # 平台侧消息 ID做唯一约束防重放 raw_data fields.Text() # 原始报文排障时对照平台文档用 session_id fields.Many2one(social.session) _sql_constraints [ (external_msg_uniq, UNIQUE(external_msg_id, direction), 同一条平台消息不能重复入库), ]几个关键选型值得说明。external_msg_id 必须建唯一约束平台在回调超时后会重试没有这层约束同一条消息会重复入库、重复建商机。raw_data 一定要存原始报文出问题对照平台文档全靠它不要只存解析后的字段。channel 和 contact 都保留外部 ID但不要在 message 表里再存外部 ID 之外的冗余字段宁可 join。contact 表还要留一个 union_mapping 字段用于把同一个用户的微信 OpenID 和抖音用户 ID 关联到同一客户档案这是企业级社交 CRM 和「各渠道各管各的联系人」的分水岭。3. 把渠道接进来微信服务号与企业微信的完整接入路径架构定了之后选两个最典型的渠道先接。服务号覆盖了 XML 回调、验签和 AES 解密企业微信覆盖了 JSON 事件回调和外部联系人映射。这两个跑通以后抖音、飞书、钉钉基本是在这套框架里换协议。下面每一步都是可以直接抄的配置路径。3.1 微信服务号接入Token 验签、AES 解密与消息入库接入链路分三段先在公众号后台配置服务器地址和 Token再在 Odoo 里建对应 channel 配置最后写一个 controller 接收回调。后台填写的服务器 URL 要指向 Odoo 的公开地址建议用独立路径/social/wechat/callback方便后续多环境切换。import hashlib import xml.etree.ElementTree as ET from odoo import http from odoo.http import request class WechatCallback(http.Controller): http.route(/social/wechat/callback, typehttp, authpublic, methods[GET, POST], csrfFalse) def callback(self, **kwargs): # 全参从 ir.config_parameter 读取避免硬编码 token request.env[ir.config_parameter].get_param(social.wechat_token) signature kwargs.get(signature, ) timestamp kwargs.get(timestamp, ) nonce kwargs.get(nonce, ) # 微信验签token、timestamp、nonce 排序后拼接再做 sha1 tmp_str .join(sorted([token, timestamp, nonce])) if hashlib.sha1(tmp_str.encode(utf-8)).hexdigest() ! signature: return signature error if request.httprequest.method GET: # 首次接入配置时微信带 echostr 来验证 URL 归属原样返回 return kwargs.get(echostr, ) # POST 是加密报文需按安全模式解密明文模式可直接解析 raw request.httprequest.data.decode(utf-8) decrypted self._decrypt(raw) # 内部用官方加解密库处理 AES-256-CBC root ET.fromstring(decrypted) msg_id root.findtext(MsgId) openid root.findtext(FromUserName) content root.findtext(Content) or root.findtext(Event) # 入库走统一消息模型唯一约束保证平台重试不产生重复记录 channel request.env[social.channel].sudo().search( [(channel_type, , wechat_mp)], limit1) request.env[social.message].sudo().create({ channel_id: channel.id, external_msg_id: msg_id, direction: inbound, msg_type: root.findtext(MsgType), content: content, raw_data: decrypted, }) return success几个参数值得反复确认。sorted 排序不能省微信要求 token、timestamp、nonce 三个参数按字典序拼接顺序错了签名永远对不上这是排查日志里出现频率最高的签名问题来源。echostr 只在首次配置时出现生产环境如果一直收到 GET 请求说明有人在探测接口日志里要留痕。_decrypt内部是 AES-256-CBC PKCS7 padding密钥由 EncodingAESKey 做 Base64 解码得到 32 字节这个逻辑直接参考官方加解密示例即可不建议自己重写非安全方案明文模式调试阶段可以临时用上线前务必切安全模式。提示微信回调要求 5 秒内响应超时后平台会重试。业务处理不要写在回调线程里消息入库后丢给后台任务队列controller 立刻返回 success是避免重复推送和超时告警的标准做法。3.2 企业微信接入客户联系回调与 external_userid 映射企业微信的接入和服务号有两点本质区别。第一凭证体系是 CorpID 应用 Secret不同应用有不同的 Secret权限范围也不同回调要选对应用第二消息标识从 OpenID 换成了 external_userid这个 ID 是客户在你的企业维度下的身份服务号里不存在这个概念。先搞定 access_token 的获取与缓存再订阅客户联系事件import requests from odoo import models, fields class SocialChannel(models.Model): _inherit social.channel access_token fields.Char() token_expires_at fields.Datetime() def fetch_wecom_token(self): self.ensure_one() # 先查缓存企业微信 token 有效期 7200 秒没必要每次都重新拉 if self.token_expires_at and self.token_expires_at fields.Datetime.now(): return self.access_token url (https://qyapi.weixin.qq.com/cgi-bin/gettoken f?corpid{self.app_id}corpsecret{self.app_secret}) resp requests.get(url, timeout5).json() if resp.get(errcode) 0: self.write({ access_token: resp[access_token], token_expires_at: fields.Datetime.add( fields.Datetime.now(), secondsresp.get(expires_in, 7200)), }) return self.access_tokentoken 缓存是个容易踩的小坑不加缓存的话每个回调都去拉一次 token量一大就会被限流。token_expires_at 建议提前 10 分钟失效因为接口返回的 expires_in 是理想值网络延迟和时钟偏差都会让它提前作废。客户联系回调的事件里ChangeType 为 add_external_contact 时带上 external_userid 和 state 参数。state 是你调用「配置客户联系」接口时设置的渠道标识很多团队忽略它导致分不清客户是从哪个二维码加进来的。我一般会把 state 设计成渠道加活动的组合比如 wechat_campaign_2025这样后续打标签、归属坐席都能直接取不用再猜来源。external_userid 要落到 social_contact 表并且和服务号联系人做归一。常见做法是在 contact 表加一个 profile_id 指向统一的客户档案模型外部 ID 只作为查找键。微信服务号的 OpenID 和企业微信的 external_userid 在数据库里是两套 key但经过手机号或 UnionID 关联后可以归到同一个 profile这是后面 AI 智能体做客户画像的数据基础。3.3 抖音、飞书、钉钉的共性路径OAuth Webhook 统一路由服务号和企业微信跑通后其他渠道的接入按共性路径复用。抖音走事件订阅飞书走事件订阅 v2.0钉钉走机器人回调。三者协议不同但接入动作高度一致申请应用拿到凭证配置事件订阅地址和加密配置后台触发一个测试事件验证回调在 Odoo 里写 handler。http.route(/social/webhook/string:channel_type, typehttp, authpublic, methods[POST], csrfFalse) def webhook(self, channel_type, **kwargs): raw request.httprequest.data.decode(utf-8) # channel_type 决定验签方式和解析器但入库字段完全一致 handler request.env[social.channel.handler].get_handler(channel_type) payload handler.verify_and_parse(raw, kwargs) message handler.to_social_message(payload) request.env[social.message].sudo().create(message) return handler.success_response()每个 handler 只做三件事验签、解析成统一消息模型、返回平台的成功应答。平台的应答格式必须按文档原样返回比如抖音要求返回 JSON 中的 code 字段飞书要求返回空字符串或特定结构钉钉要求返回加密串。有段时间我们用一个统一 success 响应结果抖音一直提示回调失败原因就是格式对不上——典型的「代码没报错平台不认账」。这个阶段的验证不要等平台推送。飞书和钉钉后台都有「发送测试事件」按钮抖音也有模拟事件工具先用它把回调链路打通再进入真实流量。测试事件通过后再回到本地跑一遍模拟脚本做业务验证两层分开排查效率高很多。4. 全渠道营销自动化与 AI 智能体融合从入站消息到商机的三级流水线渠道全部接入后真正的业务价值在消息进来之后怎么处理。这一章把流水线拆成三级入站路由决定消息给谁出站触达决定什么时候发智能体决定机器能答掉多少。三级串起来就是企业级社交 CRM 的自动化骨架。4.1 入站路由与坐席分配从渠道到人的三级规则入站消息入库后第一件事是路由。我按三级设计渠道级抖音私信全部进售前队列、内容级命中关键词「报价」「合同」进销售主管、标签级联系人打了 VIP 标签进专属坐席。三级都匹配不上时落到默认队列。路由规则模型核心字段是 channel_id、match_type、keyword_pattern、target_team、target_user、priority。注意 priority 不是简单取最大而是先按渠道过滤再按内容匹配最后按优先级排序同一个消息命中多条规则时只执行第一条。我见过有人把渠道过滤放在 keyword 匹配之后结果抖音的「报价」消息被路由到了微信坐席坐席点开链接才发现不对。渠道过滤必须是最外层条件。def route_message(message): rules request.env[social.routing.rule].search([ (active, , True), (channel_id, , message.channel_id.id), ], orderpriority desc) for rule in rules: if rule.match_type keyword and rule.keyword in message.content: # 命中关键词规则分配到指定坐席并生成待办 message.session_id.write({ user_id: rule.target_user.id, team_id: rule.target_team.id, }) message.activity_schedule( social_lead, user_idrule.target_user.id, summaryf来自{message.channel_id.name}的新会话待处理) return rule # 默认队列不指定坐席由坐席工作台自行领取 message.session_id.write({user_id: False, team_id: default_team.id})路由匹配之后还要配上 SLA 计时。我习惯在 social_session 上建一个 deadline 字段入站时按渠道设置响应时限比如微信 15 分钟、抖音 30 分钟超时未处理自动升级给主管。这个逻辑用 Odoo 的 cron 每五分钟扫一次即可不用额外引入队列系统。升级动作可以是站内消息加飞书/钉钉机器人通知通知内容是 session 链接主管点开直接进工作台。4.2 出站触达模板消息、定时任务与免打扰窗口出站触达比入站简单但合规约束多。微信服务号的模板消息要先在后台申请模板类目不同审核要求不同抖音私信的主动触达有窗口限制飞书和钉钉机器人相对宽松。设计营销自动化任务时先给渠道打个「触达能力」标签再决定能不能跑定时群发。# cron 每 5 分钟扫描一次到期触达任务 */5 * * * * odoo-bin social_campaign_worker \ --config/etc/odoo.conf --log-levelwarn触达任务用 Odoo 自带 cron 跑不需要额外组件。任务模型记录 target_channel、target_contacts、content_template、scheduled_at、status。cron 扫描到期任务按渠道组装消息调用对应发送接口。这个方案比在渠道后台做群发好在一个点发送记录、失败原因、退订状态全在 Odoo 里后续可以统计每个渠道的触达率和转化率这些数据反过来又能喂给 AI 做客户分群。发送失败要有重试和降级策略。微信客服消息超窗报 45015 类错误码时不要无限重试先降级到模板消息模板也没有就生成一条站内待办让坐席通过其他渠道联系客户。触达任务表加 state 字段记录 sent / failed / dropped每周对 failed 做一次归因分析是渠道限制还是内容问题决定要不要调整触达策略。注意免打扰窗口看起来是产品细节实际是合规和投诉管理的底线。默认晚上 20 点到次日 9 点不允许主动触达上线前逐个确认每个渠道的触达时间限制。我见过客户早上 7 点收到营销消息直接取消关注这个后悔药没有——取消关注后没有合法手段再次触达该用户。4.3 智能体融合层统一网关、Prompt 参数与转人工阈值AI 智能体是标题里最有想象空间、也最容易失控的部分。我的建议是不要在每个渠道各接一个大模型而是建一个统一的智能体网关所有渠道的入站消息先过网关统一做意图识别、回复生成、敏感词拦截和人工接管判断。好处是 Prompt 只维护一份话术风格全渠道一致审计日志集中模型要换供应商时只改网关内部。def ai_gateway_handle(message): # 1. 先跑人工规则命中强制转人工的规则时 AI 不参与 route routing_rule.match(message) if route and route.force_manual: return transfer_to_human(message, reasonrule_force_manual) # 2. 组装 Prompt带上渠道、客户画像和历史会话摘要 prompt build_prompt( templateAI_PROMPT_TEMPLATE, company_namemessage.company_id.name, channelmessage.channel_id.channel_type, contentmessage.content, profilemessage.contact_id.profile_digest(), ) result llm_client.complete( promptprompt, temperature0.2, # 低温度保证回复稳定营销内容别太飘 max_tokens200, # 限制输出长度避免长文刷屏 top_p0.8, ) # 3. 安全护栏敏感词命中或置信度不足一律转人工 if hit_sensitive_words(result.content) or result.confidence 0.6: return transfer_to_human(message, reasonguardrail) # 4. 留痕后自动回复 request.env[social.ai.log].create({ message_id: message.id, prompt: prompt, raw_response: result.content, confidence: result.confidence, decision: auto_reply, }) return send_outbound(message, result.content)参数说明值得单独讲。temperature 调到 0.2 而不是默认的 1.0是为了让客服场景的话术稳定可复现同样的问题两次问答案差异太大客户会觉得不专业。max_tokens 限制在 200 以内因为渠道消息长度和用户耐心都有限。top_p 和 temperature 是两套采样策略二选一调就行同时调容易互相干扰我一般固定 top_p0.8 只动 temperature。模型选择上优先选支持「返回结构化意图判断 置信度」的推理接口哪怕是调用通用大模型也让它先输出意图 JSON 再生成话术比直接生成回复好控制得多。转人工阈值 0.6 是常用起点具体按业务调。投诉类和涉及合同金额的对话阈值提升到 0.8售前咨询类可以降到 0.5。另一个关键点是上下文携带转人工时要把原始消息、AI 的判断、已生成的回复草稿一起传给坐席工作台坐席打开不用重新读一遍聊天记录。历史会话窗口控制在最近 10 轮以内既满足上下文需求又控制 token 成本——每轮把全部历史塞进 Prompt费用和延迟都会线性上涨这是 AI 网关上线后最容易被忽略的成本窟窿。5. 多平台接入的避坑清单5 个高频翻车现场与排查路径前面几章把链路搭通了这部分专门讲接渠道时反复踩的坑。每一条都是「现象 → 原因 → 解决」的结构按出现频率排。5.1 回调验签失败报 invalid signature 但配置明明是对的现象平台后台测试回调一直提示验签失败Odoo 日志里能看到请求进来但签名比对不过。原因绝大多数情况不是 Token 写错而是配置参数读取时机不对。有人把 Token 硬编码在旧配置里后台改了但代码还在用常量也有人把 URL 带了大小写或尾斜杠平台生成签名用的和你校验用的不是同一串。时钟偏移也会导致基于 timestamp 的验签失败但微信这套主要看 Token 一致性和拼接顺序。解决写一个独立验签脚本把平台后台展示的 Token 和收到的 signature、timestamp、nonce 打出来本地重算一次 sha1。比对过了就是 Odoo 侧拿参数的问题比对不过就是 Token 不一致。另外把验签逻辑单独提成一个纯函数不要混在 controller 里方便单测。上线前用模拟脚本跑一遍验签是最快的回归手段。5.2 emoji 入库报错psycopg2.DataError 与字符集冲突现象用户发了一条带 emoji 的消息Odoo 日志抛 psycopg2.errors.CharacterNotInRepertoire消息存不进去整条链路中断。原因PostgreSQL 默认数据库字符集如果是 UTF8emoji 这类 4 字节字符能存但很多部署从模板库建库时沿用了 SQL_ASCII 或 latin1Odoo 的 Text 字段写进去就炸。平台侧报文本身没问题问题出在数据库端。解决建库时指定 UTF8 编码并采用 template0 作为模板这是根治方案。已经跑起来的库可以在应用层做转义把 4 字节字符转成\U0001F600形式入库展示时再还原def _sanitize_content(content): # 超出 BMP 的字符转成 unicode 转义入库不炸展示时可逆 return .join( ch if ord(ch) 0x10000 else ch.encode(unicode_escape).decode() for ch in content )转义只是过渡方案因为后续做搜索和关键词匹配时转义后的内容会带来一堆麻烦。只要库还能重建优先花半小时改库字符集别让转义逻辑长期留在生产代码里。5.3 重复消息重放同一消息建了两条商机现象客户发一条消息Odoo 里出现两条一模一样的 message 记录关联的 lead 也建了两个。原因平台在回调超时后会重试推送controller 处理耗时超过平台超时阈值微信是 5 秒平台重发但第一条其实已经入库了重放的那条又走了一遍完整流程。解决social_message 的 external_msg_id 加唯一约束入库改用「先查后建」而不是裸 createdef create_or_update_message(env, vals): existing env[social.message].sudo().search([ (external_msg_id, , vals.get(external_msg_id)), (direction, , vals.get(direction)), ], limit1) return existing or env[social.message].sudo().create(vals)数据库唯一约束是最后一道防线业务层的幂等校验也要有。商机创建逻辑做成「按 message 关联查询有 lead 只更新不新建」这样才能保证平台重试多少次业务数据都只有一份。5.4 主动消息发不出去会话窗口和消息类型双重限制现象坐席在 Odoo 里回复客户接口返回错误码提示不在客服消息发送窗口。原因微信客服消息要求用户最近 48 小时内有互动超窗后主动消息被拒。很多人以为「用户关注了就能发」实际上关注不算互动必须有消息往来。另一个原因是消息类型不匹配——模板消息只能发审核通过的模板图文要传 media_id类型错了照样报错。解决发消息前先查会话窗口状态social_session 里维护 last_inbound_at出站时判断from datetime import timedelta def send_reply(session, content): # 48 小时窗口内走客服消息超窗降级到模板消息或生成待办 if session.last_inbound_at and \ fields.Datetime.now() - session.last_inbound_at timedelta(hours48): wechat_customer_service_send(session, content) else: template session.channel_id.fallback_template_id if template: wechat_template_send(session, template, content) else: session.activity_schedule(social_fallback, summary会话超窗请通过其他渠道联系客户)把这个降级链路做在发送封装里不要让坐席看到报错后自己去猜。坐席只需要知道「这条消息已通过模板消息补发」还是「需要线下联系」决策逻辑全部下沉到代码。5.5 AI 答非所问幻觉话术直接发给了客户现象客户问售后政策AI 回复了一段听起来专业但完全是编的赔偿方案客户当真了坐席后续解释成本极高。原因Prompt 里没有限定知识边界模型在拿不到答案时倾向于合理编造又没有置信度阈值低质量的输出照样发出去了。多数是参数问题——温度过高、没有约束输出格式、没有兜底话术。解决Prompt 里明确「只根据提供的产品资料回答资料里没有的回复我帮您确认后回复您」这是最有效的防幻觉手段同时把置信度阈值加上低于阈值一律转人工。另一个习惯是每个 AI 回复都留审计日志客户投诉时能快速定位是哪一轮 AI 话术出了问题而不是整个系统背锅。AI 网关的日志表至少要存 prompt、raw_response、confidence、decision 四个字段缺一个都很难回溯。6. 验证与进阶用模拟回调事件在本地复现整条会话链路本地开发最头疼的是回调平台只能回调公网地址改一行代码要等部署才能验证。我的习惯是写一个模拟回调脚本直接往本地 Odoo 打请求把验签、解析、入库、路由、AI 判断全链路在提交代码之前跑一遍。import hashlib import random import time import requests TOKEN your_token # 和 ir.config_parameter 保持一致 def make_signature(timestamp, nonce): tmp .join(sorted([TOKEN, str(timestamp), nonce])) return hashlib.sha1(tmp.encode(utf-8)).hexdigest() timestamp int(time.time()) nonce .join(random.choices(abcdefghijklmnopqrstuvwxyz, k10)) sig make_signature(timestamp, nonce) xml_payload fxml ToUserName![CDATA[gh_test]]/ToUserName FromUserName![CDATA[o_test_openid]]/FromUserName CreateTime{timestamp}/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[你们有企业版报价吗]]/Content MsgId{int(time.time() * 1000)}/MsgId /xml resp requests.post( http://localhost:8069/social/wechat/callback, params{signature: sig, timestamp: timestamp, nonce: nonce}, dataxml_payload.encode(utf-8), timeout5, ) print(resp.status_code, resp.text)跑完脚本去 Odoo 里查 social.message 表确认记录存在、路由规则命中、AI 返回了预期结果。进阶用法是把脚本扩展成批量用例MsgType 换成 event 测关注事件MsgId 改成同一个值连发两次测幂等Content 换成敏感词测转人工。平台升级接口参数时第一个跑挂的一定是这个脚本而不是线上环境——这就是它的回归测试价值。我每接一个新渠道都会先写一套对应的模拟脚本进仓库渠道后台的测试事件按钮用来验协议本地模拟脚本用来验业务逻辑两层分开各管各的。这个习惯帮我省掉过很多次「线上翻车才发现是验签顺序错了」的尴尬。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站