从0到1搭一个生产级RAG系统:串联前面21篇所有知识

写了21篇文章,从入门到微调,从RAG到Agent到部署。但一直有个问题:这些知识怎么串起来?

今天用一篇完整的项目实战,把前面所有知识串起来------从0到1搭一个生产级的企业知识库RAG系统。 不只是"能跑",而是"能上线"。


项目目标

搭一个企业级智能客服系统,需求:

需求 对应前面的知识
知识库问答 #3 RAG + #5 Embedding
多轮对话 #12 结构化输出
订单查询 #18 Function Calling
意图路由 #9 LangGraph
效果评估 #19 RAGAS评估
安全防护 #20 AI安全
部署上线 #15 Docker部署
成本优化 #16 分层模型+缓存
效果不够时升级 #11 GraphRAG / #21 微调

架构总览

less 复制代码
用户请求
   ↓
[API网关] → 安全过滤(#20)
   ↓
[LangGraph工作流](#9)
   ├→ [意图分类] → 简单问答 → 缓存(#16)→ 直接返回
   │                ↓ 未命中
   │              [便宜模型](#16分层)
   │
   ├→ [知识库问答] → Qdrant检索(#17)→ Rerank → LLM生成(#13 Prompt)
   │
   ├→ [订单查询] → Function Calling(#18)→ 安全执行
   │
   └→ [拒绝回答] → 返回"无法回答"
        ↓
[输出过滤] → 脱敏(#20)→ 返回用户

第1步:项目初始化

arduino 复制代码
mkdir smart-customer-service && cd smart-customer-service
mkdir -p app data tests config
bash 复制代码
smart-customer-service/
├── app/
│   ├── main.py           # FastAPI入口
│   ├── workflow.py       # LangGraph工作流
│   ├── retriever.py      # RAG检索
│   ├── llm.py            # 分层LLM
│   ├── cache.py          # 语义缓存
│   ├── safety.py         # 安全过滤
│   ├── function_call.py  # Function Calling
│   └── config.py         # 配置
├── data/
│   ├── documents/        # 原始文档
│   ├── eval_dataset.json # 评估数据集
│   └── golden_dataset.json # 黄金数据集
├── tests/
│   ├── test_workflow.py
│   ├── test_safety.py
│   └── test_retriever.py
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
└── .env.example

第2步:分层LLM(第16篇)

python 复制代码
# app/llm.py
from langchain_openai import ChatOpenAI
import os
​
class TieredLLM:
    """分层模型策略:简单问题用便宜模型,复杂问题用贵模型"""
​
    def __init__(self):
        self.turbo = ChatOpenAI(
            model="qwen-turbo",
            api_key=os.getenv("DASHSCOPE_API_KEY"),
        )
        self.plus = ChatOpenAI(
            model="qwen-plus",
            api_key=os.getenv("DASHSCOPE_API_KEY"),
        )
​
    def get_llm(self, complexity: str = "medium"):
        if complexity == "simple":
            return self.turbo
        return self.plus

第3步:RAG检索 + Rerank(第3、5、17篇)

python 复制代码
# app/retriever.py
from langchain_community.vectorstores import Qdrant
from langchain_openai import OpenAIEmbeddings
​
class SmartRetriever:
    """智能检索:向量检索 + 相似度过滤"""
​
    def __init__(self, qdrant_url: str, collection_name: str):
        self.embeddings = OpenAIEmbeddings(model="text-embedding-v3")
        self.vectorstore = Qdrant(
            url=qdrant_url,
            collection_name=collection_name,
            embeddings=self.embeddings,
        )
​
    def retrieve(self, query: str, k: int = 5, min_score: float = 0.7, filter_dict: dict = None):
        """检索+过滤"""
        # 多检索一些,再按相似度过滤
        docs_with_scores = self.vectorstore.similarity_search_with_score(
            query, k=k * 2, filter=filter_dict,
        )
​
        # 过滤低相关度结果
        filtered = [(doc, score) for doc, score in docs_with_scores if score >= min_score]
​
        # 取top-k
        filtered = sorted(filtered, key=lambda x: x[1], reverse=True)[:k]
​
        return [doc for doc, _ in filtered]

第4步:Function Calling(第18篇)

python 复制代码
# app/function_call.py
import json
​
# 函数定义
TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "query_order",
            "description": "查询订单状态",
            "parameters": {
                "type": "object",
                "properties": {
                    "order_id": {"type": "string", "description": "订单号ORD+数字"}
                },
                "required": ["order_id"],
            },
        },
    },
]
​
# 白名单+参数校验
ALLOWED = {"query_order"}
VALIDATORS = {
    "query_order": {"order_id": lambda x: x.startswith("ORD") and x[3:].isdigit()},
}
​
def safe_execute(name: str, args: dict) -> dict:
    """安全函数执行"""
    if name not in ALLOWED:
        return {"error": f"不允许调用:{name}"}
    for param, validator in VALIDATORS.get(name, {}).items():
        if not validator(args.get(param, "")):
            return {"error": f"参数校验失败:{param}"}
    # 业务逻辑
    if name == "query_order":
        return _query_order_impl(args["order_id"])
    return {"error": "未知函数"}
​
def _query_order_impl(order_id: str) -> dict:
    """订单查询实现"""
    # 实际项目:调订单微服务
    orders = {
        "ORD123": {"status": "已发货", "tracking": "SF123456", "eta": "6月20日"},
        "ORD456": {"status": "待支付"},
    }
    return orders.get(order_id, {"error": "订单不存在"})

第5步:安全过滤(第20篇)

python 复制代码
# app/safety.py
import re
​
class InputGuard:
    INJECTION_PATTERNS = [
        r"忽略.{0,5}(上面|之前|以上).{0,5}(所有|一切).{0,5}指令",
        r"ignore.{0,5}(all|previous).{0,5}instructions",
        r"system:",
    ]
​
    def check(self, text: str) -> dict:
        if len(text) > 500:
            return {"safe": False, "reason": "输入过长"}
        for p in self.INJECTION_PATTERNS:
            if re.search(p, text, re.IGNORECASE):
                return {"safe": False, "reason": "疑似Prompt注入"}
        return {"safe": True}
​
​
class OutputGuard:
    PII_PATTERNS = {
        "手机号": (r"1[3-9]\d{9}", lambda m: m.group()[:3] + "****" + m.group()[-4:]),
    }
​
    def filter(self, text: str) -> str:
        for name, (pattern, replacer) in self.PII_PATTERNS.items():
            text = re.sub(pattern, replacer, text)
        return text

第6步:缓存(第16篇)

python 复制代码
# app/cache.py
import hashlib
import time
​
class SimpleCache:
    def __init__(self, ttl: int = 3600):
        self.cache = {}
        self.ttl = ttl
​
    def get(self, question: str):
        key = hashlib.md5(question.encode()).hexdigest()
        if key in self.cache:
            entry = self.cache[key]
            if time.time() - entry["time"] < self.ttl:
                return entry["answer"]
            del self.cache[key]
        return None
​
    def set(self, question: str, answer: str):
        key = hashlib.md5(question.encode()).hexdigest()
        self.cache[key] = {"answer": answer, "time": time.time()}

第7步:LangGraph工作流(第9篇)

python 复制代码
# app/workflow.py
from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, START, END
​
class ChatState(TypedDict):
    question: str
    intent: str
    knowledge_answer: str
    order_answer: str
    final_answer: str
​
# 节点:意图分类
def classify_intent(state: ChatState) -> ChatState:
    question = state["question"]
    if any(kw in question for kw in ["订单", "到哪了", "发货", "ORD"]):
        return {"intent": "order"}
    elif any(kw in question for kw in ["退货", "退款", "流程", "换货"]):
        return {"intent": "knowledge"}
    else:
        return {"intent": "unknown"}
​
# 节点:知识库问答
def knowledge_qa(state: ChatState, retriever, llm) -> ChatState:
    docs = retriever.retrieve(state["question"], k=3)
    context = "\n".join([d.page_content for d in docs])
    prompt = f"知识库:{context}\n\n问题:{state['question']}\n\n基于知识库回答,不确定时说'我帮您确认':"
    answer = llm.invoke(prompt).content
    return {"knowledge_answer": answer}
​
# 节点:订单查询
def order_query(state: ChatState, llm) -> ChatState:
    # 用Function Calling
    response = llm.invoke(state["question"], tools=TOOLS)
    tool_calls = response.additional_kwargs.get("tool_calls", [])
    if tool_calls:
        name = tool_calls[0]["function"]["name"]
        args = json.loads(tool_calls[0]["function"]["arguments"])
        result = safe_execute(name, args)
        answer = llm.invoke(f"查询结果:{result}\n\n回答:{state['question']}").content
    else:
        answer = response.content
    return {"order_answer": answer}
​
# 节点:汇总回答
def compose_answer(state: ChatState) -> ChatState:
    if state.get("knowledge_answer"):
        return {"final_answer": state["knowledge_answer"]}
    elif state.get("order_answer"):
        return {"final_answer": state["order_answer"]}
    else:
        return {"final_answer": "抱歉,我暂时无法回答这个问题,建议联系人工客服。"}
​
# 路由
def route_intent(state: ChatState) -> Literal["knowledge", "order", "answer"]:
    if state["intent"] == "knowledge":
        return "knowledge"
    elif state["intent"] == "order":
        return "order"
    return "answer"
​
# 构建图
def build_workflow(retriever, llm):
    graph = StateGraph(ChatState)
    graph.add_node("classify", lambda s: classify_intent(s))
    graph.add_node("knowledge", lambda s: knowledge_qa(s, retriever, llm.plus))
    graph.add_node("order", lambda s: order_query(s, llm.plus))
    graph.add_node("answer", lambda s: compose_answer(s))
​
    graph.add_edge(START, "classify")
    graph.add_conditional_edges("classify", route_intent)
    graph.add_edge("knowledge", "answer")
    graph.add_edge("order", "answer")
    graph.add_edge("answer", END)
​
    return graph.compile()

第8步:FastAPI入口 + 全流程串联

python 复制代码
# app/main.py
from fastapi import FastAPI
from pydantic import BaseModel
from app.llm import TieredLLM
from app.retriever import SmartRetriever
from app.safety import InputGuard, OutputGuard
from app.cache import SimpleCache
from app.workflow import build_workflow
​
app = FastAPI(title="智能客服系统")
​
# 初始化组件
llm = TieredLLM()
retriever = SmartRetriever("http://localhost:6333", "knowledge_base")
input_guard = InputGuard()
output_guard = OutputGuard()
cache = SimpleCache(ttl=3600)
workflow = build_workflow(retriever, llm)
​
class ChatRequest(BaseModel):
    question: str
    user_role: str = "客服"
​
@app.post("/chat")
async def chat(request: ChatRequest):
    # 1. 输入安全检查
    check = input_guard.check(request.question)
    if not check["safe"]:
        return {"error": check["reason"]}
​
    # 2. 查缓存
    cached = cache.get(request.question)
    if cached:
        return {"answer": cached, "source": "cache"}
​
    # 3. LangGraph工作流
    result = workflow.invoke({"question": request.question})
    answer = result.get("final_answer", "抱歉,暂时无法回答")
​
    # 4. 输出脱敏
    safe_answer = output_guard.filter(answer)
​
    # 5. 存缓存
    cache.set(request.question, safe_answer)
​
    return {"answer": safe_answer, "source": "workflow"}
​
@app.get("/health")
async def health():
    return {"status": "ok"}

第9步:Docker部署(第15篇)

sql 复制代码
# Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
yaml 复制代码
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports: ["8000:8000"]
    env_file: .env
    depends_on: [qdrant]
    restart: always
​
  qdrant:
    image: qdrant/qdrant:latest
    ports: ["6333:6333"]
    volumes: [qdrant_data:/qdrant/storage]
​
volumes:
  qdrant_data:

第10步:评估+持续优化(第14、19篇)

bash 复制代码
# 运行测试
pytest tests/
​
# RAGAS评估
python -m app.evaluate --version v1.0
​
# 黄金数据集测试
python -m tests.test_workflow

全系列知识串联

文章 知识点 本项目中的应用
#1-2 入门 项目的起点
#3 RAG 核心检索能力
#4 Ollama 本地模型备选
#5 Embedding text-embedding-v3
#6 Agent 验证阶段用Agent快速Demo
#7 Dify 快速验证需求
#8 Streamlit 管理后台界面
#9 LangGraph 意图路由工作流
#10 多Agent 未来扩展方向
#11 GraphRAG 文档量大时的升级方向
#12 结构化输出 Function Calling参数
#13 Prompt工程 所有Prompt的设计规范
#14 AI测试 黄金数据集+LLM评估
#15 部署 Docker部署方案
#16 成本优化 分层模型+缓存
#17 向量数据库 Qdrant选型
#18 Function Calling 订单查询
#19 RAG评估 RAGAS指标监控
#20 AI安全 输入输出过滤
#21 微调 话术一致性升级方向
#22 完整项目 串联所有知识

这就是从0到1搭一个生产级RAG系统的完整过程。22篇文章,从入门到上线,一条线串到底。

你的AI应用是怎么搭的?评论区聊聊 👇

相关推荐
xuxigifxfh1 小时前
牛客:HJ170 01序列
java·开发语言·算法·华为机考
野生技术架构师2 小时前
牛客网互联网大厂 Java 高频面试题整理(持续更新)
java·开发语言
小师兄吃牛肉2 小时前
Java语法 | 多重循环
java·开发语言·python
Han.miracle2 小时前
MySQL InnoDB:MVCC、记录锁、间隙锁、二级索引MVCC底层原理
java·开发语言
明如正午2 小时前
用 Python 一键把 ASC 文件转成 BLF(基于 python-can)
python·can·blf·asc
不停喝水3 小时前
【前端转全栈java速通课】 项目实战④7-11节 操作数据库-登录-注册-修改密码-注销-mubatis-plus简化crud-
java·前端·数据库
y1su3 小时前
Leetcode 二分模板
java·数据结构·算法·leetcode·排序算法
leisoo80973 小时前
融资融券数据怎么查两融指标含义与杠杆观察方法 IG50免费开源股票数据API接口
开发语言·jvm·数据库·python·json