requests
呼叫等方法串接外部 API 的最佳實踐ORM、清單檔(Manifest)、JSON-RPC、控制器(Controller)、OpenAI API
想像你是一位 Odoo 開發人員,接到一個新挑戰:你的公司希望在 Odoo ERP 系統中引入 AI 能力,例如自動摘要客服工單或分類客戶留言,提升工作效率。面對這個需求,你該如何下手?
本篇文章將從開發者視角出發,逐步拆解 Odoo 模組開發的結構,並分享如何將 Odoo 串接外部 API(例如 OpenAI 的大型語言模型服務)以實現這些智慧功能。在這篇文章中,我們會導入真實情境、解析開發觀點,並輔以簡要程式碼片段,帶領讀者深入了解 Odoo 模組開發 以及 AI 服務整合 的實戰技巧。
在 Odoo 中,一切都是模組。模組是功能的基本單位,可以獨立開發、安裝或移除。每個 Odoo 模組其實就是一個特定結構的目錄,包含各種子元件。當我們啟動 Odoo 伺服器時,系統會掃描所有已安裝模組並載入其中定義的內容,包括初始化 Python 模型類別、建立/更新資料表、讀取 XML/CSV 資料檔載入視圖與權限設定,最後將所有元件註冊至 Odoo 的模型註冊表(Registry)。可以說,一個完整的 Odoo 系統就是由許多模組像積木一樣組合而成的。接下來,我們從開發觀點剖析 Odoo 模組的核心構成與實作邏輯。
一個典型的 Odoo 模組目錄包含以下主要部分:
清單檔(__manifest__.py
):模組的說明檔案,定義名稱、版本、摘要、作者、相依模組、要載入的資料檔等。清單檔宣告這個資料夾是一個 Odoo 模組。例如:
{
'name': 'My AI Integration',
'version': '1.0.0',
'summary': 'Integrate Odoo with OpenAI services',
'author': 'Your Name',
'depends': ['base', 'helpdesk'], # 相依於 base 和 helpdesk 模組
'data': [
'security/ir.model.access.csv',
'views/helpdesk_ticket_views.xml',
],
'installable': True,
'application': False,
}
上述範例中,我們宣告了一個名為 “My AI Integration” 的模組,相依於 Odoo 基礎模組和客服模組(helpdesk),並指定了需要載入的檔案(如安全權限及視圖定義)。當前端使用者在應用列表中看到此模組時,清單檔中的資訊也會用於呈現。
模型(models/
):存放資料模型定義的 Python 檔案。模型是 Odoo ORM 的核心,對應資料庫中的表格,負責定義資料結構以及商業邏輯(如欄位屬性、預設值、onchange 和 constraints 等)。模型類別繼承自 odoo.models.Model
並以 _name
屬性指定模型識別名。例如稍後我們將展示的客服工單模型擴充,就屬於這一層。
視圖(views/
):儲存 XML 格式的視圖定義,包括表單(form)、清單(tree)、看板(kanban)、統計圖表(graph)等各種 UI 畫面。視圖通常綁定到特定模型,用來描述資料該如何呈現給使用者。例如,我們可以為客服工單增加一個按鈕,讓使用者可以點擊後呼叫 AI 摘要功能,這個按鈕和摘要欄位的界面就是透過 XML 視圖定義的。
控制器(controllers/
):包含處理 HTTP 請求的 Python 程式碼,通常使用 @http.route
裝飾器定義路由。控制器允許我們建立自定義的 Web API 端點或網頁(例如提供給第三方系統呼叫 Odoo 資料的 REST API)**。**不過如果只是 Odoo 自己呼叫外部服務,未必需要寫控制器;控制器比較常用於當我們希望暴露一個 Odoo 接口給外部存取時使用,我們會在後面詳述。
資料與安全(data/
、security/
等):包含初始化資料檔(如預設設定、參數值)以及安全相關設定。例如,security/ir.model.access.csv
定義模型的存取權限,決定哪些使用者組別可以讀取、建立、修改或刪除模型記錄。如果開發自定義模型,別忘了新增對應的存取控制,否則即使安裝模組,非管理員的使用者可能無法操作該模型紀錄。
上述元素構成了 Odoo 模組的基本架構。舉例來說,一個簡單的自定義模組目錄結構可能如下(以我們的 AI 串接模組為例):
my_ai_integration/ # 模組目錄
├── __manifest__.py # 模組清單檔
├── __init__.py # 初始化檔,匯入模型等
├── models/
│ ├── __init__.py
│ └── helpdesk_ai.py # 自定義的模型或模型擴充檔案
├── views/
│ └── helpdesk_ticket_views.xml # 定義模型對應的UI畫面
├── security/
│ └── ir.model.access.csv # 存取權限設定
└── data/
└── initial_data.xml (或 demo 資料等)
當我們將模組目錄放入 Odoo 的 addons 路徑並安裝模組時,Odoo 就會依序載入上述元件:先讀 manifest 注册模組資訊和相依關係,再執行 models/
下的 Python 檔案定義模型,接著載入 data/
初始資料和 views/
視圖等 UI 設定,最後應用 security/
權限規則,讓模組完整生效。
💡 Gary’s Pro Tip|用 scaffold 快速創建模組範本
Odoo 提供了名為 scaffold 的指令,可快速建立模組範本結構。只需在終端機執行 odoo-bin scaffold <模組名稱> <放置路徑>,即可自動生成包含基本檔案(如 manifest.py、init.py、樣板模型與視圖檔案)的模組目錄。另外,開發自己的模組時別忘了將模組所在路徑新增到 Odoo 設定檔的 addons_path 中,否則 Odoo 伺服器將無法找到你的模組喔!
./odoo-bin scaffold my_ai_module ./odoo/addons
# models/helpdesk_ai.py
from odoo import models, fields, api
class HelpdeskTicket(models.Model):
_inherit = 'helpdesk.ticket' # 繼承既有的客服工單模型
_description = 'Helpdesk Ticket (with AI Summary)'
ai_summary = fields.Text(string="AI 摘要", help="由 AI 產生的工單摘要", readonly=True)
def action_generate_summary(self):
"""呼叫 OpenAI API 產生摘要並寫入 ai_summary 欄位"""
for record in self:
if not record.description:
continue
summary = self._call_openai(record.description)
record.ai_summary = summary
def _call_openai(self, text):
# 這是私有輔助方法,實際呼叫 OpenAI API 並返回摘要內容
import requests, json
api_key = self.env['ir.config_parameter'].sudo().get_param('openai.api_key')
headers = {
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
}
payload = {
'model': 'gpt-5',
'messages': [{'role': 'user', 'content': f"請總結以下內容:{text}"}],
'temperature': 0.1
}
resp = requests.post("https://api.openai.com/v1/chat/completions",
headers=headers, json=payload, timeout=10)
if resp.status_code == 200:
result = resp.json()
# 提取並回傳 AI 產生的摘要文字
return result.get('choices')[0].get('message').get('content').strip()
else:
# 簡單的錯誤處理
raise Exception(f"OpenAI API 調用失敗: {resp.status_code} - {resp.text}")
上述程式碼展示了模型開發的一些關鍵點:
_inherit = 'helpdesk.ticket'
:這表示我們並非從零開發一個新模型,而是繼承 Odoo 內建的 helpdesk.ticket
模型,藉此擴充它的欄位和功能(繼承與覆寫是 Odoo 模組擴充的強大機制之一)。如果是全新模型,則會使用 _name
來指定模型名稱。ai_summary
,類型為 Text,用來存放 AI 生成的摘要結果。我們將其設為唯讀,避免使用者手動編輯,並提供說明文字。action_generate_summary
,供稍後視圖中的按鈕調用。這個方法會對記錄逐一處理:略過沒有描述內容的工單,對其餘的呼叫內部的 _call_openai
來取得摘要,然後寫回 ai_summary
欄位。_call_openai
方法則負責與 OpenAI 的聊天模型 API 通訊:使用 Python 的 requests
函式庫發出 HTTP POST 請求。我們構造了適當的請求標頭(帶上先前在系統參數中設定的 OpenAI API 金鑰)、請求內容 JSON(指定模型、對話訊息、溫度等參數),並對 OpenAI API 進行呼叫。取得回應後,我們解析 JSON 並提取生成的摘要文字。如果 API 回傳錯誤狀態,則拋出異常供上層處理。值得一提的是,上述程式碼中我們將 OpenAI API 金鑰存放在 Odoo 的 系統參數 (ir.config_parameter
) 中,並透過 sudo()
權限讀取。這樣做的好處是避免將敏感金鑰寫在程式中,方便日後在介面上更改,且提高安全性。稍後在本文我們會更深入討論這類整合的最佳實踐。
定義好模型和方法後,我們需要在 Odoo 介面上給使用者一個入口來使用 AI 摘要功能。這就涉及 視圖(View) 開發。我們可以通過 XML 視圖,將前述模型的 ai_summary
欄位加入到客服工單的表單畫面,並增加一個按鈕來觸發 action_generate_summary
方法。例如以下是一段視圖 XML:
<!-- views/helpdesk_ticket_views.xml -->
<odoo>
<record id="view_helpdesk_ticket_form_inherit_ai" model="ir.ui.view">
<field name="name">helpdesk.ticket.form.inherit.ai</field>
<field name="model">helpdesk.ticket</field>
<field name="inherit_id" ref="helpdesk.view_helpdesk_ticket_form"/>
<field name="arch" type="xml">
<!-- 在客服工單表單的筆記欄位後插入一個摘要欄位 -->
<xpath expr="//field[@name='description']" position="after">
<field name="ai_summary" readonly="1" colspan="2"/>
</xpath>
<!-- 在表單的按鈕區域新增一個「產生摘要」按鈕 -->
<xpath expr="//header" position="inside">
<button name="action_generate_summary" type="object" string="產生AI摘要" class="btn-primary" icon="fa-magic"/>
</xpath>
</field>
</record>
</odoo>
這段 XML 利用了 Odoo 的視圖繼承機制,我們以 inherit_id
指定繼承原始的客服工單表單視圖,然後通過 xpath
定位在描述欄位之後插入我們的新欄位,以及在表單頂部的工具列(header)內加入一個按鈕。按鈕的 name
屬性對應我們模型中定義的方法名 action_generate_summary
,type="object"
表示點擊時將呼叫該伺服器動作。如此一來,使用者在前端即可看到由 AI 產生的摘要欄位,以及一鍵產生摘要的功能。
完成上述模型與視圖的開發,更新模組後,我們就能在 Odoo 客服工單頁面上體驗到 AI 串接的成果:點擊按鈕,稍等片刻,AI 產生的重點摘要便自動出現在欄位中,協助客服人員更快了解工單內容。
💡 Gary’s Pro Tip|開發者模式下調整視圖
開發 Odoo 視圖時,善用 開發者模式(Activating Developer Mode)提供的「視圖調試」功能,可以直接在前端檢視任何頁面的 XML 定義並進行繼承定位。此外,為避免視圖衝突,xpath 的選擇器應盡量精準;若不確定選擇位置,也可以使用 <field name="... position="attributes"> 等方式修改現有欄位屬性,而不直接插入新的節點。
有了模組開發的基礎,我們接下來探討如何將 Odoo 與外部服務串接。常見的情境包括:
針對這兩種方向,Odoo 提供了不同的工具與建議做法。以下我們從三個面向說明:Odoo 內建的 RPC 介面、建立自定義控制器,以及在 Odoo 中呼叫外部 API。
如果第三方應用需要讀寫 Odoo 裡的資料,最直接的辦法是使用 Odoo 官方提供的 外部 API。Odoo 自很早的版本開始就支援以 RPC (Remote Procedure Call) 的方式對外提供服務,包括傳統的 XML-RPC 以及較輕量的 JSON-RPC 兩種介面。透過 RPC,外部程式可以調用 Odoo 的任意模型方法,例如使用 execute_kw
來呼叫 search
、create
、write
等操作。
使用 RPC 串接 Odoo 的步驟大致如下:
/xmlrpc/2/common
或 /jsonrpc
端點進行使用者驗證,取得 uid
(使用者 ID)。驗證時需要提供資料庫名稱、使用者帳號與密碼(或 API 金鑰)。/xmlrpc/2/object
端點(XML-RPC)或對 /jsonrpc
發送帶 model
和 method
的 JSON 請求,讓 Odoo 執行相應的模型方法。例如,我們可以遠端呼叫 helpdesk.ticket
模型的 search_read
來獲取工單列表,或呼叫我們前面實作的 action_generate_summary
來讓 Odoo 替某張工單產生摘要。值得注意的是,預設情況下 Odoo 的許多模型方法都有存取控制,使用 RPC 呼叫時,一樣需要該使用者具有相應權限才能成功執行。透過 RPC 介面整合的優點是快速且通用:不用自己實作 API,直接利用 Odoo 全露出的資料與方法。不過缺點是開發彈性較低且效能需注意——RPC 調用每次都需要經過驗證和資料序列化,若外部應用需要大量高頻存取,可能需要考慮快取或批次處理。另外,在某些情況下,你可能不想給外部系統過多權限使用所有模型,這時 RPC 通用介面就不敷使用,需要更細粒度的控制方式。
另一種串接方法是為 Odoo 建立自定義的 API 端點。透過撰寫 控制器(Controller) 類別並使用 @odoo.http.route
來定義路由,我們可以對外提供 RESTful 風格的接口。例如,我們想提供一個僅回傳特定資訊的簡易 API,讓外部系統能 GET 該 URL 拿資料,或 POST 資料進 Odoo,而不透過一般 RPC 機制。
一個簡單的控制器範例:
# controllers/my_api_controller.py
from odoo import http
class MyAPIController(http.Controller):
@http.route('/myapi/ticket_summary/<int:ticket_id>', auth='api_key', methods=['GET'], type='json')
def get_ticket_summary(self, ticket_id):
ticket = http.request.env['helpdesk.ticket'].browse(ticket_id)
if not ticket.exists():
return {"error": "Ticket not found"}
return {"id": ticket.id, "summary": ticket.ai_summary or ""}
這段程式定義了一個 URL 路徑 /myapi/ticket_summary/<ticket_id>
,當外部對此發送 GET 請求且帶有有效的 API 金鑰(因為我們使用 auth='api_key'
驗證)時,Odoo 將回傳指定工單的 AI 摘要內容(JSON 格式)。這種方式的優點是彈性高:我們可以精確控制提供哪些資料、資料格式為何,也可以在控制器方法中編寫任意的邏輯(例如額外驗證、日誌記錄等)。特別在需要公開特定功能給外部、但不願開放整個 Odoo 資料庫的情況下,自建控制器是很好的選擇。
不過,實作控制器也要留意幾點最佳實踐:
auth='api_key'
來要求每次請求必須帶 API 金鑰,也可以用 auth='user'
讓其繼承使用者權限或 auth='public'
開放給未登入訪客(後者需自行實作驗證機制)。確保你的 API 不會因為過度開放而造成安全漏洞。@http.route
限流或 queue 機制。同時,也要注意資料庫查詢效能,避免一次取出過多記錄導致延遲。type='json'
直接返回字典物件,Odoo 會自動將其序列化為 JSON。但若需要返回非 JSON(如文件、圖片),可以使用 return http.send_file(...)
或 return werkzeug.wrappers.Response(...)
手工構造回應。根據介接對象的需要來設計即可。最後一種情境,也是我們這篇文章聚焦的:由 Odoo 主動呼叫第三方服務(例如 AI 模型 API)。事實上,在 Odoo 中呼叫外部 API 跟一般的 Python 腳本中呼叫差異不大——因為 Odoo 本質上就是 Python 應用,我們可以直接使用 Python 標準庫或第三方庫。最常用的就是前面示範過的 requests
函式庫,用於發送 HTTP 請求並接收結果。
然而,將外部 API 整合進 Odoo 環境,仍有一些開發考量與最佳實踐需要注意:
external_dependencies
宣告,需要先安裝該套件或確保環境已備妥。以我們的案例,其實使用 Python 標準庫即可完成 REST 呼叫,因此無須額外安裝套件。_call_openai
方法中,我們對非 200 狀態碼直接 raise Exception。在實務中,你可能希望更優雅地處理:例如記錄錯誤日誌、重試幾次、或至少不要讓整個 interaction 崩潰。必要時可結合 Python 的重試套件或撰寫重試邏輯。💡 Gary’s Pro Tip|測試 Odoo 外部 API 串接
在 Odoo 環境中呼叫外部 API 進行測試時,可以使用 Odoo Shell (odoo-bin shell
) 啟動互動式環境,在載入 Odoo 環境下手動執行你的方法,這比透過介面點擊更方便調試。此外,充分利用如 Postman 之類的工具來模擬外部服務或檢查 API 回傳,有助於你在開發 Odoo 模組前先確認外部服務的行為,撰寫出更健全的整合代碼。
理解了以上基礎與實踐要點,讓我們回到一開始的情境:將 AI 能力融入 Odoo。以下我們以客服工單摘要為例,說明如何運用自定義模組與 OpenAI API,讓 ERP 系統具備一定的「理解文字」能力。整體流程可用下圖表示:
如上所示,使用者在 Odoo 前端點擊按鈕後,後端會將工單的描述文字發送給 OpenAI 的模型服務,拿到回應摘要,再回寫到 Odoo 資料庫,最後透過視圖更新將結果呈現給使用者。這種串接屬於由 Odoo 呼出的形式,因此我們充分利用了 Odoo 模組開發和 Python 語言的威力。
我們建立了一個自定義模組來擴充現有模型,新增 AI 摘要欄位和生成摘要的伺服器動作,並且修改對應視圖讓使用者可以操作。程式碼編寫上,其實與一般 Python 應用並無二致,但結合 Odoo 的 MVC 架構之後,立即昇華為一個可供 ERP 端使用的功能點。
更棒的是,由於 Odoo 模組化的設計,我們的開發對原有系統是低侵入性的(不需要改核心碼,只是額外載入模組),未來升級 Odoo 或移除此功能也相對容易。這充分體現了 Odoo 作為整合平台的彈性與強大。
當前例僅是眾多可能性之一。透過類似的方式,你可以將各種 AI 能力融入不同業務場景,例如:自動分類客訴訊息、為電子郵件產生回覆草稿、根據產品描述自動標註標籤等等。開發者需要做的,就是運用 Odoo 模組開發技巧,適當地調用外部 AI 接口,並將結果接軌到系統流程中去。
「直接呼叫 API」聽起來很簡單,但真的是最佳解嗎?面對不同的情境,我們需要不同的策略。就像選武器一樣,殺雞焉用牛刀,反之亦然。讓我們比較幾種常見的整合方式:
站在開發者的角度,Odoo 的模組化架構為我們打下了扎實的基礎:透過清晰分工的模型、視圖、控制器等部分,我們能有條不紊地擴充系統功能。
而透過 RPC、控制器與外部請求等機制,我們又能將 Odoo 作為數位中樞,與各種現代服務(尤其是 AI 平台)連結。本篇範例展示了將 OpenAI 串接進 Odoo 的一種實現,你或許已經開始想像更多可能性。
總而言之,掌握 Odoo 模組開發與 API 串接的方法,將讓我們有能力打造出智慧 ERP的雛形——把生成式 AI 的威力引入企業日常工作流。這正是為後續更深入的 AI 應用所做的技術準備與探路。下一篇文章,我們將更進一步,從開發流程本身出發,探討如何運用 Coding Agent 這類 AI 工具輔助 Odoo 開發(例如自動產生程式碼雛形等),讓開發工作更加高效輕鬆!