240 lines
9.3 KiB
Python
240 lines
9.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
企业微信 AI 对话处理服务
|
||
负责接收企业微信用户消息,路由到 AI 模块处理,并推送回复
|
||
"""
|
||
|
||
import logging
|
||
|
||
from odoo import models, fields, api
|
||
|
||
_logger = logging.getLogger(__name__)
|
||
|
||
|
||
class WecomAiChat(models.Model):
|
||
"""企业微信 AI 对话处理"""
|
||
_name = 'wecom.ai.chat'
|
||
_description = '企业微信AI对话'
|
||
_order = 'write_date desc'
|
||
|
||
wecom_userid = fields.Char(string='企业微信UserID', required=True, index=True)
|
||
user_id = fields.Many2one('res.users', string='关联用户', index=True)
|
||
conversation_id = fields.Many2one('ai.conversation', string='AI对话', index=True)
|
||
last_message_time = fields.Datetime(string='最后消息时间')
|
||
log_ids = fields.One2many('wecom.ai.message.log', 'chat_id', string='消息日志')
|
||
log_count = fields.Integer(string='消息数', compute='_compute_log_count')
|
||
|
||
_sql_constraints = [
|
||
('wecom_userid_unique', 'UNIQUE(wecom_userid)', '每个企业微信用户只能有一条AI对话记录'),
|
||
]
|
||
|
||
@api.depends('log_ids')
|
||
def _compute_log_count(self):
|
||
for rec in self:
|
||
rec.log_count = len(rec.log_ids)
|
||
|
||
@api.model
|
||
def process_ai_message(self, wecom_userid, content):
|
||
"""
|
||
处理来自企业微信的 AI 消息(核心入口)
|
||
|
||
流程:
|
||
1. 通过 wecom_userid 找到 Odoo 用户
|
||
2. 获取或创建该用户的企业微信 AI 对话
|
||
3. 调用 AI 模块的 send_message 获取回复
|
||
4. 通过企业微信主动消息推送回复
|
||
|
||
:param wecom_userid: 企业微信成员ID
|
||
:param content: 消息文本内容
|
||
"""
|
||
_logger.info("开始处理企业微信AI消息: user=%s, content=%s", wecom_userid, content[:100])
|
||
|
||
# 特殊指令处理
|
||
if content in ('/reset', '/新对话', '重置对话', '新对话'):
|
||
success = self.reset_conversation(wecom_userid)
|
||
if success:
|
||
self._send_wecom_reply(wecom_userid, "对话已重置,请开始新的对话。")
|
||
else:
|
||
self._send_wecom_reply(wecom_userid, "重置失败,请稍后重试。")
|
||
return
|
||
|
||
if content in ('/help', '/帮助', '帮助'):
|
||
help_text = (
|
||
"AI助手使用说明:\n"
|
||
"• 直接发送消息即可与AI对话\n"
|
||
"• 发送 /新对话 可重置当前对话\n"
|
||
"• 发送 /帮助 查看此说明"
|
||
)
|
||
self._send_wecom_reply(wecom_userid, help_text)
|
||
return
|
||
|
||
# 1. 查找用户
|
||
user = self._find_user_by_wecom_id(wecom_userid)
|
||
if not user:
|
||
_logger.warning("企业微信用户 %s 未找到对应的Odoo账号,发送提示", wecom_userid)
|
||
self._send_wecom_reply(wecom_userid, "抱歉,您的账号尚未关联系统用户,无法使用AI助手。请联系管理员。")
|
||
return
|
||
|
||
# 2. 获取或创建对话记录
|
||
chat_record = self._get_or_create_chat(wecom_userid, user)
|
||
if not chat_record.conversation_id:
|
||
_logger.error("无法为用户 %s 创建AI对话", wecom_userid)
|
||
self._send_wecom_reply(wecom_userid, "AI服务暂时不可用,请稍后再试。")
|
||
return
|
||
|
||
# 3. 以该用户身份调用AI对话
|
||
conversation = chat_record.conversation_id
|
||
try:
|
||
# 使用 sudo 切换到目标用户身份执行 send_message
|
||
reply = conversation.with_user(user).send_message(content)
|
||
except Exception as e:
|
||
_logger.exception("AI对话处理异常 [user=%s]: %s", wecom_userid, e)
|
||
reply = "AI处理出错,请稍后重试。"
|
||
|
||
# 4. 处理回复
|
||
if isinstance(reply, dict) and 'error' in reply:
|
||
reply_text = reply.get('error', 'AI服务异常')
|
||
_logger.warning("AI返回错误: %s", reply_text)
|
||
else:
|
||
reply_text = reply or "(AI未返回内容)"
|
||
|
||
# 5. 推送回复到企业微信
|
||
# 截断超长回复(企业微信文本消息限制2048字节)
|
||
if len(reply_text.encode('utf-8')) > 2000:
|
||
reply_text = reply_text[:600] + "\n\n...(回复过长已截断,请在系统内查看完整内容)"
|
||
|
||
self._send_wecom_reply(wecom_userid, reply_text)
|
||
|
||
# 6. 记录消息日志
|
||
self._log_message(chat_record, wecom_userid, content, reply_text)
|
||
|
||
# 更新最后消息时间
|
||
chat_record.write({'last_message_time': fields.Datetime.now()})
|
||
_logger.info("企业微信AI消息处理完成: user=%s", wecom_userid)
|
||
|
||
def _find_user_by_wecom_id(self, wecom_userid):
|
||
"""通过企业微信UserID查找Odoo用户"""
|
||
# 方式1: 通过 hr.employee 的 wecom_userid 字段查找
|
||
employee = self.env['hr.employee'].sudo().search([
|
||
('wecom_userid', '=', wecom_userid)
|
||
], limit=1)
|
||
if employee and employee.user_id:
|
||
return employee.user_id
|
||
|
||
# 方式2: 通过 res.users 的 login 字段匹配(某些系统login就是wecom_userid)
|
||
user = self.env['res.users'].sudo().search([
|
||
('login', '=', wecom_userid),
|
||
('active', '=', True),
|
||
], limit=1)
|
||
if user:
|
||
return user
|
||
|
||
# 方式3: 通过 oauth_uid 匹配
|
||
user = self.env['res.users'].sudo().search([
|
||
('oauth_uid', '=', wecom_userid),
|
||
('active', '=', True),
|
||
], limit=1)
|
||
return user or False
|
||
|
||
def _get_or_create_chat(self, wecom_userid, user):
|
||
"""获取或创建企业微信用户的AI对话记录"""
|
||
chat_record = self.sudo().search([
|
||
('wecom_userid', '=', wecom_userid),
|
||
], limit=1)
|
||
|
||
if chat_record and chat_record.conversation_id:
|
||
return chat_record
|
||
|
||
# 查找可用的AI服务商
|
||
provider = self.env['ai.provider'].sudo().search([('active', '=', True)], limit=1)
|
||
if not provider:
|
||
_logger.error("没有可用的AI服务商配置")
|
||
if not chat_record:
|
||
chat_record = self.sudo().create({
|
||
'wecom_userid': wecom_userid,
|
||
'user_id': user.id,
|
||
})
|
||
return chat_record
|
||
|
||
# 创建AI对话(以目标用户身份创建,这样对话归属正确)
|
||
conversation = self.env['ai.conversation'].with_user(user).sudo().create({
|
||
'name': f'企业微信对话',
|
||
'provider_id': provider.id,
|
||
'create_uid': user.id,
|
||
})
|
||
|
||
if chat_record:
|
||
chat_record.sudo().write({
|
||
'user_id': user.id,
|
||
'conversation_id': conversation.id,
|
||
})
|
||
else:
|
||
chat_record = self.sudo().create({
|
||
'wecom_userid': wecom_userid,
|
||
'user_id': user.id,
|
||
'conversation_id': conversation.id,
|
||
})
|
||
|
||
_logger.info("为企业微信用户 %s 创建AI对话: conversation_id=%s", wecom_userid, conversation.id)
|
||
return chat_record
|
||
|
||
def _send_wecom_reply(self, wecom_userid, message):
|
||
"""通过企业微信主动推送消息回复"""
|
||
try:
|
||
self.env['wecom.apps'].sudo().sync_send_message(
|
||
category='agent',
|
||
user_id=wecom_userid,
|
||
message=message,
|
||
)
|
||
_logger.info("企业微信AI回复已推送: user=%s, len=%d", wecom_userid, len(message))
|
||
except Exception as e:
|
||
_logger.exception("企业微信AI回复推送失败 [user=%s]: %s", wecom_userid, e)
|
||
|
||
def _log_message(self, chat_record, wecom_userid, user_content, ai_reply):
|
||
"""记录企业微信AI消息日志"""
|
||
try:
|
||
self.env['wecom.ai.message.log'].sudo().create({
|
||
'chat_id': chat_record.id if chat_record else False,
|
||
'wecom_userid': wecom_userid,
|
||
'user_id': chat_record.user_id.id if chat_record and chat_record.user_id else False,
|
||
'user_message': user_content,
|
||
'ai_reply': ai_reply,
|
||
'message_time': fields.Datetime.now(),
|
||
'status': 'success',
|
||
})
|
||
except Exception as e:
|
||
_logger.warning("记录消息日志失败: %s", e)
|
||
|
||
@api.model
|
||
def reset_conversation(self, wecom_userid):
|
||
"""
|
||
重置企业微信用户的AI对话(清空历史,开始新对话)
|
||
可被用户通过发送特定指令触发,如 /reset 或 /新对话
|
||
"""
|
||
chat_record = self.sudo().search([
|
||
('wecom_userid', '=', wecom_userid),
|
||
], limit=1)
|
||
if not chat_record:
|
||
return False
|
||
|
||
user = chat_record.user_id
|
||
if not user:
|
||
return False
|
||
|
||
# 查找可用的AI服务商
|
||
provider = self.env['ai.provider'].sudo().search([('active', '=', True)], limit=1)
|
||
if not provider:
|
||
return False
|
||
|
||
# 创建新对话
|
||
conversation = self.env['ai.conversation'].with_user(user).sudo().create({
|
||
'name': '企业微信对话',
|
||
'provider_id': provider.id,
|
||
'create_uid': user.id,
|
||
})
|
||
chat_record.sudo().write({
|
||
'conversation_id': conversation.id,
|
||
})
|
||
_logger.info("企业微信用户 %s AI对话已重置: new conversation_id=%s", wecom_userid, conversation.id)
|
||
return True
|