别再空谈RAG了!手把手教你用LangChain + Chroma + 本地SearXng,从零搭建一个能联网搜索的智能问答助手
从零构建智能问答系统:LangChain + Chroma + SearXng实战指南
引言
在信息爆炸的时代,如何快速获取准确答案成为技术团队面临的共同挑战。传统搜索引擎返回的是海量网页链接,而大语言模型虽然能生成流畅回答,却存在信息滞后和幻觉问题。检索增强生成(RAG)技术结合了两者优势,让AI既能实时获取最新信息,又能生成结构化的专业回答。
本文将带你从零搭建一个能联网搜索的智能问答系统,核心技术栈包括:
- LangChain:大语言模型应用开发框架
- Chroma:轻量级向量数据库
- SearXng:隐私友好的开源元搜索引擎
不同于理论讲解,我们聚焦于可落地的工程实践,涵盖环境配置、代码实现、调试技巧全流程。即使没有AI项目经验,按照本文步骤也能在2小时内构建出可用的问答系统。
1. 环境准备与工具链配置
1.1 基础环境搭建
推荐使用Python 3.10+环境,通过conda管理依赖:
conda create -n rag python=3.10 conda activate rag安装核心依赖库:
pip install langchain chromadb sentence-transformers searxng常见问题排查:
- 如遇到
grpc安装失败,尝试先安装pip install grpcio==1.48.2 - 在ARM架构设备(如Mac M系列)上需额外安装
pip install chromadb --no-deps后手动安装依赖
1.2 本地SearXng部署
SearXng提供Docker一键部署方案:
docker pull searxng/searxng:latest docker run -d --name searxng -p 8080:8080 searxng/searxng验证服务是否正常:
curl "http://localhost:8080/search?q=test&format=json"配置要点:
- 修改
/etc/searxng/settings.yml启用更多搜索引擎 - 建议设置
server.limiter=false关闭速率限制 - 通过
docker logs searxng查看实时日志
2. 核心模块实现
2.1 文档处理流水线
建立document_processor.py实现文本加载与分块:
from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.document_loaders import DirectoryLoader def process_documents(data_dir): loader = DirectoryLoader(data_dir, glob="**/*.md") documents = loader.load() splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200, length_function=len ) return splitter.split_documents(documents)关键参数说明:
| 参数 | 推荐值 | 作用 |
|---|---|---|
| chunk_size | 500-1500 | 影响检索精度和上下文完整性 |
| chunk_overlap | 10-20% | 避免语义断裂 |
| separators | ["\n\n", "\n", " "] | 中文建议增加"。 " |
2.2 向量数据库初始化
创建vector_db.py配置Chroma数据库:
from langchain.vectorstores import Chroma from langchain.embeddings import HuggingFaceEmbeddings embedding_model = HuggingFaceEmbeddings( model_name="GanymedeNil/text2vec-large-chinese", model_kwargs={'device': 'cpu'} ) def init_vectorstore(docs, persist_dir="chroma_db"): return Chroma.from_documents( documents=docs, embedding=embedding_model, persist_directory=persist_dir )性能优化技巧:
- 使用
parquet格式存储向量:chroma_client = chromadb.PersistentClient(path=persist_dir) - 批量插入时设置
batch_size=100 - 对大规模数据启用
allow_dangerous_deserialization=True
2.3 混合检索器实现
在hybrid_retriever.py中组合本地与网络检索:
from langchain.retrievers import BaseRetriever from typing import List from langchain.schema import Document import requests class HybridRetriever(BaseRetriever): def __init__(self, vector_retriever, searxng_url): self.vector_retriever = vector_retriever self.searxng_url = searxng_url def _get_relevant_documents(self, query: str) -> List[Document]: # 本地向量检索 local_docs = self.vector_retriever.get_relevant_documents(query) # 网络搜索 search_results = requests.get( f"{self.searxng_url}/search", params={"q": query, "format": "json"} ).json() web_docs = [ Document( page_content=result.get("content", ""), metadata={"source": result["url"]} ) for result in search_results.get("results", []) ] return local_docs + web_docs注意:实际部署时应添加请求超时和重试机制,处理网络不稳定的情况
3. 问答系统集成
3.1 提示工程优化
设计兼顾准确性和安全性的提示模板:
from langchain.prompts import PromptTemplate PROMPT_TEMPLATE = """ 你是一位专业的信息助理,请根据以下规则回答问题: 可用资源: {context} 回答要求: 1. 标注信息来源(📚表示本地知识库,🌐表示网络结果) 2. 保持客观中立,不编造不存在的信息 3. 对可能引发争议的内容明确标注"该观点存在争议" 当前问题:{question} """ prompt = PromptTemplate( template=PROMPT_TEMPLATE, input_variables=["context", "question"] )3.2 完整链路组装
在qa_system.py中构建端到端流程:
from langchain.chains import RetrievalQA from langchain.llms import Ollama # 使用本地模型 def build_qa_system(retriever): llm = Ollama(model="qwen:7b") return RetrievalQA.from_chain_type( llm=llm, retriever=retriever, chain_type="stuff", chain_type_kwargs={"prompt": prompt}, return_source_documents=True )启动交互式问答:
qa = build_qa_system(hybrid_retriever) while True: query = input("\n请输入问题:") result = qa({"query": query}) print(f"\n答案:{result['result']}") print("\n来源:") for doc in result['source_documents']: print(f"- {doc.metadata['source']}")4. 高级功能扩展
4.1 结果重排序策略
添加reranker.py提升结果质量:
from sentence_transformers import CrossEncoder reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2") def rerank_documents(query, documents, top_k=3): pairs = [(query, doc.page_content) for doc in documents] scores = reranker.predict(pairs) scored_docs = list(zip(scores, documents)) scored_docs.sort(reverse=True) return [doc for _, doc in scored_docs[:top_k]]4.2 缓存机制实现
使用Redis缓存高频查询:
import redis from hashlib import md5 r = redis.Redis(host='localhost', port=6379) def get_cache_key(query): return f"qa_cache:{md5(query.encode()).hexdigest()}" def cached_query(query, qa_system): cache_key = get_cache_key(query) cached = r.get(cache_key) if cached: return json.loads(cached) result = qa_system({"query": query}) r.setex(cache_key, 3600, json.dumps(result)) # 缓存1小时 return result4.3 监控与日志
集成Prometheus监控指标:
from prometheus_client import start_http_server, Counter QUERY_COUNT = Counter('qa_query_total', 'Total query count') LATENCY_HIST = Histogram('qa_latency_seconds', 'Query latency') @LATENCY_HIST.time() def execute_query(query): QUERY_COUNT.inc() return qa_system({"query": query}) start_http_server(8000) # 暴露/metrics端点5. 生产环境部署建议
5.1 性能优化配置
Chroma数据库调优参数:
# chroma_config.yaml persist_directory: /data/chroma chroma_db_impl: "duckdb+parquet" allow_reset: false anonymized_telemetry: false启动参数建议:
uvicorn qa_server:app --host 0.0.0.0 --port 8000 \ --workers 4 \ --no-access-log \ --http h11 \ --timeout-keep-alive 305.2 安全防护措施
推荐的安全实践:
- 为SearXng配置HTTPS
- 使用
rate-limiter-flexible限制API调用频率 - 对用户输入进行严格的SQL注入检测
- 定期备份向量数据库快照
Nginx示例配置:
location /api/ { proxy_pass http://localhost:8000; limit_req zone=api burst=10 nodelay; proxy_set_header X-Real-IP $remote_addr; }5.3 持续维护方案
建议的监控指标:
| 指标名称 | 监控方式 | 告警阈值 |
|---|---|---|
| 查询延迟 | Prometheus | P99 > 3s |
| 内存使用 | cAdvisor | >80% 持续5分钟 |
| 搜索失败率 | Logstash | 错误率 > 1% |
日志分析架构:
Filebeat -> Logstash -> Elasticsearch -> Kafka(实时告警)