AI项目从入门到上线08-检索延迟1.5秒用户早跑了!RAG系统性能优化实战
前言熬夜上线第二天告警狂响。检索800ms变6秒用户早把窗口关了。老司机带你从能用到好用把RAG客服送上生产环境——活着的那种。 目录1. 开篇上线当晚的PTSD2. 整体架构一览你的RAG系统到底长在哪3. FastAPI服务封装让RAG接口化3.1 为什么选FastAPI3.2 依赖注入让代码可测试4. 向量检索性能优化从800ms到80ms的秘密4.1 致命反模式循环单条查询4.2 正确的姿势批量检索4.3 缓存策略相同的用户反复问同样问题5. 异步并发处理100个人同时问客服怎么办5.1 Uvicorn Worker配置金字塔5.2 异步事件循环的正确用法6. 认证机制选型别让公网上谁都来薅你的API6.1 先用API Key快速上线6.2 JWT进阶方案7. CORS跨域配置前端调用不再跨域红8. Nginx反向代理SSL真正上线到公网8.1 Nginx配置反向代理 SSL 限流8.2 Gunicorn Uvicorn 组合9. 监控告警别等问题炸了才知道9.1 Prometheus指标暴露9.2 告警规则10. Docker Compose编排一键部署全家桶a id“1”/a1. 开篇上线当晚的PTSD你是否遇到过——RAG客服在本地测得好好的一上线直接崩用户刷新三次没结果转头投奔竞品。而你凌晨两点盯着Grafana上那条刺眼的红色延迟曲线CPU飙到99%ChromaDB查一次要6秒。网上能搜到的所谓部署教程要么是python app.py裸奔要么Nginx配置复制粘贴跑不通。这篇文章我把自己踩过的12个生产级大坑全填平——从FastAPI服务封装到Docker Compose编排从Nginx到监控告警全程代码可复制运行。读完你会知道为什么ChromaDB批量检索能把吞吐量提升10倍为什么NginxUvicorn的QPS是裸跑Flask的14倍 这篇文章属于「AI项目实现步骤调研」系列第8篇。前两篇讲了文档向量化和RAG问答链路没看过的建议先补课——不过直接看这篇也不耽误。graph TB subgraph 客户端层 A[浏览器/微信/APP] end subgraph 接入层 - Nginx B[Nginxbr/反向代理 SSL终端] end subgraph 应用层 - FastAPI Uvicorn C[Uvicorn Workers] D[FastAPI App] E[认证中间件br/API Key / JWT] F[CORS中间件] end subgraph 服务层 G[ChromaDBbr/向量检索 缓存] H[LLM Providerbr/OpenAI / 通义千问] end subgraph 可观测性 I[Prometheus Grafanabr/延迟 / QPS / Token消耗] end A --|HTTPS| B B -- C C -- D D -- E -- F F -- G F -- H D -- I style B fill:#ff6b6b,stroke:#c92a2a,color:#fff style D fill:#4ecdc4,stroke:#0e7c7b,color:#fff style G fill:#ffe66d,stroke:#d4b300 style I fill:#a29bfe,stroke:#5f3dc4,color:#fff上图就是本文要搭建的生产级架构。别怕每个组件我都会拆开讲——看完你也能搭一套。a id“2”/a2. 整体架构一览你的RAG系统到底长在哪先看清全景否则后面每个组件你都不知道它为什么要存在。请求全链路用户发一句话 → Nginx接收HTTPS → 转发给Uvicorn → FastAPI校验API Key → 走CORS放行 → 调ChromaDB检索相关文档 → 拼接Prompt → 调LLM生成回答 → 返回结果。这里面每个环节都可能成为瓶颈环节典型延迟优化手段Nginx转发1ms几乎不用管FastAPI处理~2ms异步路由、Pydantic v2ChromaDB检索80-800ms批量检索、缓存、索引优化LLM生成500-3000msStream模式、更小模型说白了优化的核心就是死磕3和4——检索和LLM是真正的延迟大户。a id“3”/a3. FastAPI服务封装让RAG接口化3.1 为什么选FastAPIFlask处理高并发只能靠Gunicorn多进程硬堆。FastAPI基于Starletteasyncio单线程就能扛大量IO密集型请求——RAG场景恰恰就是IO密集型查向量库、调API。实测数据同一台机器ab -n 1000 -c 50框架QPSP99延迟内存占用Flask Gunicorn(4 workers)178420ms320MBFastAPI Uvicorn(4 workers)248038ms180MBFastAPI的QPS是Flask的14倍内存少一半。不是吹是实测。# app/main.py — 路由设计 from fastapi import FastAPI, Depends, HTTPException from fastapi.middleware.cors import CORSMiddleware from pydantic import BaseModel, Field from typing import List, Optional import time app FastAPI(titleRAG智能客服, version2.0.0) # 请求/响应模型 class ChatRequest(BaseModel): query: str Field(..., min_length1, max_length2000, description用户问题) session_id: Optional[str] Field(None, description会话ID多轮对话必传) top_k: int Field(5, ge1, le20, description检索文档数量) class SourceDoc(BaseModel): content: str score: float source: str class ChatResponse(BaseModel): answer: str sources: List[SourceDoc] latency_ms: int tokens_used: int # 健康检查 app.get(/health) async def health(): K8s探活端点——必须有否则Pod会被kill return {status: ok, version: 2.0.0} # 核心接口 app.post(/api/v1/chat, response_modelChatResponse) async def chat(req: ChatRequest): start time.time() # 1. Query重写复用前篇的多轮对话逻辑 rewritten_query rewrite_with_history(req.query, req.session_id) # 2. 向量检索 docs await search_documents(rewritten_query, req.top_k) # 3. LLM生成 answer, tokens await generate_answer(rewritten_query, docs) return ChatResponse( answeranswer, sources[SourceDoc(contentd.page_content, scored.score, sourced.metadata[source]) for d in docs], latency_msint((time.time() - start) * 1000), tokens_usedtokens, )⚠️避坑警告 #1/health端点千万别写复杂逻辑K8s的livenessProbe每10秒调一次你要在里面查数据库CPU直接白烧30%。一个return {status: ok}够了。3.2 依赖注入让代码可测试FastAPI的Depends是个神器——把认证、数据库连接、配置加载全抽出去# app/dependencies.py from fastapi import Header, HTTPException, Depends import os # 不要硬编码Key不要硬编码Key不要硬编码Key VALID_API_KEYS set(os.getenv(API_KEYS, ).split(,) if os.getenv(API_KEYS) else []) async def verify_api_key(x_api_key: str Header(None)): if not VALID_API_KEYS: # 没配置Key 开发模式放行 return if x_api_key not in VALID_API_KEYS: raise HTTPException(status_code401, detailInvalid API Key) return x_api_key async def get_chromadb_client(): 用yield管理ChromaDB连接生命周期 import chromadb client chromadb.PersistentClient(path./chroma_data) try: yield client finally: pass # PersistentClient无需手动关闭 # 使用时直接注入 # app.post(/api/v1/chat, dependencies[Depends(verify_api_key)])效率技巧 #1用FastAPI的lifespan事件管理启动初始化比app.on_event(startup)更优雅from contextlib import asynccontextmanager asynccontextmanager async def lifespan(app: FastAPI): # 启动时预热模型、加载配置 print( 服务启动中...) app.state.start_time time.time() yield # 关闭时清理连接 print( 服务关闭) app FastAPI(lifespanlifespan)a id“4”/a4. 向量检索性能优化从800ms到80ms的秘密这是全文最重要的优化。ChromaDB底层是SQLite 向量索引单个查询走HNSW索引很快但如果你犯了一个经典错误它会慢到让你怀疑人生。4.1 致命反模式循环单条查询# ❌ 千万别这么写 — 每次调用一个HTTP/RPC往返 async def search_one_by_one(queries: list): results [] collection client.get_collection(faq) for q in queries: # 10个查询 10个往返 r collection.query(query_embeddings[embed(q)], n_results5) results.append(r) return results10个查询每次80ms 800ms总延迟。用户点完发送都能起身接杯水了。4.2 正确的姿势批量检索# ✅ 一次查询传多个embeddingChromaDB内部并行 async def search_batch(queries: list): collection client.get_collection(faq) embeddings [embed(q) for q in queries] # 关键一次传所有embedding一个往返搞定 results collection.query( query_embeddingsembeddings, # List[List[float]] n_results5, ) return results实测对比ChromaDB2000条文档768维BGE-M3向量graph LR subgraph 单条循环查询 A1[查询1: 82ms] -- A2[查询2: 79ms] A2 -- A3[查询3: 85ms] A3 -- A4[查询4: 81ms] A4 -- A5[查询5: 78ms] A5 -- A6[总: 405ms] end subgraph 批量查询 B1[5个embeddingbr/一次请求] B1 -- B2[总: 48ms] end style A6 fill:#ff6b6b,color:#fff style B2 fill:#51cf66,color:#fff5倍差距起步。查询越多差距越大10条能从800ms压到60ms——这就是10倍吞吐量的由来。听过那句话吗“先让程序跑通再优化”——这话在RAG场景里是毒药。你不优化向量检索用户就走了没有再了。4.3 缓存策略相同的用户反复问同样问题RAG客服场景里80%的用户问的是20%的问题——“退货流程”、“怎么开发票”、“包邮吗”。from functools import lru_cache import hashlib import json # ⚠️ 生产级缓存要用Redislru_cache只适合单进程 # 这里展示思路实际部署看下一节 class QueryCache: def __init__(self, max_size1000, ttl300): self.cache {} # hash - (result, timestamp) self.max_size max_size self.ttl ttl # 5分钟过期 def _hash(self, query: str) - str: return hashlib.md5(query.strip().lower().encode()).hexdigest() def get(self, query: str): key self._hash(query) if key in self.cache: result, ts self.cache[key] if time.time() - ts self.ttl: return result del self.cache[key] return None def set(self, query: str, result): key self._hash(query) self.cache[key] (result, time.time()) # 粗暴的LRU满了清掉最老的 if len(self.cache) self.max_size: oldest min(self.cache, keylambda k: self.cache[k][1]) del self.cache[oldest]效率技巧 #2缓存key不要直接用原始字符串——怎么退货和退货怎么操作是两个不同的key。先用Query重写成标准形式或者用语义相似度做模糊缓存。但这属于进阶话题了先用hash跑通再说。a id“5”/a5. 异步并发处理100个人同时问客服怎么办5.1 Uvicorn Worker配置金字塔# 不要这么跑 uvicorn app.main:app # 1个worker串行处理 # 也不要这么跑CPU密集型才这么搞 uvicorn app.main:app --workers 16 # 过度分配上下文切换比干活还多 # ✅ 生产级配置 uvicorn app.main:app \ --host 0.0.0.0 \ --port 8000 \ --workers 4 \ # CPU核数的2倍是上限别瞎加 --loop uvloop \ # 比默认asyncio快30% --http httptools \ # 比默认h11快40% --log-level warning # 生产环境别打info磁盘会爆⚠️避坑警告 #2--workers不是越多越好。Worker数 (2 × CPU核数) 1 是上限。8核机器开到16个worker上下文切换开销反而把QPS拉下来了。我亲自踩过——16个worker跑得没4个快CPU全耗在调度上了。5.2 异步事件循环的正确用法import asyncio from typing import List # ❌ 在async函数里调同步阻塞代码 — 阻塞整个事件循环 async def bad_search(query): return chroma_client.query(...) # ChromaDB的query是同步的 # 它卡住后其他所有请求都等着 # ✅ 用run_in_executor把同步调用扔进线程池 async def good_search(query): loop asyncio.get_running_loop() return await loop.run_in_executor( None, # 默认线程池 lambda: chroma_client.query(query_texts[query], n_results5) ) # ✅ 批量并发调用LLM 向量检索 async def process_queries(queries: List[str]): tasks [] for q in queries: tasks.append(search_documents(q)) # 检索任务 tasks.append(generate_answer(q, [])) # LLM任务假设用dummy results await asyncio.gather(*tasks, return_exceptionsTrue) return results效率技巧 #3用asyncio.Semaphore控制并发上限——防止你开启的协程把LLM API打爆# 限制同时最多5个LLM请求 semaphore asyncio.Semaphore(5) async def rate_limited_generate(query, docs): async with semaphore: return await generate_answer(query, docs)a id“6”/a6. 认证机制选型别让公网上谁都来薅你的APIRAG客服一上线你的LLM API Key就像裸奔的银行卡——谁都能刷。方案复杂度安全性适用场景API Key⭐⭐⭐内部服务、小团队、快速上线JWT⭐⭐⭐⭐⭐⭐⭐Web应用前端、多用户系统OAuth2⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐企业级、第三方接入、大规模用户6.1 先用API Key快速上线# app/auth.py — 三版方案复杂度递进 # 版本1硬核MVP开发阶段 VALID_KEYS {sk-dev-12345678} def check_key(key: str): return key in VALID_KEYS # 版本2从环境变量读上线最低要求 import os VALID_KEYS set(os.getenv(API_KEYS, ).split(,)) # 版本3从Redis读 速率限制生产推荐 import redis.asyncio as redis async def check_key_with_rate(key: str): r await redis.from_url(redis://localhost:6379) is_valid await r.sismember(valid_api_keys, key) if not is_valid: raise HTTPException(401) # 速率限制每个Key每分钟最多60次请求 count await r.incr(frate:{key}) if count 1: await r.expire(frate:{key}, 60) if count 60: raise HTTPException(429, Rate limit exceeded. 歇会儿吧兄弟。) return key6.2 JWT进阶方案API Key适合机器间调用Web前端需要JWT——用户登录后前端拿token每次请求带Authorization: Bearer tokenimport jwt from datetime import datetime, timedelta SECRET_KEY os.getenv(JWT_SECRET, 千万别用默认值) ALGORITHM HS256 def create_token(user_id: str, expire_minutes: int 60): payload { sub: user_id, exp: datetime.utcnow() timedelta(minutesexpire_minutes), iat: datetime.utcnow(), } return jwt.encode(payload, SECRET_KEY, algorithmALGORITHM) def verify_token(token: str): try: payload jwt.decode(token, SECRET_KEY, algorithms[ALGORITHM]) return payload[sub] except jwt.ExpiredSignatureError: raise HTTPException(401, Token过期了重新登录一下吧) except jwt.InvalidTokenError: raise HTTPException(401, Token无效你是哪个单位的)⚠️避坑警告 #3不要把JWT Secret写死在代码里提交Git公开仓库爬虫24小时不停扫描。git clone你的项目别人就能伪造任意用户的Token。永远从环境变量读取永远用openssl rand -hex 32生成。a id“7”/a7. CORS跨域配置前端调用不再跨域红你的前端在localhost:5173API在localhost:8000——浏览器直接拦住“跨域了不给发。”# app/main.py — 正确姿势 from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins[ http://localhost:5173, # 开发环境 https://your-frontend.com, # 生产前端 ], allow_credentialsTrue, allow_methods[GET, POST], # 别用[*]不安全 allow_headers[Authorization, Content-Type, X-API-Key], )注意那个allow_credentialsTrue——如果你的前端要带Cookieallow_origins绝不能是[*]否则浏览器直接拒绝。这是CORS规范的规定不是你代码写错了。a id“8”/a8. Nginx反向代理SSL真正上线到公网有了API不代表就能给公网用。你需要Nginx在前面挡着。8.1 Nginx配置反向代理 SSL 限流# /etc/nginx/sites-available/rag-api — 生产级配置 upstream rag_backend { # 负载均衡least_conn 比默认轮询更适合LLM场景 # 因为LLM请求耗时差异大一个长请求会拖慢整个轮询 least_conn; server 127.0.0.1:8000 weight1 max_fails3 fail_timeout30s; server 127.0.0.1:8001 weight1 max_fails3 fail_timeout30s; } # 限流区域每个IP每秒最多10个请求 limit_req_zone $binary_remote_addr zoneapi_limit:10m rate10r/s; server { listen 80; server_name api.yourdomain.com; # 所有HTTP流量强制跳HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name api.yourdomain.com; # SSL证书用Certbot自动获取Lets Encrypt免费证书 ssl_certificate /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 安全头 add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection 1; modeblock; # ⏱ LLM请求超时设置 — 这很关键 # 默认的60s不够LLM回答可能要30s proxy_read_timeout 120s; proxy_connect_timeout 10s; proxy_send_timeout 10s; # WebSocket支持如果前端用SSE/WebSocket接Stream模式 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; location /api/ { # 限流 limit_req zoneapi_limit burst20 nodelay; proxy_pass http://rag_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键关闭Nginx缓冲SSE流式输出才能实时推送 proxy_buffering off; proxy_cache off; add_header X-Accel-Buffering no; } # 健康检查不设限流 location /health { proxy_pass http://rag_backend; } }看到proxy_read_timeout 120s了吗LLM请求默认60秒不够用超时Nginx会直接断开连接——前端拿到一个冰冷的502用户以为系统崩了。8.2 Gunicorn Uvicorn 组合纯Uvicorn跑生产是可行的但加一层Gunicorn做进程管理更稳健——自动重启挂掉的worker、平滑重启热重载不丢请求# 生产启动命令 gunicorn app.main:app \ --workers 4 \ --worker-class uvicorn.workers.UvicornWorker \ --bind 0.0.0.0:8000 \ --timeout 120 \ --graceful-timeout 30 \ --max-requests 10000 \ # 每个worker处理1万请求后重启防内存泄漏 --max-requests-jitter 1000 # 随机抖动避免所有worker同时重启--max-requests 10000这个参数救过我——Python程序长期运行容易内存缓慢上涨第三方库的问题定期重启是简单粗暴但有效的止血方案老运维都知道。a id“9”/a9. 监控告警别等问题炸了才知道9.1 Prometheus指标暴露# app/metrics.py from prometheus_client import Counter, Histogram, Gauge, generate_latest from fastapi import Response # 定义指标 request_count Counter( rag_requests_total, 总请求数, [endpoint, status] # label按接口和状态码分类 ) request_latency Histogram( rag_request_duration_seconds, 请求延迟(秒), [endpoint], buckets[0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0] # 分桶看分布 ) token_consumed Counter( rag_tokens_total, Token消耗, [model] ) active_requests Gauge( rag_active_requests, 当前活跃请求数 ) chromadb_latency Histogram( rag_chromadb_query_seconds, ChromaDB查询延迟(秒) ) app.get(/metrics) async def metrics(): Prometheus抓取端点 return Response(contentgenerate_latest(), media_typetext/plain) # 在中间件里自动记录 app.middleware(http) async def metrics_middleware(request, call_next): request_count.labels(endpointrequest.url.path, statusstarted).inc() active_requests.inc() start time.time() response await call_next(request) elapsed time.time() - start request_latency.labels(endpointrequest.url.path).observe(elapsed) active_requests.dec() request_count.labels(endpointrequest.url.path, statusstr(response.status_code)).inc() return response9.2 告警规则# prometheus-alerts.yml groups: - name: rag_alerts rules: - alert: HighLatency expr: histogram_quantile(0.99, rate(rag_request_duration_seconds_bucket[5m])) 5 for: 3m labels: severity: critical annotations: summary: RAG API P99延迟超过5秒 description: 过去5分钟P99延迟 {{ $value }}秒用户快跑光了 - alert: HighErrorRate expr: rate(rag_requests_total{status~5..}[5m]) / rate(rag_requests_total[5m]) 0.05 for: 2m labels: severity: critical annotations: summary: 错误率超过5% description: 当前错误率 {{ $value | humanizePercentage }} - alert: TokenSpike expr: rate(rag_tokens_total[1h]) * 3600 100000 labels: severity: warning annotations: summary: Token消耗异常升高 description: 过去1小时消耗约 {{ $value }} tokens/h检查是否被刷三根救命稻草延迟、错误率、Token异常——缺一个都可能让你在半夜被电话炸醒。a id“10”/a10. Docker Compose编排一键部署全家桶写了一大堆配置你当然不想手动一个个配。Docker Compose能让你一行命令全部跑起来。# docker-compose.yml — 生产级编排 version: 3.8 services: # Nginx nginx: image: nginx:alpine container_name: rag-nginx ports: - 80:80 - 443:443 volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro depends_on: - api restart: unless-stopped networks: - rag-network # FastAPI api: build: . container_name: rag-api expose: - 8000 environment: - CHROMA_HOSTchromadb - CHROMA_PORT8000 - REDIS_URLredis://redis:6379 - LLM_API_KEY${LLM_API_KEY} # 从.env文件注入不写死 - JWT_SECRET${JWT_SECRET} - API_KEYS${API_KEYS} env_file: - .env # Git不提交这个文件 depends_on: chromadb: condition: service_healthy redis: condition: service_started restart: unless-stopped networks: - rag-network deploy: resources: limits: memory: 2G reservations: memory: 512M # ChromaDB chromadb: image: chromadb/chroma:latest container_name: rag-chromadb expose: - 8000 volumes: - ./chroma_data:/chroma/chroma # 持久化向量数据 environment: - IS_PERSISTENTTRUE - ANONYMIZED_TELEMETRYFALSE healthcheck: test: [CMD, curl, -f, http://localhost:8000/api/v1/heartbeat] interval: 10s timeout: 5s retries: 5 restart: unless-stopped networks: - rag-network # Redis缓存速率限制 redis: image: redis:7-alpine container_name: rag-redis expose: - 6379 volumes: - ./redis_data:/data command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru restart: unless-stopped networks: - rag-network # Prometheus prometheus: image: prom/prometheus:latest container_name: rag-prometheus volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - prometheus_data:/prometheus command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.retention.time15d ports: - 9090:9090 restart: unless-stopped networks: - rag-network # Grafana grafana: image: grafana/grafana:latest container_name: rag-grafana ports: - 3000:3000 environment: - GF_SECURITY_ADMIN_PASSWORD${GRAFANA_PASSWORD:-admin} volumes: - grafana_data:/var/lib/grafana depends_on: - prometheus restart: unless-stopped networks: - rag-network networks: rag-network: driver: bridge volumes: prometheus_data: grafana_data:配套的Dockerfile# Dockerfile FROM python:3.12-slim WORKDIR /app # 先装依赖利用Docker层缓存改代码不用重装依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt \ pip install uvicorn[standard] gunicorn # 再拷代码 COPY app/ ./app/ EXPOSE 8000 # Gunicorn Uvicorn Worker CMD [gunicorn, app.main:app, \ --workers, 4, \ --worker-class, uvicorn.workers.UvicornWorker, \ --bind, 0.0.0.0:8000, \ --timeout, 120, \ --max-requests, 10000, \ --max-requests-jitter, 1000]一行启动# .env文件绝不提交Git echo LLM_API_KEYsk-your-key .env echo JWT_SECRET$(openssl rand -hex 32) .env echo API_KEYSsk-prod-abc123,sk-prod-def456 .env # 启动 docker compose up -d # 查看日志 docker compose logs -f api # 确认健康 curl http://localhost:8000/healthflowchart TD A[git pull 更新代码] -- B[docker compose build] B -- C[docker compose up -d] C -- D{健康检查通过?} D --|✅ 是| E[上线完成 ] D --|❌ 否| F[docker compose logs api] F -- G[修bug] G -- B style E fill:#51cf66,color:#fff style F fill:#ff6b6b,color:#fff这就是完整的部署循环。修bug → build → up → 健康检查。简单到让人感动——而这正是Docker Compose的魅力把在我的机器上能跑变成在任何人的机器上都能跑。a id“11”/a11. 结尾三件套️ 【源码获取】本系列所有文章的完整源码含本文的docker-compose.yml、Nginx配置、FastAPI代码关注本系列持续更新后台回复「RAG客服」获取GitHub仓库链接。 【思考题】如果用户突然问了100个不同的问题缓存命中率几乎为0而你的LLM API配额每分钟只能处理30次请求——你该如何设计一个优雅的排队优先级机制让用户感知不到延迟爆炸欢迎在评论区留下你的方案最佳方案我会在下一期文章中收录并署名致谢。 【系列文章预告】下一篇《L2实战——文档智能分析系统搭建一PDF解析与OCR》你将学到PyMuPDF vs pdfplumber vs Camelot——五大PDF解析工具实测对比扫描件OCR处理全流程PaddleOCR 图像预处理多栏布局自动检测算法表格提取的两种模式Lattice vs Stream你以为PDF解析就是reader.pages[0].extract_text()扫一张发票给AI看看它读出来的数字能直接入账吗下期见。标签#RAG部署 #FastAPI #Nginx #Docker #性能优化 #生产上线 #智能客服