面向本地LLM的大规模文档检索方案实现技术咨询
基于llama-server + GPT-OSS 20B实现本地RAG交互式问答方案
一、核心架构流程
先明确RAG的核心链路,所有操作围绕这个流程展开:
文档解析 → 文本分片 → Embedding生成 → 向量库存储 → 检索召回 → LLM生成限定范围的回答
二、文档处理:用Docling完成多格式解析
Docling的核心作用是统一提取PDF、Word、Excel等多格式文档的纯文本内容,后续所有操作都基于这些文本展开。
基础使用示例
from docling.document_converter import DocumentConverter # 初始化文档转换器 converter = DocumentConverter() # 加载并转换目标文档 doc = converter.convert("your_target_document.pdf") # 提取所有文本块并拼接成完整文本 full_text = "\n".join([block.text for block in doc.content]) # 文本分片(避免过长文本丢失上下文,带重叠窗口) def split_text(text, chunk_size=1000, chunk_overlap=200): chunks = [] start_idx = 0 text_len = len(text) while start_idx < text_len: end_idx = min(start_idx + chunk_size, text_len) chunks.append(text[start_idx:end_idx]) start_idx += chunk_size - chunk_overlap return chunks # 生成可用于Embedding的文本分片 text_chunks = split_text(full_text)
三、通过llama-server API生成Embedding
llama-server默认支持/completion接口返回Embedding,只需在请求中指定embed: true且不生成文本(n_predict: 0)即可。如果GPT-OSS 20B本身不支持Embedding,建议单独启动一个llama-server加载轻量级量化Embedding模型(如all-MiniLM-L6-v2的GGUF版),效率更高。
调用示例
import requests import json def get_embedding(text_chunk, server_url="http://localhost:8080"): payload = { "prompt": text_chunk, "n_predict": 0, # 仅返回Embedding,不生成回答 "embed": True, "temperature": 0.0 # Embedding生成不需要随机性 } resp = requests.post(f"{server_url}/completion", json=payload) if resp.status_code == 200: return resp.json()["embedding"] raise Exception(f"Embedding生成失败: {resp.text}") # 批量生成所有文本分片的Embedding embeddings = [get_embedding(chunk) for chunk in text_chunks]
四、本地向量存储与检索
用FAISS做本地向量库,无需依赖外部服务,适合大规模文档存储:
import faiss import numpy as np # 初始化FAISS索引(维度与Embedding一致) embed_dim = len(embeddings[0]) index = faiss.IndexFlatL2(embed_dim) # 将Embedding转为float32格式后加入索引 index.add(np.array(embeddings).astype("float32")) # 检索与查询相关的文本分片 def retrieve_relevant_chunks(query, top_k=3): query_embed = get_embedding(query) distances, indices = index.search(np.array([query_embed]).astype("float32"), top_k) return [text_chunks[i] for i in indices[0]]
五、对接LLM生成限定回答
拿到检索到的文本后,构造明确的Prompt,要求LLM仅基于给定文档内容回答问题。
对接llama-server
def generate_answer(query, server_url="http://localhost:8080"): relevant_chunks = retrieve_relevant_chunks(query) # 构造约束性Prompt,避免LLM生成无关内容 prompt = f"""仅基于以下文档内容回答问题,不得使用外部知识: {"\n\n".join(relevant_chunks)} 问题:{query} 回答:""" payload = { "prompt": prompt, "n_predict": 512, "temperature": 0.1, # 降低随机性,保证回答准确性 "stop": ["\n问题:"] # 设置停止符,避免生成多余内容 } resp = requests.post(f"{server_url}/completion", json=payload) if resp.status_code == 200: return resp.json()["content"].strip() raise Exception(f"LLM请求失败: {resp.text}")
对接Ollama
如果用Ollama管理模型,直接调用其API即可,无需手动启动llama-server:
import requests def generate_answer_with_ollama(query): relevant_chunks = retrieve_relevant_chunks(query) prompt = f"""仅基于以下文档内容回答问题,不得使用外部知识: {"\n\n".join(relevant_chunks)} 问题:{query} 回答:""" payload = { "model": "gpt-oss:20b", # 你的Ollama模型名称 "prompt": prompt, "stream": False, "options": {"temperature": 0.1} } resp = requests.post("http://localhost:11434/api/generate", json=payload) if resp.status_code == 200: return resp.json()["response"].strip() raise Exception(f"Ollama请求失败: {resp.text}")
六、Docling与llama.cpp/Ollama的对接逻辑
Docling仅负责文档解析环节,与llama.cpp/Ollama的对接是通过中间脚本串联的:
- 用Docling解析文档得到纯文本
- 文本分片后,通过llama-server API或Ollama的
/api/embeddings接口生成向量 - 向量存入FAISS索引,检索后构造Prompt传给LLM生成回答
如果用Ollama生成Embedding,可替换为以下代码:
def get_embedding_with_ollama(text_chunk): payload = { "model": "all-minilm", # Ollama上的轻量级Embedding模型 "prompt": text_chunk } resp = requests.post("http://localhost:11434/api/embeddings", json=payload) if resp.status_code == 200: return resp.json()["embedding"] raise Exception(f"Ollama Embedding生成失败: {resp.text}")
七、优化建议
- 文本分片:根据文档类型调整策略,比如代码文档按函数分片,长文档按章节+段落拆分
- Embedding模型:优先选择量化版轻量级模型(如all-MiniLM-L6-v2 GGUF),平衡速度与内存占用
- 向量库优化:大规模数据可改用FAISS的IVF索引,提升检索效率
- 缓存机制:缓存已生成的Embedding和检索结果,减少重复请求
内容的提问来源于stack exchange,提问作者bud




