首先分析 __manifest__.py 代码
# -*- coding: utf-8 -*-
# Copyright (c) 2020-Present InTechual Solutions. (<https://intechualsolutions.com/>)
{
'name': 'Odoo ChatGPT Integration',
'version': '16.0.1.0.1',
'license': 'AGPL-3',
'summary': 'Odoo ChatGPT Integration',
'description': 'Allows the application to leverage the capabilities of the GPT language model to generate human-like responses, providing a more natural and intuitive user experience',
'author': 'InTechual Solutions',
'company': 'InTechual Solutions',
'maintainer': 'InTechual Solutions',
'website': 'https://intechualsolutions.com',
'depends': ['base', 'base_setup', 'mail'],
'data': [
'data/mail_channel_data.xml',
'data/user_partner_data.xml',
'views/res_config_settings_views.xml',
],
'external_dependencies': {'python': ['openai']},
'images': ['static/description/main_screenshot.png'],
'installable': True,
'application': False,
'auto_install': False,
}
发现一段有意思的参数
'external_dependencies': {'python': ['openai']},
这个可以控制在模块安装的时候强制检测有没有安装python的包,如果没有的话,在运行环境手动安装好依赖库后才能安装模块
pip install openai
images 应该是描述页面的主截图?
接着分析views视图目录,发现里面就一个 res_config_settings_views.xml 文件
<?xml version="1.0"?>
<odoo>
<record id="is_res_config_settings_view" model="ir.ui.view">
<field name="name">res.config.settings.view.form.is.chatgpt.inherit</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='integration']" position="after">
<h2>ChatGPT</h2>
<div class="col-xs-12 row o_settings_container" id="chatgpt_integraion">
<div class="col-xs-12 col-md-6 o_setting_box">
<div class="o_setting_right_pane border-start-0">
<div class="content-group">
<div class="row mt8">
<label class="col-lg-3" string="API Key" for="openapi_api_key"/>
<field name="openapi_api_key" title="OpenAPI API Key"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>
这个是用于在设置界面增加配置选项的,配置机器人的api_key,界面效果如下
发现字段文本太长了,一行代码写不下,随便改改
<field name="openapi_api_key" title="OpenAPI API Key" style="width: 100% !important;"/>
这样就好多了
接着分析models视图目录,发现里面就两个文件
res_config_settings.py
# -*- coding: utf-8 -*-
# Copyright (c) 2020-Present InTechual Solutions. (<https://intechualsolutions.com/>)
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
openapi_api_key = fields.Char(string="API Key", help="Provide the API key here", config_parameter="is_chatgpt_integration.openapi_api_key")
这个也看的明白,就是给系统配置加一个配置参数
mail_channel.py.py
# -*- coding: utf-8 -*-
# Copyright (c) 2020-Present InTechual Solutions. (<https://intechualsolutions.com/>)
import openai
from odoo import api, fields, models, _
from odoo.exceptions import UserError
class Channel(models.Model):
_inherit = 'mail.channel'
def _notify_thread(self, message, msg_vals=False, **kwargs):
rdata = super(Channel, self)._notify_thread(message, msg_vals=msg_vals, **kwargs)
chatgpt_channel_id = self.env.ref('is_chatgpt_integration.channel_chatgpt')
user_chatgpt = self.env.ref("is_chatgpt_integration.user_chatgpt")
partner_chatgpt = self.env.ref("is_chatgpt_integration.partner_chatgpt")
author_id = msg_vals.get('author_id')
chatgpt_name = str(partner_chatgpt.name or '') + ', '
prompt = msg_vals.get('body')
if not prompt:
return rdata
openai.api_key = self.env['ir.config_parameter'].sudo().get_param('is_chatgpt_integration.openapi_api_key')
Partner = self.env['res.partner']
partner_name = ''
if author_id:
partner_id = Partner.browse(author_id)
if partner_id:
partner_name = partner_id.name
if author_id != partner_chatgpt.id and chatgpt_name in msg_vals.get('record_name', '') or 'ChatGPT,' in msg_vals.get('record_name', '') and self.channel_type == 'chat':
try:
response = openai.Completion.create(
model="text-davinci-003",
prompt=prompt,
temperature=0.6,
max_tokens=3000,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
user = partner_name,
)
res = response['choices'][0]['text']
self.with_user(user_chatgpt).message_post(body=res, message_type='comment', subtype_xmlid='mail.mt_comment')
except Exception as e:
raise UserError(_(e))
elif author_id != partner_chatgpt.id and msg_vals.get('model', '') == 'mail.channel' and msg_vals.get('res_id', 0) == chatgpt_channel_id.id:
try:
response = openai.Completion.create(
model="text-davinci-003",
prompt=prompt,
temperature=0.6,
max_tokens=3000,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
user = partner_name,
)
res = response['choices'][0]['text']
chatgpt_channel_id.with_user(user_chatgpt).message_post(body=res, message_type='comment', subtype_xmlid='mail.mt_comment')
except Exception as e:
raise UserError(_(e))
return rdata
这个文件就是机器人响应的后端核心逻辑了,大致说一下代码思路:
1.继承修改聊天通道模型 mail.channel 的 _notify_thread方法
可以看到代码利去获取了发送消息的用户的联系人id author_id 和 用户输入的消息 prompt
观察了一下代码,如果第一次用户输入的消息prompt是空,那么返回的rdata是个空列表
不然的话输了内容,感觉这段 _notify_thread 代码应该是运行了两次,第一次是用户的联系人id,第2次是chatgpt的联系人id,根据这个69我找到的
接下来去获取了全局参数配置里的chatgpt对应的api_key
还去获取了发送消息的联系人的名称
然后判断是群聊还是私聊
if author_id != partner_chatgpt.id and chatgpt_name in msg_vals.get('record_name', '') or 'ChatGPT,' in msg_vals.get('record_name', '') and self.channel_type == 'chat':
这句代码判断发送消息的联系人不等于chatgpt的联系人,并且chatgpt的联系人名称在聊天包括的联系人列表里,或者判断 ChatGPT, 逗号出现在record_name也代表不止一个,表示是私聊
elif author_id != partner_chatgpt.id and msg_vals.get('model', '') == 'mail.channel' and msg_vals.get('res_id', 0) == chatgpt_channel_id.id:
这句代码判断不是自己给自己发消息,并且所在的聊天通道id就是chatgpt的通道,属于判断频道聊天也就是群聊
然后不管是私聊还是群聊,拿到参数去调用openapi请求回复,以下是通用代码
response = openai.Completion.create(
model="text-davinci-003",
prompt=prompt,
temperature=0.6,
max_tokens=3000,
top_p=1,
frequency_penalty=0,
presence_penalty=0,
user = partner_name,
)
res = response['choices'][0]['text']
当然,代码是需要加try的,防止请求chatgpt发送问题。这个经常出问题的
res就是chatgpt正常返回的文本结果
如果是频道聊天,就拿到chatgpt所在的频道并且用chatgpt的联系人来发送消息就行了
chatgpt_channel_id.with_user(user_chatgpt).message_post(body=res, message_type='comment', subtype_xmlid='mail.mt_comment')
如果是私聊,就拿 user_chatgpt这个用户调用消息发送即可
self.with_user(user_chatgpt).message_post(body=res, message_type='comment', subtype_xmlid='mail.mt_comment')
但是实际测试过程中发现bug,这个嗲吗基于无法生效,所以小改了一下
判断私聊的代码改为如下:
if author_id != partner_chatgpt.id and (chatgpt_name in msg_vals.get('record_name', '') or 'ChatGPT,' in msg_vals.get('record_name', '') ) and self.channel_type == 'chat':
私聊里发送消息的代码改为如下:
channel = self.env[msg_vals.get('model')].browse(msg_vals.get('res_id'))
channel.with_user(user_chatgpt).message_post(body=res, message_type='comment',subtype_xmlid='mail.mt_comment')
然后私聊就正常了
接着分析data数据目录,发现里面就两个文件
mail_channel_data.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record model="mail.channel" id="channel_chatgpt">
<field name="name">ChatGPT</field>
<field name="description">ChatGPT Integration</field>
<field name="image_128" type="base64" file="is_chatgpt_integration/static/description/chatgpt.png"/>
</record>
<record model="mail.message" id="module_install_notification">
<field name="model">mail.channel</field>
<field name="res_id" ref="is_chatgpt_integration.channel_chatgpt"/>
<field name="message_type">email</field>
<field name="subtype_id" ref="mail.mt_comment"/>
<field name="subject">Welcome to ChatGPT Channel!</field>
<field name="body"><![CDATA[<p>Welcome to the #ChatGPT channel.</p>
<p>Ask your questions to ChatGPT</b>.</p>]]></field>
</record>
<record model="mail.channel.member" id="channel_member_chatgtp_channel_for_admin">
<field name="partner_id" ref="base.partner_admin"/>
<field name="channel_id" ref="is_chatgpt_integration.channel_chatgpt"/>
<field name="fetched_message_id" ref="is_chatgpt_integration.module_install_notification"/>
<field name="seen_message_id" ref="is_chatgpt_integration.module_install_notification"/>
</record>
<record model="mail.channel" id="is_chatgpt_integration.channel_chatgpt">
<field name="group_ids" eval="[Command.link(ref('base.group_user'))]"/>
</record>
</data>
</odoo>
这个初始化了一个聊天频道,把所有内部用户拉进去了。是chatgpt给内部用户进行群聊的
这个不是很清楚
这个初始化了频道创建后的第一条消息
这个初始化了频道的基本信息,名称,图标,描述等
user_partner_data.xml
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="partner_chatgpt" model="res.partner">
<field name="name">ChatGPT</field>
<field name="image_1920" type="base64" file="is_chatgpt_integration/static/description/chatgpt.png"/>
</record>
<record id="user_chatgpt" model="res.users">
<field name="login">chatgpt</field>
<field name="password">chatgpt</field>
<field name="partner_id" ref="is_chatgpt_integration.partner_chatgpt"/>
<field name="company_id" ref="base.main_company"/>
<field name="company_ids" eval="[Command.link(ref('base.main_company'))]"/>
<field name="groups_id" eval="[Command.link(ref('base.group_user'))]"/>
</record>
</data>
</odoo>
这个初始化chatgpt的用户登录账号密码,联系人id 公司id,群组id,和对应的联系人名称,头像
接着分析controllers路由接口目录,发现里面就一个文件
main.py
# -*- coding: utf-8 -*-
# Copyright (c) 2020-Present InTechual Solutions. (<https://intechualsolutions.com/>)
from odoo import http
class ChatgptController(http.Controller):
@http.route(['/chatgpt_form'], type='http', auth="public", csrf=False,
website=True)
def question_submit(self):
return http.request.render('is_chatgpt_integration.connector')
这个不知道干嘛用的,貌似没有这个页面,访问这个接口也找到不到
好了,总的分析就这点东西,剩下的一些静态图片和模块描述页面