乐于分享
好东西不私藏

从零搭建轻量图文联动 AI 工程助手|多模态 RAG 知识库实战落地

从零搭建轻量图文联动 AI 工程助手|多模态 RAG 知识库实战落地

Hello,这里是鲸宝电台。

今天这篇推文,来自社区小伙伴 王宣 的硬核实战分享。

如果你也攒了一堆课程笔记、代码截图、架构流程图,却总是“存了就沉了、想找找不到”——这篇分享,值得你逐行读完。

CPU 可跑、图文双入库、混合检索,全程开发异常全量复盘。王宣用一套轻量工程,完整拆解了从零搭建多模态 RAG 知识库的落地全流程。

筒交给王宣~


CPU 可跑 | 图文双入库 | 工业级混合检索 | 全量开发异常复盘

大家好,我是王宣,今天我分享一套轻量化 CPU 可运行的图文多模态 RAG 知识库工程

项目采用文档 + 图片双链路解析、父子块分层分块、BM25 与向量混合检索的工业落地思路,实现本地图文素材统一入库、检索与智能问答;全程模块化低耦合设计,兼容 OpenAI 标准协议,可灵活对接各类大模型 API。接下来我将从项目背景出发,带大家完整拆解这套项目的落地全流程。

在系统学习大模型应用开发之前,我日常积累了大量项目文档、实训笔记、代码截图与架构流程图,但一直存在一个核心痛点:个人学习素材多为图文混杂形态,普通知识库仅支持文本存储,图片里的代码、架构逻辑完全无法沉淀复用。

同时,市面上绝大多数开源多模态 RAG 项目存在明显门槛问题:依赖 GPU、部署繁琐、代码臃肿,并不适合学生低配设备的学习落地场景。过去我搭建普通文本 RAG 时,也经常遇到资料齐全但检索精度不足、关键截图信息完全丢失、学习沉淀断层的问题。为了解决开发者「图文资料无法统一检索、轻量化设备无法落地多模态知识库」的真实痛点,我从零落地了轻量图文联动 AI 工程辅助开发工具,完整实现文本 + 图像双素材统一结构化入库、混合检索、智能问答。

本文基于个人学习积累和实际开发经验撰写,不堆砌理论、不依赖重型硬件,所有代码、步骤、优化思路与开发异常复盘经验均可直接复现,适合每一位新手开发者搭建属于自己的私有化多模态知识库。


  • 一、项目背景与真实落地痛点

作为开发者,我们的学习资产通常分为两类:
  • 文本资料:课程笔记、Markdown 文档、PDF 论文、实训报告

  • 视觉资料:代码截图、项目架构图、技术展会实拍图、流程示意图

传统 RAG 项目普遍存在三大痛点:

1. 视觉信息完全丢失

绝大多数新手 RAG 仅支持文本解析,大量重要截图代码、手绘架构、流程图解,无法入库、无法检索、无法问答,导致一半以上的学习沉淀无法被高效复用。

2. 工业级项目门槛过高

成熟多模态 RAG 架构复杂、依赖高端显卡、部署链路长,学生笔记本完全带不起来,新手也很难从零跑通完整工程。

3. 单一检索方式精度不足

纯向量检索容易丢失专业代码关键词,纯关键词检索无法理解语义关联,单一检索永远无法适配开发者技术资料检索场景。

基于以上问题,我想搭建一套:CPU 可跑、轻量化、易部署、图文双通、检索精准、完全可复现的个人工程化知识库系统。

项目名称:轻量图文联动 AI 工程助手


  • 二、整体设计思路与架构方案

本项目弃用复杂过度设计,以「够用、轻量、实用、可迭代」为核心原则,搭建双链路并行多模态 RAG 架构,这也是本文区别于普通的新手 RAG 教程的重要内容。

1. 双链路整体架构

  • 文本处理链路:文档解析 → 文本清洗 → 分层分块 → 向量化入库 → 语义检索

  • 视觉图像链路:图片降噪预处理 → OCR 文字提取 → 结构化描述生成 → 统一入库检索

所有文档、截图、架构图最终归一化为结构化文本向量,进入同一套知识库,实现图文一体化问答。

2. 核心工程优化思路(工业级落地标准)

在落地过程中,我参考了真实的 RAG 工程优化逻辑,实现以下三处关键优化:

(1)父子双层分块策略

放弃新手通用的固定长度粗暴切分:

  • 子块(256token):负责精细细节检索,精准命中代码、参数、配置细节

  • 父块(768token):负责补全上下文,解决短片段语义断裂、逻辑缺失

  • 重叠分:规避边界知识点丢失

2)图文差异化解析策略

针对开发者最常用的两类图片做专项适配:

  • 代码截图:灰度降噪 + 高斯模糊预处理,提升低质量截图识别率,精准提取代码内容

  • 架构流程图:通过 OCR 提取图中所有文字信息,自动拼接为结构化描述文本,将不可检索的图形信息转化为可检索的文本内容,适配轻量化场景需求

(3)混合检索融合方案

解决单一检索缺陷:

  • BM25 关键词检索(权重 0.4):保证代码、参数、专业术语精准命中

  • 向量语义检索(权重 0.6):保证语义关联、模糊知识点、上下文联想匹配

两者加权融合,适配开发者技术资料检索的真实场景。


  • 三、轻量化技术栈选型(适配学生设备)

为保障快速落地、适配低配 CPU运行,我对技术栈做了准确取舍,只保留核心工程能力:

整体原则:不做过度开发、不做冗余功能、只做最实用的工程落地。


  • 四、环境搭建-可复现

1. 环境要求

  • Python 3.10(兼容性最稳、适配所有依赖)

  • 无需 GPU,普通学生笔记本适配

2. 完整依赖安装

本文所有依赖已经过真机调试,本机安装无冲突、无版本异常:

python==3.10numpy==1.26.4pandas==2.2.2PyPDF2==3.0.1python-docx==1.1.2markdown==3.6langchain==0.2.10langchain-community==0.2.9faiss-cpu==1.8.0rank_bm25==0.2.21paddlepaddle==2.6.1paddleocr==2.7.3transformers==4.41.2torch==2.3.0+cpupillow==10.3.0opencv-python==4.9.0.80openai==1.30.1tqdm==4.66.4
执行安装:
pip install -r requirements.txt
说明:PaddleOCR 首次运行自动下载轻量识别模型,全程自动化,无需手动配置。

3. 标准化项目目录

采用工程化规范结构,清晰分层、便于后期迭代:

visual_rag_demo/├── data/              # 存放所有图文学习素材├── preprocess/        # 文本+图片预处理模块│   ├── __init__.py│   ├── text_processor.py   # 多格式文本解析清洗│   └── image_processor.py  # OpenCV降噪+PaddleOCR解析├── text_chunk/        # 双层分块核心逻辑(原名chunk,避坑重命名)│   ├── __init__.py│   └── chunk_manager.py    # 父子块分片实现├── embedding/         # 向量模型加载│   ├── __init__.py│   └── embedding_manager.py├── vector_store/      # FAISS本地向量库│   ├── __init__.py│   └── faiss_store.py├── retrieve/          # 混合检索实现│   ├── __init__.py│   └── retriever.py├── qa/                # LLM问答生成│   ├── __init__.py│   └── qa_engine.py├── main.py            # 项目主入口├── test_demo.py       # 效果对照测试└── requirements.txt   # 依赖清单

【开发异常笔记】 项目目录名 chunk/ 与 Python 标准库模块 chunk(用于读取 WAVE 文件)冲突,导致 PaddleOCR 导入失败。解决方案:重命名为 text_chunk/,并全局修改导入路径。


  • 五、核心模块实战落地

1. 多格式文本清洗与解析

针对 Markdown、TXT、PDF、DOCX 做统一读取与清洗,过滤空行、乱码、冗余符号,保留代码标点、标题结构、技术符号,最大程度保留技术文档有效信息。

解决新手常见问题:文档入库后信息污染、有效知识点被过滤、乱码导致检索失效。

【核心实现代码】

import osimport refrom PyPDF2 import PdfReaderfrom docx import Documentclass TextProcessor:    def __init__(self):        self.supported_extensions = [".txt"".md"".pdf"".docx"]    def load_file(self, file_path):        """统一读取不同格式文档内容"""        ext = os.path.splitext(file_path)[1].lower()        if ext not in self.supported_extensions:            raise ValueError(f"不支持的文件格式: {ext}")        if ext == ".txt":            return self._read_txt(file_path)        elif ext == ".md":            return self._read_md(file_path)        elif ext == ".pdf":            return self._read_pdf(file_path)        elif ext == ".docx":            return self._read_docx(file_path)    def _read_txt(self, file_path):        with open(file_path, "r", encoding="utf-8", errors="ignore"as f:            return f.read()    def _read_md(self, file_path):        """读取Markdown文件,保留标题结构和代码块"""        with open(file_path, "r", encoding="utf-8", errors="ignore"as f:            return f.read()    def _read_pdf(self, file_path):        """读取PDF文件,提取纯文本"""        reader = PdfReader(file_path)        text = ""        for page in reader.pages:            page_text = page.extract_text()            if page_text:                text += page_text + "\n"        return text    def _read_docx(self, file_path):        """读取DOCX文件"""        doc = Document(file_path)        text = ""        for para in doc.paragraphs:            text += para.text + "\n"        return text    def clean_text(self, text):        """文本清洗:过滤空行、乱码、冗余符号,保留技术文档有效信息"""        if not text:            return ""        lines = text.split("\n")        cleaned_lines = []        for line in lines:            line = line.strip()            if not line:                continue            if len(line) < 2 and not line.strip():                continue            cleaned_lines.append(line)        cleaned_text = "\n".join(cleaned_lines)        cleaned_text = re.sub(r"\n{3,}""\n\n", cleaned_text)        cleaned_text = re.sub(r"[ \t]+"" ", cleaned_text)        return cleaned_text.strip()    def process_file(self, file_path):        """完整处理流程:读取→清洗"""        raw_text = self.load_file(file_path)        cleaned_text = self.clean_text(raw_text)        return cleaned_text

2. 工业级双层分层分块

普通教程仅做固定长度切割,极易打断代码逻辑、段落语义。本项目采用「大父块兜底、小子块检索」的工程级方案:

  • 长文档保留整体结构

  • 短片段保留细节精度

  • 重叠分片杜绝边界丢失

这也是企业级 RAG 落地最常用的基础优化手段。

【开发异常笔记】 原计划使用 LangChain 的 RecursiveCharacterTextSplitter,但在指定版本 langchain==0.2.10 中无法稳定导入。解决方案:手动实现递归分割逻辑,脱离 LangChain 依赖,代码量不大且功能完整。

【核心实现代码】

from langchain_core.documents import Documentclass RecursiveTextSplitter:    def __init__(self, chunk_size=256, chunk_overlap=32, separators=None):        self.chunk_size = chunk_size        self.chunk_overlap = chunk_overlap        self.separators = separators or ["\n\n""\n""。""!""?"".""!""?"" """]    def _split_text(self, text, separators):        if not separators:            chunks = [text[i:i + self.chunk_size] for i in range(0len(text), self.chunk_size - self.chunk_overlap)]            return chunks        separator = separators[0]        if separator == "":            chunks = [text[i:i + self.chunk_size] for i in range(0len(text), self.chunk_size - self.chunk_overlap)]            return chunks        parts = text.split(separator)        chunks = []        current_chunk = parts[0if parts else ""        for part in parts[1:]:            combined = current_chunk + separator + part            if len(combined) <= self.chunk_size:                current_chunk = combined            else:                if current_chunk:                    chunks.append(current_chunk)                if len(part) > self.chunk_size:                    sub_chunks = self._split_text(part, separators[1:])                    chunks.extend(sub_chunks)                    current_chunk = ""                else:                    current_chunk = part        if current_chunk:            chunks.append(current_chunk)        return chunks    def split_text(self, text):        return self._split_text(text, self.separators)class ChunkManager:    def __init__(self, child_chunk_size=256, child_chunk_overlap=32                 parent_chunk_size=768, parent_chunk_overlap=64):        self.child_splitter = RecursiveTextSplitter(            chunk_size=child_chunk_size,            chunk_overlap=child_chunk_overlap        )        self.parent_splitter = RecursiveTextSplitter(            chunk_size=parent_chunk_size,            chunk_overlap=parent_chunk_overlap        )    def split_text(self, text, source="", source_type="text"):        """双层分块:先生成父块,再对每个父块生成子块"""        parent_chunks = self.parent_splitter.split_text(text)        all_chunks = []        parent_id = 0        for parent_text in parent_chunks:            child_chunks = self.child_splitter.split_text(parent_text)            parent_doc = Document(                page_content=parent_text,                metadata={                    "chunk_type""parent",                    "parent_id"None,                    "child_ids": [],                    "source": source,                    "source_type": source_type,                    "chunk_id"f"parent_{parent_id}"                }            )            all_chunks.append(parent_doc)            child_ids = []            for child_idx, child_text in enumerate(child_chunks):                child_id = f"child_{parent_id}_{child_idx}"                child_ids.append(child_id)                child_doc = Document(                    page_content=child_text,                    metadata={                        "chunk_type""child",                        "parent_id"f"parent_{parent_id}",                        "source": source,                        "source_type": source_type,                        "chunk_id": child_id                    }                )                all_chunks.append(child_doc)            parent_doc.metadata["child_ids"] = child_ids            parent_id += 1        return all_chunks

3. 视觉图文双分支解析(重点)

针对开发者图文混杂素材,做双分支智能处理,这是本项目同普通文本 RAG 的重要区分。

代码截图分支

通过 OpenCV 降噪预处理,解决拍摄模糊、反光、色差导致的识别失败问题,精准提取截图中代码、注释、配置参数。

架构流程图分

通过 OCR 提取图中所有文字信息,自动拼接为结构化描述文本,将不可检索的图形信息转化为可检索的内容。

最终所有图片内容与文档内容统一格式入库,实现图文知识库一体化检索。

【核心实现代码】

① OpenCV 图片降噪预处理
import cv2import numpy as npclass ImageProcessor:    def __init__(self):        self.ocr = PaddleOCR(use_angle_cls=True, lang="ch", use_gpu=False)    def preprocess_image(self, image_path):        """OpenCV图像预处理:灰度转换、高斯模糊降噪、二值化"""        img = cv2.imread(image_path)        if img is None:            raise ValueError(f"无法读取图片: {image_path}")        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)        blurred = cv2.GaussianBlur(gray, (33), 0)        _, thresh = cv2.threshold(blurred, 0255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)        return thresh
② PaddleOCR 提取截图文字
    def extract_text_from_image(self, image_path, use_preprocess=True):        """使用PaddleOCR提取图片中的文字内容"""        if use_preprocess:            processed_img = self.preprocess_image(image_path)            result = self.ocr.ocr(processed_img, cls=True)        else:            result = self.ocr.ocr(image_path, cls=True)        text_lines = []        if result and result[0]:            for line in result[0]:                if line[1and len(line[1]) > 0:                    text_lines.append(line[1][0])        return "\n".join(text_lines)    def analyze_code_screenshot(self, image_path):        """代码截图专项解析:预处理+OCR提取,过滤低置信度结果"""        processed_img = self.preprocess_image(image_path)        result = self.ocr.ocr(processed_img, cls=True)        code_lines = []        if result and result[0]:            for line in result[0]:                if line[1and len(line[1]) > 0:                    confidence = line[1][1]                    if confidence > 0.6:                        code_lines.append(line[1][0])        code_text = "\n".join(code_lines)        return {"type""code""content": code_text, "source": image_path}    def analyze_flowchart(self, image_path):        """架构流程图解析:提取文字并生成结构化描述"""        text = self.extract_text_from_image(image_path, use_preprocess=True)        structured_desc = f"【架构流程图描述】\n"        structured_desc += f"图片来源: {os.path.basename(image_path)}\n"        structured_desc += f"图中文字内容:\n{text}\n"        structured_desc += "\n【解析说明】\n"        structured_desc += "该图片为技术架构图或流程图,包含上述文字描述的模块和流程信息。"        return {"type""flowchart""content": structured_desc, "source": image_path}    def process_image(self, image_path):        """统一处理图片:自动识别类型并解析"""        text = self.extract_text_from_image(image_path, use_preprocess=True)        code_keywords = ["def ""class ""import ""from ""="";""{""}""//""#"]        has_code = any(keyword in text for keyword in code_keywords)        if has_code:            return self.analyze_code_screenshot(image_path)        else:            return self.analyze_flowchart(image_path)

【开发异常笔记】 PaddleOCR 初始化时会打印大量 DEBUG 日志,属于正常行为。首次运行会自动下载识别模型到 ~/.paddleocr/ 目录。

4. BM25 + 向量混合检索优化

单一检索模式永远存在短板:

  • 纯向量:语义强,但代码关键词、变量名、配置名容易漏召

  • 纯关键词:匹配精准,但无法理解语义、无法关联相似知识点

加权融合方案(0.4 BM25 + 0.6 向量),完美适配技术文档检索场景,兼顾精准度与泛化能力。

【核心实现代码】

import numpy as npfrom rank_bm25 import BM25Okapiclass HybridRetriever:    def __init__(self, vector_store, embedding_manager, bm25_weight=0.4, vector_weight=0.6):        self.vector_store = vector_store        self.embedding_manager = embedding_manager        self.bm25_weight = bm25_weight        self.vector_weight = vector_weight        self.bm25 = None        self.corpus = []        self._build_bm25_index()    def _tokenize_chinese(self, text):        """中文文本分词:字符级分词,保留英文单词和数字"""        tokens = []        i = 0        while i < len(text):            if text[i].isascii():                j = i                while j < len(text) and text[j].isascii():                    j += 1                tokens.append(text[i:j])                i = j            else:                tokens.append(text[i])                i += 1        return tokens    def _build_bm25_index(self):        """构建BM25索引"""        documents = self.vector_store.get_all_documents()        self.corpus = [doc.page_content for doc in documents]        if self.corpus:            tokenized_corpus = [self._tokenize_chinese(doc) for doc in self.corpus]            self.bm25 = BM25Okapi(tokenized_corpus)    def _normalize_scores(self, results, score_key="score"):        """归一化分数到[0,1]"""        if not results:            return results        scores = [r[score_key] for r in results]        max_score = max(scores)        min_score = min(scores)        if max_score == min_score:            for r in results:                r[score_key + "_normalized"] = 0.5        else:            for r in results:                r[score_key + "_normalized"] = (r[score_key] - min_score) / (max_score - min_score)        return results    def hybrid_search(self, query, k=10):        """混合检索:BM25 + 向量检索加权融合"""        bm25_results = self._bm25_search(query, k=k)        vector_results = self._vector_search(query, k=k)        bm25_results = self._normalize_scores(bm25_results, "score")        vector_results = self._normalize_scores(vector_results, "score")        merged_scores = {}        for r in bm25_results:            doc_id = id(r["document"])            merged_scores[doc_id] = {                "document": r["document"],                "bm25_score": r["score_normalized"],                "vector_score"0.0            }        for r in vector_results:            doc_id = id(r["document"])            if doc_id in merged_scores:                merged_scores[doc_id]["vector_score"] = r["score_normalized"]            else:                merged_scores[doc_id] = {                    "document": r["document"],                    "bm25_score"0.0,                    "vector_score": r["score_normalized"]                }        final_results = []        for doc_id, scores in merged_scores.items():            final_score = (scores["bm25_score"] * self.bm25_weight +                          scores["vector_score"] * self.vector_weight)            final_results.append({                "document": scores["document"],                "bm25_score": scores["bm25_score"],                "vector_score": scores["vector_score"],                "final_score": final_score            })        final_results.sort(key=lambda x: x["final_score"], reverse=True)        final_results = self._expand_to_parent(final_results)        return final_results[:k]

5. 主程序调用 Demo

通过一段入口代码串联全部模块,可一键启动测试整个知识库。

【核心实现代码】

# 构建知识库(无需API Key)python main.py --build --data data# 交互式问答(需要API Key)python main.py --interactive --api-key "your-api-key" --base-url "https://qianfan.baidubce.com/v2/"

  • 六、低配设备开发-异常总结

以下问题均为本人实测经验总结:

1. 模糊截图识别率低

解决方案:通过灰度转换、高斯模糊降噪预处理,大幅提升低质量截图识别效果。代码中 preprocess_image() 方法可实现完整预处理流程。

2. CPU 推理卡顿缓慢

解决方案:关闭冗余梯度计算(torch.no_grad())、开启向量归一化、精简推理参数,低配设备提速明显。

3. 长短文档检索精度两极化

解决方案:双层分块 + 分层召回,子块定位细节、父块补充上下文。检索结果自动扩展到父块,确保回答有足够的背景信息。

4. LLM 幻觉编造内容

解决方案:强约束 Prompt,强制仅基于检索内容作答,无资料如实告知,杜绝编造。

5. 向量维度不匹配报错

解决方案:全局统一 BGE 768 维向量输出,全程维度对齐,规避入库报错。

6. OpenAI SDK >=1.0 版本代理参数问题

【开发异常笔记】 OpenAI SDK >=1.0 版本移除了直接传入 proxies 参数的方式,使用 httpx.Client 注入代理时,SDK 内部会自动读取系统代理环境变量(如 HTTP_PROXY),导致 Client.__init__() got an unexpected keyword argument 'proxies' 错误。

解决方案:手动创建干净的 httpx.Client(不传递任何代理参数),然后通过 http_client 参数注入给 OpenAI,绕过 SDK 内部自动配置代理的逻辑。

7. 百度千帆兼容接口三类适配问题

【开发异常笔记 1:权限隔离】 千帆平台「网页体验权限」与「API 调用权限」是两套独立体系,不代表 API 密钥具备调用资格,需单独开通对应模型的 API 调用权限,否则返回 invalid_model 错误。

【开发异常笔记 2:模型命名严格】 兼容接口模型名称有固定规范,不可简写:ERNIE 5.1 对应 ernie-5.1,ERNIE 3.5 8K 对应 ernie-3.5-8k,大小写、版本号错误都会校验失败。

【开发异常笔记 3:尾斜杠差异】base_url 末尾必须带斜杠 /,否则 OpenAI SDK 拼接路径会出现 v2chat/completions 路由错误;官方 OpenAI 接口有兼容容错,国产兼容网关无此处理。


  • 七、效果对照实验:图文 RAG 真实优势

以下是三组开发者高频场景对照测试:

场景 1:查询文档接口参数

场景 2:查询截图中的代码逻辑

场景 3:查询架构图模块流转关系

实验结论:图文混合 RAG 能够有效补齐传统知识库「视觉信息缺失」的短板,更贴合开发者真实学习与复盘场景。


  • 八、项目局限与后续迭代规划

为保证工程严谨性,客观说明当前项目边界:

现有局限

  1. 基于 OCR 的图像文字提取方式,对无文字的纯图形解析能力有限

  2. 依赖 LLM 接口,暂不支持纯离线运行

  3. 暂无前端可视化界面,仅命令行交互

补充

LLM问答层兼容OpenAI标准协议,可灵活替换云厂商API、本地量化模型等多种接入方式

后续迭代方向

  1. 接入 Gradio 极简前端:实现网页一键上传、可视化问答

  2. 引入本地量化小模型:实现离线图文检索

  3. 增加检索评估脚本:量化召回精度、实现数据驱动迭代

  4. 支持 PDF 表格、公式专项解析:拓展更多素材格式


  • 九、总结与学习复盘

本次实战从零落地轻量图文联动 AI 工程助手,相比纯文本 RAG 教程,本项目具备三个核心价值:

1. 场景真实落地

针对开发者图文混杂学习素材的沉淀难题,解决普通 RAG 无法处理截图、架构图的行业痛点。

2. 工程思路规范

完整落地分层分块、混合检索、图文结构化解析等工业级优化思路,整个工程具备完整模块化分层设计,具备独立的预处理、分块、检索、问答逻辑,拥有工程参考价值。开发阶段遇到的核心问题均完成落地解决:

  • 目录命名冲突chunk/ 与 Python 标准库冲突,重命名为 text_chunk/

  • LangChain 版本兼容RecursiveCharacterTextSplitter 导入异常,手动实现分割逻辑

  • OpenAI SDK 代理问题:使用 httpx.Client 创建客户端规避代理参数报错

  • 大模型适配:统一规范模型名称,参数配置内置在 qa/qa_engine.py ,支持手动修改,可扩展命令行传参

3. 门槛低、可复刻

全程基于 CPU 轻量化运行,步骤完整无跳步,配套开发异常记录,适合入门多模态 RAG 开发。

通过本次项目实践,完整打通「图文预处理→分块优化→向量入库→混合检索→智能问答」全链路;同时为私有化知识库搭建提供一套低成本、易落地、可持续迭代的标准化方案。

以上是本次项目分享,文档内代码均可参考调试测试,欢迎交流。


的GitHub仓库:

https://github.com/Fan-Ruixuan/light-multimodal-engineering-rag


感谢王宣的分享。

这套方案最打动鲸宝的,不是技术栈有多全,而是它的出发点——“让学过的东西,随时能被找到。”

笔记、截图、架构图……那些深夜敲下的代码、反复调试的参数、灵感突现时画的草图,不该沉在文件夹深处吃灰。它们应该在你需要的那一刻,精准地跳出来,成为你的底气。

Datawhale 一直相信:学习不是看过就够,而是要用起来、查得到、复现得了。 每一次积累,都是未来自己的装备库。

让知识,随时在线,随时可用。

这里是鲸宝电台,听真实的学习故事,遇同频的开发者伙伴。

我们下期见

爱学习的小鲸鱼 敬上

🐳让学习更有温度,让创意点亮社区🐳