公司动态
基于腾讯云轻量服务器与RAG技术构建低成本私有知识库AI助手
1. 项目缘起为什么选择“轻量服务器RAG”这个组合最近两年AI应用开发的门槛肉眼可见地降低了。以前想做个能“懂你”的智能问答助手要么得养一个庞大的算法团队要么就得忍受公有云API高昂的调用成本和数据隐私的顾虑。现在随着大语言模型LLM的开源化和RAG检索增强生成技术的成熟个人开发者或小团队完全有能力在可控的成本下搭建一个专属的、高质量的AI知识库助手。我选择“腾讯云轻量服务器 RAG方案”这个组合核心驱动力就三个字性价比。这里的性价比不只是指金钱成本更是时间成本、运维复杂度和最终效果的平衡。首先看硬件基础——腾讯云轻量应用服务器。对于个人项目或初创团队来说它有几个难以拒绝的优点一是开箱即用预装了应用镜像比如Docker、WordPress省去了从零配置操作系统的麻烦二是价格亲民入门配置2核4G每月几十块钱还自带流量包对于访问量不大的知识库应用完全够用三是网络优化国内访问速度稳定这对于需要实时响应的问答应用至关重要。你不需要一开始就为可能不存在的百万级并发去购买高配ECS轻量服务器提供了一个完美的“试验田”和“起步器”。然后是技术核心——RAG方案。简单来说RAG解决了大模型的两个核心痛点“幻觉”胡编乱造和“知识陈旧”。它通过将你的私有知识库文档、笔记、手册转换成向量并存储在用户提问时先从这个“记忆库”里检索出最相关的片段再把这些片段连同问题一起交给大模型生成答案。这样答案的准确性和专业性就有了保障。更重要的是整个流程——文档处理、向量化、检索、生成——现在都有成熟的开源框架比如LangChain、LlamaIndex和工具比如Chroma、Qdrant来实现技术栈非常清晰。所以这个组合的本质是用最轻量、易得的云资源搭载一套经过验证的、能落地的AI应用架构。它让你能把精力集中在“如何用好我的知识”这个核心问题上而不是纠缠于基础设施的泥潭。接下来我就带你一步步拆解如何从零开始把这个组合跑起来。2. 核心组件选型与架构设计不只是“跑起来”更要“跑得稳”在动手敲命令之前花点时间设计架构是绝对值得的。一个好的架构能让你后续的开发、调试、扩展事半功倍。我们的目标是在轻量服务器上构建一个稳定、可维护的RAG系统。下面是我经过多次实践后总结出的组件选型与架构思路。2.1 服务器环境与基础服务我们以一台腾讯云轻量应用服务器2核4GUbuntu 22.04 LTS为例。这个配置足以支撑一个小型知识库的日常运行。操作系统选择Ubuntu LTS版本社区支持好软件包丰富。容器化Docker Docker Compose是必选项。它能把复杂的依赖环境Python版本、各种库打包隔离保证环境一致性也极大简化了部署流程。腾讯云轻量服务器很多镜像已经预装了Docker如果没有安装也非常简单。向量数据库这是RAG的“记忆中枢”。我们需要一个轻量、高效、易于集成的向量数据库。经过对比我推荐PGVectorPostgreSQL的向量扩展或Qdrant。PGVector优势是与成熟的关系型数据库PostgreSQL无缝集成可以利用其事务、备份等成熟特性管理起来更顺手。如果你的知识库还需要一些结构化查询比如按文档类别过滤PGVector是很好的选择。Qdrant专为向量搜索设计的数据库纯内存或磁盘存储都支持性能非常出色API简洁。对于纯向量检索场景它更轻快。考虑到轻量服务器的资源和我们的入门目标我优先推荐PGVector。因为它可以和后续的Web应用共用数据库实例减少组件数量。Docker部署一个带PGVector的PostgreSQL就是一条命令的事。大语言模型LLM这是“大脑”。有两种选择调用云端API如OpenAI的GPT、国内深度求索的DeepSeek等。优点是模型能力强、省事但会产生持续费用且有网络延迟和数据出境风险需注意合规。本地部署开源模型如ChatGLM3、Qwen、Llama等通过Ollama或vLLM部署。优点是数据完全私有、无网络延迟但对服务器算力有要求2核4G跑7B参数模型勉强可行但响应慢。建议在项目初期为了快速验证和降低成本可以先使用云端API。等流程跑通、确有需求后再考虑升级服务器配置来本地部署轻量化模型。本文将以使用DeepSeek的API为例进行说明因为它对国内开发者友好价格也实惠。2.2 RAG流程与框架选择一个标准的RAG流程包括文档加载 - 文本分割 - 向量化嵌入 - 存储 - 检索 - 提示构建 - 生成回答。我们需要一个框架来编排这些步骤。主流选择是LangChain或LlamaIndex。LangChain功能极其强大模块化程度高几乎可以构建任何AI应用链。但学习曲线稍陡概念较多。LlamaIndex专为RAG场景设计API更简洁直观对数据连接器和检索器做了很多优化上手更快。对于我们的“低成本知识库助手”目标LlamaIndex是更优的选择。它能让我们的代码更清晰更快地看到效果。我们将使用LlamaIndex来连接我们的文档、向量数据库和LLM。2.3 最终架构图逻辑层面整个系统的逻辑架构如下我们将在服务器上通过Docker Compose来编排这些服务用户 Web/API 请求 | v [Nginx / Python Web框架 (如FastAPI)] # 对外接口 | v [核心应用服务 (Python LlamaIndex)] # 处理RAG流程 | \ | \ v v [向量数据库 (PGVector)] [大模型 API (如DeepSeek)] | / | / v / [原始文档存储 (本地目录或OSS)] --(文档读取与处理)简单解释一下数据流用户通过网页或API提问。Web服务如FastAPI接收到问题调用核心的RAG应用服务。RAG应用使用LlamaIndex先将用户问题转换成向量。用这个向量去PGVector数据库中检索出最相关的几段文本知识片段。将问题和检索到的知识片段组合成一个详细的提示Prompt发送给DeepSeek API。将DeepSeek返回的答案通过Web服务返回给用户。文档入库流程则是离线进行的将PDF、Word、TXT等文档放入指定目录运行一个脚本LlamaIndex会负责读取、分割、向量化并存入PGVector。这个架构清晰地将计算密集型任务文档处理、向量化与实时服务问答解耦非常适合在资源有限的服务器上稳定运行。3. 实战部署一步步在轻量服务器上搭建系统理论说完了我们进入最实在的实操环节。请确保你有一台全新的腾讯云轻量Ubuntu服务器并已通过SSH登录。3.1 基础环境准备与Docker部署首先更新系统并安装必要的工具。# 更新软件包列表 sudo apt-get update sudo apt-get upgrade -y # 安装常用工具可选但推荐 sudo apt-get install -y git curl wget vim # 安装 Docker如果镜像未预装 # 具体安装命令请参考腾讯云官方文档或Docker官网通常是一段curl脚本。 # 假设已经安装好Docker和Docker Compose。接下来我们创建项目目录并编写docker-compose.yml文件来启动最核心的向量数据库。# 创建项目目录 mkdir ~/rag-knowledge-base cd ~/rag-knowledge-base mkdir data docs scripts # 创建数据、文档和脚本目录 # 创建 docker-compose.yml vim docker-compose.yml将以下内容写入docker-compose.yml。这里我们启动一个带有PGVector扩展的PostgreSQL数据库并挂载数据卷到本地防止容器重启后数据丢失。version: 3.8 services: postgres: image: ankane/pgvector:latest # 这个镜像已经包含了pgvector扩展 container_name: rag-pgvector restart: unless-stopped environment: POSTGRES_USER: rag_user POSTGRES_PASSWORD: your_secure_password_here # 务必修改为强密码 POSTGRES_DB: rag_db ports: - 5432:5432 # 将主机5432端口映射到容器方便本地调试连接 volumes: - ./data/postgres:/var/lib/postgresql/data # 持久化数据 healthcheck: test: [CMD-SHELL, pg_isready -U rag_user -d rag_db] interval: 10s timeout: 5s retries: 5保存文件后启动服务docker-compose up -d使用docker ps命令检查容器是否正常运行。现在你的向量数据库就已经在后台运行了。3.2 Python环境与依赖安装我们将在宿主机上而非容器内开发Python应用这样调试更方便。当然你也可以选择将应用也容器化这里我们先采用宿主机模式。# 进入项目目录 cd ~/rag-knowledge-base # 创建Python虚拟环境推荐使用Python 3.9 python3 -m venv venv source venv/bin/activate # 升级pip pip install --upgrade pip # 安装核心依赖 pip install llama-index llama-index-vector-stores-postgres llama-index-llms-openai # 解释 # llama-index: 核心框架 # llama-index-vector-stores-postgres: 用于连接PGVector # llama-index-llms-openai: 虽然叫openai但其接口兼容所有遵循OpenAI API格式的模型包括DeepSeek # 安装文档读取器按需安装 pip install llama-index-readers-file pymupdf # 用于读取PDF pip install python-docx # 用于读取Word pip install unstructured # 强大的非结构化文本提取库 # 安装Web框架用于提供API pip install fastapi uvicorn python-multipart3.3 构建知识库文档处理与向量入库这是构建智能助手的基础。我们在scripts目录下创建一个名为ingest.py的脚本。# ~/rag-knowledge-base/scripts/ingest.py import os import sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from llama_index.core import VectorStoreIndex, SimpleDirectoryReader, StorageContext from llama_index.core.node_parser import SentenceSplitter from llama_index.vector_stores.postgres import PGVectorStore from llama_index.embeddings.openai import OpenAIEmbedding import psycopg2 from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT # 1. 配置连接参数请根据你的docker-compose配置修改 db_name rag_db user rag_user password your_secure_password_here # 修改为你的密码 host localhost port 5432 # 2. 确保数据库和扩展存在首次运行 conn psycopg2.connect(dbnamepostgres, useruser, passwordpassword, hosthost, portport) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cur conn.cursor() cur.execute(fSELECT 1 FROM pg_database WHERE datname {db_name};) if not cur.fetchone(): cur.execute(fCREATE DATABASE {db_name};) print(fDatabase {db_name} created.) cur.close() conn.close() # 连接到目标数据库创建pgvector扩展 conn psycopg2.connect(dbnamedb_name, useruser, passwordpassword, hosthost, portport) conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cur conn.cursor() cur.execute(CREATE EXTENSION IF NOT EXISTS vector;) cur.close() conn.close() # 3. 初始化向量存储和嵌入模型 vector_store PGVectorStore.from_params( databasedb_name, hosthost, passwordpassword, portport, useruser, table_namellama_index_vectors, # 存储向量的表名 embed_dim1536, # OpenAI text-embedding-ada-002 的维度DeepSeek Embedding也兼容 ) # 注意这里使用OpenAIEmbedding但我们会配置其base_url指向DeepSeek embed_model OpenAIEmbedding( api_keyyour_deepseek_api_key_here, # 替换为你的DeepSeek API Key api_basehttps://api.deepseek.com/v1, # DeepSeek的API端点 modeltext-embedding-3-small, # DeepSeek当前支持的嵌入模型请查阅其最新文档 ) # 4. 读取文档 documents_path os.path.join(os.path.dirname(__file__), ../docs) documents SimpleDirectoryReader(documents_path).load_data() print(fLoaded {len(documents)} documents.) # 5. 分割文本将长文档切成小块便于检索 node_parser SentenceSplitter(chunk_size512, chunk_overlap50) nodes node_parser.get_nodes_from_documents(documents) # 6. 构建索引并存储 storage_context StorageContext.from_defaults(vector_storevector_store) index VectorStoreIndex( nodesnodes, storage_contextstorage_context, embed_modelembed_model, ) print(Knowledge base indexing completed!)关键点说明与实操心得嵌入模型Embedding Model这是将文本转换成向量的模型其质量直接决定检索的准确性。我们使用了DeepSeek的Embedding API需要在其平台申请API Key。embed_dim1536是text-embedding-ada-002的标准维度DeepSeek的模型与之兼容。务必查阅DeepSeek最新文档确认模型名。文本分割Chunkingchunk_size512和chunk_overlap50是常用参数。chunk_size太小会丢失上下文太大会引入噪声。chunk_overlap可以避免在句子中间切断语义。你需要根据自己文档的特点如技术文档句子长问答记录句子短进行调整。运行脚本前将你的知识文档PDF、TXT等放入~/rag-knowledge-base/docs/目录。然后运行cd ~/rag-knowledge-base source venv/bin/activate python scripts/ingest.py你会看到加载和处理的日志。完成后你的知识就已经以向量的形式存入PGVector数据库了。3.4 构建问答服务FastAPI后端与RAG查询链现在我们来创建提供问答服务的API。在项目根目录创建app.py。# ~/rag-knowledge-base/app.py from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional import os from llama_index.core import VectorStoreIndex, Settings from llama_index.core.retrievers import VectorIndexRetriever from llama_index.core.query_engine import RetrieverQueryEngine from llama_index.core.postprocessor import SimilarityPostprocessor from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding from llama_index.vector_stores.postgres import PGVectorStore # 配置LLM和Embedding同样指向DeepSeek Settings.llm OpenAI( api_keyyour_deepseek_api_key_here, # 替换 api_basehttps://api.deepseek.com/v1, modeldeepseek-chat, # DeepSeek的聊天模型 temperature0.1, # 较低的温度使输出更确定适合知识问答 ) Settings.embed_model OpenAIEmbedding( api_keyyour_deepseek_api_key_here, # 替换 api_basehttps://api.deepseek.com/v1, modeltext-embedding-3-small, ) # 初始化向量存储连接 vector_store PGVectorStore.from_params( databaserag_db, hostlocalhost, passwordyour_secure_password_here, # 替换 port5432, userrag_user, table_namellama_index_vectors, ) # 从已有的向量存储加载索引 index VectorStoreIndex.from_vector_store(vector_storevector_store) # 配置检索器从索引中取出前k个最相关的片段 retriever VectorIndexRetriever( indexindex, similarity_top_k5, # 每次检索5个最相关的文本块 ) # 配置后处理器可以按相似度分数过滤这里我们简单设置一个阈值 postprocessor SimilarityPostprocessor(similarity_cutoff0.7) # 相似度低于0.7的结果将被过滤 # 组装查询引擎 query_engine RetrieverQueryEngine( retrieverretriever, node_postprocessors[postprocessor], ) app FastAPI(titleRAG Knowledge Base QA API) class QueryRequest(BaseModel): question: str stream: Optional[bool] False # 是否启用流式输出可选功能 class QueryResponse(BaseModel): answer: str source_nodes: List[dict] # 可以返回答案的来源片段增加可信度 app.post(/query, response_modelQueryResponse) async def query_knowledge_base(request: QueryRequest): 核心问答接口。 try: # 使用查询引擎获取答案 response query_engine.query(request.question) # 整理响应包含答案和来源 source_info [] for node in response.source_nodes: source_info.append({ text: node.node.get_content()[:200], # 截取部分文本 score: node.score, # 相似度分数 # 可以添加更多元数据如文件名、页码等 }) return QueryResponse( answerresponse.response, source_nodessource_info ) except Exception as e: raise HTTPException(status_code500, detailfQuery failed: {str(e)}) app.get(/health) async def health_check(): return {status: healthy} if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)关键点说明与实操心得similarity_top_k这个参数控制每次检索返回多少个相关片段。太少可能信息不足太多可能引入无关噪声并增加API调用成本因为提示词会变长。从3-5开始调整。similarity_cutoff这是一个重要的质量阀门。如果检索到的片段与问题相似度太低比如低于0.7很可能是无关信息让大模型基于这些信息生成答案会导致“幻觉”。设置一个阈值可以过滤掉低质量检索结果。流式输出代码中预留了stream参数。对于需要长时间生成的答案流式输出能极大提升用户体验。LlamaIndex和FastAPI都支持流式响应实现稍复杂但值得探索。来源追溯返回source_nodes至关重要。它让用户知道答案是从哪段原文来的增加了系统的可信度和可解释性这在企业应用中几乎是刚需。启动API服务cd ~/rag-knowledge-base source venv/bin/activate python app.py服务将在http://你的服务器IP:8000启动。你可以访问http://你的服务器IP:8000/docs查看自动生成的API文档并进行测试。3.5 前端界面可选但推荐一个简单的聊天界面为了让体验更完整我们可以用一个简单的HTML页面作为前端。在根目录创建static文件夹并新建index.html。!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title我的知识库AI助手/title style body { font-family: sans-serif; max-width: 800px; margin: 20px auto; padding: 20px; } #chatbox { border: 1px solid #ccc; height: 400px; overflow-y: auto; padding: 10px; margin-bottom: 10px; } .message { margin-bottom: 10px; } .user { text-align: right; color: blue; } .bot { text-align: left; color: green; } .source { font-size: 0.8em; color: #666; border-left: 3px solid #eee; padding-left: 5px; margin-top: 5px; } #inputArea { display: flex; } #questionInput { flex-grow: 1; padding: 10px; } #sendBtn { padding: 10px 20px; } /style /head body h2 知识库AI问答助手/h2 div idchatbox/div div idinputArea input typetext idquestionInput placeholder请输入您的问题... button idsendBtn发送/button /div script const chatbox document.getElementById(chatbox); const input document.getElementById(questionInput); const sendBtn document.getElementById(sendBtn); const apiBase http://localhost:8000; // 部署时改为你的服务器IP function addMessage(sender, text, sources []) { const msgDiv document.createElement(div); msgDiv.className message ${sender}; msgDiv.innerHTML strong${sender user ? 您 : 助手}:/strong ${text}; if (sources sources.length 0) { const sourceDiv document.createElement(div); sourceDiv.className source; sourceDiv.innerHTML strong参考来源:/strongbr sources.map(s · ${s.text}... (相关度: ${s.score.toFixed(3)})).join(br); msgDiv.appendChild(sourceDiv); } chatbox.appendChild(msgDiv); chatbox.scrollTop chatbox.scrollHeight; } async function sendQuestion() { const question input.value.trim(); if (!question) return; addMessage(user, question); input.value ; sendBtn.disabled true; try { const response await fetch(${apiBase}/query, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ question: question, stream: false }) }); if (!response.ok) throw new Error(HTTP error! status: ${response.status}); const data await response.json(); addMessage(bot, data.answer, data.source_nodes); } catch (error) { console.error(Error:, error); addMessage(bot, 抱歉出错了: ${error.message}); } finally { sendBtn.disabled false; input.focus(); } } sendBtn.addEventListener(click, sendQuestion); input.addEventListener(keypress, (e) { if (e.key Enter) sendQuestion(); }); /script /body /html为了让FastAPI能提供这个静态页面我们需要修改一下app.py添加静态文件服务。# 在 app.py 的 FastAPI 实例化后添加 from fastapi.staticfiles import StaticFiles app.mount(/static, StaticFiles(directorystatic), namestatic) # 添加一个根路径路由返回前端页面 from fastapi.responses import FileResponse app.get(/) async def read_index(): return FileResponse(static/index.html)现在重启app.py访问http://你的服务器IP:8000就能看到一个简单的聊天界面可以和你的知识库对话了。4. 优化、监控与成本控制让系统真正可用系统跑起来只是第一步要让它稳定、可靠、成本可控还需要做一些优化和运维工作。4.1 性能与效果优化检索优化混合检索Hybrid Search除了向量相似度检索可以结合关键词BM25检索。有些问题用关键词匹配更准。LlamaIndex支持很容易地集成混合检索器。重排序Re-ranking先用向量检索出Top K比如20个结果再用一个更精细但更慢的“重排序模型”对这20个结果排序选出最相关的Top N比如5个给大模型。这能显著提升精度。可以试试开源的bge-reranker模型。元数据过滤在存储向量时为每个文本块附加元数据如文档标题、章节、日期。检索时可以先根据元数据过滤如“只搜索2023年后的产品手册”再进行向量搜索能极大提升准确率和效率。提示工程Prompt Engineering默认的提示可能不够好。你可以自定义查询引擎的提示模板明确指示模型“基于以下上下文回答问题如果上下文不包含答案就说不知道”。from llama_index.core import PromptTemplate qa_prompt_tmpl ( “上下文信息如下\n” “---------------------\n” “{context_str}\n” “---------------------\n” “请严格基于上述上下文不依赖外部知识回答以下问题\n” “问题{query_str}\n” “答案” ) qa_prompt PromptTemplate(qa_prompt_tmpl) # 然后在创建query_engine时指定在提示词中要求模型以特定格式如Markdown列表、表格回答可以使输出更结构化。缓存对于常见问题答案可以缓存起来避免重复进行向量检索和LLM调用大幅降低延迟和成本。可以使用Redis或简单的内存缓存如cachetools。4.2 系统监控与日志一个看不见的系统是危险的。你需要知道它是否健康用户问了什么回答得怎么样。应用日志使用Python的logging模块将关键步骤收到请求、检索结果、调用LLM、返回答案以及错误信息记录到文件。可以按天切割日志文件。关键指标响应时间从收到问题到返回答案的总耗时。拆解为检索时间、LLM生成时间。Token消耗每次问答消耗的输入和输出Token数这是成本的核心。缓存命中率如果引入了缓存。错误率API调用失败的比例。简单监控可以写一个脚本定期调用/health接口或者检查关键进程如Python app, PostgreSQL是否存活。更正式一点可以集成Prometheus和Grafana。4.3 成本控制精打细算在轻量服务器上成本主要来自两块云服务器本身和大模型API调用。服务器成本腾讯云轻量服务器是包月/包年付费。2核4G配置对于初期完全足够。主要关注流量如果知识库对外公开需注意流量超额费用。可以通过Nginx配置压缩、浏览器缓存来节省流量。API调用成本大头嵌入成本文档入库时的一次性向量化以及用户每次提问时问题的向量化。这部分成本相对固定且较低。LLM生成成本这是变动成本与问答频率和答案长度正相关。控制策略如下优化检索检索越精准提供给LLM的上下文就越短、越相关消耗的输入Token就越少生成无关内容的风险也越低。设置回答长度限制在调用LLM API时设置max_tokens参数。实施限流在FastAPI层面对/query接口进行限流例如每分钟每个IP 10次防止恶意调用。使用更经济的模型在效果可接受的前提下选择输入输出单价更低的模型。DeepSeek的定价就非常有竞争力。异步处理与队列对于非实时性要求高的场景如批量生成文档摘要可以将任务放入队列如CeleryRedis在服务器闲时处理。4.4 安全与权限考虑API密钥管理绝对不要将API密钥硬编码在代码中使用环境变量。# 在启动应用前设置环境变量 export DEEPSEEK_API_KEYyour_key_here在代码中通过os.getenv(“DEEPSEEK_API_KEY”)读取。数据库密码同样使用环境变量或Docker secrets管理。API访问控制如果你的知识库是私有的需要为FastAPI接口添加认证如API Token、JWT。可以使用FastAPI的依赖注入系统轻松实现。输入输出过滤对用户输入的问题进行基本的清洗和过滤防止Prompt注入攻击。对模型输出也可以进行敏感词过滤。走到这一步你已经拥有了一个功能完整、架构清晰、且具备一定优化和运维考虑的个人知识库AI助手。它运行在你的腾讯云轻量服务器上数据私有成本可控。你可以随时往docs文件夹里添加新文档然后重新运行ingest.py脚本更新知识库。随着需求的增长你可以沿着本文提到的优化方向持续迭代例如引入更复杂的检索策略、搭建监控面板、或者将整个应用Docker化以实现一键部署。这个项目就像一个乐高底座为你探索更广阔的AIGC应用世界提供了坚实而灵活的起点。