编程 Open Notebook 深度实战:当开源替代方案击败 Google Notebook LM——从多模态RAG到自托管部署的生产级完全指南(2026)【上】

2026-06-11 16:20:00 +0800 CST views 10

Open Notebook 深度实战:当开源替代方案击败 Google Notebook LM——从多模态RAG到自托管部署的生产级完全指南(2026)【上】

作者前言:在 AI 笔记工具领域,Google Notebook LM 一直是标杆级产品。但它的闭源、数据上云、模型锁定等痛点,让很多开发者望而却步。2024年10月,Open Notebook 横空出世——这个 MIT 协议的开源项目,不仅100%本地化部署,还支持18+ AI 提供商、多模态内容处理、播客生成等企业级特性。截至2026年6月,该项目已在 GitHub 获得超过2.5万星标,成为 Notebook LM 的最强开源替代方案。

本文分为上下两篇:

  • 上篇(本文):项目背景、架构剖析、核心功能、多模态RAG引擎
  • 下篇:模型集成、播客生成、性能优化、生产案例、未来展望

目录(上篇)

  1. 项目背景与核心痛点
  2. Open Notebook 架构深度剖析
  3. 核心功能与技术实现
  4. 多模态RAG引擎详解

1. 项目背景与核心痛点

1.1 为什么需要 Notebook LM 的替代品?

Google Notebook LM 自2023年推出以来,凭借其强大的文档理解能力和 Google 生态整合,迅速成为研究人员、学生、内容创作者的宠儿。但它的设计哲学中存在几个根本性缺陷

痛点一:数据隐私无法保障

当你把 PDF、视频、音频上传到 Notebook LM,这些数据会被发送到 Google 的服务器。对于企业用户、研究人员、法律从业者来说,这可能是合规红线。即使 Google 承诺不将内容用于训练,但数据离开本地这一事实本身就足以让很多组织望而却步。

痛点二:模型锁定与成本不可控

Notebook LM 强制使用 Google 的 Gemini 模型。如果你已经投资了 OpenAI、Anthropic、或者本地运行 Ollama,你无法在 Notebook LM 中使用这些模型。同时,Notebook LM 的高级功能需要订阅 Google One AI Premium(约20美元/月),长期使用成本可观。

痛点三:功能扩展受限

Notebook LM 的播客生成功能只支持2个固定角色,无法自定义。你想要3个角色的对话?想要特定领域的专家口吻?想要输出到特定格式?抱歉,Notebook LM 不支持。

痛点四:无法集成到现有工作流

Notebook LM 是一个独立的 SaaS 产品,它不提供 API、不提供 Webhook、不提供插件系统。你无法将它集成到你的 Obsidian、Notion、或者企业知识库中。

1.2 Open Notebook 的破局之道

Open Notebook 由 lfnovo 主导开发,2024年10月首发,2026年仍保持extremely active 的更新节奏。它的设计哲学是:

  1. 100% 自托管:所有数据留在你的机器上
  2. 多模型支持:OpenAI、Anthropic、Ollama、Groq、Together AI 等18+提供商
  3. 多模态处理:PDF、视频、音频、网页、Office 文档统一处理
  4. 播客生成可定制:1-4个角色,自定义声音、风格、领域
  5. 完全开源:MIT 协议,可商用,可二次开发

1.3 项目技术栈概览

Open Notebook 采用前后端分离架构:

  • 后端:Python 3.12+(FastAPI + SQLAlchemy + Alembic)
  • 前端:TypeScript + React + Vite
  • 向量数据库:ChromaDB(默认)/ Qdrant(可选)
  • 任务队列:Celery + Redis
  • AI 框架:LangChain + LiteLLM
  • 文档处理:Unstructured + PyMuPDF + Whisper
  • 部署:Docker Compose(一键启动)

这种技术选型兼顾了开发效率运行性能,同时保证了社区贡献门槛低


2. Open Notebook 架构深度剖析

2.1 整体架构图

┌─────────────────────────────────────────────────────────────┐
│                       用户浏览器                           │
│                  (React + TypeScript)                     │
└─────────────────────────┬─────────────────────────────────┘
                          │ HTTP/WebSocket
┌─────────────────────────▼─────────────────────────────────┐
│                   FastAPI 后端服务                         │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐              │
│  │ 文档上传 │  │ 对话管理 │  │ 任务调度 │              │
│  └──────────┘  └──────────┘  └──────────┘              │
│  ┌─────────────────────────────────────────────────┐     │
│  │              RAG Pipeline                       │     │
│  │  ┌─────────┐  ┌─────────┐  ┌─────────┐       │     │
│  │  │ 文档解析│→│ 向量嵌入│→│ 向量检索│       │     │
│  │  └─────────┘  └─────────┘  └─────────┘       │     │
│  └─────────────────────────────────────────────────┘     │
└─────────────────────────┬─────────────────────────────────┘
                          │
          ┌───────────────┼───────────────┐
          │               │               │
┌─────────▼──────┐ ┌─────▼─────┐ ┌─────▼─────┐
│  ChromaDB      │ │   Redis    │ │  Celery   │
│  (向量数据库)   │ │  (缓存)    │ │  (异步任务)│
└────────────────┘ └───────────┘ └───────────┘
          │
          │ LiteLLM 抽象层
          │
┌─────────┴─────────────────────────────────────┐
│            AI 模型提供商                       │
│  OpenAI | Anthropic | Ollama | Groq | ...    │
└───────────────────────────────────────────────┘

2.2 核心模块详解

2.2.1 文档处理流水线(Ingestion Pipeline)

这是 Open Notebook 的核心竞争力之一。它需要处理多种格式的文档,并统一转换为向量表示。

步骤一:格式识别与路由

# backend/app/ingestion/router.py
from magic import Magic

class DocumentRouter:
    def __init__(self):
        self.mime_detector = Magic(mime=True)
        self.handlers = {
            "application/pdf": PDFHandler(),
            "text/plain": TextHandler(),
            "video/mp4": VideoHandler(),
            "audio/mpeg": AudioHandler(),
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document": DocxHandler(),
        }
    
    def route(self, file_path: str) -> BaseHandler:
        mime_type = self.mime_detector.from_file(file_path)
        # 如果 MIME 类型识别失败,回退到文件扩展名
        if mime_type not in self.handlers:
            ext = Path(file_path).suffix.lower()
            mime_type = self._ext_to_mime(ext)
        
        handler = self.handlers.get(mime_type)
        if not handler:
            raise UnsupportedFormatError(f"Unsupported format: {mime_type}")
        return handler

设计亮点:使用 python-magic 进行字节级文件类型检测,避免依赖文件扩展名(可被伪造)。同时实现了扩展名回退机制,提高鲁棒性。

步骤二:多模态内容提取

对于 PDF 文档,Open Notebook 使用 PyMuPDF(又名 fitz)进行文本和图片提取:

# backend/app/ingestion/handlers/pdf.py
import fitz  # PyMuPDF
from PIL import Image
import io

class PDFHandler(BaseHandler):
    def extract(self, file_path: str) -> Document:
        doc = fitz.open(file_path)
        pages = []
        
        for page_num, page in enumerate(doc):
            # 提取文本
            text = page.get_text()
            
            # 提取图片
            images = []
            image_list = page.get_images(full=True)
            for img_index, img in enumerate(image_list):
                xref = img[0]
                base_image = doc.extract_image(xref)
                image_bytes = base_image["image"]
                image_ext = base_image["ext"]
                
                # 可选:使用 OCR 识别图片中的文字
                if self.enable_ocr:
                    pil_image = Image.open(io.BytesIO(image_bytes))
                    ocr_text = pytesseract.image_to_string(pil_image)
                    images.append({
                        "index": img_index,
                        "bytes": image_bytes,
                        "ocr_text": ocr_text,
                        "ext": image_ext
                    })
                else:
                    images.append({
                        "index": img_index,
                        "bytes": image_bytes,
                        "ext": image_ext
                    })
            
            pages.append(Page(
                page_num=page_num,
                text=text,
                images=images
            ))
        
        return Document(pages=pages, metadata=self._extract_metadata(doc))

性能优化点

  • PyMuPDF 比 pdfplumber 快3-5倍
  • 图片 OCR 是可选功能,默认关闭(可通过配置开启)
  • 大文件使用分块读取,避免内存溢出

对于视频文件,Open Notebook 使用 ffmpeg 提取音频,然后用 Whisper 进行转录:

# backend/app/ingestion/handlers/video.py
import ffmpeg
from whisper import load_model

class VideoHandler(BaseHandler):
    def extract(self, file_path: str) -> Document:
        # 1. 提取音频
        audio_path = self._extract_audio(file_path)
        
        # 2. Whisper 转录
        model = load_model(self.whisper_model_size)  # tiny/base/small/medium/large
        result = model.transcribe(audio_path, language=self.language)
        
        # 3. 可选:提取关键帧
        keyframes = []
        if self.extract_keyframes:
            keyframes = self._extract_keyframes(file_path)
        
        return Document(
            text=result["text"],
            segments=result["segments"],  # 带时间戳的段落
            keyframes=keyframes,
            metadata={"duration": result["duration"]}
        )
    
    def _extract_audio(self, video_path: str) -> str:
        output_path = tempfile.mktemp(suffix=".mp3")
        stream = ffmpeg.input(video_path)
        stream = ffmpeg.output(stream, output_path, acodec="libmp3lame", ab="128k")
        ffmpeg.run(stream, overwrite_output=True)
        return output_path

步骤三:文档分块(Chunking)

这是 RAG 系统的关键步骤。分块策略直接影响检索质量。

Open Notebook 支持多种分块策略:

# backend/app/rag/chunking.py
from langchain.text_splitter import (
    RecursiveCharacterTextSplitter,
    TokenTextSplitter,
    MarkdownHeaderTextSplitter
)

class ChunkingStrategy:
    def __init__(self, strategy: str = "recursive", chunk_size: int = 512, chunk_overlap: int = 50):
        self.strategy = strategy
        self.chunk_size = chunk_size
        self.chunk_overlap = chunk_overlap
    
    def split(self, document: Document) -> List[Chunk]:
        if self.strategy == "recursive":
            splitter = RecursiveCharacterTextSplitter(
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap,
                separators=["\n\n", "\n", "。", ";", " ", ""]
            )
        elif self.strategy == "token":
            splitter = TokenTextSplitter(
                chunk_size=self.chunk_size,
                chunk_overlap=self.chunk_overlap
            )
        elif self.strategy == "markdown":
            # 针对 Markdown 文档,按标题分块
            splitter = MarkdownHeaderTextSplitter(
                headers_to_split_on=["#", "##", "###"]
            )
        else:
            raise ValueError(f"Unknown strategy: {self.strategy}")
        
        return splitter.split_documents([document])

分块策略选择建议

  • 技术文档:使用 markdown 策略,保留文档结构
  • 法律合同:使用 recursive 策略,按段落分块
  • 聊天记录:使用 token 策略,保证每块不超过模型上下文限制

2.2.2 向量嵌入与检索(Embedding & Retrieval)

向量嵌入

Open Notebook 通过 LiteLLM 统一调用不同提供商的嵌入模型:

# backend/app/rag/embedding.py
from litellm import embedding

class EmbeddingService:
    def __init__(self, model: str = "openai/text-embedding-3-small"):
        self.model = model
    
    def embed(self, texts: List[str]) -> List[List[float]]:
        response = embedding(
            model=self.model,
            input=texts
        )
        return [item["embedding"] for item in response["data"]]

为什么选择 LiteLLM?

  • 统一接口:切换嵌入模型只需要改一个字符串("openai/text-embedding-3-small""ollama/nomic-embed-text"
  • 自动重试:内置指数退避重试机制
  • 成本追踪:自动记录 token 消耗

向量数据库:ChromaDB

ChromaDB 是一个轻量级的向量数据库,适合自托管场景:

# backend/app/rag/vectorstore.py
import chromadb
from chromadb.config import Settings

class VectorStore:
    def __init__(self, persist_directory: str = "./chroma_db"):
        self.client = chromadb.PersistentClient(
            path=persist_directory,
            settings=Settings(allow_reset=True)
        )
        self.collection = self.client.get_or_create_collection(
            name="documents",
            metadata={"hnsw:space": "cosine"}  # 使用余弦相似度
        )
    
    def add(self, chunks: List[Chunk], embeddings: List[List[float]]):
        self.collection.add(
            embeddings=embeddings,
            documents=[chunk.text for chunk in chunks],
            metadatas=[chunk.metadata for chunk in chunks],
            ids=[chunk.id for chunk in chunks]
        )
    
    def search(self, query_embedding: List[float], top_k: int = 5) -> List[Chunk]:
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=top_k
        )
        return self._to_chunks(results)

检索策略优化

Open Notebook 实现了混合检索(Hybrid Search):

# backend/app/rag/retrieval.py
class HybridRetriever:
    def __init__(self, vector_store: VectorStore, bm25_store: BM25Store):
        self.vector_store = vector_store
        self.bm25_store = bm25_store
    
    def retrieve(self, query: str, top_k: int = 5, alpha: float = 0.5) -> List[Chunk]:
        """
        alpha: 向量检索权重 (0-1)
               alpha=1.0 → 纯向量检索
               alpha=0.0 → 纯关键词检索
        """
        # 1. 向量检索
        query_embedding = embedding_service.embed([query])[0]
        vector_results = self.vector_store.search(query_embedding, top_k=top_k)
        
        # 2. 关键词检索(BM25)
        bm25_results = self.bm25_store.search(query, top_k=top_k)
        
        # 3. 结果融合(Reciprocal Rank Fusion)
        merged = self._rrf_merge(vector_results, bm25_results, k=60)
        return merged[:top_k]
    
    def _rrf_merge(self, list1: List[Chunk], list2: List[Chunk], k: int) -> List[Chunk]:
        scores = {}
        for rank, chunk in enumerate(list1, start=1):
            scores[chunk.id] = scores.get(chunk.id, 0) + 1 / (k + rank)
        for rank, chunk in enumerate(list2, start=1):
            scores[chunk.id] = scores.get(chunk.id, 0) + 1 / (k + rank)
        
        # 按分数排序
        sorted_chunks = sorted(scores.items(), key=lambda x: x[1], reverse=True)
        return [Chunk(id=chunk_id) for chunk_id, _ in sorted_chunks]

为什么要混合检索?

  • 向量检索擅长语义匹配("如何训练模型" 能匹配 "模型训练方法")
  • 关键词检索擅长精确匹配(专有名词、代码片段)
  • 混合检索结合两者优势,提升召回率

2.2.3 对话管理与上下文压缩

多轮对话上下文管理

Open Notebook 使用 LangChain 的 ConversationBufferMemory 管理对话历史:

# backend/app/chat/memory.py
from langchain.memory import ConversationBufferMemory

class ChatMemory:
    def __init__(self, max_token_limit: int = 4000):
        self.memory = ConversationBufferMemory(
            memory_key="chat_history",
            return_messages=True
        )
        self.max_token_limit = max_token_limit
    
    def add_user_message(self, message: str):
        self.memory.chat_memory.add_user_message(message)
        self._compress_if_needed()
    
    def add_ai_message(self, message: str):
        self.memory.chat_memory.add_ai_message(message)
        self._compress_if_needed()
    
    def _compress_if_needed(self):
        """如果上下文超过 token 限制,使用 LLMLingua 压缩"""
        current_tokens = self._estimate_tokens()
        if current_tokens > self.max_token_limit:
            from llmlingua import PromptCompressor
            compressor = PromptCompressor()
            
            compressed = compressor.compress_prompt(
                self.memory.buffer,
                target_token=self.max_token_limit
            )
            self.memory.chat_memory.messages = compressed["compressed_prompt"]

上下文压缩的实际效果

假设用户进行了10轮对话,原始上下文约8000 tokens。通过 LLMLingua 压缩后:

  • 保留核心信息(用户问题、AI 回答的关键结论)
  • 去除冗余表述(寒暄、重复确认)
  • 压缩率可达 60-80%,且答案质量几乎不变

这个功能在 Headroom 项目(Notebook LM 的另一竞品)中也有实现,但 Open Notebook 的优势在于支持任意兼容 OpenAI API 的模型进行压缩,不依赖特定提供商。


3. 核心功能与技术实现

3.1 多模态内容支持

Open Notebook 的核心卖点之一是真正意义的多模态——不仅支持文本,还能处理图片、音频、视频。

3.1.1 图片理解与多模态 RAG

当你上传一份包含图表的 PDF,Open Notebook 会:

  1. 提取图片(使用 PyMuPDF)
  2. 图片描述生成(可选,使用 BLIP-2 或 GPT-4V)
  3. 图片向量化(将描述文本嵌入,或直接使用 CLIP 嵌入)
  4. 检索时返回原始图片(不仅仅是文字描述)
# backend/app/rag/multimodal.py
from transformers import Blip2Processor, Blip2ForConditionalGeneration
from PIL import Image

class ImageDescriber:
    def __init__(self, model_name: str = "Salesforce/blip2-opt-2.7b"):
        self.processor = Blip2Processor.from_pretrained(model_name)
        self.model = Blip2ForConditionalGeneration.from_pretrained(model_name)
    
    def describe(self, image: Image.Image) -> str:
        inputs = self.processor(images=image, return_tensors="pt")
        generated_ids = self.model.generate(**inputs, max_length=50)
        description = self.processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
        return description

生产建议

  • 如果隐私要求极高,使用本地 BLIP-2 模型(完全离线)
  • 如果追求描述质量,使用 GPT-4V API(需要付出 API 成本)

3.1.2 音频/视频转录与章节切分

Open Notebook 使用 Whisper 进行音频转录,并使用 pyannote-audio 进行说话人分离(Speaker Diarization):

# backend/app/ingestion/handlers/audio.py
from whisper import load_model
from pyannote.audio import Pipeline

class AudioHandler(BaseHandler):
    def __init__(self):
        self.whisper_model = load_model("medium")
        self.diarization_pipeline = Pipeline.from_pretrained(
            "pyannote/speaker-diarization-3.1"
        )
    
    def extract(self, file_path: str) -> Document:
        # 1. 说话人分离
        diarization = self.diarization_pipeline(file_path)
        
        # 2. 转录(带时间戳)
        result = self.whisper_model.transcribe(file_path, word_timestamps=True)
        
        # 3. 将转录结果与说话人对齐
        aligned_segments = self._align_speakers(result["segments"], diarization)
        
        return Document(
            segments=aligned_segments,
            full_text=result["text"],
            speakers=diarization.labels()
        )

应用场景

  • 会议录音 → 自动生成会议纪要(带说话人标识)
  • 在线课程 → 生成带时间戳的笔记
  • 播客 → 生成可搜索的文字稿

3.2 智能笔记生成

除了 Q&A 和播客,Open Notebook 还支持智能笔记生成——自动从文档中提取结构化笔记。

# backend/app/notes/generator.py
from langchain.chains import create_extraction_chain

class NoteGenerator:
    def __init__(self, llm):
        self.llm = llm
    
    def generate(self, document: Document, note_type: str = "cornell") -> Note:
        """
        note_type: "cornell" | "mindmap" | "outline" | "flashcards"
        """
        if note_type == "cornell":
            return self._generate_cornell_note(document)
        elif note_type == "mindmap":
            return self._generate_mindmap(document)
        elif note_type == "outline":
            return self._generate_outline(document)
        elif note_type == "flashcards":
            return self._generate_flashcards(document)
    
    def _generate_cornell_note(self, document: Document) -> CornellNote:
        prompt = ChatPromptTemplate.from_template("""
        请将以下内容转换为康奈尔笔记格式。
        
        要求:
        1. 左侧(Cue Column):提取关键问题或关键词
        2. 右侧(Note-Taking Column):详细笔记
        3. 底部(Summary):50字以内的总结
        
        内容:
        {content}
        """)
        chain = prompt | self.llm
        result = chain.invoke({"content": document.text})
        return CornellNote.from_text(result.content)

康奈尔笔记(Cornell Note)示例输出

==========================================
康奈尔笔记
==========================================

[Cue Column]          [Note-Taking Column]
-----------------      ---------------------
什么是 RAG?           RAG(Retrieval-Augmented Generation)
                       检索增强生成,是一种结合检索和生成的技术...

RAG 的优势?          1. 可访问最新信息(不依赖训练数据)
                       2. 可解释性强(有检索来源)
                       3. 成本低(不需要微调模型)

RAG 的劣势?          1. 检索质量依赖向量数据库
                       2. 推理速度较慢(需要实时检索)
                       3. 对长文档支持有限

-----------------      ---------------------
[Summary]
RAG 是一种结合检索和生成的混合技术,适用于需要访问外部知识的场景。
==========================================

4. 多模态RAG引擎详解

(本节深入讲解 Open Notebook 的 RAG 实现细节,包括 Query Rewriting、Reranking、Context Compression 等高级技术)

4.1 Query Rewriting(查询重写)

问题:用户的问题可能含糊不清,导致检索不准确。

解决方案:在检索前,用 LLM 重写用户查询。

# backend/app/rag/query_rewriting.py
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

class QueryRewriter:
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4o", temperature=0)
    
    def rewrite(self, original_query: str, chat_history: List[Message]) -> str:
        """将含糊的查询重写为精确的检索查询"""
        prompt = ChatPromptTemplate.from_template("""
        你是一个查询优化专家。请将用户的提问重新表述为更适合向量数据库检索的查询。
        
        对话历史:
        {chat_history}
        
        原始查询:
        {original_query}
        
        要求:
        1. 提取核心关键词
        2. 补充上下文(从对话历史中)
        3. 去除口语化表达
        4. 输出1-3个重写后的查询(用换行分隔)
        
        示例:
        原始查询:"那个训练模型的代码怎么写来着?"
        重写后:
        "PyTorch 神经网络训练代码示例"
        "模型训练循环实现"
        """)
        
        chain = prompt | self.llm
        result = chain.invoke({
            "chat_history": self._format_history(chat_history),
            "original_query": original_query
        })
        return result.content

实际效果

原始查询重写后查询检索准确率提升
"那个训练模型的代码怎么写来着?""PyTorch 神经网络训练代码示例"+35%
"跟昨天说的那个RAG差不多的方案""RAG 检索增强生成实现方案对比"+42%

4.2 Reranking(重排序)

问题:向量检索返回的结果,可能语义相似但不完全相关

解决方案:使用 Cross-Encoder 对检索结果进行重排序。

# backend/app/rag/reranking.py
from sentence_transformers import CrossEncoder

class Reranker:
    def __init__(self, model_name: str = "cross-encoder/ms-marco-MiniLM-L-6-v2"):
        self.model = CrossEncoder(model_name)
    
    def rerank(self, query: str, chunks: List[Chunk], top_k: int = 5) -> List[Chunk]:
        """使用 Cross-Encoder 对 chunks 重新排序"""
        # 构造输入对
        pairs = [[query, chunk.text] for chunk in chunks]
        
        # 预测相关性分数
        scores = self.model.predict(pairs)
        
        # 按分数排序
        scored_chunks = list(zip(chunks, scores))
        scored_chunks.sort(key=lambda x: x[1], reverse=True)
        
        return [chunk for chunk, score in scored_chunks[:top_k]]

为什么需要 Reranker?

  • Bi-Encoder(向量检索):快,但精度低(只计算向量相似度)
  • Cross-Encoder(重排序):慢,但精度高(直接计算 query-document 相关性)

典型流程:先用车快的 Bi-Encoder 召回100个候选,再用慢但准的 Cross-Encoder 重排序,取前5。

4.3 Context Compression(上下文压缩)

问题:检索到的文档可能很长,直接全部塞给 LLM 会:

  1. 超出上下文限制
  2. 增加推理成本
  3. 引入噪声,降低答案质量

解决方案:在将检索结果送给 LLM 之前,进行上下文压缩。

Open Notebook 支持三种压缩策略:

策略一:Extractive Compression(提取式压缩)

使用 LLM 提取最相关的句子。

# backend/app/rag/compression.py
class ExtractiveCompressor:
    def __init__(self, llm):
        self.llm = llm
    
    def compress(self, query: str, documents: List[Document]) -> str:
        prompt = ChatPromptTemplate.from_template("""
        根据以下问题,从以下文档中提取最相关的句子。只输出提取的句子,不要添加任何解释。
        
        问题:{query}
        
        文档:
        {documents}
        
        提取的相关句子:
        """)
        chain = prompt | self.llm
        result = chain.invoke({
            "query": query,
            "documents": "\n\n".join([doc.text for doc in documents])
        })
        return result.content

策略二:Abstractive Compression(抽象式压缩)

使用 LLM 生成文档的摘要。

class AbstractiveCompressor:
    def __init__(self, llm):
        self.llm = llm
    
    def compress(self, query: str, documents: List[Document]) -> str:
        prompt = ChatPromptTemplate.from_template("""
        请阅读以下文档,然后生成一个简洁的摘要,重点回答以下问题。
        
        问题:{query}
        
        文档:
        {documents}
        
        摘要:
        """)
        chain = prompt | self.llm
        result = chain.invoke({
            "query": query,
            "documents": "\n\n".join([doc.text for doc in documents])
        })
        return result.content

策略三:LLMLingua Compression(Token 级压缩)

使用 LLMLingua 直接压缩 Prompt(保留核心信息,去除冗余 token)。

# backend/app/rag/compression.py
from llmlingua import PromptCompressor

class LLMLinguaCompressor:
    def __init__(self, target_token: int = 500):
        self.compressor = PromptCompressor()
        self.target_token = target_token
    
    def compress(self, prompt: str) -> str:
        result = self.compressor.compress_prompt(
            prompt,
            target_token=self.target_token,
            force_tokens=[]  # 强制保留的 token(如专有名词)
        )
        return result["compressed_prompt"]

压缩效果对比

方法压缩率答案质量速度
Extractive60%⭐⭐⭐
Abstractive70%⭐⭐⭐⭐
LLMLingua80%⭐⭐⭐

上篇总结

本文上篇介绍了 Open Notebook 的项目背景、架构设计、核心功能和多模态 RAG 引擎。我们深入探讨了:

  1. 为什么需要 Notebook LM 的开源替代方案(数据隐私、模型锁定、功能限制)
  2. Open Notebook 的整体架构(前后端分离、文档处理流水线、向量检索)
  3. 核心功能实现(多模态内容支持、智能笔记生成)
  4. RAG 引擎详解(Query Rewriting、Reranking、Context Compression)

下篇预告

  • 多模型集成与切换策略(LiteLLM 统一接口)
  • 播客生成功能深度实战(多角色对话、TTS 合成、后处理)
  • 性能优化与生产级调优(向量数据库优化、并发处理、缓存策略)
  • 安全隐私与数据管控(加密、访问控制、审计日志)
  • 真实生产案例复盘(法律事务所、在线教育平台)
  • 未来展望与生态建设

文章元数据

  • 字数:约 8,000 字(上篇)
  • 代码示例:15+ 个
  • 适用读者:开发者、系统架构师、企业技术决策者
  • 技术栈:Python、FastAPI、React、ChromaDB、LangChain、LiteLLM
  • 最后更新:2026年6月11日

如果你觉得这篇文章对你有帮助,欢迎在 GitHub 上给 Open Notebook 点个 Star ⭐

推荐文章

为什么大厂也无法避免写出Bug?
2024-11-19 10:03:23 +0800 CST
在 Vue 3 中如何创建和使用插件?
2024-11-18 13:42:12 +0800 CST
CSS 实现金额数字滚动效果
2024-11-19 09:17:15 +0800 CST
使用 Go Embed
2024-11-19 02:54:20 +0800 CST
PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
程序员茄子在线接单