ODOO18菜鸟二次开发系列(8)-RAG增强向量知识库
Odoo RAG系统实现指南 本文详细介绍了如何在Odoo中构建RAG(检索增强生成)系统,用于创建企业内部知识库问答系统。系统包含两个核心流程: 知识库索引流程: 用户上传文档(PDF/TXT等)到知识库 系统自动处理文档内容,分割成文本块 调用Embedding模型(Ollama)转换为向量 存储文本块和向量到向量数据库(ChromaDB) 问答流程: 用户提问转换为向量 从向量数据库检索相似
文章目录
前言
在 Odoo 中实现 RAG (Retrieval-Augmented Generation) 是一个非常强大且实用的方案,尤其适合构建企业内部的知识库问答系统。它能充分利用 Odoo 强大的数据管理能力和灵活的业务流程框架。
下面,我将为您提供一个完整、分步、可操作的指南,说明如何在 Odoo 中从零开始构建一个 RAG 系统。我们将创建一个新的自定义模块 llm_rag 来实现这个功能。
核心架构
我们将实现两个核心流程:
1,知识库索引流程 (后台):
- 用户在 Odoo 中上传文档 (PDF, TXT等) 到一个“知识库”记录中。
- 一个后台任务会自动或手动触发,将文档内容抽取出来。
- 文档内容被分割成小文本块 (Chunks)。
- 调用 Embedding 模型 (通过 Ollama) 将每个文本块转换为向量。
- 将文本块和其对应的向量存入一个向量数据库 (Vector Database)。
2,问答流程 (前台):
- 用户在一个聊天界面提出问题。
- 问题被 Embedding 模型转换为向量。
- 用这个向量去向量数据库中进行相似度搜索,找出最相关的几个文本块 (Context)。
- 构建一个包含“相关资料”和“用户问题”的增强提示 (Augmented Prompt)。
- 将这个增强提示发送给一个生成式 LLM (通过 Ollama),生成最终答案。
- 答案显示在聊天界面。

一、准备工作
在开始之前,请确保:
1,安装 Python 库: 您的 Odoo 运行环境需要安装一些额外的库。激活您的 Odoo 虚拟环境 (.venv) 后运行:
pip install chromadb pypdf langchain-text-splitters
chromadb: 一个简单易用的本地向量数据库,非常适合入门。
pypdf: 用于读取 PDF 文件的内容。
langchain-text-splitters: 提供强大的文本分割工具。
2,准备 Ollama 模型: 确保您的 Ollama 服务正在运行,并且已经拉取了至少两个模型:
一个 Embedding 模型: 用于将文本转换为向量。mxbai-embed-large 是一个很好的选择。
一个生成式模型: 用于生成答案。llama3, qwen2 或 deepseek-llm 都可以。
ollama pull mxbai-embed-large
ollama pull llama3
二、二次开发
1.创建 Odoo 自定义模块
创建一个名为 llm_rag 的新 Odoo 模块,目录结构如下:
llm_rag/
├── init.py
├── manifest.py
├── models/
│ ├── init.py
│ ├── vector_store_service.py # 存放与ChromaDB交互的逻辑
│ ├── rag_knowledge_base.py # 知识库模型
│ └── rag_chat_session.py # 聊天会话模型
└── views/
├── rag_knowledge_base_views.xml
└── rag_chat_session_views.xml
manifest.py:>代码如下:
{
'name': 'LLM RAG Engine',
'version': '1.0',
'summary': 'Implementation of a RAG system within Odoo',
'author': 'Your Name',
'depends': ['base', 'mail', 'llm'], # 假设您有一个'llm'模块处理Ollama交互
'data': [
'views/rag_knowledge_base_views.xml',
'views/rag_chat_session_views.xml',
],
'application': True,
}
2.向量数据库服务
创建一个非 Odoo模型的 Python 类来封装所有与 ChromaDB 的交互,这样可以保持代码整洁。
models/vector_store_service.py代码如下:
import chromadb
from odoo.tools import config
# 初始化 ChromaDB 客户端
# data_path 指向 Odoo 的数据目录,确保数据持久化
db_path = config.get('data_dir') + "/chromadb"
client = chromadb.PersistentClient(path=db_path)
class VectorStoreService:
def __init__(self, collection_name):
self.collection = client.get_or_create_collection(name=collection_name)
def add_documents(self, documents, metadatas, ids):
"""添加文档块和向量到集合中"""
# 注意:ChromaDB 会自动处理 embedding,但我们手动处理以适配 Odoo 的 LLM Provider
# 这里我们假设 `documents` 已经是 embedding 之后的结果
# 但为了演示清晰,我们先传入文本,让 ChromaDB 的 embedding function 处理
# 在实际集成中,你会先用Ollama生成embeddings,再用collection.add(embeddings=..., documents=..., metadatas=..., ids=...)
self.collection.add(
documents=documents,
metadatas=metadatas,
ids=ids
)
def query(self, query_texts, n_results=5):
"""根据查询文本检索相关文档"""
return self.collection.query(
query_texts=[query_texts],
n_results=n_results
)
3.定义 Odoo 模型
知识库模型 (rag.knowledge.base),这个模型用于管理知识源文档。
models/rag_knowledge_base.py代码如下:
import base64
import logging
from PyPDF2 import PdfReader
from io import BytesIO
from langchain_text_splitters import RecursiveCharacterTextSplitter
from odoo import models, fields, api, _
from odoo.exceptions import UserError
from .vector_store_service import VectorStoreService
_logger = logging.getLogger(__name__)
class RagKnowledgeBase(models.Model):
_name = 'rag.knowledge.base'
_description = 'RAG Knowledge Base'
name = fields.Char(required=True)
attachment_ids = fields.Many2many('ir.attachment', string='Source Documents')
state = fields.Selection([('draft', 'Draft'), ('indexed', 'Indexed')], default='draft')
collection_name = fields.Char(string="Vector Collection Name", compute="_compute_collection_name", store=True)
@api.depends('id')
def _compute_collection_name(self):
for rec in self:
rec.collection_name = f"rag_collection_{rec.id}"
def action_index_documents(self):
"""处理附件、分割、嵌入并存入向量数据库"""
if not self.attachment_ids:
raise UserError(_("Please attach at least one document."))
vector_service = VectorStoreService(self.collection_name)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
all_chunks = []
for attachment in self.attachment_ids:
_logger.info(f"Processing attachment: {attachment.name}")
content = base64.b64decode(attachment.datas).decode('utf-8', errors='ignore')
# 简单的内容提取,可以根据 mimetype 扩展
if attachment.mimetype == 'application/pdf':
text = ""
pdf_reader = PdfReader(BytesIO(base64.b64decode(attachment.datas)))
for page in pdf_reader.pages:
text += page.extract_text()
else: # 默认为纯文本
text = base64.b64decode(attachment.datas).decode('utf-8', errors='ignore')
chunks = text_splitter.split_text(text)
all_chunks.extend(chunks)
if not all_chunks:
raise UserError(_("Could not extract any text from the documents."))
# 嵌入并添加
# 假设您的 llm.provider 模型有一个 embed 方法
llm_provider = self.env['llm.provider'].search([('provider', '=', 'ollama')], limit=1)
if not llm_provider:
raise UserError(_("Ollama provider not found."))
# Ollama 的 embed API 一次只处理一个文本
embeddings = [llm_provider.embed('mxbai-embed-large', chunk) for chunk in all_chunks]
# 为 ChromaDB 准备数据
doc_ids = [f"doc_{self.id}_chunk_{i}" for i in range(len(all_chunks))]
metadatas = [{'source': attachment.name} for attachment in self.attachment_ids for _ in text_splitter.split_text(base64.b64decode(attachment.datas).decode('utf-8', errors='ignore'))]
vector_service.collection.add(
embeddings=embeddings,
documents=all_chunks,
metadatas=metadatas,
ids=doc_ids
)
self.state = 'indexed'
_logger.info(f"Successfully indexed {len(all_chunks)} chunks for knowledge base '{self.name}'.")
return True
4.聊天会话模型 (rag.chat.session)
这个模型提供用户交互的界面。
models/rag_chat_session.py代码如下(示例):
from odoo import models, fields, api, _
from .vector_store_service import VectorStoreService
class RagChatSession(models.Model):
_name = 'rag.chat.session'
_inherit = ['mail.thread']
_description = 'RAG Chat Session'
name = fields.Char(default="New Chat")
knowledge_base_id = fields.Many2one('rag.knowledge.base', required=True)
user_input = fields.Text(string="Your Question")
def action_send_message(self):
self.ensure_one()
if not self.user_input:
return
# 1. 检索 (Retrieve)
vector_service = VectorStoreService(self.knowledge_base_id.collection_name)
llm_provider = self.env['llm.provider'].search([('provider', '=', 'ollama')], limit=1)
query_embedding = llm_provider.embed('mxbai-embed-large', self.user_input)
results = vector_service.collection.query(
query_embeddings=[query_embedding],
n_results=5
)
context_docs = "\n---\n".join(results['documents'][0])
# 2. 增强 (Augment)
prompt_template = f"""
You are a helpful assistant. Answer the user's question based ONLY on the following context.
If the context does not contain the answer, say "I cannot answer this question based on the provided information."
CONTEXT:
{context_docs}
USER'S QUESTION:
{self.user_input}
ANSWER:
"""
# 3. 生成 (Generate)
# 假设您的 llm.provider 有一个 generate 方法
response_text = llm_provider.generate('llama3', prompt_template)
# 在 Odoo 的 chatter 中记录问答
self.message_post(body=f"<b>You:</b><br/>{self.user_input}")
self.message_post(body=f"<b>Assistant:</b><br/>{response_text}")
# 清空输入框
self.user_input = ""
4.创建用户界面 (Views)
views/rag_knowledge_base_views.xml::
<odoo>
<record id="view_rag_knowledge_base_form" model="ir.ui.view">
<field name="name">rag.knowledge.base.form</field>
<field name="model">rag.knowledge.base</field>
<field name="arch" type="xml">
<form>
<header>
<button name="action_index_documents" string="Index Documents" type="object" class="btn-primary" attrs="{'invisible': [('state', '=', 'indexed')]}"/>
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<field name="name"/>
<field name="collection_name" readonly="1"/>
</group>
<notebook>
<page string="Documents">
<field name="attachment_ids"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- Tree and Action views... -->
</odoo>
views/rag_chat_session_views.xml:
<odoo>
<record id="view_rag_chat_session_form" model="ir.ui.view">
<field name="name">rag.chat.session.form</field>
<field name="model">rag.chat.session</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<field name="name"/>
<field name="knowledge_base_id"/>
</group>
<div class="o_form_label">
<label for="user_input"/>
</div>
<field name="user_input" placeholder="Ask something..."/>
<button name="action_send_message" string="Send" type="object" class="btn-primary"/>
</sheet>
<div class="o_attachment_preview"/>
<div class="o_thread"/>
</form>
</field>
</record>
<!-- Tree and Action views... -->
</odoo>
三、使用方法
1,安装模块: 将 llm_rag 模块放入您的 addons 目录,然后在 Odoo 中安装它。
2,创建知识库:
- 进入 “RAG Knowledge Base” 菜单。
- 创建一个新记录,给它一个名字。
- 在 “Documents” 标签页下,上传PDF 或 TXT 文件。
- 点击 “Index Documents” 按钮。后台会开始处理文件。
3,开始聊天:
- 进入 “RAG Chat Session” 菜单。
- 创建一个新的聊天会话。
- 选择您刚刚创建的知识库。
- 在 “Your Question” 输入框中提问,然后点击 “Send”。
- 在下方的 Chatter 中查看您的问题和 AI 的回答。
总结
本章提供了一个功能齐全的 RAG 框架。您可以基于此进行大量扩展:
- 异步处理: 将索引过程 (action_index_documents) 放到 Odoo 的队列任务 (queue_job) 中,避免
UI 阻塞。 - 安全性: 使用 Odoo 的记录规则 (ir.rule) 来控制谁可以访问哪些知识库。
- 更广泛的文档支持: 集成 unstructured.io 等库来支持 Word, PowerPoint, HTML 等更多格式。
- 优化检索: 使用更高级的检索策略,例如 Re-ranking。
- 引用来源: 在答案旁边显示它所参考的源文本块,增加可信度。
下一章介绍AI聊天机器人开发
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)