使用 LangChain 搭建本地大模型 RAG 问答应用)
上一篇博客介绍了在 Hailo-10 上实现语音识别应用这次我们来聊聊它的另一个重要功能本地大模型推理。当然如果只是做离线对话的话应用场景可能比较有限下面结合 LangChain 实现一个简单的 RAG 应用。设备仍然使用之前介绍的 ASUS UGen300。1环境配置与音频模型只能在 Linux 上运行不同本地大模型推理可以在 Windows 上实现。这里是在安装了 Raspberry Pi OS 的 Raspberry Pi 5 上实现的。安装 HailoRT首先要安装 HailoRT这是 Hailo 的运行时库包含设备驱动、Python 绑定、命令行工具。可以去 Hailo Developer Zone 注册账号下载也可以在 ASUS 的官网下载。详细步骤见上一篇博客张高兴的 Hailo-10 开发指南一实现离线语音识别。安装 Hailo-OllamaHailo-Ollama 是 Hailo 官方提供的推理服务API 和 Ollama 完全兼容意味着能用 Ollama 的工具LangChain、Open-WebUI 等基本都能直接使用。在安装完 ASUS 的驱动捆绑包之后Hailo-Ollama 也已经安装完成。如果你的 Hailo-10 是 M.2 版本的可能需要单独安装 Hailo-Ollama在 Hailo Developer Zone 中下载安装 Hailo Model Zoo GenAI。2Python 环境pip install pdfplumber langchain langchain-ollama langchain-text-splitterslangchain-chroma langchain-huggingface sentence-transformerschromadb requests启动 Hailo-Ollama 服务服务启动很简单直接在命令行中运行下面的命令默认监听 0.0.0.0:8000。hailo-ollama下面看看 Hailo-10 目前支持哪些模型curl --silent http://localhost:8000/hailo/v1/list可以看到目前支持 deepseek_r1:1.5b、llama3.2:1b、qwen2.5-coder:1.5b、qwen2.5:1.5b、qwen2:1.5b、qwen3:1.7b 这些模型。执行下面的命令拉取 qwen3:1.7b 模型curl --silent http://localhost:8000/api/pull-H ‘Content-Type: application/json’-d ‘{ “model”: “qwen3:1.7b”, “stream” : true }’拉取完成后验证一下模型是否能正常推理curl --silent http://localhost:8000/api/chat-H ‘Content-Type: application/json’-d ‘{“model”: “qwen3:1.7b”, “messages”: [{“role”: “user”, “content”: “用一句话解释什么是向量数据库”}]}’到这里整个链路就通了。接下来你可以通过调用接口的方式在自己的应用里使用这个模型了也可以使用支持 Ollama 的工具比如 Open-WebUI 来和模型进行交互。比如拉取 Open-WebUI 的 Docker 镜像docker run -d --nethost -e OLLAMA_BASE_URLhttp://127.0.0.1:8000 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main3实现 RAG 应用模型本身不会主动知道你公司内部文档里写了什么。RAG 是把文档切碎存进向量数据库提问时先搜索最相关的段落再把这些段落塞进 prompt 让模型回答。通过 RAG 模型可以理解一些最新的、专业的知识而不需要模型本身学会这些知识。比如这里使用的是 ASUS UGen300 的用户手册模型本身不可能学会里面的内容但通过 RAG 的方式模型就能理解用户手册里的内容了。当然你也可以替换成其他的文档完成后面的操作。4核心流程很简单文档处理 → 向量化 → RAG 链路 → 问答。下面新建 rag_demo.py 文件.引用相关包import jsonimport osimport requestsfrom typing import Any, Iterator, List, Optionalfrom langchain_core.language_models.chat_models import BaseChatModelfrom langchain_core.messages import (AIMessage,AIMessageChunk,BaseMessage,HumanMessage,SystemMessage,)from langchain_core.outputs import ChatGeneration, ChatGenerationChunk, ChatResultfrom langchain_core.documents import Documentfrom langchain_text_splitters import RecursiveCharacterTextSplitterfrom langchain_huggingface import HuggingFaceEmbeddingsfrom langchain_chroma import Chromaimport pdfplumberPDF 文档处理用 pdfplumber 逐页解析 PDF构造 LangChain Document 对象。PDF_FILES [“data/UGen300-manual.pdf”,]all_docs []for pdf_path in PDF_FILES:if not os.path.exists(pdf_path):print(f 跳过不存在{pdf_path})continuecount 0with pdfplumber.open(pdf_path) as pdf:for i, page in enumerate(pdf.pages):text (page.extract_text() or “”).strip()all_docs.append(Document(page_contenttext,metadata{“source”: pdf_path, “page”: i 1},))count 1print(f共加载 {len(all_docs)} 页文本)3. 文本切分文档切分成更小的段落方便后续的向量化和检索。chunk_size 为每个 chunk 最大字符数chunk_overlap 让相邻 chunk 共享边界内容避免关键信息被截断。splitter RecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50,)chunks splitter.split_documents(all_docs)print(f共生成 {len(chunks)} 个 chunk)4. 向量化和存储使用 HuggingFaceEmbeddings 生成文本的向量表示并存储到 ChromaDB。all-MiniLM-L6-v2 是最常用的轻量通用 embedding 模型速度快在本地运行不需要 API key也不依赖 GPU。ChromaDB 是一个轻量的本地向量数据库适合小规模数据的存储和检索。CHROMA_DB_DIR “./chroma_db”COLLECTION_NAME “hailo_docs”embeddings HuggingFaceEmbeddings(model_name“sentence-transformers/all-MiniLM-L6-v2”,model_kwargs{“device”: “cpu”},encode_kwargs{“normalize_embeddings”: True})print(“Embedding 模型就绪”)db_exists os.path.exists(CHROMA_DB_DIR) and os.listdir(CHROMA_DB_DIR)if db_exists:# 数据库已存在直接加载vectorstore Chroma(collection_nameCOLLECTION_NAME,embedding_functionembeddings,persist_directoryCHROMA_DB_DIR,)print(f已加载现有数据库 {CHROMA_DB_DIR}“)else:# 首次构建vectorstore Chroma.from_documents(documentschunks,embeddingembeddings,collection_nameCOLLECTION_NAME,persist_directoryCHROMA_DB_DIR,)print(f数据库构建完成已持久化到 {CHROMA_DB_DIR}”)5. 自定义 HailoChatOllama 类Hailo-Ollama 的底层用的是 oatpp HTTP 框架它对请求体的 JSON 字段类型要求很严。LangChain 官方的 ChatOllama 会往请求里附加 options、keep_alive 等额外字段oatpp 碰到未知字段直接报类型解析错误。解决方案是继承 BaseChatModel 自己写一个简化版class HailoChatOllama(BaseChatModel):model: strbase_url: strtimeout: int 120property def _llm_type(self) - str: return hailo-ollama def _to_ollama_messages(self, messages: List[BaseMessage]) - list: role_map {system: system, human: user, ai: assistant} return [ {role: role_map.get(m.type, m.type), content: m.content} for m in messages ] def _generate( self, messages: List[BaseMessage], stop: Optional[List[str]] None, run_manager: Any None, **kwargs: Any, ) - ChatResult: payload { model: self.model, messages: self._to_ollama_messages(messages), stream: False, } resp requests.post( f{self.base_url}/api/chat, jsonpayload, timeoutself.timeout, ) resp.raise_for_status() content resp.json()[message][content] return ChatResult( generations[ChatGeneration(messageAIMessage(contentcontent))] )