Odoo 20 发布后,官方宣传里 AI 占了很大篇幅。营销归营销,我们把 20.0 的源码翻了一遍,对照 19.0,从代码层面看 AI 到底变了什么。先说清楚取材范围:社区版(odoo/odoo)两个大版本里都没有 ai 模块,产品化的 AI 功能几乎全部在企业版(odoo/enterprise)。所以本文的主战场是企业版,社区版只讲框架层的配套变化。
19.0 已经有什么:别以为 AI 是 20 才有的
19.0 的企业版已经有完整的 AI 基础设施,共 16 个 ai_* 模块:ai(基础模块)、ai_fields(AI 计算字段)、ai_server_actions(AI 服务端动作)、ai_documents、ai_knowledge、ai_crm、ai_livechat、ai_website 等。它的架构是:
- 自带 Key 直连模型厂商:
ai/utils/llm_providers.py里硬编码PROVIDERS列表(OpenAI、Google 两家,含各自 embedding 模型与候选 LLM 清单),ai/utils/llm_api_service.py的LLMApiService直接用requests调 OpenAI 的/responses和 Google 的generateContent接口。API Key 来自系统参数ai.openai_key/ai.google_key(ai/models/res_config_settings.py)或环境变量。 - 会话就是 Discuss 频道:
discuss.channel扩展了channel_type="ai_chat"(ai/models/discuss_channel.py),聊天历史直接取频道里的 mail message。 - Agent + 话题 + 工具:
ai.agent上有llm_model、response_style、topic_ids;工具(use_in_ai的服务端动作)挂在ai.topic上,工具方法直接写在ai.agent模型里(_ai_tool_search、_ai_tool_open_menu_list等,以检索和打开视图为主)。工具循环在LLMApiService.request_llm内部完成,注释原话是 “will call the tools until we are done”。 - RAG 已就位:pgvector 向量检索(
ai/orm/field_vector.py,配合ai_auto_install检测 pgvector)、agent sources(ai.agent.source挂附件与网页)、[SOURCE:210]风格的引用标注,语音转写(Whisper/audio/transcriptions)和实时转写的 client secrets 也已经在 19.0 出现。此外还有ai.composer(文本字段的 AI 起草)、ai.prompt_button(记录上的快捷 AI 按钮)、is_ask_ai_agent(数据库级自然语言问答的专用 agent)——今天 20 宣传里的不少能力,19.0 的代码里已经有雏形。
所以准确的说法是:20.0 不是"从无到有",而是把 19.0 这套东西推倒重做了一层。旧架构的局限也很实在:对话状态散落在频道消息里(每次请求都靠重新拼聊天历史),工具循环是请求进程内的同步 while 循环,没有工具确认和挂起恢复。
企业版 20.0:ai_* 模块从 16 个涨到 50 个
数一下 20.0 企业版源码树,ai_* 前缀模块有 50 个,而 19.0 是 16 个。新增的大头是"每个业务应用一个 AI 挂载模块":ai_helpdesk、ai_project、ai_purchase、ai_sale、ai_stock、ai_calendar、ai_mass_mailing、ai_social、ai_esg、ai_product 等,manifest 的 summary 高度一致——“Augment XX with AI Agents”。另一组是 ai_marketing_automation 及其 5 个桥接模块(把 CRM、优惠券、SMS、电商规则喂给营销活动生成器),还有 ai_agentic、ai_agentic_documents、ai_agentic_knowledge、ai_mcp、ai_html_builder、ai_cloud_storage、ai_timesheet_grid 等。19.0 的 ai_app(AI 应用壳)在 20.0 消失,由 ai_agentic 承接——两者的 manifest description 一字不差,但 ai_agentic 额外依赖 base_automation 并引入了 ai.skill 视图。模块名清单本身就是"20 在 AI 上投入了什么"的直接证据。
Provider 层:从自带 Key 直连,到 IAP 网关
这是最主要的一处改动。20.0 的 ai 模块里,llm_providers.py 和 llm_api_service.py 整个删除,取而代之的是 ai/utils/ai_utils.py:
DEFAULT_ODOO_AI_ENDPOINT = 'https://ai.api.odoo.com'
所有 LLM 调用改为 call_odoo_ai() 走 iap_jsonrpc 打到 Odoo 官方 AI 网关,错误类型换成了 InsufficientCreditError,没有余额就发通知。这意味着你的 AI 使用的 token 消耗将由 Odoo 来定价,你的 AI provider 的订阅不能直接支持。
顺带一提,19.0 的 ai.agent 上有 llm_model 选择字段(GPT-4o 到 GPT-5 一串)和 response_style(analytical/balanced/creative,映射到不同 reasoning 档位);20.0 的 ai.agent 把这些字段全删了——模型选择权收归服务端,客户端只剩 enable_think_longer 这类开关(ai.session 字段)。搜索类工具也加了护栏:内部模型(ai. 前缀)与一组敏感模型(res.groups、res.users.settings、base.automation 等)被 AI_MODELS_BLOCKLIST 挡在 AI 可见范围之外(ai/utils/ai_utils.py)。
会话:从 Discuss 频道到 ai.session 状态机
19.0 没有会话模型,对话散在 ai_chat 频道的消息流里。20.0 新增了专门的 ai.session 和 ai.session.event(ai/models/ai_session.py),这不再是一张聊天记录表,而是一台agent 循环状态机:
loop_state = fields.Selection([
("ready", "Ready"),
("waiting_model", "Waiting for Model"),
("waiting_confirmation", "Waiting for Confirmation"),
("waiting_answer", "Waiting for Answer"),
("waiting_client_result", "Waiting for Client Result"),
("waiting_external_result", "Waiting for External Result"),
("waiting_child", "Waiting for Child Session"),
], ...)
配合 pending_tool_call(等待客户端执行的工具)、resume_token(挂起后恢复的令牌)、request_uuid(唯一索引)与 request_webhook_secret,模型上还挂了一组 SQL CHECK 约束,保证"处于等待态就必须保留恢复数据"。每次 LLM 往返记录成一条 ai.session.event,内容是带类型的 JSON parts:文本、工具调用请求、工具结果、thinking 签名(ai/utils/types.py 里的 TextPart / ToolCallPart / ToolResultPart)。
会话本身也不再只属于聊天:每条 ai.session 都带 res_model/res_id(当前正在看的那条业务记录)、state(工具调用之间传递数据的 JSON)、show_agent_steps(把 agent 步骤展示给用户)、turn_notifications(本轮攒着、最终回复后统一弹出的通知),以及 auto_confirm(免确认执行工具)。调用方式也变了:19.0 是本地进程内同步循环;20.0 把补全请求发给 IAP 网关后挂起,由网关回调 /ai/completion_result_ready 继续循环(ai/controllers/thread.py)。这个回调路由 auth="public" 但用 HMAC 签名验证(secrets.compare_digest 比对 request_webhook_secret),而且路由类型用的是新的 type="json2"——和官方"JSON-2 API 成为标准"的说法正好互相印证。工具循环轮数有 request_round_limit 约束兜底,防止死循环烧钱。
工具调用:注册表、命名工具和客户端工具
19.0 的工具方法直接长在 ai.agent 上;20.0 把它们搬进了抽象模型 ai.tool(ai/models/ai_tool.py),工具按名字注册、按 _access_domain 收权限,新增了一批 19.0 没有的"动手类"工具:_ai_tool_create_records、_ai_tool_update_records(带预览确认)、_ai_tool_ask_user_question(向用户追问,会话进入 waiting_answer)、_ai_tool_load_skills,以及子代理工具 _ai_tool_start_session——父会话可以委派 allowed_agent_ids 里的子 agent 开子会话,深度限制 4 层,这是 19.0 完全没有的多代理协作。
工具本体仍是 ir.actions.server(use_in_ai 字段 19.0 就有),但 20.0 给它加了 ai_tool_name 字段:必须匹配 [a-zA-Z0-9_]{1,64},还有部分唯一索引 UNIQUE(ai_tool_name) WHERE use_in_ai。也就是说 20.0 里"给 AI 用的服务端动作"是按名字精确注册、带 schema 校验参数的正式接口,不再是按 ID 捞一把。19.0 的工具全在服务端执行;20.0 出现了客户端工具:有些工具(如打开某个视图、切换看板)由浏览器执行,服务端通过 bus 频道 ai.session/client_tools 下发,执行完带着 resume_token 回来继续循环。危险动作还有确认闸门:工具可以要求用户点头,会话进入 waiting_confirmation,用户可选 confirm_once / auto_confirm / decline(ai/utils/ai_utils.py 里的 UserInputResponse)——“AI 改你数据之前先问你"这个能力,是 20.0 才有的。
从话题到技能:ai.skill
19.0 把"指令 + 工具"的打包叫话题(ai.topic),20.0 改名 ai.skill,语义更清楚了:ai/models/ai_skill.py 里每个 skill 有 instructions 和 tool_ids(指向 use_in_ai 的服务端动作),按有无工具分为 “executable” 或 “guidance” 两类;系统预装的 native skill 带保护,创建、修改、删除都会被拦下。新 agent 默认勾上全部 native skill(ai.agent._default_skill_ids);会话侧则有 enable_tools / enable_skills 开关,模型还能通过 _ai_tool_load_skills 按需拉取技能内容。
网页搜索、抓取与通话
20.0 的 ai.session 上有 enable_web_search 开关,网页搜索本身被实现成一个内置 AI 服务端动作(ai.ir_actions_server_ai_web_search,见 ai/models/ir_actions_server.py)。批量网页抓取也是新东西:ai.web.page(URL 唯一、sha1 内容校验、多 agent source 共享同一份抓取结果)和 ai.web.scraper.batch(状态机 to_submit → submitted → done/failed),抓取任务提交给服务端处理,完成后回调 /ai/url_scraper_result_ready/<batch_uuid>。通话侧新增 discuss.call.history、mail.call.artifact 和 call.transcription.mixin,rtc 控制器支持按时间段转写通话录音——19.0 只有单段音频转写,没有这套"通话记录 + 分段转写 + 摘要"的组合。
MCP:Odoo 作为工具服务器
新增的 ai_mcp 模块把 Odoo 变成一台 MCP 服务器:/mcp 路由(auth="bearer", bearer_scope="mcp")处理 initialize / ping / tools/list / tools/call 四个方法,把 use_in_ai 的服务端动作暴露成 MCP 工具,外部 Claude 之类的客户端用带 mcp scope 的 API Key 直连,请求分发在 ai.mcp.request.dispatcher 抽象模型里完成,且有独立的访问控制测试(ai_mcp/tests/test_mcp_access.py)。这依赖社区版 20.0 的一个框架变化,见下一节。
社区版框架层的配套变化
社区版两个大版本都没有 AI 模块,但 20.0 有几处框架级改动是上述功能的地基:
- HTTP 层重构:
odoo/http.py从单文件拆成odoo/http/包(dispatcher.py、routing_map.py、session.py、stream.py等)。 - bearer 认证支持 scope:19.0 就有
auth="bearer",但ir_http.py里写死scope='rpc';20.0 要求路由显式声明bearer_scope并按它校验(odoo/addons/base/models/ir_http.py,scope=routing['bearer_scope'])。没有这个,ai_mcp的scope='mcp'无从实现。 - ORM 重构:
odoo/models.py等拆成odoo/orm/包;SQL 约束从_sql_constraints字典改为models.Constraint/models.UniqueIndex类(odoo/orm/table_objects.py)。20.0 企业版ai模块里有 9 处用的是新写法。 - 安全声明文件改名:
ir.model.access.csv改为ir.access.csv(20.0 的企业版 ai 模块 manifest 也同步改了)。 - 仓库顶层新增
skills/目录:内置odoo-guidelines、odoo-review、odoo-security、odoo-web-guidelines四份文档——这是官方给 AI 编码代理准备的"开发技能”,Odoo 自身的开发流程也开始 AI 化了。
对 19.0 用户意味着什么:迁移与注意点
- 计费模式变了,这是最大的坑。19.0 自备 OpenAI/Google Key,成本直接付给厂商;20.0 走 IAP 信用点,预付给 Odoo,连实时转写都有单独的用量上报接口(
/ai/transcription/report_realtime_session_usage,带iap_transaction_token)。升级前先评估用量和成本结构,并且确认你的区域/部署形态能用ai.api.odoo.com(自建内网部署还要考虑数据出域合规——对话和上下文会发到 Odoo 官方网关)。 - 二开接口基本全变。如果 19.0 上你们扩展过
ai.topic、调过LLMApiService、或者在ai.agent上加过_ai_tool_*,这些类和文件在 20.0 里已经不存在:ai.topic→ai.skill(还分"指导型"和"可执行型",内置 native skill 禁止改删),工具方法 →ai.tool注册表,频道即会话 →ai.session。按 19.0 接口写的集成代码要重写。好消息是工具的载体没变,仍然是ir.actions.server——你们 19.0 上写过的 AI 服务端动作,升级后补一个ai_tool_name就能挂回工具面板。 - 社区版升级注意:导入路径受
odoo/http/、odoo/orm/拆包影响(顶层from odoo.http import route这类常用导入仍有兼容导出,但深层路径要核对);自定义模块的ir.model.access.csv要改名为ir.access.csv;_sql_constraints建议迁到models.Constraint。 - pgvector 依赖不变:
ai_fields、embedding 这套 19.0 已有,20.0 继续依赖 pgvector,数据库侧没有新的运维负担。 - 没升级的 19.0 用户:19.0 的 AI 能用但天花板明显——没有工具确认、没有子代理、没有 MCP、没有通话摘要,且 Key 直连模式下没有官方网关侧的模型路由。如果 AI 是你们选型的重点,20.0 值得认真评估。